Дизассемблирование

Содержание

Введение

1. Идентификация математических операторов

1.1 Идентификация оператора "+"

1.2 Идентификация оператора "-"

1.3 Идентификация оператора "/"

1.4 Идентификация оператора "%"

1.5 Идентификация оператора "*"

1.6 Комплексные операторы

2. Идентификация SWITCH - CASE – BREAK

2.1 Отличия switch от оператора case языка Pascal

2.2 Обрезка (балансировка) длинных деревьев

2.3 Сложные случаи балансировки

или оптимизирующая балансировка

2.4 Ветвления в case-обработчиках.

Заключение

Введение

Дизассемблирование (От англ. disassemble - разбирать, демонтировать) – это процесс или способ получения исходного текста программы на ассемблере из программы в машинных кодах. Полезен при определении степени оптимальности транслятора и при генерации кодов собственной программы. Позволяет понять алгоритм или метод построения программ, у которых отсутствуют исходные тексты. Существуют специальные программы дизассемблеры, которые выполняют этот процесс.

Одним из передовых продуктов для дизассемблирования программ является пакет программ от CSO Computer Services - IDA (Interactive Disassembler). IDA не является автоматическим дизассемблером. Это означает, что IDA выполняет дизассемблирование лишь тех участков программного кода, на которые имеются явные ссылки. При этом IDA старается извлечь из кода максимум информации, не делая никаких излишних предположений. После завершения предварительного анализа программы, когда все обнаруженные явные ссылки исчерпаны, IDA останавливается и ждет вмешательства; просмотрев готовые участки текста, можно как бы подсказать ей, что нужно делать дальше. После каждого вмешательства снова запускается автоматический анализатор IDA, который на основе полученных сведений пытается продолжить дизассемблирование.

IDA является не только дизассемблером, но и одним из самых мощных средств исследования программ. Это возможно благодаря наличию развитой навигационной системы, позволяющей быстро перемещаться между различными точками программы, объектами и ссылками на них, отыскивать неявные ссылки и т.д. Исследование даже больших и сложных программ в IDA занимает в десятки и сотни раз меньше времени, чем путем просмотра текста, полученного обычным дизассемблером.

Целью, данной работы, является задача дизассемблирования программ написанных на языке программирования C/C++ и скомпилированных на компиляторах Microsoft Visual C++ 6.0, Borland C++ 5.0 и WATCOM.

В процесс дизассемблирования входит:

1) идентификация математических операций таких как: сложение, вычитание, деление, операция вычисления остатка, умножение и определения комплексных операций;

2) Идентификация операторов SWITCH - CASE – BREAK.

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

1. Идентификация математических операторов

1.1 Идентификация оператора "+"

В общем случае оператор "+" транслируется либо в машинную инструкцию ADD, "перемалывающую" целочисленные операнды, либо в инструкцию FADDx, обрабатывающую вещественные значения. Оптимизирующие компиляторы могут заменять "ADD xxx, 1" более компактной командой "INC xxx", а конструкцию "c = a + b + const" транслировать в машинную инструкцию "LEA c, [a + b + const]". Такой трюк позволяет одним махом складывать несколько переменных, возвратив полученную сумму в любом регистре общего назначения, - не обязательно в левом слагаемом как это требует мнемоника команды ADD. Однако, "LEA" не может быть непосредственно декомпилирована в оператор "+", поскольку она используется не только для оптимизированного сложения (что, в общем-то, побочный продукт ее деятельности), но и по своему непосредственному назначению - вычислению эффективного смещения. Рассмотрим следующий пример: main(){int a, b,c;c = a + b;printf("%x\n",c);c=c+1;printf("%x\n",c); }

Демонстрация оператора "+"

Результат его компиляции компилятором Microsoft Visual C++ 6.0 с настройками по умолчанию должен выглядеть так: main proc near ; CODE XREF: start+AF p var_c = dword ptr -0Chvar_b = dword ptr -8var_a = dword ptr -4 push ebpmov ebp, esp; Открываем кадр стека sub esp, 0Ch; Резервируем память для локальных переменных mov eax, [ebp+var_a]; Загружаем в EAX значение переменной var_a add eax, [ebp+var_b]; Складываем EAX со значением переменной var_b и записываем результат в EAX mov [ebp+var_c], eax; Копируем сумму var_a и var_b в переменную var_c, следовательно:; var_c = var_a + var_b mov ecx, [ebp+var_c]push ecxpush offset asc_406030 ; "%x\n"call _printfadd esp, 8; printf("%x\n", var_c) mov edx, [ebp+var_c]; Загружаем в EDX значение переменной var_c add edx, 1; Складываем EDX со значением 0х1, записывая результат в EDX mov [ebp+var_c], edx; Обновляем var_c; var_c = var_c +1 mov eax, [ebp+var_c]push eaxpush offset asc_406034 ; "%x\n"call _printfadd esp, 8; printf("%\n",var_c) mov esp, ebppop ebp; Закрываем кадр стека retn main endp

Теперь посмотрим, как будет выглядеть тот же самый пример, скомпилированный с ключом "/Ox" (максимальная оптимизация): main proc near ; CODE XREF: start+AF ppush ecx; Резервируем место для одной локальной переменной; (компилятор посчитал, что три переменные можно ужать в одну и это дейст. так) mov eax, [esp+0]; Загружаем в EAX значение переменной var_a mov ecx, [esp+0]; Загружаем в EAX значение переменной var_b; (т.к .переменная не инициализирована загружать можно откуда угодно) push esi; Сохраняем регистр ESI в стеке lea esi, [ecx+eax]; Используем LEA для быстрого сложения ECX и EAX с последующей записью суммы; в регистр ESI; "Быстрое сложение" следует понимать не в смысле, что команда LEA выполняется; быстрее чем ADD, - количество тактов той и другой одинаково, но LEA; позволяет избавиться от создания временной переменной для сохранения; промежуточного результата сложения, сразу направляя результат в ESI; Таким образом, эта команда декомпилируется как; reg_ESI = var_a + var_b push esipush offset asc_406030 ; "%x\n"call _printf; printf("%x\n", reg_ESI) inc esi; Увеличиваем ESI на единицу; reg_ESI = reg_ESI + 1 push esipush offset asc_406034 ; "%x\n"call _printfadd esp, 10h; printf("%x\n", reg_ESI) pop esipop ecxretn main endp

Остальные компиляторы (Borland C++, WATCOM C) генерируют приблизительно идентичный код, поэтому, приводить результаты бессмысленно - никаких новых "изюминок" они в себе не несут.


Страница: