Автор: Пользователь скрыл имя, 19 Октября 2011 в 15:00, реферат
Дизассемблирование (От англ. disassemble - разбирать, демонтировать) – это процесс или способ получения исходного текста программы на ассемблере из программы в машинных кодах. Полезен при определении степени оптимальности транслятора и при генерации кодов собственной программы. Позволяет понять алгоритм или метод построения программ, у которых отсутствуют исходные тексты. Существуют специальные программы дизассемблеры, которые выполняют этот процесс.
Введение.
Структура природы человека.
Биологическое и социальное в человеке.
Роль биологических и географических факторов в формировании социальной жизни.
Социальная жизнь.
shl eax, 4
; Умножаем
var_a на 16
push eax
push offset aXXX ; "%x %x %x\n"
call _printf
add esp, 14h
; printf("%x
%x %x\n", var_a * 16, var_a * 4 + 5, var_a * 13)
retn
main endp
Этот код, правда, все же не быстрее предыдущего, не оптимизированного, и укладывается в те же три такта, но в других случаях выигрыш может оказаться вполне ощутимым.
Другие
компиляторы так же используют LEA
для быстрого умножения чисел. Вот,
к примеру, Borland поступает так:
_main proc near ; DATA XREF: DATA:00407044 o
lea edx, [eax+eax*2]
; EDX = var_a*3
mov ecx, eax
; Загружаем
в ECX неинициализированную
shl ecx, 2
; ECX = var_a
* 4
push ebp
; Сохраняем
EBP
add ecx, 5
; Добавляем к var_a * 4 значение 5
; К сожалению
Borland не использует LEA для сложения.
lea edx, [eax+edx*4]
; EDX = var_a + (var_a *3) *4 = var_a * 13
; Здесь
Borland и MS единодушны.
mov ebp, esp
; Открываем кадр стека в середине функции.
; Выше
находтся "потерянная" команда push
EBP
push edx
; Передаем
printf произведение var_a * 13
shl eax, 4
; Умножаем ((var_a *4) + 5) на 16
; Здесь проискодит глюк компилятора, посчитавшего что: раз переменная var_a
; неинициализирована,
то ее можно и не загружать…
push ecx
push eax
push offset aXXX ; "%x %x %x\n"
call printf
add esp, 10h
xor eax, eax
pop ebp
retn
_main endp
Хотя
"визуально" Borland генерирует не очень
“красивый” код, его выполнение укладывается
в те же три такта процессора. Другое дело
WATCOM, показывающий удручающе отсталый
результат на фоне двух предыдущих компиляторов:
main proc near
push ebx
; Сохраняем
EBX в стеке
mov eax, ebx
; Загружаем
в EAX значение неинициализированной
регистровой переменной var_a
shl eax, 2
; EAX = var_a
* 4
sub eax, ebx
; EAX = var_a * 4 - var_a = var_a * 3
; Здесь
WATCOM! Сначала умножает "с запасом",
а потом лишнее отнимает!
shl eax, 2
; EAX = var_a
* 3 * 4 = var_a * 12
add eax, ebx
; EAX = var_a * 12 + var_a = var_a * 13
; Четыре инструкции, в то время как
; Microsoft
Visual C++ вполне обходится и двумя!
push eax
; Передаем
printf значение var_a * 13
mov eax, ebx
; Загружаем
в EAX значение неинициализированной
регистровой переменной var_a
shl eax, 2
; EAX = var_a
* 4
add eax, 5
; EAX = var_a * 4 + 5
; Пользоваться
LEA WATCOM то же не умеет!
push eax
; Передаем
printf значение var_a * 4 + 5
shl ebx, 4
; EBX = var_a
* 16
push ebx
; Передаем
printf значение var_a * 16
push offset aXXX ; "%x %x %x\n"
call printf_
add esp, 10h
; printf("%x
%x %x\n",var_a * 16, var_a * 4 + 5, var_a*13)
pop ebx
retn
main_ endp
В
результате, код, сгенерированный компилятором
WATCOM требует шести тактов, т.е. вдвое
больше, чем у конкурентов.
§1.6
Комплексные операторы
Язык Си\Си++ выгодно отличается от большинства своих конкурентов поддержкой комплексных операторов: x= (где x - любой элементарный оператор), ++ и - -. Комплексные операторы семейства "a x= b" транслируются в "a = a x b" и они идентифицируются так же, как и элементарные операторы. Операторы "++" и "--": в префиксной форме они выражаются в тривиальные конструкции "a = a +1" и "a = a - 1" и не представляющие никакого интереса.
Для
улучшения читабельности
Легко показать, что switch эквивалентен конструкции "IF (a == x1) THEN оператор1 ELSE IF (a == x2) THEN оператор2 IF (a == x2) THEN оператор2 IF (a == x2) THEN оператор2 ELSE …. оператор по умолчанию". Если изобразить это ветвление в виде логического дерева, то образуется характерная "косичка", прозванная так за сходство с завитой в косу прядью волос – см. рис. 1
Казалось
бы, идентифицировать switch никакого труда
не составит, – даже не строя дерева, невозможно
не обратить внимания на длинную цепочку
гнезд, проверяющих истинность условия
равенства некоторой переменной с серией
непосредственных значений (сравнения
переменной с другой переменной switch не
допускает).
Рисунок
1 Трансляция оператора
switch в общем случае
Однако
в реальной жизни все происходит
совсем не так. Компиляторы (даже не оптимизирующие)
транслируют switch в настоящий "мясной
рулет", содержащий всевозможные операции
отношений. Вот что из этого выйдет, откомпилировав
приведенный выше пример компилятором
Microsoft Visual C++:
main proc near ;
CODE XREF: start+AF p
var_tmp = dword ptr -8
var_a = dword ptr
–4
push ebp
mov ebp, esp
; Открываем
кадр стека
sub esp, 8
; Резервируем
место для локальных переменных
mov eax, [ebp+var_a]
; Загружаем
в EAX значение переменной var_a
mov [ebp+var_tmp], eax
; Обратите внимание – switch создает собственную временную переменную!
; Даже
если значение сравниваемой
; будет изменено, это не повлияет на результат выборов!
; В дальнейшем во избежании путаницы, следует условно называть
; переменную
var_tmp переменной var_a
cmp [ebp+var_tmp], 2
; Сравниваем значение переменной var_a с двойкой
; В исходном
коде CASE начинался с нуля, а заканчивался
0x666
jg short loc_401026
; Переход, если var_a > 2
; Обратите
на этот момент особое
; операции отношения не было!
; Причем, этот переход не ведет к вызову функции printf, т.е. этот фрагмент
; кода
получен не прямой трансляцией некой ветки
case, а как-то иначе!
cmp [ebp+var_tmp], 2
; Сравниваем значение var_a с двойкой
; Очевидный
"прокол" компилятора (обратите
внимание никакие флаги не менялись!)
jz short loc_40104F
; Переход к вызову printf("a == 2"), если var_a == 2
; Этот
код явно получен трансляцией ветки CASE
2: printf("a == 2")
cmp [ebp+var_tmp], 0
; Сравниваем
var_a с нулем
jz short loc_401031
; Переход к вызову printf("a == 0"), если var_a == 0
; Этот
код получен трансляцией ветки
CASE 0: printf("a == 0")
cmp [ebp+var_tmp], 1
; Сравниваем
var_a с единицей
jz short loc_401040
; Переход к вызову printf("a == 1"), если var_a == 1
; Этот
код получен трансляцией ветки
CASE 1: printf("a == 1")
jmp short loc_40106D
; Переход к вызову printf("Default")
; Этот
код получен трансляцией ветки
Default: printf("a == 0")
loc_401026: ; CODE XREF: main+10 j
; Эта ветка получает управление, если var_a > 2
cmp [ebp+var_tmp], 666h
; Сравниваем
var_a со значением 0x666
jz short loc_40105E
; Переход к вызову printf("a == 666h"), если var_a == 0x666
; Этот
код получен трансляцией ветки
CASE 0x666: printf("a == 666h")
jmp short loc_40106D
; Переход к вызову printf("Default")
; Этот
код получен трансляцией ветки
Default: printf("a == 0")
loc_401031: ; CODE XREF: main+1C j
; // printf("A == 0")
push offset aA0 ; "A == 0"
call _printf
add esp, 4
jmp short loc_40107A
; ^^^^^^^^^^^^^^^^^^^^^^ - а вот это оператор break, выносящий управление
; за пределы switch – если бы его не было, то начали бы выполняться все
; остальные ветки CASE, не зависимо от того, к какому значению var_a они
; принадлежат!
loc_401040: ; CODE XREF: main+22 j
; // printf("A == 1")
push offset aA1 ; "A == 1"
call _printf
add esp, 4
jmp short loc_40107A
; ^ break
loc_40104F: ; CODE XREF: main+16 j
; // printf("A == 2")
push offset aA2 ; "A == 2"
call _printf
add esp, 4
jmp short loc_40107A
; ^ break
loc_40105E: ; CODE XREF: main+2D j
; // printf("A == 666h")
push offset aA666h ; "A == 666h"
call _printf