Я давно хочу написать свой парсер кода, ну и асм/дизасм мб Чтобы нормальный препроцессор для exe сделать, ну и для всяких других задач типа конвертации x86<->x64 например. К сожалению, нужного (даже похожего) функционала никто не дает, иначе зачем бы я за это брался - хватает опен-соурс либ. Точнее, я нашел одну либу похожую по смыслу, но она коммерческая и без исходников http://dsmhelp.narod.ru/epicasm.htm Так вот, у меня определенные концептуальные проблемы с постановкой задачи: 1. Что собственно писать? парсер инструкций в структуры фиксированного размера? тогда как это отлаживать? А если писать стандартный (текстовый) дизасм/асм, то как быть с беспотерьностью? Одно из приложений для такого парсера - настоящий дизасм-фильтр. Который сможет работать с любыми константами в инструкциях, отслеживать регистры и т.п. Ну как он может быть устроен-то понятно, на примере дурилки той же (компрессор durilca). Кроме адресов в коллах, есть еще адреса в других инструкциях. Конкретно в коллах их легко ловить простым фильтром. В E8-фильтре в paq, кстати, есть поддержка некоторых инструкций MOV [addr32]. Но просто константы - это далеко не все. Есть еще, например, такая вещь, как аллокация регистров в компиляторе. Т.е. компилятор может в разных версиях программы скомпилировать одну и ту же функцию в тот же в принципе код, но использование регистров там обычно в другом порядке. Значит, если каким-то образом (вроде mtf) "нормализовать" кодировку регистров, то можно увеличить количество матчей среди всяких стандартных кодовых шаблонов компилятора и т.п. Для экзешников от intelC это должно сильно помогать, по крайней мере. 2. Что делать с префиксами? У x86 инструкции сейчас может быть до 14 префиксов, а если поддерживать 8086, то бесконечно (ну, до мегабайта реально). Это очень плохо согласуется с дизасмом в структуру фиксированного размера. И даже если брать 14 (исходя из современного ограничения максимальной длины команды) - это много, учитывая что экзешник на 100M кода сейчас может быть запросто. Тут надо учитывать, что в нормальном коде префиксов сейчас практически не используется (ну, segfs для SEH разве что), так что если оставить для них 14 байт буфера, плюс держать в структуре пять индексов для позиций актуальных версий каждого типа префикса, то структура будет занимать минимум вдвое больше памяти, чем надо. Требуется придумать что-то хитрое, чтобы обойти этот вопрос. Факт тот, что в инструкции может быть до 14 префиксов, из них 5 имеющих значение и мне надо: 1) иметь возможность без потерь восстановить такую инструкцию; 2) иметь доступ ко всей полезной информации о поведении инструкции уже в разобранном виде; 3) иметь возможность модификации всей этой полезной информации, и чтобы она отражалась в "восстановленном" коде; Префиксы - на x86 это lock rep/repnz segcs/ds/es/ss/fs/gs op32 addr32 на x64 есть еще, причем допустимы любые повторения и комбинации, причем префиксы из одной группы отменяют эффект предыдущего. Фактически, просто получается инструкция с "дырками". Если я в случайных местах среди 14 префиксов выбрал 5 реально влияющих на инструкцию (с промежутками между ними), во что мне это обратно ассемблировать? А если frontend решил переделать эту инструкцию во что-то другое, но инфа о префиксах осталась старая? А если удалять старые префиксы при изменении инструкции, то поменяется длина инструкции, как тут с сохранением работоспособности кода? При этом, например, нопами добивать после инструкции нельзя - поменяется адрес возврата из call. И перед инструкцией лучше тоже не стоит - поменяется фактическое количество инструкций, и придется все сдвигать. 3. Что делать с кодировкой адресов в x86? Я хочу сделать не как обычно пишут дизасмы - копируют таблички откуда-то, и прикручивают свой блекджек и т.п., а в духе парсер-генераторов. Т.е. чтобы можно было к описанию системы команд добавить описание одной инструкции, перекомпилить - и появилась поддержка этой инструкции. Причем описание именно парсинга хотелось бы сделать в виде битовых полей Что-то такое: 00110111 AAA 11010101 iiiiiiii AAD $1 11010100 iiiiiiii AAM $1 00111111 AAS 0x40 01000rrr INC $1 0x48 01001rrr DEC $1 0x50 01010rrr PUSH $1 0x58 01011rrr POP $1 0x70 0111cccc I8 Jcc $1 (O,NO,B,NB,Z,NZ,BE,NBE,S,NS,P,NP,L,NL,LE,NLE) 0x90 10010rrr XCHG $1,EAX 0xB0 10110bbb I8 MOV $1,$2 (AL,CL,DL,BL,AH,CH,DH,BH) 0xB8 10111rrr I32 MOV $1,$2 0xD8 11011??? ESC $1 но это плохо согласуется с идеей парсинга инструкции в структуру-дескриптор (неясно что в текстовом описании инструкции писать для структуры). А главная проблема - кодировка адресов на x86/x64. В идеале я хотел бы чтобы можно было написать rrrr rrrr rrrr (три регистра по 4 бита) и пользоваться ими как $1,$2,$3 ну или что-то из этой серии. Но в x86 для адресов упакованный код с ограниченным множеством комбинаций. Идеи были такие, например: a) сделать два уровня парсинга - один распакует упакованный адрес в явные поля регистров, а второй уже как в примере выше; b) считать префиксы отдельными инструкциями, которые просто устанавливают какие-то внутренние флаги; Проблема в том, что (b), скажем, хорошо решает проблему с неэффективностью хранения префиксов, но сильно усложняет логику алгоритма более высокого уровня, которому придется работать уже не с одной статической структурой на инструкцию, а с чем-то переменной длины (как и если просто сделать дескриптор инструкции динамической структурой). А у вариант (a) проблема в том, что код x86 так устроен, что большую часть кода уже надо уметь парсить, чтобы до кодировки адресов вообще добраться. 4. Наложение кода инструкций. Процессор работает с инструкциями, как с данными. Что в регистр адреса инструкции записали (EIP/RIP), оттуда и начинает парсить код и выполнять. А у дизассемблера ситуация сложнее - он должен создавать отдельный дескриптор для каждой инструкции, но код самих этих инструкций при этом может частично перекрываться по адресам. .00401E26: A9 66 E8 00 00 test eax,00000E866 .00401E27: 66 E8 00 00 call 00001E2B Т.е. если написать умный дизасм, который отследит команды перехода в программе, и реально прочитает обе инструкции, то возникнут проблемы с ассемблированием после модификации такого кода. 5. Самомодификация кода. Есть еще такая задача, как трассировка работы программы. Сейчас мне это кажется более реальным подходом для различного рода трансформаций кода (типа той же декомпиляции), чем статический анализ, т.к. даже без всяких защит, современный код GUI программ обычно не имеет линейной связности. Получается, что надежно восстановить логику работы программы можно только при выполнении. Но что делать, если при выполнении используется самомодификация или генерация кода? Конечно, можно адреса инструкций в трассе вообще не учитывать при обработке кода, а создание функций и циклов реализовать за счет нахождения повторений. Но простейшая самомодификация легко "победит" такой алгоритм, т.к. он, скажем, не сможет свернуть цикл, в котором инкрементируется константа в инструкции типа mov eax,0.