Téma volání knihovních funkcí z assembleru dnes dokončíme. Ukážeme si totiž, jakým způsobem se volá funkce printf s proměnným počtem parametrů, což je zejména na architektuře x86-64 řešeno poněkud zvláštním způsobem. Všechny dnes popsané příklady budou implementovány jak pro již zmíněnou architekturu x86-64, tak i pro 32bitové procesory ARM. Právě porovnáním stejných příkladů implementovaných pro různé architektury lze získat povědomí o tom, jak se jednotlivé procesorové architektury od sebe odlišují a v jakých oblastech jsou naopak podobné.
Obsah
1. Použití assembleru v Linuxu: volání knihovní funkce printf s proměnným počtem parametrů
2. Volání funkcí a předávání parametrů na architektuře x86-64
3. Volání funkcí a předávání parametrů na architektuře ARM32
4. Rekapitulace šestnácté části – výpis řetězce funkcí printf
5. Formátovací řetězec a předání dalších parametrů funkci printf
6. První demonstrační příklad – tisk znaku a dekadické hodnoty funkcí printf
7. Tisk ASCII tabulky: předání čtyř parametrů funkci printf
8. Druhý demonstrační příklad – kódy znaků z ASCII tabulky
9. Volání funkce printf pro výpis hodnoty typu double
10. Třetí demonstrační příklad – výpis hodnoty typu double
11. Volání funkce printf na architektuře ARM
12. Tisk znaku a dekadické hodnoty funkcí printf na architektuře ARM
13. Tisk ASCII znaků na architektuře ARM
14. Výpis hodnoty typu double na architektuře ARM
15. Repositář s demonstračními příklady
1. Použití assembleru v Linuxu: volání knihovní funkce printf s proměnným počtem parametrů
V předchozích dvou částech [1] [2] seriálu o použití assembleru v Linuxu jsme se seznámili se způsobem volání funkcí umístěných v externích knihovnách, zejména pak ve standardní céčkové knihovně. Připomeňme si, že se způsob volání funkcí a samozřejmě i způsob předávání parametrů těmto funkcím odlišuje podle toho, jaká procesorová architektura je použita. U některých operačních systémů je navíc použit odlišný způsob volání, nás samozřejmě bude zajímat především Linux. Situaci na architekturách x86-64 a ARM 32 shrnují navazující dvě kapitoly.
2. Volání funkcí a předávání parametrů na architektuře x86-64
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. Pokud má funkce větší množství parametrů, je nutné je předat na zásobníku, ovšem v praxi se většinou s touto potřebou často nesetkáme. Důležité je také vědět, že po návratu z funkce jsou zachovány obsahy jen několika registrů, konkrétně R12 až R15 a taktéž RBX, RSP a RBP. Zajímavá je funkce registrů R10 a R11, které sice nejsou použity pro předání parametrů, ovšem jejich obsah současně není zachován (resp. není zaručeno, že je obsah zachován). Proto jsou tyto registry používány pro uložení mezivýsledků s krátkou dobou života.
3. Volání funkcí a předávání parametrů na architektuře ARM32
Na platformě 32bitových mikroprocesorů ARM se parametry taktéž předávají přes pracovní registry, samozřejmě za předpokladu, že jich není příliš mnoho (v opačném případě se další parametry předávají přes zásobník). První čtyři parametry se předávají v registrech R0 až R3. Další čtyři registry R4 až R8 jsou použity například pro uložení hodnot lokálních proměnných atd. Registry R12 až R15 mají různé speciální použití (programový čítač, link registr, ukazatel na vrchol zásobníku...) a význam zbývajících tří registrů R9 až R11 se liší na základě použité instrukční sady (ARM32 versus Thumb). Volaná funkce/subrutina musí zachovat obsah těchto registrů: R4-R8, R10, R11 a SP. Norma dále předepisuje, že 64bitové hodnoty se předávají ve dvojici registrů a hodnota 128bitová ve čtyřech registrech (může se jednat o jediný takto předaný parametr).
4. Rekapitulace šestnácté části – výpis řetězce funkcí printf
V šestnácté části tohoto seriálu jsme si ukázali základní způsob volání knihovní funkce printf. Tato funkce (ale samozřejmě nejenom ona) je zvláštní tím, že podporuje proměnný počet parametrů, které navíc mohou být různého typu. O vyzvednutí a interpretaci parametrů se funkce printf stará sama na základě obsahu formátovacího řetězce (historické překladače navíc vůbec nekontrolovaly, jestli počet a typ parametrů s formátovacím řetězcem souhlasí, nedělají to logicky ani assemblery), ovšem i volající kód musí dodržovat jistá pravidla. O způsobem předávání parametrů jsme se zmínili ve druhé kapitole, ovšem tuto informaci musíme doplnit o vysvětlení významu registru RAX:
- Celočíselné parametry a adresy se postupně předávají v registrech: RDI, RSI, RDX, RCX, R8 a R9 (dále pak na zásobníku)
- Hodnoty single(float) a double se postupně předávají v registrech: XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 a XMM7 (dále pak opět na zásobníku)
- V registru RAX (ve skutečnosti však jen v AL, tj. ve spodních osmi bitech) je nutné předat počet hodnot typu float či double.
Volání funkce printf, které se předá pouze formátovací řetězec, bude implementováno takto. Povšimněte si především vynulování registru AL před vlastním voláním (sami si můžete vyzkoušet, že aplikace zhavaruje, pokud tuto instrukci zakomentujete):
# 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
5. Formátovací řetězec a předání dalších parametrů funkci printf
Zkusme si nyní vytvořit složitější příklad, který bude zhruba odpovídat tomuto céčkovému kódu:
printf("char=%c code=%d\n", '*', 42);
Formátovací řetězec uložený do sekce data se samozřejmě změní, aby odpovídal výše zobrazenému céčkovému kódu:
.section .data
hello_world_message:
.asciz "char=%c code=%d\n" # zprava, ktera se ma vytisknout na standardni vystup
Změní se i volání funkce printf, neboť nyní ji musíme předat tři parametry – adresu formátovacího řetězce, ASCII kód znaku hvězdička a numerický kód stejného znaku (ve skutečnosti se v obou případech jedná o stejné konstanty). Z rekapitulace je zřejmé, že tři parametry se budou postupně předávat v registrech RDI, RSI a RDX. Žádný parametr typu float či double nepředáváme, takže registr AL musí být nulový:
mov rdi, offset hello_world_message
mov rsi, '*' # druhym parametrem je kod znaku
mov rdx, 42 # tretim parametrem je cele cislo
xor al, al # pocet parametru predanych ve vektorovych registrech
call printf # volani funkce 'printf' ze standardni knihovny
6. První demonstrační příklad – tisk znaku a dekadické hodnoty funkcí printf
Úplný zdrojový kód demonstračního příkladu, v němž se volá funkce printf se třemi parametry, vypadá následovně:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - volani funkce 'printf' s vetsim poctem parametru
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
#-----------------------------------------------------------------------------
.section .data
hello_world_message:
.asciz "char=%c code=%d\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
# prvnim parametrem je adresa zpravy
mov rdi, offset hello_world_message
mov rsi, '*' # druhym parametrem je kod znaku
mov rdx, 42 # tretim parametrem je cele cislo
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
Překlad a slinkování na platformě x86-64 je jednoduché, protože opět použijeme překladač gcc:
gcc -g main_64bit.s
Po spuštění příkladu by se na standardní výstup měla vytisknout hvězdička i její ASCII kód:
<strong>./a.out</strong>
char=* code=42
7. Tisk ASCII tabulky: předání čtyř parametrů funkci printf
Předchozí příklad můžeme ještě upravit takovým způsobem, aby došlo k vytištění všech tisknutelných ASCII znaků, tj. znaků s kódy od 32 (mezera) až po 126 (tilda). Provedeme dvě úpravy:
- Kromě dekadické hodnoty znaku se vytiskne i hodnota hexadecimální.
- Volání funkce i naplnění jejich parametrů bude provedeno v programové smyčce.
První úprava si vyžádá změnu formátovacího řetězce:
.section .data
hello_world_message:
.asciz "char=%c code=%d hex=%02x\n" # zprava, ktera se ma vytisknout na standardni vystup
Funkce printf se zavolá s kódem znaku uloženým v registru R12. Je to totiž jeden z registrů, jejichž obsah nebude volanou funkcí poškozen:
mov rdi, offset hello_world_message
mov rsi, r12 # druhym parametrem je kod znaku
mov rdx, r12 # tretim parametrem je cele cislo
mov rcx, r12 # tretim parametrem je taktez cele cislo
xor al, al # pocet parametru predanych ve vektorovych registrech
call printf # volani funkce 'printf' ze standardni knihovny
Nyní nám již zbývá použít registr R12 jako počitadlo smyčky, což je téma, které již známe z úvodních částí tohoto seriálu:
mov r12, 32 # pocitadlo je v prvnim registru, jehoz obsah je zachovan
# i po zavolani funkce printf()
loop:
...
...
...
inc r12 # zvyseni hodnoty pocitadla
cmp r12, 127 # konec smycky?
jne loop # ne? takze dalsi iterace
Smyčku a volání funkce printf sestavíme dohromady a získáme fragment celého programu:
mov r12, 32 # pocitadlo je v prvnim registru, jehoz obsah je zachovan
# i po zavolani funkce printf()
loop:
# prvnim parametrem je adresa zpravy
mov rdi, offset hello_world_message
mov rsi, r12 # druhym parametrem je kod znaku
mov rdx, r12 # tretim parametrem je cele cislo
mov rcx, r12 # tretim parametrem je taktez cele cislo
xor al, al # pocet parametru predanych ve vektorovych registrech
call printf # volani funkce 'printf' ze standardni knihovny
inc r12 # zvyseni hodnoty pocitadla
cmp r12, 127 # konec smycky?
jne loop # ne, dalsi iterace
8. Druhý demonstrační příklad – kódy znaků z ASCII tabulky
Úplný zdrojový kód dnešního druhého demonstračního příkladu upraveného pro architekturu x86-64 vypadá následovně:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - volani funkce 'printf' s vetsim poctem parametru
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
#-----------------------------------------------------------------------------
.section .data
hello_world_message:
.asciz "char=%c code=%d hex=%02x\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
mov r12, 32 # pocitadlo je v prvnim registru, jehoz obsah je zachovan
# i po zavolani funkce printf()
loop:
# prvnim parametrem je adresa zpravy
mov rdi, offset hello_world_message
mov rsi, r12 # druhym parametrem je kod znaku
mov rdx, r12 # tretim parametrem je cele cislo
mov rcx, r12 # tretim parametrem je taktez cele cislo
xor al, al # pocet parametru predanych ve vektorovych registrech
call printf # volani funkce 'printf' ze standardni knihovny
inc r12 # zvyseni hodnoty pocitadla
cmp r12, 127 # konec smycky?
jne loop # ne, dalsi iterace
add rsp, 8 # obnoveni puvodni hodnoty RSP
xor eax, eax # navratova hodnota (exit status)
ret # ukonceni aplikace
Po překladu a spuštění tohoto příkladu získáme tento výstup:
char= code=32 hex=20
char=! code=33 hex=21
char=" code=34 hex=22
char=# code=35 hex=23
char=$ code=36 hex=24
char=% code=37 hex=25
char=& code=38 hex=26
char=' code=39 hex=27
char=( code=40 hex=28
char=) code=41 hex=29
char=* code=42 hex=2a
char=+ code=43 hex=2b
char=, code=44 hex=2c
char=- code=45 hex=2d
char=. code=46 hex=2e
char=/ code=47 hex=2f
char=0 code=48 hex=30
char=1 code=49 hex=31
char=2 code=50 hex=32
char=3 code=51 hex=33
char=4 code=52 hex=34
char=5 code=53 hex=35
char=6 code=54 hex=36
char=7 code=55 hex=37
char=8 code=56 hex=38
char=9 code=57 hex=39
char=: code=58 hex=3a
char=; code=59 hex=3b
char=< code=60 hex=3c
char== code=61 hex=3d
char=> code=62 hex=3e
char=? code=63 hex=3f
char=@ code=64 hex=40
char=A code=65 hex=41
char=B code=66 hex=42
char=C code=67 hex=43
char=D code=68 hex=44
char=E code=69 hex=45
char=F code=70 hex=46
char=G code=71 hex=47
char=H code=72 hex=48
char=I code=73 hex=49
char=J code=74 hex=4a
char=K code=75 hex=4b
char=L code=76 hex=4c
char=M code=77 hex=4d
char=N code=78 hex=4e
char=O code=79 hex=4f
char=P code=80 hex=50
char=Q code=81 hex=51
char=R code=82 hex=52
char=S code=83 hex=53
char=T code=84 hex=54
char=U code=85 hex=55
char=V code=86 hex=56
char=W code=87 hex=57
char=X code=88 hex=58
char=Y code=89 hex=59
char=Z code=90 hex=5a
char=[ code=91 hex=5b
char=\ code=92 hex=5c
char=] code=93 hex=5d
char=^ code=94 hex=5e
char=_ code=95 hex=5f
char=` code=96 hex=60
char=a code=97 hex=61
char=b code=98 hex=62
char=c code=99 hex=63
char=d code=100 hex=64
char=e code=101 hex=65
char=f code=102 hex=66
char=g code=103 hex=67
char=h code=104 hex=68
char=i code=105 hex=69
char=j code=106 hex=6a
char=k code=107 hex=6b
char=l code=108 hex=6c
char=m code=109 hex=6d
char=n code=110 hex=6e
char=o code=111 hex=6f
char=p code=112 hex=70
char=q code=113 hex=71
char=r code=114 hex=72
char=s code=115 hex=73
char=t code=116 hex=74
char=u code=117 hex=75
char=v code=118 hex=76
char=w code=119 hex=77
char=x code=120 hex=78
char=y code=121 hex=79
char=z code=122 hex=7a
char={ code=123 hex=7b
char=| code=124 hex=7c
char=} code=125 hex=7d
char=~ code=126 hex=7e
9. Volání funkce printf pro výpis hodnoty typu double
Další úkol, který dnes vyřešíme, spočívá ve vytištění hodnoty typu double, tj. hodnoty reprezentované v systému plovoucí řádové čárky. Konkrétně se bude jednat o číslo π, které již dokážeme získat, a to konkrétně instrukcí fldpi matematického koprocesoru (π se uloží na zásobník matematického koprocesoru). Nejdříve si zarezervujeme místo v paměti, kam se tato konstanta uloží. Tato paměťová oblast představovaná sekcí .bss je vytvořena až po spuštění aplikace (tedy v runtime). Rezervovat je nutné osm bajtů:
.section .bss
.lcomm number, 8 # na toto misto se bude ukladat konstanta typu double
Do této oblasti se uloží binární reprezentace konstanty π:
fldpi
fstp qword ptr number
Následně připravíme parametry pro funkci printf. Prvním parametrem je stále formátovací řetězec, takže se pro jeho předání použije celočíselný registr RDI. Ovšem druhý parametr je typu double a musí se tedy předat v „multimediálním“ registru XMM0! Příprava obou parametrů vypadá takto:
mov rdi, offset hello_world_message
movsd xmm0, qword ptr number
Právě nyní nastal okamžik pro přípravu registru AL (či celého registru EAX), který musí obsahovat počet parametrů předaných v registrech XMM?:
mov eax, 1 # pocet parametru predanych ve vektorovych registrech
Nyní je již možné funkci printf bez problémů zavolat:
call printf # volani funkce 'printf' ze standardni knihovny
10. Třetí demonstrační příklad – výpis hodnoty typu double
Úplný zdrojový kód dnešního třetího demonstračního příkladu upraveného pro architekturu x86-64 vypadá následovně:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - volani funkce 'printf' pro vypis hodnoty typu double
# - pro zapis je pouzita "Intel" syntaxe.
#
# Autor: Pavel Tisnovsky
.intel_syntax noprefix
#-----------------------------------------------------------------------------
.section .data
hello_world_message:
.asciz "float = %f\n" # zprava, ktera se ma vytisknout na standardni vystup
#-----------------------------------------------------------------------------
.section .bss
.lcomm number, 8 # na toto misto se bude ukladat konstanta typu double
#-----------------------------------------------------------------------------
.section .text
.global main # tento symbol ma byt dostupny i linkeru
main:
sub rsp, 8 # zajistit zarovnani RSP na adresu delitelnou 16
# prvnim parametrem je adresa zpravy
mov rdi, offset hello_world_message
fldpi # druhym parametrem je double konstanta Pi
fstp qword ptr number
movsd xmm0, qword ptr number
mov eax, 1 # 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
Po překladu a spuštění získáme na standardním výstupu následující zprávu:
float = 3.141593
11. Volání funkce printf na architektuře ARM
Všechny předchozí demonstrační příklady si nyní ukážeme v úpravě určené pro 32bitové mikroprocesory s architekturou ARM. V prvním příkladu se volá funkce printf a předává se jí jediný parametr – ukazatel na řetězec, který se má vytisknout. Základní kostru aplikace známe z předchozího dílu:
main:
stmfd sp!, {lr} @ ulozeni zvolenych registru na zasobnik
...
...
...
mov r0, #42 @ navratova hodnota (exit status)
ldmfd sp!, {pc} @ obnova registru, ukonceni aplikace
@ (rizeni se vrati na adresu ulozenou v LR)
Volání funkce printf je jednoduché, protože parametry se předávají postupně v registrech R0, R1, R2 a R3. V tomto příkladu se předává jediný parametr v registru R0 (povšimněte si použití znaku =, který zde nahrazuje slova dword ptr):
ldr r0, =hello_world_message @ adresa zpravy, ktera se ma vytisknout
@ (jde o jediny parametr predany funkci printf())
Volání knihovních funkcí zajišťuje instrukce BL neboli branch and link:
bl printf @ zavolani knihovni funkce printf()
Úplný zdrojový kód vypadá následovně:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - volani funkce 'printf'
# - varianta urcena pro klasickou 32bitovou architekturu ARM
#
# Autor: Pavel Tisnovsky
#-----------------------------------------------------------------------------
.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:
stmfd sp!, {lr} @ ulozeni zvolenych registru na zasobnik
ldr r0, =hello_world_message @ adresa zpravy, ktera se ma vytisknout
@ (jde o jediny parametr predany funkci printf())
bl printf @ zavolani knihovni funkce printf()
mov r0, #42 @ navratova hodnota (exit status)
ldmfd sp!, {pc} @ obnova registru, ukonceni aplikace
@ (rizeni se vrati na adresu ulozenou v LR)
Překlad se provede takto (předpokládám, že se nejedná o cross kompilaci):
gcc main_arm.s
Funkce main je přeložena do strojového kódu následujícím způsobem (povšimněte si umístění konstanty ihned za kód funkce):
000083cc <main>:
83cc: e92d4000 stmfd sp!, {lr}
83d0: e59f0008 ldr r0, [pc, #8] ; 83e0 <main+0x14>
83d4: ebffffc5 bl 82f0 <printf@plt>
83d8: e3a0002a mov r0, #42 ; 0x2a
83dc: e8bd8000 ldmfd sp!, {pc}
83e0: 00010584 .word 0x00010584
12. Tisk znaku a dekadické hodnoty funkcí printf na architektuře ARM
Příprava pro tisk znaku a dekadické hodnoty vypadá v assembleru procesorů ARM prakticky stejně, jako tomu bylo na architektuře x86-64, pouze nesmíme zapomenout na to, že konstanty je nutné uvozovat znakem #:
ldr r0, =hello_world_message @ adresa zpravy, ktera se ma vytisknout
mov r1, #'*' @ druhym parametrem je kod znaku
mov r2, #42 @ tretim parametrem je cele cislo
bl printf @ zavolani knihovni funkce printf()
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - volani funkce 'printf' s vetsim poctem parametru
# - varianta urcena pro klasickou 32bitovou architekturu ARM
#
# Autor: Pavel Tisnovsky
#-----------------------------------------------------------------------------
.section .data
hello_world_message:
.asciz "char=%c code=%d\n" @ zprava, ktera se ma vytisknout na standardni vystup
#-----------------------------------------------------------------------------
.section .text
.global main @ tento symbol ma byt dostupny i linkeru
main:
stmfd sp!, {lr} @ ulozeni zvolenych registru na zasobnik
ldr r0, =hello_world_message @ adresa zpravy, ktera se ma vytisknout
mov r1, #'*' @ druhym parametrem je kod znaku
mov r2, #42 @ tretim parametrem je cele cislo
bl printf @ zavolani knihovni funkce printf()
mov r0, #42 @ navratova hodnota (exit status)
ldmfd sp!, {pc} @ obnova registru, ukonceni aplikace
@ (rizeni se vrati na adresu ulozenou v LR)
Funkce main je přeložena do strojového kódu následujícím způsobem:
000083cc <main>:
83cc: e92d4000 stmfd sp!, {lr}
83d0: e59f0010 ldr r0, [pc, #16] ; 83e8 <main+0x1c>
83d4: e3a0102a mov r1, #42 ; 0x2a
83d8: e3a0202a mov r2, #42 ; 0x2a
83dc: ebffffc3 bl 82f0 <printf@plt>
83e0: e3a0002a mov r0, #42 ; 0x2a
83e4: e8bd8000 ldmfd sp!, {pc}
83e8: 0001058c .word 0x0001058c
13. Tisk ASCII znaků na architektuře ARM
Programová smyčka určená pro tisk ASCII znaků je založena na kódu, který opět známe z předchozích částí tohoto seriálu. Registr R4 byl zvolen z toho důvodu, že jeho obsah není při volání funkce pozměněn:
mov r4, #32 @ inicializace pocitadla
loop:
...
...
...
add r4, r4, #1 @ zvyseni hodnoty pocitadla
cmp r4, #127 @ porovnani s koncovou hodnotou smycky
bne loop @ dosahli jsme konce? pokud ne, skok na zacatek smycky
Do této smyčky pouze vložíme volání funkce printf s tím, že její parametry jsou naplněny právě na základě aktuálního obsahu registru R4 (počitadla smyčky):
mov r4, #32 @ inicializace pocitadla
loop:
ldr r0, =ASCII_char_message @ adresa zpravy, ktera se ma vytisknout
mov r1, r4 @ druhym parametrem je kod znaku
mov r2, r4 @ tretim parametrem je totez cele cislo
mov r3, r4 @ ctvrtym parametrem je totez cele cislo
bl printf @ zavolani knihovni funkce printf()
add r4, r4, #1 @ zvyseni hodnoty pocitadla
cmp r4, #127 @ porovnani s koncovou hodnotou smycky
bne loop @ dosahli jsme konce? pokud ne, skok na zacatek smycky
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - volani funkce 'printf' pro zobrazeni casti ASCII tabulky
# - varianta urcena pro klasickou 32bitovou architekturu ARM
#
# Autor: Pavel Tisnovsky
#-----------------------------------------------------------------------------
.section .data
ASCII_char_message:
.asciz "char=%c code=%d hex=%02x\n" @ zprava, ktera se ma vytisknout na standardni vystup
#-----------------------------------------------------------------------------
.section .text
.global main @ tento symbol ma byt dostupny i linkeru
main:
stmfd sp!, {lr} @ ulozeni zvolenych registru na zasobnik
mov r4, #32 @ inicializace pocitadla
loop:
ldr r0, =ASCII_char_message @ adresa zpravy, ktera se ma vytisknout
mov r1, r4 @ druhym parametrem je kod znaku
mov r2, r4 @ tretim parametrem je totez cele cislo
mov r3, r4 @ ctvrtym parametrem je totez cele cislo
bl printf @ zavolani knihovni funkce printf()
add r4, r4, #1 @ zvyseni hodnoty pocitadla
cmp r4, #127 @ porovnani s koncovou hodnotou smycky
bne loop @ dosahli jsme konce? pokud ne, skok na zacatek smycky
mov r0, #42 @ navratova hodnota (exit status)
ldmfd sp!, {pc} @ obnova registru, ukonceni aplikace
@ (rizeni se vrati na adresu ulozenou v LR)
Funkce main je přeložena do strojového kódu následujícím způsobem:
000083d4 <loop>:
83d4: e59f0020 ldr r0, [pc, #32] ; 83fc <loop+0x28>
83d8: e1a01004 mov r1, r4
83dc: e1a02004 mov r2, r4
83e0: e1a03004 mov r3, r4
83e4: ebffffc1 bl 82f0 <printf@plt>
83e8: e2844001 add r4, r4, #1
83ec: e354007f cmp r4, #127 ; 0x7f
83f0: 1afffff7 bne 83d4 <loop>
83f4: e3a0002a mov r0, #42 ; 0x2a
83f8: e8bd8000 ldmfd sp!, {pc}
83fc: 000105a0 .word 0x000105a0
14. Výpis hodnoty typu double na architektuře ARM
Poslední příklad používá ABI, v němž se i čísla typu float či double předávají v celočíselných registrech. Tento příklad vlastně ani nevyžaduje přítomnost matematického koprocesoru, na druhou stranu je však nutné konstantu typu double zakódovat ručně, popř. lze využít utilitku fp2hex:
number:
.word 0xbff00000 @ prvnich 32 bitu cisla
.word 0x10010000 @ druhych 32 bitu cisla
Naplnění parametrů funkce printf s jejím následným zavoláním (povšimněte si, v jakém pořadí se naplňuje 64bitový parametr):
ldr r0, =hello_world_message @ adresa zpravy, ktera se ma vytisknout
ldr r1, number+4
ldr r2, number
bl printf @ zavolani knihovni funkce printf()
Opět se podívejme na úplný zdrojový kód tohoto demonstračního příkladu:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - volani funkce 'printf' pro vypis hodnoty typu double
# - varianta urcena pro klasickou 32bitovou architekturu ARM
#
# Autor: Pavel Tisnovsky
#-----------------------------------------------------------------------------
.section .data
hello_world_message:
.asciz "double = %f\n" @ zprava, ktera se ma vytisknout na standardni vystup
#-----------------------------------------------------------------------------
.section .text
.global main @ tento symbol ma byt dostupny i linkeru
main:
stmfd sp!, {lr} @ ulozeni zvolenych registru na zasobnik
ldr r0, =hello_world_message @ adresa zpravy, ktera se ma vytisknout
ldr r1, number+4
ldr r2, number
bl printf @ zavolani knihovni funkce printf()
mov r0, #42 @ navratova hodnota (exit status)
ldmfd sp!, {pc} @ obnova registru, ukonceni aplikace
@ (rizeni se vrati na adresu ulozenou v LR)
number:
.word 0xbff00000 @ prvnich 32 bitu cisla
.word 0x10010000 @ druhych 32 bitu cisla
Funkce main je přeložena do strojového kódu následujícím způsobem:
000083cc <main>:
83cc: e92d4000 stmfd sp!, {lr}
83d0: e59f0018 ldr r0, [pc, #24] ; 83f0 <number+0x8>
83d4: e59f1010 ldr r1, [pc, #16] ; 83ec <number+0x4>
83d8: e59f2008 ldr r2, [pc, #8] ; 83e8 <number>
83dc: ebffffc3 bl 82f0 <printf@plt>
83e0: e3a0002a mov r0, #42 ; 0x2a
83e4: e8bd8000 ldmfd sp!, {pc}
Ihned za kódem funkce main je umístěna trojice 32bitových konstant. První dvě konstanty představují binární obraz čísla -1.0, třetí konstanta pak adresu formátovacího řetězce funkce printf:
000083e8 <number>:
83e8: bff00000 .word 0xbff00000
83ec: 10010000 .word 0x10010000
83f0: 00010594 .word 0x00010594
15. Repositář s demonstračními příklady
Všechny 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. 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: tisk znaku a dekadické hodnoty funkcí printf
# | Soubor | Popis | Odkaz do repositáře |
---|---|---|---|
1 | main_64bit.s | hlavní program pro procesory x86-64 | https://github.com/tisnik/presentations/blob/master/assembler/46_stdc_printf_vararg/main_64bit.s |
2 | main_arm.s | hlavní program pro 32bitové procesory ARM | https://github.com/tisnik/presentations/blob/master/assembler/46_stdc_printf_vararg/main_arm.s |
3 | assemble_64bit | skript pro překlad s využitím gcc (verze pro x86-64) | https://github.com/tisnik/presentations/blob/master/assembler/46_stdc_printf_vararg/assemble_64bit |
4 | assemble_arm | skript pro překlad s využitím gcc (verze pro ARM) | https://github.com/tisnik/presentations/blob/master/assembler/46_stdc_printf_vararg/assemble_arm |
5 | disassemble | skript pro disassembling | https://github.com/tisnik/presentations/blob/master/assembler/46_stdc_printf_vararg/disassemble |
Druhý demonstrační příklad: kódy znaků z ASCII tabulky
# | Soubor | Popis | Odkaz do repositáře |
---|---|---|---|
1 | main_64bit.s | hlavní program pro procesory x86-64 | https://github.com/tisnik/presentations/blob/master/assembler/47_stdc_printf_float/main_64bit.s |
2 | main_arm.s | hlavní program pro 32bitové procesory ARM | https://github.com/tisnik/presentations/blob/master/assembler/47_stdc_printf_float/main_arm.s |
3 | assemble_64bit | skript pro překlad s využitím gcc (verze pro x86-64) | https://github.com/tisnik/presentations/blob/master/assembler/47_stdc_printf_float/assemble_64bit |
4 | assemble_arm | skript pro překlad s využitím gcc (verze pro ARM) | https://github.com/tisnik/presentations/blob/master/assembler/47_stdc_printf_float/assemble_arm |
5 | disassemble | skript pro disassembling | https://github.com/tisnik/presentations/blob/master/assembler/47_stdc_printf_float/disassemble |
Třetí demonstrační příklad: výpis hodnoty typu double na standardní výstup
# | Soubor | Popis | Odkaz do repositáře |
---|---|---|---|
1 | main_64bit.s | hlavní program pro procesory x86-64 | https://github.com/tisnik/presentations/blob/master/assembler/48_ascii_chars/main_64bit.s |
2 | main_arm.s | hlavní program pro 32bitové procesory ARM | https://github.com/tisnik/presentations/blob/master/assembler/48_ascii_chars/main_arm.s |
3 | assemble_64bit | skript pro překlad s využitím gcc (verze pro x86-64) | https://github.com/tisnik/presentations/blob/master/assembler/48_ascii_chars/assemble_64bit |
4 | assemble_arm | skript pro překlad s využitím gcc (verze pro ARM) | https://github.com/tisnik/presentations/blob/master/assembler/48_ascii_chars/assemble_arm |
5 | disassemble | skript pro disassembling | https://github.com/tisnik/presentations/blob/master/assembler/48_ascii_chars/disassemble |
16. Odkazy na Internetu
- ARM Documentation: B, BL, BX, BLX, and BXJ
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204j/Cihfddaf.html - Branch and Call Sequences Explained
https://community.arm.com/groups/processors/blog/2013/09/25/branch-and-call-sequences-explained - Improving ARM Code Density and Performance
New Thumb Extensions to the ARM Architecture Richard Phelan - The ARM Processor Architecture
http://www.arm.com/products/processors/technologies/instruction-set-architectures.php - Thumb-2 instruction set
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344c/Beiiegaf.html - Introduction to ARM thumb
http://www.eetimes.com/discussion/other/4024632/Introduction-to-ARM-thumb - ARM, Thumb, and ThumbEE instruction sets
http://www.keil.com/support/man/docs/armasm/armasm_CEGBEIJB.htm - An Introduction to ARM Assembly Language
http://dev.emcelettronica.com/introduction-to-arm-assembly-language - Processors - ARM
http://www.arm.com/products/processors/index.php - The ARM Instruction Set
http://simplemachines.it/doc/arm_inst.pdf - ARM Architecture (Wikipedia)
http://en.wikipedia.org/wiki/ARM_architecture - 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: 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