Proces překladu a externí assembler
Pozor! Toto cvičení bude probíhat v GNU/Linuxu.
Proces překladu
Program v jazyce C je překládán v následujících krocích:
- Preprocesor provede expanzi maker a provede inkluzi hlavičkových souborů. Jak vypadá výsledek tohoto kroku zjistíte pomocí
gcc -E foo.c
- Překladač jazyka C zdrojový kód přeloží a vygeneruje kód v assembleru. Výsledek tohoto kroku zjistíte pomocí
gcc -S foo.c
a v aktuálním adresáři se vytvoří souborfoo.S
. Všimněte si, že kód je zapsaný v AT&T syntaxi. - Assembler vygeneruje objektový kód. Výsledek tohoto kroku získáte pomocí
gcc -c foo.c
. V aktuálním adresáři se vytvoří souborfoo.o
představující zkompilovaný strojový kód daných funkcí. Obsah tohoto souboru můžete prozkoumat pomocí objdump -d foo.o
objdump -d -M intel foo.o
- Linker sloučí objektové soubory a provede provázání s knihovnami. Pokud překladač nemá specifikovaný přepínač
-o
, generuje se výstup překladu do souborua.out
. Obsah výsledného souboru lze opět prozkoumat pomocí příkazuobjdump
.
Překladač běžně tyto kroky dělá současně a k překladu často stačí použít gcc -o foo foo.c
.
Úkol č. 1
Přeložte následující kód a zjistěte, jak vypadá výstup jednotlivých kroků. U překladu je možné pomocí přepínačů -O0
, -O1
, -O2
a -O3
nastavit úroveň optimalizací. Zjistěte jaké úpravy kódu (toho v assembleru) překladač udělal při zapnutí jednotlivých optimalizací. Vyzkoušejte i pro jiný kód.
#include <stdio.h> int fact(int x) { if (x == 1) return 1; return x * fact(x - 1); } int main() { printf("fact(5) = %i\n", fact(5)); }
Externí assembler
Doposud jsme v rámci cvičení uvažovali pouze inline assembler, což mělo své výhody i nevýhody související zejména s provázaností s jazykem C. Abychom se těmto problémům vyhli, můžeme použít externí assembler. Ve Windows se nejčastěji používá Microsoft MASM v GNU/Linuxu pak GAS (AT&T syntaxe) nebo NASM (Intel syntaxe). V následujícím cvičení budeme používat právě NASM.
Nejkratší program
Následující program definuje jeden globální symbol _start
, místo odkud program bude spuštěn. Nastaví parametry systémového volání sys_exit a provede jej.
; tato sekce slouží k uložení kódu programu section .text global _start _start: mov eax,1 ; systemove volani (sys_exit) mov ebx,0 ; navratovy kod 0 (ok) int 80h
Program přeložíte a slinkujete pomocí příkazů: (pokud se zdrojový soubor jmenuje ex0.s)
nasm -felf ex0.s ld -o ex0 ex0.o # v pripade 64bitoveho OS pouzijeme pro vygenerovani 32bitoveho objektoveho souboru ld -melf_i386 -o ex0 ex0.o
Funkci programu můžete ověřit vyzvednutím návratového kódu programu příkazem:
echo $?
Složitější program
; tato sekce slouží k uložení konstantních dat section .data hello: db 'Hello world!',10,0 ; "Hello world!" + '\n' + '\0' section .text global _start extern printf ; deklaruje, že bude volat externí symbol (i.e., funkci z jiného souboru/knihovny) _start: mov eax, hello push eax call printf add esp, 4 mov eax,1 ; systemove volani (sys_exit) mov ebx,0 ; navratovy kod 0 (ok) int 80h
Jelikož program vyžaduje funkci z knihovny libc, je potřeba ji připojit při linkování. Tzn. kompilaci provést následovně.
nasm -felf ex1.s ld -o ex1 --dynamic-linker /lib/ld-linux.so.2 ex1.o -lc # v pripade 64bitoveho OS pouzijeme pro vygenerovani 32bitoveho objektoveho souboru ld -melf_i386 -o ex1 --dynamic-linker /lib/ld-linux.so.2 ex1.o -lc
Propojení s programy v C
Díky tomu, že externí assembler generuje stejný objektový kód jako C a k linkování dochází až na konci překladu, je možné propojit programy v assembleru i v C.
Uvažujme násleudující kód v assembleru, který představuje funkci s konstantní hodnotou:
section .text global foo foo: mov eax, 42 retPo přeložení pomocí
nasm -felf ex2-asm.s
získáme objektový soubor obsahující jednu funkci void foo();
V C pak můžeme deklarovat její prototyp a funkci zavolat.
#include <stdio.h> int foo(); /* prototyp funkce */ int main() { printf("FOO:%i\n", foo()); }Při překladu je ovšem potřeba přilinkovat i objektový soubor s funkcí
foo
. tj. gcc -o ex2 ex2-c.c ex2-asm.o
. V případě 64bitového OS je potřeba ještě přidat přepínač -m32
pro vygenerování 32bitového binárního souboru.
Úkol č.2
- V externím assembleru napište program, který vypíše faktoriál čísel 1 až 6.
- Upravte tento program, aby fungoval jako funkce jednoho parametru (počet prvních čísel faktoriálu) a tuto funkci zavolejte z C.
Pro získání bodového hodnocení je potřeba splnit oba úkoly č. 2.
Další užitečné zdroje
- P. Tišnovský: Použití assembleru v Linuxu.