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

16. Odkazy na Internetu

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ě R12R15 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 R0R3. Další čtyři registry R4R8 jsou použity například pro uložení hodnot lokálních proměnných atd. Registry R12R15 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ů R9R11 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:

  1. 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)
  2. 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)
  3. 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:

  1. Kromě dekadické hodnoty znaku se vytiskne i hodnota hexadecimální.
  2. 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

  1. ARM Documentation: B, BL, BX, BLX, and BXJ
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204j/Cihfddaf.html
  2. Branch and Call Sequences Explained
    https://community.arm.com/groups/processors/blog/2013/09/25/branch-and-call-sequences-explained
  3. Improving ARM Code Density and Performance
    New Thumb Extensions to the ARM Architecture Richard Phelan
  4. The ARM Processor Architecture
    http://www.arm.com/products/processors/technologies/instruction-set-architectures.php
  5. Thumb-2 instruction set
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344c/Beiiegaf.html
  6. Introduction to ARM thumb
    http://www.eetimes.com/discussion/other/4024632/Introduction-to-ARM-thumb
  7. ARM, Thumb, and ThumbEE instruction sets
    http://www.keil.com/support/man/docs/armasm/armasm_CEGBEIJB.htm
  8. An Introduction to ARM Assembly Language
    http://dev.emcelettronica.com/introduction-to-arm-assembly-language
  9. Processors - ARM
    http://www.arm.com/products/processors/index.php
  10. The ARM Instruction Set
    http://simplemachines.it/doc/arm_inst.pdf
  11. ARM Architecture (Wikipedia)
    http://en.wikipedia.org/wiki/ARM_architecture
  12. C Functions Without Arguments
    https://eklitzke.org/c-functions-without-arguments
  13. GNU Assembler Examples
    http://cs.lmu.edu/~ray/notes/gasexamples/
  14. Simply FPU
    http://www.website.masmforum.com/tutorials/fptute/
  15. Art of Assembly language programming: The 80x87 Floating Point Coprocessors
    https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-3.html
  16. Art of Assembly language programming: The FPU Instruction Set
    https://courses.engr.illinois.edu/ece390/books/artofasm/CH14/CH14-4.html
  17. INTEL 80387 PROGRAMMER'S REFERENCE MANUAL
    http://www.ragestorm.net/downloads/387intel.txt
  18. x86 Instruction Set Reference: FLD
    http://x86.renejeschke.de/html/file_module_x86_id_100.html
  19. x86 Instruction Set Reference: FLD1/FLDL2T/FLDL2E/FLDPI/FLDLG2/FLDLN2/FLDZ
    http://x86.renejeschke.de/html/file_module_x86_id_101.html
  20. x86 Instruction Set Reference: FLD
    http://x86.renejeschke.de/html/file_module_x86_id_100.html
  21. x86 Instruction Set Reference: FST/FSTP
    http://x86.renejeschke.de/html/file_module_x86_id_117.html
  22. x86 Instruction Set Reference: BTC
    http://x86.renejeschke.de/html/file_module_x86_id_23.html
  23. x86 Instruction Set Reference: BTR
    http://x86.renejeschke.de/html/file_module_x86_id_24.html
  24. x86 Instruction Set Reference: BTS
    http://x86.renejeschke.de/html/file_module_x86_id_25.html
  25. x86 Instruction Set Reference: BSF
    http://x86.renejeschke.de/html/file_module_x86_id_19.html
  26. x86 Instruction Set Reference: BSR
    http://x86.renejeschke.de/html/file_module_x86_id_20.html
  27. x86 Instruction Set Reference: BSWAP
    http://x86.renejeschke.de/html/file_module_x86_id_21.html
  28. x86 Instruction Set Reference: XCHG
    http://x86.renejeschke.de/html/file_module_x86_id_328.html
  29. x86 Instruction Set Reference: SETcc
    http://x86.renejeschke.de/html/file_module_x86_id_288.html
  30. X86 Assembly/Arithmetic
    https://en.wikibooks.org/wiki/X86_Assembly/Arithmetic
  31. Art of Assembly - Arithmetic Instructions
    http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-2.html
  32. The GNU Assembler Tutorial
    http://tigcc.ticalc.org/doc/gnuasm.html
  33. The GNU Assembler - macros
    http://tigcc.ticalc.org/doc/gnuasm.html#SEC109
  34. ARM subroutines & program stack
    http://www.toves.org/books/armsub/
  35. Generating Mixed Source and Assembly List using GCC
    http://www.systutorials.com/240/generate-a-mixed-source-and-assembly-listing-using-gcc/
  36. Calling subroutines
    http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0100a/armasm_cihcfigg.htm
  37. ARM Assembly Language Programming
    http://peter-cockerell.net/aalp/html/frames.html
  38. ASM Flags
    http://www.cavestory.org/guides/csasm/guide/asm_flags.html
  39. Status Register
    https://en.wikipedia.org/wiki/Status_register
  40. Intel x86 JUMP quick reference
    http://unixwiz.net/techtips/x86-jumps.html
  41. Linux assemblers: A comparison of GAS and NASM
    http://www.ibm.com/developerworks/library/l-gas-nasm/index.html
  42. Programovani v assembleru na OS Linux
    http://www.cs.vsb.cz/grygarek/asm/asmlinux.html
  43. Is it worthwhile to learn x86 assembly language today?
    https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1
  44. Why Learn Assembly Language?
    http://www.codeproject.com/Articles/89460/Why-Learn-Assembly-Language
  45. Is Assembly still relevant?
    http://programmers.stackexchange.com/questions/95836/is-assembly-still-relevant
  46. Why Learning Assembly Language Is Still a Good Idea
    http://www.onlamp.com/pub/a/onlamp/2004/05/06/writegreatcode.html
  47. Assembly language today
    http://beust.com/weblog/2004/06/23/assembly-language-today/
  48. Assembler: Význam assembleru dnes
    http://www.builder.cz/rubriky/assembler/vyznam-assembleru-dnes-155960cz
  49. Assembler pod Linuxem
    http://phoenix.inf.upol.cz/linux/prog/asm.html
  50. AT&T Syntax versus Intel Syntax
    https://www.sourceware.org/binutils/docs-2.12/as.info/i386-Syntax.html
  51. Linux Assembly website
    http://asm.sourceforge.net/
  52. Using Assembly Language in Linux
    http://asm.sourceforge.net/articles/linasm.html
  53. vasm
    http://sun.hasenbraten.de/vasm/
  54. vasm – dokumentace
    http://sun.hasenbraten.de/vasm/release/vasm.html
  55. The Yasm Modular Assembler Project
    http://yasm.tortall.net/
  56. 680x0:AsmOne
    http://www.amigacoding.com/index.php/680x0:AsmOne
  57. ASM-One Macro Assembler
    http://en.wikipedia.org/wiki/ASM-One_Macro_Assembler
  58. ASM-One pages
    http://www.theflamearrows.info/documents/asmone.html
  59. Základní informace o ASM-One
    http://www.theflamearrows.info/documents/asminfo.html
  60. Linux Syscall Reference
    http://syscalls.kernelgrok.com/
  61. Programming from the Ground Up Book - Summary
    http://savannah.nongnu.org/projects/pgubook/
  62. IBM System 360/370 Compiler and Historical Documentation
    http://www.edelweb.fr/Simula/
  63. IBM 700/7000 series
    http://en.wikipedia.org/wiki/IBM_700/7000_series
  64. IBM System/360
    http://en.wikipedia.org/wiki/IBM_System/360
  65. IBM System/370
    http://en.wikipedia.org/wiki/IBM_System/370
  66. Mainframe family tree and chronology
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_FT1.html
  67. 704 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP704.html
  68. 705 Data Processing System
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP705.html
  69. The IBM 704
    http://www.columbia.edu/acis/history/704.html
  70. IBM Mainframe album
    http://www-03.ibm.com/ibm/history/exhibits/mainframe/mainframe_album.html
  71. Osmibitové muzeum
    http://osmi.tarbik.com/
  72. Tesla PMI-80
    http://osmi.tarbik.com/cssr/pmi80.html
  73. PMI-80
    http://en.wikipedia.org/wiki/PMI-80
  74. PMI-80
    http://www.old-computers.com/museum/computer.asp?st=1&c=1016
  75. The 6502 overflow flag explained mathematically
    http://www.righto.com/2012/12/the-6502-overflow-flag-explained.html
  76. X86 Opcode and Instruction Reference
    http://ref.x86asm.net/coder32.html