V demonstračních příkladech, které jsme si ukazovali v předchozích částech tohoto seriálu, jsme si prozatím vystačili s voláním několika funkcí jádra operačního systému (jedná se o takzvané syscalls). V praxi se však dříve či později dostaneme do situace, kdy je zapotřebí používat i další knihovny, například standardní knihovnu jazyka C. Dnes si proto ukážeme, jaké konvence je zapotřebí dodržet při volání funkcí z této knihovny na 64bitové platformě x86-64.
Obsah
1. Použití assembleru v Linuxu: volání funkcí ze standardní knihovny jazyka C
2. Zjednodušení práce s assemblerem a linkerem – přímé použití gcc
3. Vstupní bod do aplikace, předání návratové hodnoty shellu (exit code)
4. První příklad – kostra aplikace s funkcí main naprogramovanou v assembleru
5. Volání knihovní funkce puts
6. Zarovnání vrcholu zásobníku na adresu dělitelnou šestnácti
7. Druhý příklad – výpis řetězce na obrazovku pomocí funkce puts
8. Kontrola zarovnání v debuggeru (GDB)
9. Volání knihovní funkce printf a význam obsahu registru AL
10. Třetí příklad – výpis řetězce na obrazovku pomocí funkce printf
11. Repositář s demonstračními příklady
1. Použití assembleru v Linuxu: volání funkcí ze standardní knihovny jazyka C
Prakticky jakákoli rozsáhlejší aplikace využívá funkce, které jsou dostupné v (nativních) knihovnách, ať již statických či dynamických. To se samozřejmě týká i jazyka symbolických instrukcí (assembleru), protože by bylo velmi pracné neustále znovu implementovat i tak základní a často používané funkce, jako je načtení celého čísla ze standardního vstupu, výpis zformátovaného čísla s desetinnou čárkou, otevření souboru, zápis či čtení ze souboru, uzavření souboru apod. Mnohé z těchto funkcí jsou implementovány ve standardní knihovně programovacího jazyka C, která je nainstalována na každém Linuxu. Dnes již sice poněkud starší, ale stále užitečný popis funkcí ze standardní céčkové knihovny lze najít na adrese https://www-s.acm.illinois.edu/webmonkeys/book/c_guide/index.html (v tomto popisu nenalezneme některé změny implementované v rámci standardizace C99). Ve skutečnosti je možné standardní céčkovou knihovnu používat i v assembleru, a to poměrně jednoduchým způsobem, což je téma, kterému se budeme věnovat v navazujících kapitolách.
2. Zjednodušení práce s assemblerem a linkerem – přímé použití gcc
Při nutnosti použití vybraných nativních knihoven volaných z programu napsaného v assembleru je zapotřebí zaručit, aby se objektový kód vytvořený assemblerem korektně slinkoval se zvolenou statickou knihovnou či knihovnami. To je možné zajistit několika způsoby, typicky specifikací všech knihoven a objektových kódů, které mají společně vytvořit výslednou aplikaci. Tento seznam se následně předá linkeru, který je na Linuxu představován nástrojem ld (i když je nutné upozornit na to, že způsob zadávání parametrů linkeru je poněkud těžkopádný). To však není vše, protože kromě pouhého slinkování objektového souboru s knihovními funkcemi je taktéž nutné zajistit, aby výsledný spustitelný nativní soubor obsahoval správný vstupní bod (entry point) atd.
Zkušení assemblerovští mágové si vystačí s nástroji as a ld, ovšem existuje i mnohem jednodušší varianta překladu, kterou si ukážeme dneska. Tato varianta spočívá v tom, že se namísto explicitního volání assembleru (as) a linkeru (ld) jednoduše zavolá gcc, kterému se jako jediný parametr předá jméno zdrojového souboru se zdrojovým kódem napsaným v assembleru. Pokud bude jméno tohoto souboru obsahovat koncovku „.s“ (tedy například „test.s“), rozezná gcc, že se jedná o assembler a sám automaticky nejenom spustí as a ld, ale navíc i zajistí korektní slinkování se standardní céčkovou knihovnou apod. Jedinou vážnější nevýhodou tohoto postupu je větší velikost výsledného spustitelného souboru, která dosahuje přibližně šesti kilobajtů (na platformě x86-64).
3. Vstupní bod do aplikace, předání návratové hodnoty shellu (exit code)
Pokud se pro překlad assemblerovského programu použije nástroj gcc, bude kostra aplikace vypadat poněkud odlišně v porovnání s programy, s nimiž jsme se až doposud setkávali. Je tomu tak z toho důvodu, že gcc implicitně vytvoří vstupní bod (entry point) do programu, který je označen návěštím _start a obsahuje nativní kód získaný z objektového souboru crt0.o. Tento kód volá funkci main, což v praxi znamená, že kostra našeho assemblerovského programu se nepatrně změní a z pohledu programátora zjednoduší. Připomeňme si, že původně vypadala kostra následovně (tučně je zvýrazněno návěští představující vstupní bod do aplikace):
# asmsyntax=as
.intel_syntax noprefix
# Linux kernel system call table
sys_exit=1
#-----------------------------------------------------------------------------
.section .data
#-----------------------------------------------------------------------------
.section .bss
#-----------------------------------------------------------------------------
.section .text
<strong>.global _start</strong> # tento symbol ma byt dostupny i linkeru
<strong>_start:</strong>
mov eax, sys_exit # cislo sycallu pro funkci "exit"
mov ebx, 0 # exit code = 0
int 0x80 # volani Linuxoveho kernelu
Nyní (tedy při překladu s využitím gcc) dojde ke změně názvu programátorem vytvářené funkce, protože namísto návěští _start:
.section .text
<strong>.global _start</strong> # tento symbol ma byt dostupny i linkeru
<strong>_start:</strong>
Se použije návěští main:
.section .text
<strong>.global main</strong> # tento symbol ma byt dostupny i linkeru
<strong>main:</strong>
Navíc jsme dříve ukončovali program explicitním zavoláním syscallu a předáním návratového kódu v registru EBX, tedy následujícím způsobem:
mov eax, sys_exit # cislo sycallu pro funkci "exit"
mov ebx, 0 # exit code = 0
int 0x80 # volani Linuxoveho kernelu
Nová podoba kódu je pro programátora jednodušší, protože funkci main může opustit instrukcí RET (návrat ze subrutiny), přičemž návratový kód (exit code) je předán v registru EAX:
xor eax, eax # navratova hodnota (exit status)
ret # ukonceni aplikace
Poznámka: použití souboru crt0.o je sice možné obejít a spolehnout se na vlastní kód, ve skutečnosti je však poměrně příjemné, že za nás kód naprogramovaný v crt0.o provede jak základní inicializaci, tak i zpracování vstupních parametrů atd.
4. První příklad – kostra aplikace s funkcí main naprogramovanou v assembleru
První demonstrační příklad je velmi primitivní, protože po svém spuštění pouze nastaví hodnotu registru EAX na nulu (využití instrukce xor je často používaným trikem, v assembleru však již spíše idiomem) a vyskočí ze subrutiny instrukcí ret:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
#-----------------------------------------------------------------------------
.section .data
#-----------------------------------------------------------------------------
.section .text
.global main # tento symbol ma byt dostupny i linkeru
main:
xor eax, eax # navratova hodnota (exit status)
ret # ukonceni aplikace
Překlad a slinkování se provede tímto příkazem:
gcc main.s
Výsledkem by měl být soubor a.out o velikosti přibližně 8,5 kB, který je však možné zmenšit příkazem strip na přibližně 6,2 kB.
Disassemblerem se můžeme podívat na interní strukturu, která je již poněkud složitá, ale funkci main i sekci _start zde nalezneme:
objdump -M intel-mnemonic -f -d -t -h a.out
Disassembly of section .init:
00000000004003a8 <_init>:
4003a8: 48 83 ec 08 sub rsp,0x8
4003ac: 48 8b 05 45 0c 20 00 mov rax,QWORD PTR [rip+0x200c45] # 600ff8 <_DYNAMIC+0x1d0>
4003b3: 48 85 c0 test rax,rax
4003b6: 74 05 je 4003bd <_init+0x15>
4003b8: e8 33 00 00 00 call 4003f0 <__gmon_start__@plt>
4003bd: 48 83 c4 08 add rsp,0x8
4003c1: c3 ret
Disassembly of section .plt:
00000000004003d0 <__libc_start_main@plt-0x10>:
4003d0: ff 35 32 0c 20 00 push QWORD PTR [rip+0x200c32] # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4003d6: ff 25 34 0c 20 00 jmp QWORD PTR [rip+0x200c34] # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
4003dc: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
00000000004003e0 <__libc_start_main@plt>:
4003e0: ff 25 32 0c 20 00 jmp QWORD PTR [rip+0x200c32] # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
4003e6: 68 00 00 00 00 push 0x0
4003eb: e9 e0 ff ff ff jmp 4003d0 <_init+0x28>
00000000004003f0 <__gmon_start__@plt>:
4003f0: ff 25 2a 0c 20 00 jmp QWORD PTR [rip+0x200c2a] # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
4003f6: 68 01 00 00 00 push 0x1
4003fb: e9 d0 ff ff ff jmp 4003d0 <_init+0x28>
Disassembly of section .text:
0000000000400400 <_start>:
400400: 31 ed xor ebp,ebp
400402: 49 89 d1 mov r9,rdx
400405: 5e pop rsi
400406: 48 89 e2 mov rdx,rsp
400409: 48 83 e4 f0 and rsp,0xfffffffffffffff0
40040d: 50 push rax
40040e: 54 push rsp
40040f: 49 c7 c0 60 05 40 00 mov r8,0x400560
400416: 48 c7 c1 f0 04 40 00 mov rcx,0x4004f0
40041d: 48 c7 c7 ed 04 40 00 mov rdi,0x4004ed
400424: e8 b7 ff ff ff call 4003e0 <__libc_start_main@plt>
400429: f4 hlt
40042a: 66 0f 1f 44 00 00 nop WORD PTR [rax+rax*1+0x0]
0000000000400430 <deregister_tm_clones>:
400430: b8 3f 10 60 00 mov eax,0x60103f
400435: 55 push rbp
400436: 48 2d 38 10 60 00 sub rax,0x601038
40043c: 48 83 f8 0e cmp rax,0xe
400440: 48 89 e5 mov rbp,rsp
400443: 77 02 ja 400447 <deregister_tm_clones+0x17>
400445: 5d pop rbp
400446: c3 ret
400447: b8 00 00 00 00 mov eax,0x0
40044c: 48 85 c0 test rax,rax
40044f: 74 f4 je 400445 <deregister_tm_clones+0x15>
400451: 5d pop rbp
400452: bf 38 10 60 00 mov edi,0x601038
400457: ff e0 jmp rax
400459: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0]
0000000000400460 <register_tm_clones>:
400460: b8 38 10 60 00 mov eax,0x601038
400465: 55 push rbp
400466: 48 2d 38 10 60 00 sub rax,0x601038
40046c: 48 c1 f8 03 sar rax,0x3
400470: 48 89 e5 mov rbp,rsp
400473: 48 89 c2 mov rdx,rax
400476: 48 c1 ea 3f shr rdx,0x3f
40047a: 48 01 d0 add rax,rdx
40047d: 48 d1 f8 sar rax,1
400480: 75 02 jne 400484 <register_tm_clones+0x24>
400482: 5d pop rbp
400483: c3 ret
400484: ba 00 00 00 00 mov edx,0x0
400489: 48 85 d2 test rdx,rdx
40048c: 74 f4 je 400482 <register_tm_clones+0x22>
40048e: 5d pop rbp
40048f: 48 89 c6 mov rsi,rax
400492: bf 38 10 60 00 mov edi,0x601038
400497: ff e2 jmp rdx
400499: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0]
00000000004004a0 <__do_global_dtors_aux>:
4004a0: 80 3d 91 0b 20 00 00 cmp BYTE PTR [rip+0x200b91],0x0 # 601038 <__TMC_END__>
4004a7: 75 11 jne 4004ba <__do_global_dtors_aux+0x1a>
4004a9: 55 push rbp
4004aa: 48 89 e5 mov rbp,rsp
4004ad: e8 7e ff ff ff call 400430 <deregister_tm_clones>
4004b2: 5d pop rbp
4004b3: c6 05 7e 0b 20 00 01 mov BYTE PTR [rip+0x200b7e],0x1 # 601038 <__TMC_END__>
4004ba: f3 c3 repz ret
4004bc: 0f 1f 40 00 nop DWORD PTR [rax+0x0]
00000000004004c0 <frame_dummy>:
4004c0: 48 83 3d 58 09 20 00 cmp QWORD PTR [rip+0x200958],0x0 # 600e20 <__JCR_END__>
4004c7: 00
4004c8: 74 1e je 4004e8 <frame_dummy+0x28>
4004ca: b8 00 00 00 00 mov eax,0x0
4004cf: 48 85 c0 test rax,rax
4004d2: 74 14 je 4004e8 <frame_dummy+0x28>
4004d4: 55 push rbp
4004d5: bf 20 0e 60 00 mov edi,0x600e20
4004da: 48 89 e5 mov rbp,rsp
4004dd: ff d0 call rax
4004df: 5d pop rbp
4004e0: e9 7b ff ff ff jmp 400460 <register_tm_clones>
4004e5: 0f 1f 00 nop DWORD PTR [rax]
4004e8: e9 73 ff ff ff jmp 400460 <register_tm_clones>
00000000004004ed <main>:
4004ed: 31 c0 xor eax,eax
4004ef: c3 ret
00000000004004f0 <__libc_csu_init>:
4004f0: 41 57 push r15
4004f2: 41 89 ff mov r15d,edi
4004f5: 41 56 push r14
4004f7: 49 89 f6 mov r14,rsi
4004fa: 41 55 push r13
4004fc: 49 89 d5 mov r13,rdx
4004ff: 41 54 push r12
400501: 4c 8d 25 08 09 20 00 lea r12,[rip+0x200908] # 600e10 <__frame_dummy_init_array_entry>
400508: 55 push rbp
400509: 48 8d 2d 08 09 20 00 lea rbp,[rip+0x200908] # 600e18 <__init_array_end>
400510: 53 push rbx
400511: 4c 29 e5 sub rbp,r12
400514: 31 db xor ebx,ebx
400516: 48 c1 fd 03 sar rbp,0x3
40051a: 48 83 ec 08 sub rsp,0x8
40051e: e8 85 fe ff ff call 4003a8 <_init>
400523: 48 85 ed test rbp,rbp
400526: 74 1e je 400546 <__libc_csu_init+0x56>
400528: 0f 1f 84 00 00 00 00 nop DWORD PTR [rax+rax*1+0x0]
40052f: 00
400530: 4c 89 ea mov rdx,r13
400533: 4c 89 f6 mov rsi,r14
400536: 44 89 ff mov edi,r15d
400539: 41 ff 14 dc call QWORD PTR [r12+rbx*8]
40053d: 48 83 c3 01 add rbx,0x1
400541: 48 39 eb cmp rbx,rbp
400544: 75 ea jne 400530 <__libc_csu_init+0x40>
400546: 48 83 c4 08 add rsp,0x8
40054a: 5b pop rbx
40054b: 5d pop rbp
40054c: 41 5c pop r12
40054e: 41 5d pop r13
400550: 41 5e pop r14
400552: 41 5f pop r15
400554: c3 ret
400555: 66 66 2e 0f 1f 84 00 data32 nop WORD PTR cs:[rax+rax*1+0x0]
40055c: 00 00 00 00
0000000000400560 <__libc_csu_fini>:
400560: f3 c3 repz ret
Disassembly of section .fini:
0000000000400564 <_fini>:
400564: 48 83 ec 08 sub rsp,0x8
400568: 48 83 c4 08 add rsp,0x8
40056c: c3 ret
5. Volání knihovní funkce puts
Způsob překladu i slinkování aplikace již známe, dokonce i víme, jak má vypadat kostra assemblerovské aplikace překládané s využitím nástroje gcc. Podívejme se tedy, jakým způsobem lze volat nějakou knihovní funkci ze standardní céčkovské knihovny. Na první pohled to není nic těžkého, protože se funkce zavolá instrukcí CALL, které se předá návěští, jehož jméno odpovídá jménu funkce. To znamená, že například standardní funkci puts() (výpis řetězce na standardní výstup) můžeme zavolat instrukcí CALL puts. Druhý problém, který je nutné vyřešit, spočívá v tom, jak funkcím předávat parametry. Na platformě x86-64 se pro předání prvních šesti celočíselných parametrů nebo adres používají registry, a to v tomto pořadí: RDI, RSI, RDX, RCX, R8 a R9. Pokud je zapotřebí předat hodnotu s plovoucí řádovou čárkou, použijí se registry XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 a XMM7.
Funkci puts() se předává jediný parametr, kterým je počáteční adresa řetězce, který se má vytisknout. Tuto adresu nejprve načteme do registru RDI (první parametr). Povšimněte si, že řetězec je alokován pomocí deklarace .asciiz zajišťující mj. i uložení nuly za konec řetězce. To je céčkovskou funkcí puts() vyžadováno. První verze programu typu „Hello world!“ by mohla vypadat následovně:
#-----------------------------------------------------------------------------
.section .data
hello_world_message:
.asciz "Hello world!\n" # zprava, ktera se ma vytisknout na standardni vystup
#-----------------------------------------------------------------------------
.section .text
.global main # tento symbol ma byt dostupny i linkeru
main:
# jedinym parametrem je adresa zpravy
mov rdi, offset hello_world_message
call puts # volani funkce 'puts' ze standardni knihovny
6. Zarovnání vrcholu zásobníku na adresu dělitelnou šestnácti
Volání funkce puts() sice bude fungovat, ale není zcela korektní, což by se mohlo projevit při volání složitějších funkcí s více parametry. Je tomu tak z toho důvodu, že na platformě x86-64 je nutné, aby byl při volání funkcí vrchol zásobníku zarovnán na celočíselný násobek šestnácti. A právě zde leží jádro problému – již při volání subrutiny main je registr RSP ukazující na vrchol zásobníku posunut o osm bajtů, protože na zásobník bylo nutné uložit návratovou adresu volajícího kódu. Proto musíme ještě před voláním funkce puts() na zásobník uložit dalších osm bajtů a po návratu z této funkce obnovit původní hodnotu registru RSP. Řešení je jednoduché – RSP se nejprve zmenší o hodnotu osm (zásobník totiž roste směrem k nižším adresám) a po návratu z této funkce se hodnota naopak o osmičku zvětší. Korektní kód tedy bude vypadat následovně:
#-----------------------------------------------------------------------------
.section .data
hello_world_message:
.asciz "Hello world!\n" # zprava, ktera se ma vytisknout na standardni vystup
#-----------------------------------------------------------------------------
.section .text
.global main # tento symbol ma byt dostupny i linkeru
main:
sub rsp, 8 # zajistit zarovnani RSP na adresu delitelnou 16
# jedinym parametrem je adresa zpravy
mov rdi, offset hello_world_message
call puts # volani funkce 'puts' ze standardni knihovny
add rsp, 8 # obnoveni puvodni hodnoty RSP
7. Druhý příklad – výpis řetězce na obrazovku pomocí funkce puts
Korektní varianta programu typu „Hello world!“ vypadá následovně. Pro překlad tohoto programu opět použijte nástroj gcc a nikoli as:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - volani funkce 'puts'
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
#-----------------------------------------------------------------------------
.section .data
hello_world_message:
.asciz "Hello world!\n" # zprava, ktera se ma vytisknout na standardni vystup
#-----------------------------------------------------------------------------
.section .text
.global main # tento symbol ma byt dostupny i linkeru
main:
sub rsp, 8 # zajistit zarovnani RSP na adresu delitelnou 16
# jedinym parametrem je adresa zpravy
mov rdi, offset hello_world_message
call puts # volani funkce 'puts' ze standardni knihovny
add rsp, 8 # obnoveni puvodni hodnoty RSP
xor eax, eax # navratova hodnota (exit status)
ret # ukonceni aplikace
8. Kontrola zarovnání v debuggeru (GDB)
Pojďme si nyní zkontrolovat, zda instrukce pro zarovnání vrcholu zásobníku na adresu dělitelnou šestnácti skutečně fungují. Nejdříve si upravený program přeložíme, a to s použitím volby -g, aby se do výsledného kódu vložily i ladicí informace:
<strong>gcc -g main_64bit.s</strong>
Po úspěšném překladu můžeme spustit interaktivní debugger, což zajišťuje následující příkaz:
<strong>gdb a.out</strong>
...
...
...
Reading symbols from a.out...done.
(gdb)
Pokud se namísto posledního řádku vypíše toto chybové hlášení:
Reading symbols from a.out...(no debugging symbols found)...done.
znamená to, že při překladu nebyl použit přepínač -g.
Dále již můžeme pokračovat v ladění. Nastavíme breakpoint na první instrukci v subrutině main:
(gdb) <strong>break main</strong>
Breakpoint 1 at 0x40052d: file main_64bit.s, line 25.
Laděnou aplikaci spustíme, po spuštění se aplikace zastaví na nastaveném breakpointu:
(gdb) <strong>run</strong>
Starting program: /home/tester/44_stdc_puts/a.out
Breakpoint 1, main () at main_64bit.s:25
25 sub rsp, 8 # zajistit zarovnani RSP na adresu delitelnou 16
Vypíšeme si informace o všech registrech. Zajímat nás samozřejmě bude především obsah registru RSP:
(gdb) <strong>info registers</strong>
rax 0x40052d 4195629
rbx 0x0 0
rcx 0x0 0
rdx 0x7fffffffe118 140737488347416
rsi 0x7fffffffe108 140737488347400
rdi 0x1 1
rbp 0x0 0x0
rsp <strong>0x7fffffffe028 0x7fffffffe028</strong>
r8 0x7ffff7dd4e80 140737351863936
r9 0x7ffff7dea530 140737351951664
r10 0x7fffffffdeb0 140737488346800
r11 0x7ffff7a36e50 140737348070992
r12 0x400440 4195392
r13 0x7fffffffe100 140737488347392
r14 0x0 0
r15 0x0 0
rip 0x40052d 0x40052d <main>
eflags 0x246 [ PF ZF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
Vidíme, že registr RSP skutečně neobsahuje hodnotu dělitelnou šestnácti, ovšem po provedení další instrukce příkazem step se situace změní:
(gdb) <strong>step</strong>
main () at main_64bit.s:28
28 mov rdi, offset hello_world_message
Opět si vypíšeme obsah všech registrů:
<strong>(gdb) info registers</strong>
rax 0x40052d 4195629
rbx 0x0 0
rcx 0x0 0
rdx 0x7fffffffe118 140737488347416
rsi 0x7fffffffe108 140737488347400
rdi 0x1 1
rbp 0x0 0x0
rsp <strong>0x7fffffffe020 0x7fffffffe020</strong>
r8 0x7ffff7dd4e80 140737351863936
r9 0x7ffff7dea530 140737351951664
r10 0x7fffffffdeb0 140737488346800
r11 0x7ffff7a36e50 140737348070992
r12 0x400440 4195392
r13 0x7fffffffe100 140737488347392
r14 0x0 0
r15 0x0 0
rip 0x400531 0x400531 <main+4>
eflags 0x202 [ IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
Nyní již RSP obsahuje adresu dělitelnou šestnácti, což se v hexadecimálním výpisu snadno pozná podle nuly v nejnižší cifře.
9. Volání knihovní funkce printf a význam obsahu registru AL
Způsobem, který jsme si popsali v předchozích kapitolách, je možné volat většinu funkcí ze standardní céčkové knihovny. Stačí si zapamatovat pořadí registrů pro předávání parametrů:
- Celočíselné parametry a adresy: RDI, RSI, RDX, RCX, R8 a R9
- Hodnoty single(float) a double: XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 a XMM7
Výjimkou jsou funkce s proměnným počtem parametrů. U těch je totiž ještě nutné v registru RAX (ve skutečnosti však jen v AL, tj. spodních osmi bitech) předat počet předávaných hodnot typu float či double. Pokud na nastavení tohoto registru zapomenete, skončí většinou pokus o zavolání funkcí s proměnným počtem parametrů (typickým příkladem je printf()) pádem aplikace!
10. Třetí příklad – výpis řetězce na obrazovku pomocí funkce printf
Se znalostí nutnosti nastavení registru AL (či celého registru EAX) na hodnotu odpovídající počtu parametrů typu float/double předávaných variadické funkci můžeme vytvořit demonstrační příklad, v němž se volá variadická funkce printf(). Této funkci se předá jediný parametr představující řetězec, který se má vytisknout. V řetězci nejsou uvedeny žádné speciální formátovací znaky, takže se funkci printf() nemusí předávat žádné další parametry. Zdrojový kód vypadá následovně:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - volani funkce 'printf'
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
#-----------------------------------------------------------------------------
.section .data
hello_world_message:
.asciz "Hello world!\n" # zprava, ktera se ma vytisknout na standardni vystup
#-----------------------------------------------------------------------------
.section .text
.global main # tento symbol ma byt dostupny i linkeru
main:
sub rsp, 8 # zajistit zarovnani RSP na adresu delitelnou 16
# jedinym parametrem je adresa zpravy
mov rdi, offset hello_world_message
xor al, al # pocet parametru predanych ve vektorovych registrech
call printf # volani funkce 'printf' ze standardni knihovny
add rsp, 8 # obnoveni puvodni hodnoty RSP
xor eax, eax # navratova hodnota (exit status)
ret # ukonceni aplikace
Podívejme se na to, jak se funkce (či subrutina) main přeloží do strojového kódu:
000000000040052d <main>:
40052d: 48 83 ec 08 sub rsp,0x8
400531: 48 c7 c7 40 10 60 00 mov rdi,0x601040
400538: 30 c0 xor al,al
40053a: e8 d1 fe ff ff call 400410 <printf@plt>
40053f: 48 83 c4 08 add rsp,0x8
400543: 31 c0 xor eax,eax
400545: c3 ret
400546: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
40054d: 00 00 00
Poznámka: poslední dva řádky jsou ve skutečnosti pouze „smetí“ vzniklé zarovnáním kódu a nikoli reálné instrukce, které by se skutečně spouštěly (ostatně instrukce NOP s prapodivným adresováním je skutečně nesmyslná).
11. Repositář s demonstračními příklady
Všechny tři dnes popisované demonstrační příklady byly, podobně jako ve všech předchozích částech tohoto seriálu, společně s podpůrnými skripty určenými pro jejich překlad či naopak pro disassembling, uloženy do GIT repositáře dostupného na adrese https://github.com/tisnik/presentations/. Všechny příklady jsou určeny pro GNU Assembler a používají Intel syntaxi, která je pro mnoho programátorů čitelnější, než původní AT&T syntaxe. Následují tabulky obsahující odkazy na zdrojové kódy příkladů i na již zmíněné podpůrné skripty:
První demonstrační příklad: šablona pro překlad nástrojem gcc
# | Soubor | Popis | Odkaz do repositáře |
---|---|---|---|
1 | main.s | hlavní program pro procesory x86-64 | https://github.com/tisnik/presentations/blob/master/assembler/43_stdc_stub/main.s |
2 | assemble | skript pro překlad s využitím gcc | https://github.com/tisnik/presentations/blob/master/assembler/43_stdc_stub/assemble |
3 | disassemble | skript pro disassembling | https://github.com/tisnik/presentations/blob/master/assembler/43_stdc_stub/disassemble |
Druhý demonstrační příklad: volání knihovní funkce puts()
# | Soubor | Popis | Odkaz do repositáře |
---|---|---|---|
1 | main_64bit.s | hlavní program pro procesory x86-64 | https://github.com/tisnik/presentations/blob/master/assembler/44_stdc_puts/main_64bit.s |
2 | assemble_64bit | skript pro překlad s využitím gcc | https://github.com/tisnik/presentations/blob/master/assembler/44_stdc_puts/assemble_64bit |
3 | disassemble | skript pro disassembling | https://github.com/tisnik/presentations/blob/master/assembler/44_stdc_puts/disassemble |
Druhý demonstrační příklad: volání knihovní funkce printf()
# | Soubor | Popis | Odkaz do repositáře |
---|---|---|---|
1 | main.s | hlavní program pro procesory x86-64 | https://github.com/tisnik/presentations/blob/master/assembler/45_stdc_printf/main_64bit.s |
2 | assemble | skript pro překlad s využitím gcc | https://github.com/tisnik/presentations/blob/master/assembler/45_stdc_printf/assemble_64bit |
3 | disassemble | skript pro disassembling | https://github.com/tisnik/presentations/blob/master/assembler/45_stdc_printf/disassemble |
12. Odkazy na Internetu
- C Functions Without Arguments
https://eklitzke.org/c-functions-without-arguments - GNU Assembler Examples
http://cs.lmu.edu/~ray/notes/gasexamples/ - Simply FPU
http://www.website.masmforum.com/tutorials/fptute/ - Art of Assembly language programming: The 80x87 Floating Point Coprocessors
https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-3.html - Art of Assembly language programming: The FPU Instruction Set
https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-4.html - INTEL 80387 PROGRAMMER'S REFERENCE MANUAL
http://www.ragestorm.net/downloads/387intel.txt - x86 Instruction Set Reference: FLD
http://x86.renejeschke.de/html/file_module_x86_id_100.html - x86 Instruction Set Reference: FLD1/FLDL2T/FLDL2E/FLDPI/FLDLG2/FLDLN2/FLDZ
http://x86.renejeschke.de/html/file_module_x86_id_101.html - x86 Instruction Set Reference: FLD
http://x86.renejeschke.de/html/file_module_x86_id_100.html - x86 Instruction Set Reference: FST/FSTP
http://x86.renejeschke.de/html/file_module_x86_id_117.html - x86 Instruction Set Reference: FADD/FADDP/FIADD
http://x86.renejeschke.de/html/file_module_x86_id_81.html - x86 Instruction Set Reference: FSUB/FSUBP/FISUB
http://x86.renejeschke.de/html/file_module_x86_id_121.html - x86 Instruction Set Reference: FDIV/FDIVP/FIDIV
http://x86.renejeschke.de/html/file_module_x86_id_91.html - x86 Instruction Set Reference: BT
http://x86.renejeschke.de/html/file_module_x86_id_22.html - x86 Instruction Set Reference: BTC
http://x86.renejeschke.de/html/file_module_x86_id_23.html - x86 Instruction Set Reference: BTR
http://x86.renejeschke.de/html/file_module_x86_id_24.html - x86 Instruction Set Reference: BTS
http://x86.renejeschke.de/html/file_module_x86_id_25.html - x86 Instruction Set Reference: BSF
http://x86.renejeschke.de/html/file_module_x86_id_19.html - x86 Instruction Set Reference: BSR
http://x86.renejeschke.de/html/file_module_x86_id_20.html - x86 Instruction Set Reference: BSWAP
http://x86.renejeschke.de/html/file_module_x86_id_21.html - x86 Instruction Set Reference: XCHG
http://x86.renejeschke.de/html/file_module_x86_id_328.html - x86 Instruction Set Reference: SETcc
http://x86.renejeschke.de/html/file_module_x86_id_288.html - X86 Assembly/Arithmetic
https://en.wikibooks.org/wiki/X86_Assembly/Arithmetic - Art of Assembly - Arithmetic Instructions
http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-2.html - The GNU Assembler Tutorial
http://tigcc.ticalc.org/doc/gnuasm.html - The GNU Assembler - macros
http://tigcc.ticalc.org/doc/gnuasm.html#SEC109 - ARM subroutines & program stack
http://www.toves.org/books/armsub/ - Generating Mixed Source and Assembly List using GCC
http://www.systutorials.com/240/generate-a-mixed-source-and-assembly-listing-using-gcc/ - Calling subroutines
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0100a/armasm_cihcfigg.htm - ARM Assembly Language Programming
http://peter-cockerell.net/aalp/html/frames.html - ASM Flags
http://www.cavestory.org/guides/csasm/guide/asm_flags.html - Status Register
https://en.wikipedia.org/wiki/Status_register - Intel x86 JUMP quick reference
http://unixwiz.net/techtips/x86-jumps.html - Linux assemblers: A comparison of GAS and NASM
http://www.ibm.com/developerworks/library/l-gas-nasm/index.html - Programovani v assembleru na OS Linux
http://www.cs.vsb.cz/grygarek/asm/asmlinux.html - Is it worthwhile to learn x86 assembly language today?
https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1 - Why Learn Assembly Language?
http://www.codeproject.com/Articles/89460/Why-Learn-Assembly-Language - Is Assembly still relevant?
http://programmers.stackexchange.com/questions/95836/is-assembly-still-relevant - Why Learning Assembly Language Is Still a Good Idea
http://www.onlamp.com/pub/a/onlamp/2004/05/06/writegreatcode.html - Assembly language today
http://beust.com/weblog/2004/06/23/assembly-language-today/ - Assembler: Význam assembleru dnes
http://www.builder.cz/rubriky/assembler/vyznam-assembleru-dnes-155960cz - Assembler pod Linuxem
http://phoenix.inf.upol.cz/linux/prog/asm.html - AT&T Syntax versus Intel Syntax
https://www.sourceware.org/binutils/docs-2.12/as.info/i386-Syntax.html - Linux Assembly website
http://asm.sourceforge.net/ - Using Assembly Language in Linux
http://asm.sourceforge.net/articles/linasm.html - vasm
http://sun.hasenbraten.de/vasm/ - vasm – dokumentace
http://sun.hasenbraten.de/vasm/release/vasm.html - The Yasm Modular Assembler Project
http://yasm.tortall.net/ - 680x0:AsmOne
http://www.amigacoding.com/index.php/680x0:AsmOne - ASM-One Macro Assembler
http://en.wikipedia.org/wiki/ASM-One_Macro_Assembler - ASM-One pages
http://www.theflamearrows.info/documents/asmone.html - Základní informace o ASM-One
http://www.theflamearrows.info/documents/asminfo.html - Linux Syscall Reference
http://syscalls.kernelgrok.com/ - Programming from the Ground Up Book - Summary
http://savannah.nongnu.org/projects/pgubook/ - IBM System 360/370 Compiler and Historical Documentation
http://www.edelweb.fr/Simula/ - IBM 700/7000 series
http://en.wikipedia.org/wiki/IBM_700/7000_series - IBM System/360
http://en.wikipedia.org/wiki/IBM_System/360 - IBM System/370
http://en.wikipedia.org/wiki/IBM_System/370 - Mainframe family tree and chronology
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_FT1.html - 704 Data Processing System
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP704.html - 705 Data Processing System
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP705.html - The IBM 704
http://www.columbia.edu/acis/history/704.html - IBM Mainframe album
http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_album.html - Osmibitové muzeum
http://osmi.tarbik.com/ - Tesla PMI-80
http://osmi.tarbik.com/cssr/pmi80.html - PMI-80
http://en.wikipedia.org/wiki/PMI-80 - PMI-80
http://www.old-computers.com/museum/computer.asp?st=1&c=1016 - The 6502 overflow flag explained mathematically
http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html - X86 Opcode and Instruction Reference
http://ref.x86asm.net/coder32.html