Мое почтение всем. Пишу (точнее, почти написал) дизассемблер. Есть несколько вопросов по поводу внешнего интерфейса. Т.е. как будет удобнее работать с дизассемблером. Итак: 1. Размер таблиц. Сейчас размер всех таблиц составляет около 120 KB. Существует мнение, что это много. С другой стороны, ничего лишнего в таблицах нет (как мне кажется), поэтому выкидывать ничего не хочется. Чтобы было понятно, о чем речь, вот структура элемента таблицы: Код (Text): struct INTERNAL_OPERAND { uint8_t type; uint8_t size; }; #pragma pack(push, 0x4) struct OPCODE { uint16_t id; //ID инструкции. uint64_t group; //Группы, в которые инструкция входит. char *mnemonic; struct INTERNAL_OPERAND ops[3]; //Три операнда. uint16_t props; //Свойства инструкции. Например, PROP_MODRM, PROP_RING0, etc. uint8_t tested_flags; //Флаги, которые тестирует инструкция. uint8_t modified_flags; //Флаги, которые модифицирует инструкция. uint8_t set_flags; //Флаги, которые устанавливает инструкция. uint8_t cleared_flags; //Флаги, которые очищает инструкция. uint8_t undefined_flags; //Неопределнные флаги. uint8_t arch; //Архитектура, к которой принадлежит инструкция. }; #pragma pack(pop) Т.е., как мне кажется, ни добавить, ни убавить. pack (4) необходим хотя бы потому, что есть uint64_t. Выравнивание на 8 для 32битного процессора не нужно, а у кого 64 бита могут выравнять и по 8, если сильно приспичит. Выравнивание на 1 дает примерно 90 KB на таблицы, что тоже довольно много. Есть мысль вынести флаги в отдельную таблицу (черт, я ж с ума сойду генератор таблиц переписывать), т.к. для MMX/SSE инструкций флагов у меня нет. На сколько при этом уменьшится таблицы я пока не считал. 2. Размер выходной структуры. Выходная структура сейчас выглядит так: Код (Text): #pragma pack(push, 0x4) struct DISPLACEMENT { uint8_t size; uint8_t offset; union VALUE { int16_t d16; int32_t d32; int64_t d64; } value; }; struct INSTRUCTION { uint16_t id; //ID инструкции. uint16_t flags; //Флаги инструкции. uint16_t prefixes; //Префиксы инструкции. uint8_t len; //Длина инструкции. Скоро умрет, длину буду возвращать как результат дизассемблирования. uint8_t opcode_offset; //Смещение на начало кода операции относительно начала инструкции. struct OPERAND ops[3]; //Операнды. struct DISPLACEMENT disp; //Displacement :). uint8_t addrsize; //Размер адреса инструкции. uint8_t modrm; //Байт MODRM. uint8_t sib; //Байт SIB. uint8_t rex; //Префикс REX. char mnemonic[MAX_MNEMONIC_LEN]; }; #pragma pack(pop) Операнд: Код (Text): #pragma pack(push, 0x4) struct OPERAND { uint8_t flags; //Флаги. Содержат бит присутствия операнда, и тип операнда. uint16_t size; //Размер операнда. union { struct REG { uint8_t code; uint8_t type; } reg; struct IMM { uint8_t size; uint8_t offset; //Смещение относительно начала инструкции. union { int8_t imm8; int16_t imm16; int32_t imm32; int64_t imm64; }; } imm; union FAR_ADDR { struct FAR_ADDR32 { int16_t offset; uint16_t seg; } far_addr32; struct FAR_ADDR48 { int32_t offset; uint16_t seg; } far_addr48; } far_addr; struct ADDR { uint8_t seg; uint8_t mod; uint8_t base; uint8_t index; uint8_t scale; } addr; } value; }; #pragma pack (pop) Опять же, из-за наличия int64_t необходимо выравнивать как минимум на 4. Вопросы по этим структурам такие: чего не хватает? Или что-то лишнее? Человек, пощупавший дизассемблер, сказал, что imm8/16/32/64 не очень удобны, т.к. приходится делать четыре ветвления. С одной стороны, это не нужно, т.к. imm/disp расширяются со знаком до максимального значения. С другой, я просто не представляю, как сделать доступ к imm/disp лучше. Еще один вопрос связан со смещениями opcode_offset и смещениями к imm/disp. Сейчас они занимают один байт (относительно начала инструкции). Насколько это удобно? Может, стоит таки сделать их в абсолютного адреса? При выравнивании на 4/8 размер 1 байт мало что дает. Но выравнивание -- отдельный вопрос. Стоит ли выравнивать структуры на границу 4/8 байт? Выравнивание на 1 байт дает неплохое преимущество в размере, а это, как мне кажется, важно. Особенно при дизассемблировании больших кусков кода. Упадет ли при этом скорость -- непонятно. И еще: нужна ли дизассемблеру многопоточность? Я сейчас стараюсь избегать глобальных переменных. Во-первых, они сами по себе неприятны, во-вторых, если будет многопоточность, то их придется либо защищать, либо класть в TLS. И то и другое мне не очень улыбается, т.к. придется писать разные версии для разных платформ. И еще: человек, который попробовал дизассемблер, жалуется, что тип операнда REL (relative, операнд относительных call/jcc/jmp) избыточен, и его можно заменить на IMM (immediate). Сейчас так оно и работает, но что-то я боюсь убирать REL. Кому-нибудь он может пригодиться? Полностью заголовочные можно увидеть тут: disasm.h: http://pastebin.com/m58f38c4e tables.h: http://pastebin.com/m317445fd Ну, и, конечно, пожелания/дополнения приветствуются. Извините за длинный пост. Заранее благодарю за ответы.
Ээ, а кого парит размер таблиц? Сколько у тебя RAM в PC? Для чего проект? Что б вставить в троя или писать мини-IDA? Кста, #pragma pack ты понимаешь неверно. Поубирай их - код может заработать быстрее, т.к. структуры будут выровнены на правильные (для платформы) адреса. По поводу многопоточности - если ты когда-нить дорастёшь до поедания много-мегабайтных файлов, то многопоточность даст много-кратный прирост скорости на много-ядерных машинах. В IDA, кста, этого нету и она не работает быстрей на таких машинах... что есть тупо.
по поводу внешнего интерфейса. придумайте (т.е. напишите сворачивание блоков кода с возможностью комментирования этих блоков.
Насчёт размера таблиц согласны с #2. Нам кажется, что в этом случае практически не имеет значения – 32k отводить на таблицы или 320k. Хотя если переставить местами некоторые элементы в структуре OPCODE, то для х86 можно: * оставить #pragma(4) и сократить размер структуры до 28 байт * убрать #pragma и сохранить текущий размер. Для него можно добавить в юнион imm_generic, который будет аналогичен imm64
s0larian Это очень хороший вопрос. Я стараюсь писать как можно более обще. Т.е. совсем не против, если мой дизассемблер будет использоваться в агрессивных программах . Но, все же, основная цель -- облегчение последующего анализа кода. Насчет непонимания pack, это весьма вероятно, т.к. пост Sol_Ksacap о перестановке меня немного удивил. Насколько я знаю, все члены инструкции выравниваются по размеру максимального члена. Но, похоже, ошибаюсь. Но совсем бирать pack не надо, т.к. размер таки возрастает, это проверено экспериментально. Я исходил из того, что все члены "подтянутся" на 8 байт, что для 32битной архитектуры совсем не приносит никакой пользы, т.к. процессор оперирует максимум четырьмя байтами (за исключением нескольких инструкций). Ну, а у кого 64 бита, тому можно и pack(8), т.к. памяти, наверняка, много. Насчет много поточности, тут скорее задел на будущее. Та же IDA, например, работает довольно шустро даже на больших файлах. А учитывая, сколько она при этом делает, то пол-секунды вряд ли сыграют серьезную роль . Правда, если речь идет о более "агрессивном" анализе (в хорошем смысле этого слова ), то, многопоточность, вероятно, будет весьма кстати. Хотя опять же под вопросом. На мой взгляд, основная потеря происходит при потере процессора на операциях ввода/вывода, хотя операционная система пытается скомпенсировать этот фактор. У меня операций ввода/вывода практически нет, кроме вывода результата дизассемблирования. А при анализе результат, вероятно, будет выводиться только в конце. t00x У меня пока нет "потокового" дизассемблирования. Т.е. дизассемблер, скорее, пока что движек, а не дизассемблер. Для полного дизассемблирования нужно подумать об интерфейсе, т.е либо делать его интерактивным, как в IDA, либо искать мощного вдохновения для автоматического дизассемблирования . Sol_Ksacap Интересная новость. Видимо, я неправильно понимаю пакование. Сейчас читать и экспериментировать нет сил, т.к. не соображаю уже. В общем, если завтра не успею разбраться, то опишите, что и как . Кстати, а вас там как минимум двое сидит? По union тот же щупатель дизассемблера согласился, что все с ними Ок, можно не менять. imm/disp в любом случае расширяются со знаком до максимального размера (8 байт), поэтому использовать можно любой размер, главное, не меньший оригинала. У меня еще была мысль сделать байтовый union на весь struct, чтобы иметь возможно без ухищрений обращаться к отдельным байтам, но не знаю, стоит ли. Хуже от этого не станет, но исходник более монструозный получится. А что насчет REL/IMM? Щупатель возмущен им страшно, а мне убирать жалко и боязно. Вдруг пригодится? Ну и комментарии/пожелания/поправления, конечно же, приветствуются .
Mika0x65 Размер памяти не критичен, выжен размер не распакованного кода/кода в модуле. Поэтому решение - качественное сжатие(LZ плохое).
дада.. легкость юзабилити - самое главное. лично мне очень оч. прикалоло CADT engine by MS-REM... перестраиваемое под любые задачи... http://pastebin.com/f39b09df6 помниться, даже чтото получалось с 16-битным х86 эмулем на основе этого проекта, для вызова ф-кций из BIOS в 32-битной Оськи.. (правда винч потом накрыло от некоректной работы с int13)
Код (Text): #pragma pack(push, 0x4) struct OPCODE { uint16_t id; // ! 2 пустых слота после этого элемента ! uint64_t group; // элемент выровнен на 4 char *mnemonic; <..> }; #pragma pack(pop) // sizeof(OPCODE) == 32 == 2_ok + 2_пустых + 26_ok + 2_для_выравнивания_на_4 Можно переставить элементы – сначала int64, затем указатели, потом int32, int16 и int8 – тогда не будет пустых элементов. (sizeof(OPCODE) == 28 [ + 4_для_выравнивания_на_8_если_убрать_прагму]) Маловероятно. Просто часть затянувшегося эксперимента Мб сделать версию, где мнемоники хранятся в юникоде? Мотивация: допустим, часть программы написана с использованием юникода – тогда использование станет проще, если не нужно будет перегонять строки туда-сюда.
Никто ещё не предлагал заменить *char на двухбайтное смещение отно-но начала таблицы мнемоник? Их ведь не настолько много, чтобы не влезть в 64К. Правда экономия в 2 байта на запись - это как-то мелочно. Но ведь возможно.
дада.. маленький размер тоже важен! тот же CADT в бинарной массе гдет 15кб весом. и безо всякого пакования (подержка инструкций до P-III). /me воюет с obj на предмет путешествия в NTVDM
впринципе, применительно к томуже CADT хочу вообще избавиться от строковых мнемоник в "серверной части"... ибо его основная задача распарсить команды на операции/операнды/ и установить длину всего этого дела, о чем и проинформировать вызывающую сторону.. а уж та, пусть и парсит, если оно так надо хоть в AT&T виде хоть в Unicode/Utf8/koi8...
Clerk Да, спасибо. Такая мысль была, забыл написать. Добавлено в копилку . bugaga Да, легкость -- один из приоритетов. Собственно, потому и спрашиваю, что добавить/убрать, чтобы жить было легче и веселей. Sol_Ksacap Все, понял насчет выравнивания. Компилятор умнее меня . Надо подумать. С одной стороны, текущее расположение удобно, но это сугубо на мой вкус. С другой стороны, в эти структуры кроме меня никто лезть не будет, а уж я как-нибудь перетерплю. В общем, приду домой, перегенерирую таблицы, пересчитаю выигрыш. А результаты эксперимнта давно пора огласить, общественность интересуется. CyberManiac Ага, интересное предложение. Два байта, учитывая желание сделать экономно и то, что инструкций довольно много, значительны. Правда, меня сильно смущает, как будет выглядеть поддержка такой организации. bugaga Ага, вот и конкретные цифры. Кода у меня мало, а таблицы большие. Попробую применить совет Sol_Ksacap, посмотрим, что будет с размером. А вот насчет серверной части и всего прочего, не уловил. В чем кармический смысл?
я для своих целей когда то написал туеву хучу кода вокруг движка HDE32, реализовав практически все здесь описанные возможности для х86. но пока я писал меня не покидала мысль что я хернёй маюсь, и надо бы написать с нуля. автору респект, если что - могу попытаться заняться бета-тестированием из того, что бы я ещё добавил - очень иногда полезно иметь обычные BOOL AffectsFlags и ChecksFlags (ну типа в принципе - смотрит ли инструкция флаги и меняет ли) для быстрого ветвления. не надо убирать поле длины инструкции. надо сделать юнион на поля модрм и сиб байтов и ещё можно сделать типа табличное поле, которое будет показывать какие составные части есть у данного опкода (модрм, сиб, имм/рел и т.д) можно добавить поле типа relative link - т.е. ссылка на инструкцию, на которую идёт бранч от данной я всё это добавлял чтобы прикрутить конструктор инструкций ещё можно кое чего добавить, чтобы была возможность разбиения на функции/блоки...
Если парить мозг такой мелочной оптимизацией, то надо хранить не смещение а просто 8ми битный индекс, указывающий в таблицу мнемоник
Ты не допонял - если бы IDA делала свой анализ/дизасм в нескольких потоках то на 4х-ядерной машине время сократилось бы примерно в 4 раза. (я думаю что эти операции не I/O-bound a CPU-bound, т.к. база/образ в filesystem cache)