Как я переписывал 100 килобайт x86-кода на чистый Си (Russian-only post so far, to be translated)

То была DLL-ка с секцией кода ~100 килобайт, она брала на вход многоканальный сигнал и выдавала другой многоканальный сигнал. Там много всего было связано с обработкой сигналов. Внутри было очень много FPU-кода. Написано по-олдскульному, так, как писали в то время, когда передача параметров через аргументы ф-ций была дорогой, и потому использовалось много глобальных переменных и массивов, почти всё хранилось в них, а ф-ции, напротив, имели сравнительно мало аргументов, если вообще. Функции большие, их было около ста.

Тесты были, много.

Проблема была в том, что функции слишком большие и Hex-Rays неизменно выдавал немного неверный код. Нужно было очень внимательно всё чистить вручную. В процессе работы, я нашел в нем каких-то баг: https://github.com/DennisYurichev/RE-for-beginners/blob/master/other/hexrays_RU.tex.

Все 100 ф-ций декомпилировать сразу нельзя -- где-то будут ошибки, тесты не пройдут, и где вы будете искать эти ошибки? Приходится переписывать по чуть-чуть.

В DLL-ке есть некая корневая ф-ция, скажем, ProcessMain(). Я переписываю её на Си при помощи Hex-Rays, она запускается из обычного .exe-процесса. Все ф-ции из DLL-ки, которые вызываются далее, у меня вызывались через указатели на ф-ции. DLL-ка загружена, и пока они все там.

ASLR отключил, и DLL-ка каждый раз грузится по одному и тому же адресу, потому и адреса всех ф-ций одни и те же. Важно, что и адреса глобальных массивов тоже одни и те же.

Затем переписываю ф-ции, вызывающиеся непосредственно из ProcessMain(), затем еще ниже, итд. Таким образом, ф-ции я постепенно перетаскивал из DLL в свою .exe. Каждый раз тестируя.

Много раз бывало и так -- ф-ция слишком большая, например, несколько килобайт x86-кода, и после декомпиляции в Си, там что-то косячит, неизвестно где. Из IDA я экспортировал её листинг в текст на ассемблере и компилировал при помощи обычного ассемблера (ML в MSVC). Она компилируется в .obj-файл и прикомпилируется к главной .exe, и пока всё ОК. Затем я делил эту ф-цию на более мелкие, здорово пригодился (когда бы еще?) опыт написания программ на чистом ассемблере в середине 90-х (руки до сих пор помнят). Если всё работает, более мелкие ф-ции постепенно переписывал на Си при помощи Hex-Rays, в то время как "головная" ф-ция более высокого уровня всё еще на ассемблере.

Интересно, что было много глобальных массивов, но границы между ними были сильно размыты. Но я вижу что есть какой-то большой кусок в секции .data, где лежит всё подряд. Дошел до стадии, когда на Си переписано уже всё, а все обращения к массивам происходят по адресам внутри секции .data в подгружаемой DLL-ке, впрочем, там почти не было констант. Затем, чтобы совсем отказаться от DLL-ки, я сделал большой глобальный "кусок" уже у себя на Си, и вся работа с массивами шла через мой "кусок", при том, что все массивы всё еще не были отделены друг от друга.

Вот реальный фрагмент оттуда, как было в начале. Значение -- это адрес в .data-секции в DLL-ке:

int *a_val511=0x1002B588;
int *a_val483=0x1002B590;
int *a_val481=0x1002B5B8;
int *a_val515=0x1002B6E4;
...

И все обращения происходят через указатели.

Потом я сделал "кусок":

char lump[0x1000000];

/* 0x1002B588 */int *a_val511=(int*)&lump[0x2B588];
/* 0x1002B590 */int *a_val483=(int*)&lump[0x2B590];
/* 0x1002B5B8 */int *a_val481=(int*)&lump[0x2B5B8];
/* 0x1002B6E4 */int *a_val515=(int*)&lump[0x2B6E4];
...

DLL-ку теперь можно было наконец-то отцепить и разбираться с границами массивов. Этот процесс я хотел немного автоматизировать и использовал для этого Pin. Я написал утилиту, которая показывала, по каким адресам в глобальном "куске" были обращения из каждого адреса. Точнее, в каких пределах? Так стало проще видеть границы массивов.

"На войне все средства хороши", так что я доходил и до того, что использовал Mathematica и Z3 для сокращения слишком длинных выражений (Hex-Rays не всё может оптимизировать): https://github.com/DennisYurichev/SAT_SMT_by_example/blob/master/proofs/simplify_EN.tex

Очень хорошим тестом было пересобрать всё под Linux при помощи GCC и заставить работать -- как всегда, это было нелегко. Плюс, чтобы работало корректно и под x86 и под x64.


→ [list of blog posts]

Please drop me email about any bug(s) and suggestion(s): dennis(@)yurichev.com.