Пусть есть код x86 процессора. И пусть нам необходимо написать процедуру, дисассемблирующую (разбирающую) команды процессора (инструкции).
Здесь дисассемблировать значит решить (не все но некоторые) такие задачи, как:
Поскольку (для одной инструкции) значения битов, установленных в предыдущих байтах определяют дальнейший ход разбора последующих байтов, и все инструкции переменной длины, то разбирать их придется байт за байтом.
Возможно несколько способов побайтового разбора инструкций.
disasm: ... lodsb ; взять байт - код инструкции movzx ebx, al jmp jmptable[ebx*4] ; вызвать соответствующую процедуру ... jmptable dd opc_00, opc_01, ...
Такой способ достаточно быстрый, занимает много памяти, весьма удобен для различных эмуляторов, и имеет потенциальную возможность для дисассемблирования всех инструкций. (так как 256 указателей)
Этод способ не сильно отличается от предыдущего, за исключением того, что вместо указателей на процедуры в таблице хранятся флаги, в соответствии с которыми выполняются те или иные действия.
disasm: ... lodsb ; взять байт - код инструкции movzx ebx, al mov ecx, flagtable[ebx*4] ; получить флаги ... test ecx, flag_prefix ; выполнять действия в соответствии jnz __prefix ; с флагами ... test ecx, flag_modrm jnz __modrm ... flagtable dd flag_modrm+flag_s+flag_w, ...
Надо сказать, что возможны модификации этих способов, когда вместо таблиц для байтов будут хранится таблицы для слов.
xxxtable dd 65536 dup (?) ; 256k
Лет десять назад за такие вещи сожгли бы на костре ;-), а сегодня уже не каждый скажет, что это заняло бы много памяти. Хотя для некоторых случаев можно было бы увеличить скорость дисассемблирования.
Здесь обсуждается по-сути, преобразование вышеперечисленных таблиц в код.
Но вы ошибаетесь, если думаете, что я предложу сделать так:
disasm: ... lodsb ... cmp al, 00h je opc_00 ... cmp al, 0FFh je opc_FF ...
Здесь идея заключается в том, чтобы таблицу переходов преобразовать в код процессора следующим образом:
disasm: ... lodsb shl al, 1 jnc opc_0xxxxxxx jc opc_1xxxxxxx ... opc_0xxxxxxx: shl al, 1 jnc opc_00xxxxxx jc opc_01xxxxxx ...
Преимущество метода в том, что дерево вовсе не является строго "двоичным", и для битовых масок, которые содержат "любые" значения цепочка команд будет несколько короче.
Например для сегментных префиксов 26,2E,36,3E и соответствующей им битовой маски 001xx110 будут выполнены примерно следующие инструкции:
... test al, 10000000b jz opc_0xxxxxxx ; jmp opc_0xxxxxxx: test al, 01000000b jz opc_00xxxxxx ; jmp opc_00xxxxxx: test al, 00100000b jz opc_000xxxxx ; no jmp jnz opc_001xxxxx ; jmp opc_001xxxxx: ; здесь два элемента дерева пропущены - и в этом плюс test al, 00000100b jz opc_001xx0xx ; no jmp jz opc_001xx1xx ; jmp ...
В этом методе последовательно выполняются проверки битовых масок.
Итак, пусть нам нужно выявить все сегментные префиксы, как то - ES,CS,SS,DS,FS и GS.
Очевидно, что опкоды их кодируются так:
ES: 26 00100110 CS: 2E 00110110 SS: 36 00101110 DS: 3E 00111110 FS: 64 01100100 GS: 65 01100101
и тогда мы имеем две битовых маски: 001xx110 и 0110010x
Возникает вопрос, как вместо 6-ти сравнений и переходов сделать что-то более правильное.
Эта проблема решается достаточно просто. Например вот так:
disasm: ... lodsb ... push eax and al, 11111110 ; 64/65 cmp al, 01100100 pop eax je __prefix_seg ... push eax and al, 11100111b ; 26/2E/36/3E cmp al, 00100110b pop eax je __prefix_seg ...
Но вся проблема в том, что редкий маньяк будет в ручную переделывать битовые маски из таблицы опкодов в битовые значения для команд and и cmp, ибо опкодов тьма, а набивать еще в два раза больше - совсем тяжело.
В общем, к чему я это все. Гвоздем этого текста призван стать нижеследующий макрос.
; --- begin CMPJ.MAC ----------------------------------------------------- ; programmed/debugged under TASM32 5.0 ; by default assigned BX = not AX cmpj_al equ al ; регистры по умолчанию cmpj_bl equ bl cmpj_ax equ ax cmpj_bx equ bx ; примеры использования макроса: ; cmpj E9,label --> cmp al, E9 / je label ; cmpj 6x,label --> test bl,60 / jnz skip ; / test al,90 / jz label / skip: ; cmpj 0x,label --> test al,F0 / jz label ; cmpj xF,label --> test bl,0F / jz label ; cmpj xx,label --> jmp label ; cmpj 8x,label --> push eax / and al, F0 ; / cmp al, 80 / pop eax / je label ; cmpj 100010xx,label ; cmpj 1xx4,label ; cmpj xx000xxx11111111,label ; виды параметров, передаваемых макросу: ; cmpj HH,label hex form, 2 digits (0..9,A..F or "x") ; cmpj HHHH,label hex form, 4 digits (0..9,A..F or "x") ; cmpj BBBBBBBB,label binary form, 8 digits (0..1 or "x") ; cmpj BBBBBBBBBBBBBBBB,label binary form, 16 digits (0..1 or "x") ; lower-case hex-digits "a".."f" are available cmpj macro mask, label local count,base,reg0,reg1,max,andmask,cmpmask,i local skip,mask0,mask1 count = 0 irpc c,<mask> count = count + 1 endm ;irpc if count eq 2 base = 16 max = 255 reg1 = cmpj_al reg0 = cmpj_bl elseif count eq 4 base = 16 max = 65535 reg1 = cmpj_ax reg0 = cmpj_bx elseif count eq 8 base = 2 max = 255 reg1 = cmpj_al reg0 = cmpj_bl elseif count eq 16 base = 2 max = 65535 reg1 = cmpj_ax reg0 = cmpj_bx else %out cmpj: invalid bitmask(mask) length .err exitm endif andmask = 0 cmpmask = 0 irpc c,<mask> andmask = andmask * base cmpmask = cmpmask * base if ("&c" ge "0") and ("&c" le "9") i = "&c"-"0" elseif ("&c" ge "a") and ("&c" le "f") i = "&c"-"a"+10 elseif ("&c" ge "A") and ("&c" le "F") i = "&c"-"A"+10 elseif ("&c" eq "x") or ("&c" eq "X") i = -1 else %out cmpj: invalid digit in bitmask(mask) -- c .err exitm endif if i ge base %out cmpj: too big digit in bitmask(mask) -- c .err exitm endif if i ne -1 andmask = andmask + base-1 cmpmask = cmpmask + i endif endm;irpc mask0 = cmpmask mask1 = andmask xor cmpmask if andmask eq max cmp reg1, cmpmask je label else if cmpmask eq 0 if andmask eq 0 jmp label else test reg1, andmask jz label endif else ; push eax ; and reg, andmask ; cmp reg, cmpmask ; pop eax ; je label if mask1 eq 0 test reg0, mask0 jz label else test reg0, mask0 jnz skip test reg1, mask1 jz label skip: endif endif endif endm;cmpj ; --- end CMPJ.MAC -------------------------------------------------------
Используется макрос так: в AX загружается значение опкода и следующего за
ним байта (на случай двухбайтовых проверок), в BX загружается not AX.
Первым параметром макросу подается битовая маска, вторым - имя метки,
на которую передавать управление.
disasm: ... mov eax, [esi] mov ebx, eax not ebx ... cmpj 001xx000,__prefix_seg cmpj 0110010x,__prefix_seg ...
Вот собственно и все. Возможности макроса понятны из комментариев к нему.