Hello, world!

From Wiki.conus.info

Jump to: navigation, search

Начнем с знаменитого примера из книги The C Programming Language:

#include <stdio.h>
 
int main() 
{
	printf("hello, world");
	return 0;
};

Компилируем в MSVC 2010: cl 1.cpp /Fa1.asm

(Ключ /Fa означает сгенерировать листинг на ассемблере)

CONST	SEGMENT
$SG3830	DB	'hello, world', 00H
CONST	ENDS
PUBLIC	_main
EXTRN	_printf:PROC
; Function compile flags: /Odtp
_TEXT	SEGMENT
_main	PROC
; File c:\...\1.cpp
; Line 4
	push	ebp
	mov	ebp, esp
; Line 5
	push	OFFSET $SG3830
	call	_printf
	add	esp, 4
; Line 6
	xor	eax, eax
; Line 7
	pop	ebp
	ret	0
_main	ENDP
_TEXT	ENDS

Компилятор сгенерировал файл 1.obj, который впоследствии будет слинкован линкером в 1.exe. Здесь файл состоит из двух сегментов: CONST (для данных-констант) и _TEXT (для кода). Строка "hello, world" в Си имеет тип const char*, однако не имеет имени. Компилятору нужно как-то с ней работать, так что он дает ей внутреннее имя $SG3830. Как видно, строка заканчивается нулевым байтом - это требования стандарта Си насчет строк. В сегменте кода _TEXT находится пока только одна функция - _main.

Функция _main, как и практически все функции, начинается с пролога и заканчивается эпилогом. Об этом смотрите подробнее тут: пролог и эпилог в функции.

Далее следует вызов функции _printf(): CALL _printf. Перед этим вызовом, адрес строки (или указатель на нее) с нашим приветствием при помощи инструкции PUSH помещается в стек. После того как функция _printf() возвращает управление, адрес строки все еще лежит в стеке. Так как он больше не нужен, то указатель стека (регистр ESP) корректируется. "ADD ESP, 4" означает прибавить 4 к значению в регистре ESP. Так как, это 32-битный код, для передачи адреса нужно аккурат 4 байта. Некоторые компиляторы, например Intel C++ Compiler, в этой же ситуации, могут вместо ADD сгенерировать "POP ECX" (это можно встретить в коде Oracle RDBMS, им скомпилированном), что почти то же самое, только портится значение в регистре ECX. Возможно, компилятор применяет POP ECX потому что эта инструкция короче.

Немного о стеке: стек.

После вызова printf, в оригинальном коде на Си было "return 0" - вернуть 0 в качестве результата. В сгенерированном коде это обеспечивается инструкцией "XOR EAX, EAX". XOR, на самом деле, как нетрудно догадаться, "исключающее ИЛИ", но компиляторы часто используют его вместо простого "MOV EAX, 0" - опкод немного короче. Бывает так, что некоторые компиляторы генерируют "SUB EAX, EAX", что значит, отнять значение EAX от EAX, в любом случае это даст 0 в результате.

Самая последняя инструкция RET возвращает управление в вызывающую функцию.

Теперь скомпилируем то же самое компилером GCC 4.4.1 в Linux: gcc 1.c -o 1

Затем при помощи IDA посмотрим как создалась функция _main. С другой стороны, мы можем посмотреть результат работы GCC при помощи ключа -S -masm=intel.

main            proc near
 
var_10          = dword ptr -10h
 
                push    ebp
                mov     ebp, esp
                and     esp, 0FFFFFFF0h
                sub     esp, 10h
                mov     eax, offset aHelloWorld ; "hello, world"
                mov     [esp+10h+var_10], eax
                call    _printf
                mov     eax, 0
                leave
                retn
main            endp

Почти то же самое, за исключением того что в прологе функции мы видим AND ESP, 0FFFFFFF0h - эта инструкция выравнивает значение в ESP по 16-байтной границе, делая некоторые значения в стеке также выровненными по этой границе. SUB ESP, 10h выделяет в стеке 16 байт, хотя, как будет видно далее, нам достаточно только 4. Это происходит потому что количество выделяемого места в локальном стеке тоже выровнено по 16-байтной границе.

Указатель на строку затем записывается прямо в стек без помощи инструкции PUSH. Затем вызывается printf. В отличие от MSVC, GCC в компиляции без оптимизации генерит MOV EAX, 0 вместо более короткого опкода.

Последняя инструкция LEAVE - это аналог команд MOV ESP, EBP и POP EBP - то есть возврат указателя стека и регистра EBP в первоначальное состояние. Это необходимо, т.к., в начале функции мы модифицировали его (при помощи AND ESP, ...).

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox