Zkusme se velmi zbezne podivat na zoubek assembleru. Zkompilujme jednoduchy program #include int main(void) { return printf("Hello world!\n") <= 0; } pomoci gcc -Wall -O0 -S hello.c. Ve vyslednem souboru nas zajima blok obsahujici definici funkce main, zbytek muzeme ignorovat. Pripomenme, ze instrukce ma suffix podle velikosti operandu: 'l' je 32-bitove, 'q' je 64-bitove, 'b' je 8-bitove, atd. Za ni pak nasleduji operandy v poradi , registry jsou uvozene %, konstanty $. Z prednasky byste si meli pamatovat, co je a jak zhruba vypada stack frame, a jake ma procesor registry a nejzakladnejsi instrukce. Alternativne detaily snadno naleznete napr. na wikipedii. Pro slozitejsi instrukce budete ale stejne potrebovat nejakou dobrou referenci. Ucebnicovym zpusobem to pekne podava http://www.arl.wustl.edu/~lockwood/class/cs306/books/artofasm/toc.html nejspolehlivejsi referencni prirucky pak byvaji vyvojarske manualy AMD a Intelu - odkazy viz stranky prednasky. Uvedomme si, ze program je prelozeny zcela bez optimalizaci a za pouziti genericke instrukcni sady. Zkuste vyzkouset a posoudit, jaky efekt ma na assemblerovou formu zapnuti optimalizaci (-O3). Nasleduje oanotovany assemblerovy zdrojak, ktery vygenerovalo gcc na architekture i386: .section .rodata .LC0: .string "Hello world!\n" # sekce .rodata spustitelneho souboru # obsahuje tento string, $.LC0 odpovida # adrese, na ktere je string v pameti # pri spusteni nahrany .text .globl main .type main, @function main: pushl %ebp # hodnotu %ebp (base pointer) uloz na stack movl %esp, %ebp # hodnotu %esp (stack pointer) uloz do # registru %ebp andl $-16, %esp # zaokrouhli %esp na nejbl. nizsi nasobek 16 subl $16, %esp # a odecti dalsich 16 byte (tzn. rezervuj # na stacku 16 byte) # tato dvojice instrukci "zalozila" novy stack frame, ktery ma v sobe # volnych 16 byte - pro ukladani parametru volanych funkci, pripadne # pro lokalni promenne movl $.LC0, %eax # do registru %eax uloz pointer na "Hello world!\n" movl %eax, (%esp) # hodnotu z %rax uloz na vrchol zasobniku # (kde ocekava volana funkce parametry) # funkce printf ocekava (podle volaci konvence i386 ABI) # parametry naskladane na zasobniku call printf # zavolej funkci printf() # funkce printf vraci navratovou hodnotu v registru %rax testl %eax, %eax setle %al # timto jsme nastavili %al na 1, pokud %eax <= 0, jinak na 0 # jak presne funguje porovnavani si povime na pristim cviceni movzbl %al, %eax # %al presun do %eax; ale co znamena zbl? # cilovy operand je l (32bit), # zdrojovy operand je b (8bit), # ale velikost operandu je ruzna - cim # doplnit pribyvsi byty? jedna varianta # je _nulami_ (z); dalsi variante je # "rozmazat" do nich nejvyssi (znamenkovy) # bit (s), tedy aby -1 zustala -1; tomu # se take rika "sign extend" leave # zrus stack frame (obnov puvodni) ret .size main, .-main .ident "GCC: (Gentoo 4.5.3-r2 p1.1, pie-0.4.7) 4.5.3" .section .note.GNU-stack,"",@progbits Stejny zdrojak na architekture amd64 bude vypadat podobne: .file "hello.c" .section .rodata .LC0: .string "Hello world!\n" # sekce .rodata spustitelneho souboru # obsahuje tento string, $.LC0 odpovida # adrese, na ktere je string v pameti # pri spusteni nahrany .text .globl main .type main, @function main: pushq %rbp # hodnotu %rbp (base pointer) uloz na stack movq %rsp, %rbp # hodnotu %rsp (stack pointer) uloz do # registru %rbp # tato dvojice instrukci "zalozila" novy stack frame, ktery je # vsak (a zustane) prazdny - %rsp == %rbp movl $.LC0, %eax # do registru %eax uloz pointer na "Hello world!\n" movq %rax, %rdi # hodnotu z %rax uloz do %rdi (registr, # kterym na x86_64 predavame prvni parametr) movl $0, %eax # do %eax uloz 0 (hint na pocet parametru) # funkce printf ocekava (podle volaci konvence x86_64 ABI) # v %rdi prvni parametr call printf # zavolej funkci printf() # funkce printf vraci navratovou hodnotu v registru %rax testl %eax, %eax setle %al # timto jsme nastavili %al na 1, pokud %eax <= 0, jinak na 0 # jak presne funguje porovnavani si povime na pristim cviceni movzbl %al, %eax # %al presun do %eax; ale co znamena zbl? # cilovy operand je l (32bit), # zdrojovy operand je b (8bit), # ale velikost operandu je ruzna - cim # doplnit pribyvsi byty? jedna varianta # je _nulami_ (z); dalsi variante je # "rozmazat" do nich nejvyssi (znamenkovy) # bit (s), tedy aby -1 zustala -1; tomu # se take rika "sign extend" popq %rbp # zrus stack frame (obnov puvodni) ret # vyskoc z funkce (return) ...