Tady, jak to jde:
.file "test.c"
Původní název zdrojového souboru (používaný ladicími programy).
.section .rodata
.LC0:
.string "Hello world!"
V sekci ".rodata" je zahrnut řetězec zakončený nulou ("ro" znamená "pouze pro čtení":aplikace bude moci číst data, ale jakýkoli pokus o zápis do nich vyvolá výjimku).
.text
Nyní zapisujeme věci do sekce ".text", kam jde kód.
.globl main
.type main, @function
main:
Definujeme funkci nazvanou "hlavní" a globálně viditelnou (jiné soubory objektů ji budou moci vyvolat).
leal 4(%esp), %ecx
Ukládáme do registru %ecx
hodnotu 4+%esp
(%esp
je ukazatel zásobníku).
andl $-16, %esp
%esp
je mírně upraven tak, aby byl násobkem 16. Pro některé datové typy (formát s plovoucí desetinnou čárkou odpovídající C's double
a long double
), výkon je lepší, když jsou přístupy do paměti na adresách, které jsou násobky 16. Zde to není ve skutečnosti potřeba, ale při použití bez příznaku optimalizace (-O2
...), kompilátor má tendenci produkovat poměrně hodně generického zbytečného kódu (tj. kódu, který by mohl být v některých případech užitečný, ale ne zde).
pushl -4(%ecx)
Tohle je trochu divné:v tom okamžiku slovo na adrese -4(%ecx)
je slovo, které bylo na vrcholu zásobníku před andl
. Kód toto slovo načte (což by mimochodem měla být zpáteční adresa) a znovu jej vloží. Tento druh emuluje to, co by bylo získáno voláním funkce, která měla 16bajtový zarovnaný zásobník. Můj odhad je, že toto push
je pozůstatkem sekvence kopírování argumentů. Protože funkce upravila ukazatel zásobníku, musí zkopírovat argumenty funkce, které byly přístupné přes starou hodnotu ukazatele zásobníku. Zde není žádný argument kromě návratové adresy funkce. Upozorňujeme, že toto slovo nebude použito (opět se jedná o kód bez optimalizace).
pushl %ebp
movl %esp, %ebp
Toto je standardní prolog funkcí:ušetříme %ebp
(protože se to chystáme upravit), pak nastavte %ebp
ukazovat na rám zásobníku. Poté %ebp
se použije pro přístup k argumentům funkce, takže %esp
opět zdarma. (Ano, není tam žádný argument, takže to je pro tuto funkci k ničemu.)
pushl %ecx
Ušetříme %ecx
(budeme jej potřebovat při ukončení funkce, abychom obnovili %esp
na hodnotu, kterou měl před andl
).
subl $20, %esp
Na zásobníku vyhradíme 32 bajtů (nezapomeňte, že zásobník roste "dolů"). Tento prostor bude použit k uložení argumentů do printf()
(to je přehnané, protože existuje jeden argument, který bude používat 4 bajty [to je ukazatel]).
movl $.LC0, (%esp)
call printf
Argument "posuneme" na printf()
(tj. ujišťujeme se, že %esp
ukazuje na slovo, které obsahuje argument, zde $.LC0
, což je adresa konstantního řetězce v sekci rodata). Potom zavoláme printf()
.
addl $20, %esp
Když printf()
vrátí, odstraníme prostor přidělený pro argumenty. Toto addl
zruší to, co subl
výše ano.
popl %ecx
Obnovíme %ecx
(stlačeno výše); printf()
možná jej upravili (konvence volání popisují, který registr může funkce upravit, aniž by je po ukončení obnovovala; %ecx
je jedním takovým registrem).
popl %ebp
Epilog funkce:obnoví %ebp
(odpovídá pushl %ebp
výše).
leal -4(%ecx), %esp
Obnovujeme %esp
na jeho počáteční hodnotu. Tento operační kód se uloží do %esp
hodnotu %ecx-4
. %ecx
byl nastaven v prvním operačním kódu funkce. Tím se zruší jakákoliv změna %esp
, včetně andl
.
ret
Ukončení funkce.
.size main, .-main
Tím se nastaví velikost main()
funkce:kdykoli během sestavování, ".
" je alias pro "adresu, na kterou právě přidáváme věci". Pokud by sem byla přidána další instrukce, šla by na adresu určenou ".
". Tedy ".-main
", zde je přesná velikost kódu funkce main()
. .size
direktiva instruuje assembler, aby zapsal tyto informace do objektového souboru.
.ident "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)"
GCC prostě ráda zanechává stopy své činnosti. Tento řetězec skončí jako druh komentáře v souboru objektu. Linker jej odstraní.
.section .note.GNU-stack,"",@progbits
Speciální sekce, kde GCC píše, že kód může pojmout nespustitelný zásobník. Toto je normální případ. Pro některá speciální použití jsou potřeba spustitelné zásobníky (ne standardní C). Na moderních procesorech může jádro vytvořit nespustitelný zásobník (zásobník, který spustí výjimku, pokud se někdo pokusí spustit jako kód nějaká data, která jsou v zásobníku); Někteří lidé to považují za „bezpečnostní prvek“, protože umístění kódu do zásobníku je běžný způsob, jak využít přetečení vyrovnávací paměti. S touto sekcí bude spustitelný soubor označen jako "kompatibilní s nespustitelným zásobníkem", který jádro jako takový rád poskytne.
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $20, %esp
tyto instrukce se ve vašem programu c nesrovnávají, vždy se provádějí na začátku každé funkce (ale záleží na kompilátoru/platformě)
movl $.LC0, (%esp)
call printf
tento blok odpovídá vašemu volání printf(). první instrukce umístí na zásobník svůj argument (ukazatel na "ahoj světe") a poté zavolá funkci.
addl $20, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
tyto instrukce jsou opačné k prvnímu bloku, jsou to nějaký druh manipulace se zásobníkem. vždy také proveden
Zde je nějaký doplněk k @Thomas Pornin
odpověď uživatele.
.LC0
lokální konstanta, např. řetězcový literál..LFB0
začátek místní funkce,.LFE0
ukončení místní funkce,
Přípona těchto štítků je číslo a začíná od 0.
Toto je konvence assembleru gcc.