Už si vzpomínám – alokace paměti
Ve chvíli, kdy budeme potřebovat v našich aplikacích alokovat pamět, máme v Assembleru možnost. V Linuxu narozdíl od DOSu nežádáme o blok paměti, nýbrž prosíme kernel o prodloužení sekce .bss, což je prostor pro neinicializované proměnné. Údržba volné a přidělené paměti sekce .bss je na nás. Nepoužijeme-li knihovnu jazyka C, musíme si napsat vlastní manažer paměti. To je však samo o sobě skoro na nový seriál, proto si budeme muset vystačit s použitím heap.asm z Asmutils.
Syscall, které prodlužuje nebo zkracuje délku sekce .bss, se jmenuje brk. Jeho hodnota je 45.
Jediný parametr této funkce obsahuje adresu nového konce .bss. Funkce vrací pro kontrolu poslední platnou adresu datové sekce. Potřebujeme-li zjistit poslední platnou adresu, zavoláme volání brk s nulovým ukazatelem. Funkce pak vrátí poslední adresu. Vypadá to asi takto:
sys_brk 0 add eax, pocet_nutnych_byte sys_brk eax
(K dosavadnímu konci datové sekce .bss přičteme počet byte, které chceme přidělit, a zavoláme znovu brk.)
Muška jenom zlatá – odlaďujeme
Standardním debuggerem pro Linux je gdb, který je příliš nepraktický a složitý pro debugging assemblerovských aplikací. Proto zde uvedu debugger vytvořený přímo pro debugging aplikací v Assembleru, je jím ALD (Assembly language debugger).
Program je na počátku vývoje, proto podporuje pouze procesory x86 kompatibilní a formát ELF. I přesto je však plnohodnotný a dostačující. Autorem projektu je Patrick Alken.
Debugger spustíme příkazem ald
murphy@murphy# ald Assembly Language Debugger 0.1.6 Copyright (C) 2000-2002 Patrick Alken ald>
Naši aplikaci nahrajeme příkazem load:
ald>load nas_programek
echo : ELF Intel 80686 (32 bit), LSB, Executable, Version 1 (current)
Loading debugging symbols ... (no symbols found)
ald>
Nejdůležitejším příkazem je příkaz s (step), který provede jednu instrukci programu a vrátí řízení debuggeru:
(výpis je ilustrativní)
ald> s
eax = 0x00000000 ebx = 0x00000000 ecx = 0x00000000 edx = 0x000000000
esp = 0xBFFFF8CC ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000
ds = 0x0000002B es = 0x0000002B fs = 0x00000000 gs = 0x00000000
ss = 0x0000002B cs = 0x00000013 eip = 0x08448025 eflags = 0x00000345
Flags : PF ZF TF IF
06448082 5A push ebx
Toto nám ukazuje, že další instrukce, která se bude provádět, bude push ebx, ležící na adrese 0×06448082, registr příznaků je nastaven na ZF, zopakování stejného příkazu provedeme stiskem klávesy Enter.
Takto můžeme projet celý program, obsah připravené paměti si prohlédneme příkazem e a zadaním registru. Např.: e ebx
Příkazem c provedeme zbytek programu, kdykoli lze program přerušit přes Ctrl+c. Příkazem help získáme popis všech možných příkazů.
ALD podporuje nastavení breakpointů. Jakmile program narazí na breakpoint, přeruší se a řízení se vrátí zpět ALD. Tato možnost nám dává šanci prohlédnout konkrétní místo v aplikaci (předpokládané místo chyby nebo při běhu bez debuggingu se divně chovající), aniž bychom museli krokovat celý program.
Pokud chceme používat názvy symbolů při debuggingu (možné ve verzi ALD vyšší než 0.1.3), musíme přidat symbolickou informaci do objektového souboru parametrem -g, při spuštění nasm: nasm -g, nebo přidat v souboru MCONFIG řádek DEBUG=y, pokud používáme Asmutils).
Connection … – propojení s jazykem C
V poslední kapitole seriálu si řekneme krátce něco o propojení s jazykem C. Nutno uvést, že funkce jazyka C jsou pro programování v Assembleru dostupné se jménem na svém začátku doplněným o podtržítko, takže getche = _getche, avšak my používáme formát ELF, který toto nevyžaduje, v tom případě podtržítko není na škodu, ale také není nutné. Já ho v ukázce uvádět nebudu.
Ve vyšších jazycích se proměnné předávají buď hodnotou, nebo odkazem, ukládají se na zásobník. Je to stejné jako v Assembleru funkce CALL a RET, uloženy jsou jako přes instrukci PUSH. Avšak tento přístup je velmi nevýhodný, protože přístup k proměnným přes tyto instrukce je velmi nepohodlny a nepříliš šťastný. Proto se do dalšího registru poznamenává adresa offset na zásobníku těsně po vstupu do volaného podprogramu, a tak adresujeme všechny parametry i lokální proměnné relativně k této adrese. Používá se registr (E)BP. Po vstupu do podprogramu je původní hodnota (E)BP uložena na zásobník a registr (E)BP je naplněn hodnotou ukazatele na vrchol zásobníku (E)SP.
Zásobníkovému bloku, které se skládá s parametrů, návratové adresy, lokálních proměnných a původní hodnoty (E)BP, se říká stackframe.
Vytvoříme si jednoduchý program v jazyce C, jenž bude volat funkci print_us, kterou implementujeme v Assembleru, a bude vypisovat součet globální proměnné s jejím jediným parametrem typu int.
#include <stdio.h> const int plus = 10 ; void print_us(int); int main(void){ print_us(15); }
V Assembleru nejdříve zpřístupníme globální proměnné plus a printf.
extern pus extern printf %include "misc/c32.mac"
Tímto si zpřístupníme makra proc, arg a endproc, které nám ulehčí konstrukci podprogramu:
proc print_us %$co arg
Makrem proc označíme začátek funkce print_us. Makro bude provádět sekvenci push ebp, mov ebp, esp. Další makro arg nadefinuje jediný parametr.
mov eax, [ebp + %$co] add eax, [plus] push eax push str1 call printf endproc
Tento program sečte předanou hodnotu s nadefinovanou konstantou, výsledek by tedy měl být 25 ;).
Přeložíme
nasm -f elf print_us.asm gcc -o print_us print_us.c print_us.o
Závěr
Dnes jsme si ukázali poslední věci, které potřebujeme pro začátek psaní programů v Assembleru pro Linux. Doufám, že se vám tento seriál líbil a přinesl vám alespoň něco málo nového nebo alespoň hezké čtení.