Дизассемблер.

Тема в разделе "WASM.ASSEMBLER", создана пользователем Mika0x65, 6 июл 2009.

  1. Mika0x65

    Mika0x65 New Member

    Публикаций:
    0
    Регистрация:
    30 июл 2005
    Сообщения:
    1.384
    Мое почтение всем.

    Пишу (точнее, почти написал) дизассемблер. Есть несколько вопросов по поводу внешнего интерфейса. Т.е. как будет удобнее работать с дизассемблером. Итак:

    1. Размер таблиц. Сейчас размер всех таблиц составляет около 120 KB. Существует мнение, что это много. С другой стороны, ничего лишнего в таблицах нет (как мне кажется), поэтому выкидывать ничего не хочется. Чтобы было понятно, о чем речь, вот структура элемента таблицы:

    Код (Text):
    1. struct INTERNAL_OPERAND
    2. {
    3.     uint8_t type;
    4.     uint8_t size;
    5. };
    6.  
    7. #pragma pack(push, 0x4)
    8. struct OPCODE
    9. {
    10.     uint16_t id;  //ID инструкции.
    11.     uint64_t group; //Группы, в которые инструкция входит.
    12.     char *mnemonic;
    13.     struct INTERNAL_OPERAND ops[3]; //Три операнда.
    14.     uint16_t props; //Свойства инструкции. Например, PROP_MODRM, PROP_RING0, etc.
    15.     uint8_t tested_flags; //Флаги, которые тестирует инструкция.
    16.     uint8_t modified_flags;  //Флаги, которые модифицирует инструкция.
    17.     uint8_t set_flags;  //Флаги, которые устанавливает инструкция.
    18.     uint8_t cleared_flags; //Флаги, которые очищает инструкция.
    19.     uint8_t undefined_flags; //Неопределнные флаги.
    20.     uint8_t arch; //Архитектура, к которой принадлежит инструкция.
    21. };
    22. #pragma pack(pop)
    Т.е., как мне кажется, ни добавить, ни убавить. pack (4) необходим хотя бы потому, что есть uint64_t. Выравнивание на 8 для 32битного процессора не нужно, а у кого 64 бита могут выравнять и по 8, если сильно приспичит. Выравнивание на 1 дает примерно 90 KB на таблицы, что тоже довольно много. Есть мысль вынести флаги в отдельную таблицу (черт, я ж с ума сойду генератор таблиц переписывать), т.к. для MMX/SSE инструкций флагов у меня нет. На сколько при этом уменьшится таблицы я пока не считал.

    2. Размер выходной структуры. Выходная структура сейчас выглядит так:
    Код (Text):
    1. #pragma pack(push, 0x4)
    2. struct DISPLACEMENT
    3. {
    4.     uint8_t size;
    5.     uint8_t offset;
    6.     union VALUE
    7.     {
    8.         int16_t d16;
    9.         int32_t d32;
    10.         int64_t d64;
    11.     } value;
    12. };
    13.  
    14. struct INSTRUCTION
    15. {
    16.     uint16_t id; //ID инструкции.
    17.     uint16_t flags; //Флаги инструкции.
    18.     uint16_t prefixes; //Префиксы инструкции.
    19.     uint8_t  len; //Длина инструкции. Скоро умрет, длину буду возвращать как результат дизассемблирования.
    20.     uint8_t  opcode_offset; //Смещение на начало кода операции относительно начала инструкции.
    21.  
    22.     struct OPERAND ops[3]; //Операнды.
    23.     struct DISPLACEMENT disp; //Displacement :).
    24.     uint8_t addrsize; //Размер адреса инструкции.
    25.     uint8_t modrm; //Байт MODRM.
    26.     uint8_t sib; //Байт SIB.
    27.     uint8_t rex; //Префикс REX.
    28.  
    29.     char mnemonic[MAX_MNEMONIC_LEN];
    30. };
    31. #pragma pack(pop)
    Операнд:
    Код (Text):
    1. #pragma pack(push, 0x4)
    2. struct OPERAND
    3. {
    4.     uint8_t flags; //Флаги. Содержат бит присутствия операнда, и тип операнда.
    5.     uint16_t size; //Размер операнда.
    6.  
    7.     union
    8.     {
    9.         struct REG
    10.         {
    11.             uint8_t code;
    12.             uint8_t type;
    13.         } reg;
    14.  
    15.         struct IMM
    16.         {
    17.             uint8_t size;
    18.             uint8_t offset; //Смещение относительно начала инструкции.
    19.             union
    20.             {
    21.                 int8_t  imm8;
    22.                 int16_t imm16;
    23.                 int32_t imm32;
    24.                 int64_t imm64;
    25.             };
    26.         } imm;
    27.  
    28.         union FAR_ADDR
    29.         {
    30.             struct FAR_ADDR32
    31.             {
    32.                 int16_t offset;
    33.                 uint16_t seg;
    34.             } far_addr32;
    35.  
    36.             struct FAR_ADDR48
    37.             {
    38.                 int32_t offset;
    39.                 uint16_t seg;
    40.             } far_addr48;
    41.         } far_addr;
    42.  
    43.         struct ADDR
    44.         {
    45.             uint8_t seg;
    46.             uint8_t mod;
    47.             uint8_t base;
    48.             uint8_t index;
    49.             uint8_t scale;
    50.         } addr;
    51.     } value;
    52. };
    53. #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

    Ну, и, конечно, пожелания/дополнения приветствуются.

    Извините за длинный пост. Заранее благодарю за ответы.
     
  2. s0larian

    s0larian New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    489
    Адрес:
    Крыжёпполь
    Ээ, а кого парит размер таблиц? Сколько у тебя RAM в PC? Для чего проект? Что б вставить в троя или писать мини-IDA?

    Кста, #pragma pack ты понимаешь неверно. Поубирай их - код может заработать быстрее, т.к. структуры будут выровнены на правильные (для платформы) адреса.

    По поводу многопоточности - если ты когда-нить дорастёшь до поедания много-мегабайтных файлов, то многопоточность даст много-кратный прирост скорости на много-ядерных машинах. В IDA, кста, этого нету и она не работает быстрей на таких машинах... что есть тупо.
     
  3. t00x

    t00x New Member

    Публикаций:
    0
    Регистрация:
    15 фев 2007
    Сообщения:
    1.921
    по поводу внешнего интерфейса.
    придумайте (т.е. напишите:) сворачивание блоков кода с возможностью комментирования этих блоков.
     
  4. Sol_Ksacap

    Sol_Ksacap Миша

    Публикаций:
    0
    Регистрация:
    6 мар 2008
    Сообщения:
    623
    Насчёт размера таблиц согласны с #2. Нам кажется, что в этом случае практически не имеет значения – 32k отводить на таблицы или 320k. Хотя если переставить местами некоторые элементы в структуре OPCODE, то для х86 можно:
    * оставить #pragma(4) и сократить размер структуры до 28 байт
    * убрать #pragma и сохранить текущий размер.

    Для него можно добавить в юнион imm_generic, который будет аналогичен imm64 :)
     
  5. Mika0x65

    Mika0x65 New Member

    Публикаций:
    0
    Регистрация:
    30 июл 2005
    Сообщения:
    1.384
    s0larian
    Это очень хороший вопрос. Я стараюсь писать как можно более обще. Т.е. совсем не против, если мой дизассемблер будет использоваться в агрессивных программах :). Но, все же, основная цель -- облегчение последующего анализа кода.

    Насчет непонимания pack, это весьма вероятно, т.к. пост Sol_Ksacap о перестановке меня немного удивил. Насколько я знаю, все члены инструкции выравниваются по размеру максимального члена. Но, похоже, ошибаюсь. Но совсем бирать pack не надо, т.к. размер таки возрастает, это проверено экспериментально. Я исходил из того, что все члены "подтянутся" на 8 байт, что для 32битной архитектуры совсем не приносит никакой пользы, т.к. процессор оперирует максимум четырьмя байтами (за исключением нескольких инструкций). Ну, а у кого 64 бита, тому можно и pack(8), т.к. памяти, наверняка, много.

    Насчет много поточности, тут скорее задел на будущее. Та же IDA, например, работает довольно шустро даже на больших файлах. А учитывая, сколько она при этом делает, то пол-секунды вряд ли сыграют серьезную роль :). Правда, если речь идет о более "агрессивном" анализе (в хорошем смысле этого слова :) ), то, многопоточность, вероятно, будет весьма кстати. Хотя опять же под вопросом. На мой взгляд, основная потеря происходит при потере процессора на операциях ввода/вывода, хотя операционная система пытается скомпенсировать этот фактор. У меня операций ввода/вывода практически нет, кроме вывода результата дизассемблирования. А при анализе результат, вероятно, будет выводиться только в конце.

    t00x
    У меня пока нет "потокового" дизассемблирования. Т.е. дизассемблер, скорее, пока что движек, а не дизассемблер. Для полного дизассемблирования нужно подумать об интерфейсе, т.е либо делать его интерактивным, как в IDA, либо искать мощного вдохновения для автоматического дизассемблирования :).

    Sol_Ksacap
    Интересная новость. Видимо, я неправильно понимаю пакование. Сейчас читать и экспериментировать нет сил, т.к. не соображаю уже. В общем, если завтра не успею разбраться, то опишите, что и как :). Кстати, а вас там как минимум двое сидит? :)

    По union тот же щупатель дизассемблера согласился, что все с ними Ок, можно не менять. imm/disp в любом случае расширяются со знаком до максимального размера (8 байт), поэтому использовать можно любой размер, главное, не меньший оригинала. У меня еще была мысль сделать байтовый union на весь struct, чтобы иметь возможно без ухищрений обращаться к отдельным байтам, но не знаю, стоит ли. Хуже от этого не станет, но исходник более монструозный получится.


    А что насчет REL/IMM? Щупатель возмущен им страшно, а мне убирать жалко и боязно. Вдруг пригодится?

    Ну и комментарии/пожелания/поправления, конечно же, приветствуются :).
     
  6. Clerk

    Clerk Забанен

    Публикаций:
    0
    Регистрация:
    4 янв 2008
    Сообщения:
    6.689
    Адрес:
    РБ, Могилёв
    Mika0x65
    Размер памяти не критичен, выжен размер не распакованного кода/кода в модуле. Поэтому решение - качественное сжатие(LZ плохое).
     
  7. bugaga

    bugaga New Member

    Публикаций:
    0
    Регистрация:
    1 июл 2007
    Сообщения:
    361
    дада.. легкость юзабилити - самое главное. лично мне очень оч. прикалоло CADT engine by MS-REM... перестраиваемое под любые задачи...
    http://pastebin.com/f39b09df6

    помниться, даже чтото получалось с 16-битным х86 эмулем на основе этого проекта, для вызова ф-кций из BIOS в 32-битной Оськи..
    (правда винч потом накрыло от некоректной работы с int13)
     
  8. Sol_Ksacap

    Sol_Ksacap Миша

    Публикаций:
    0
    Регистрация:
    6 мар 2008
    Сообщения:
    623
    Код (Text):
    1. #pragma pack(push, 0x4)
    2. struct OPCODE
    3. {
    4.     uint16_t id;  // ! 2 пустых слота после этого элемента !
    5.     uint64_t group; // элемент выровнен на 4
    6.     char *mnemonic;
    7.     <..>
    8. };
    9. #pragma pack(pop)
    10. // sizeof(OPCODE) == 32 == 2_ok + 2_пустых + 26_ok + 2_для_выравнивания_на_4
    Можно переставить элементы – сначала int64, затем указатели, потом int32, int16 и int8 – тогда не будет пустых элементов. (sizeof(OPCODE) == 28 [ + 4_для_выравнивания_на_8_если_убрать_прагму])

    Маловероятно. Просто часть затянувшегося эксперимента :)

    Мб сделать версию, где мнемоники хранятся в юникоде? Мотивация: допустим, часть программы написана с использованием юникода – тогда использование станет проще, если не нужно будет перегонять строки туда-сюда.
     
  9. CyberManiac

    CyberManiac New Member

    Публикаций:
    0
    Регистрация:
    2 сен 2003
    Сообщения:
    2.473
    Адрес:
    Russia
    Никто ещё не предлагал заменить *char на двухбайтное смещение отно-но начала таблицы мнемоник? Их ведь не настолько много, чтобы не влезть в 64К. Правда экономия в 2 байта на запись - это как-то мелочно. Но ведь возможно.
     
  10. bugaga

    bugaga New Member

    Публикаций:
    0
    Регистрация:
    1 июл 2007
    Сообщения:
    361
    дада.. маленький размер тоже важен! тот же CADT в бинарной массе гдет 15кб весом. и безо всякого пакования (подержка инструкций до P-III).

    /me воюет с obj на предмет путешествия в NTVDM
     
  11. bugaga

    bugaga New Member

    Публикаций:
    0
    Регистрация:
    1 июл 2007
    Сообщения:
    361
    впринципе, применительно к томуже CADT хочу вообще избавиться от строковых мнемоник в "серверной части"... ибо его основная задача распарсить команды на операции/операнды/ и установить длину всего этого дела, о чем и проинформировать вызывающую сторону.. а уж та, пусть и парсит, если оно так надо хоть в AT&T виде хоть в Unicode/Utf8/koi8...
     
  12. Mika0x65

    Mika0x65 New Member

    Публикаций:
    0
    Регистрация:
    30 июл 2005
    Сообщения:
    1.384
    Clerk
    Да, спасибо. Такая мысль была, забыл написать. Добавлено в копилку :).

    bugaga
    Да, легкость -- один из приоритетов. Собственно, потому и спрашиваю, что добавить/убрать, чтобы жить было легче и веселей.

    Sol_Ksacap
    Все, понял насчет выравнивания. Компилятор умнее меня :). Надо подумать. С одной стороны, текущее расположение удобно, но это сугубо на мой вкус. С другой стороны, в эти структуры кроме меня никто лезть не будет, а уж я как-нибудь перетерплю. В общем, приду домой, перегенерирую таблицы, пересчитаю выигрыш.

    А результаты эксперимнта давно пора огласить, общественность интересуется.

    CyberManiac
    Ага, интересное предложение. Два байта, учитывая желание сделать экономно и то, что инструкций довольно много, значительны. Правда, меня сильно смущает, как будет выглядеть поддержка такой организации.

    bugaga
    Ага, вот и конкретные цифры. Кода у меня мало, а таблицы большие. Попробую применить совет Sol_Ksacap, посмотрим, что будет с размером. А вот насчет серверной части и всего прочего, не уловил. В чем кармический смысл?
     
  13. bugaga

    bugaga New Member

    Публикаций:
    0
    Регистрация:
    1 июл 2007
    Сообщения:
    361
    смысл... да так.. сослужить службу в нелехком деле х86-ой эмуляции...
     
  14. Synth

    Synth New Member

    Публикаций:
    0
    Регистрация:
    29 окт 2007
    Сообщения:
    50
    я для своих целей когда то написал туеву хучу кода вокруг движка HDE32, реализовав практически все здесь описанные возможности для х86. но пока я писал меня не покидала мысль что я хернёй маюсь, и надо бы написать с нуля.
    автору респект, если что - могу попытаться заняться бета-тестированием

    из того, что бы я ещё добавил - очень иногда полезно иметь обычные BOOL AffectsFlags и ChecksFlags (ну типа в принципе - смотрит ли инструкция флаги и меняет ли) для быстрого ветвления.
    не надо убирать поле длины инструкции.
    надо сделать юнион на поля модрм и сиб байтов
    и ещё можно сделать типа табличное поле, которое будет показывать какие составные части есть у данного опкода (модрм, сиб, имм/рел и т.д)
    можно добавить поле типа relative link - т.е. ссылка на инструкцию, на которую идёт бранч от данной
    я всё это добавлял чтобы прикрутить конструктор инструкций


    ещё можно кое чего добавить, чтобы была возможность разбиения на функции/блоки...
     
  15. dendi

    dendi New Member

    Публикаций:
    0
    Регистрация:
    3 сен 2007
    Сообщения:
    233
    ещё было бы отлично сделать обратно ассемблер в инструкцию из структуры :)
     
  16. Synth

    Synth New Member

    Публикаций:
    0
    Регистрация:
    29 окт 2007
    Сообщения:
    50
    я как раз таким страдал )))
     
  17. s0larian

    s0larian New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    489
    Адрес:
    Крыжёпполь
    Если парить мозг такой мелочной оптимизацией, то надо хранить не смещение а просто 8ми битный индекс, указывающий в таблицу мнемоник :)
     
  18. s0larian

    s0larian New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    489
    Адрес:
    Крыжёпполь
    Ты не допонял - если бы IDA делала свой анализ/дизасм в нескольких потоках то на 4х-ядерной машине время сократилось бы примерно в 4 раза. (я думаю что эти операции не I/O-bound a CPU-bound, т.к. база/образ в filesystem cache)
     
  19. s0larian

    s0larian New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2004
    Сообщения:
    489
    Адрес:
    Крыжёпполь
    Mika0x65, кста, ты на libdisasm2 (из bastard) смотрел? Там всё это уже давно написано.