Předchozí článek, v němž jsme si ve stručnosti popsali konvence používané při volání knihovních funkcí na mikroprocesorech s 64bitovou architekturou x86-64, dnes doplníme, protože si řekneme, jakým způsobem se volají knihovní funkce na 32bitových mikroprocesorech s architekturou ARM. Dnešní díl tedy bude zaměřen více prakticky, neboť assembler se na ARMech s velkou pravděpodobností používá častěji, než je tomu na obecně výkonnějších 64bitových čipech x86-64, které jsou navíc určeny pro odlišný segment trhu.
Obsah
1. Použití assembleru v Linuxu: konvence při volání knihovních funkcí na mikroprocesorech ARM
2. Kostra programu napsaného v assembleru se subrutinou main
3. První demonstrační příklad – návrat ze subrutiny main s předáním návratového kódu
4. Alternativní způsob ukončení subrutiny main instrukcí bx
5. Druhý demonstrační příklad – použití instrukce bx
6. Uložení návratové adresy na zásobník
7. Třetí demonstrační příklad – uložení návratové adresy na zásobník
8. Uložení návratové adresy i registrů, které se používají v subrutině
9. Čtvrtý demonstrační příklad – uložení a obnova obsahu pracovních registrů
10. Volání knihovní funkce puts() z assembleru
11. Pátý demonstrační příklad – zavolání funkce puts()
12. Spuštění příkladu v debuggeru
13. Repositář s demonstračními příklady
1. Použití assembleru v Linuxu: konvence při volání knihovních funkcí na mikroprocesorech ARM
Na předchozí díl seriálu o použití assembleru v Linuxu, v němž jsme si popsali způsob volání funkcí standardní céčkové knihovny na 64bitových mikroprocesorech s architekturou x86-64, dnes navážeme, protože si řekneme a na několika demonstračních příkladech ukážeme, jakým způsobem je možné provést stejnou činnost, nyní ovšem na 32bitových RISCových mikroprocesorech s architekturou ARM. Jen pro úplnost si připomeňme některé instrukce, které na těchto mikroprocesorech existují a s nimiž se setkáme v navazujících kapitolách. Mnemotechnické zkratky těchto instrukcí i jejich chování se (podle očekávání) odlišují od procesorů Intel:
# | Instrukce | Význam |
---|---|---|
1 | MOV | přenos dat mezi registry, načtení konstanty do registru atd. |
2 | B | nepodmíněný skok popř. skok při splnění určené podmínky |
3 | BL | skok do podprogramu (branch and link) |
4 | BX | skok + přepnutí mikroprocesoru do režimu Thumb či zpět |
5 | STM | uložení vybrané skupiny registrů do operační paměti |
6 | LDM | načtení vybrané skupiny registrů z operační paměti |
Základní instrukcí skoku je instrukce pojmenovaná jednoduše B, což je zkratka odvozená od slova branch. 32bitové slovo této instrukce je rozděleno na tři části. V nejvyšších čtyřech bitech se nachází kód podmínky, což v důsledku znamená, že jediná instrukce B může nahradit všechny formy podmíněných skoků (přesněji řečeno čtrnáct typů podmíněných skoků a jeden skok nepodmíněný). Za těmito čtyřmi bity následuje taktéž čtyřbitový operační kód 1010 a ve zbylých 24 bitech instrukčního slova je pak uložena konstanta, z níž se vypočítá offset skoku. Skok je vždy proveden na adresu dělitelnou čtyřmi, tudíž je při výpočtu offsetu 24bitová konstanta posunuta o dva bity doleva.
Další důležitou instrukcí mikroprocesorů ARM je instrukce sloužící pro skok do podprogramu. Zatímco na mnoha jiných architekturách mikroprocesorů se ukládá návratová adresa do zásobníku, na mikroprocesorech ARM je pro uložení návratové hodnoty použit pracovní registr R14 nazývaný z tohoto důvodu taktéž link register. Pro skok do podprogramu se používá instrukce BL, neboli branch and link. Při provádění této instrukce provede mikroprocesor ve skutečnosti dvě operace: vypočítá adresu skoku stejným způsobem, jako tomu bylo u instrukce B (branch) a uloží tuto adresu do registru R15/PC (tato adresa se použije pro načtení následující instrukce). Současně však taktéž vloží adresu aktuální PC-4 do registru R14/LR, protože právě hodnota aktuální PC-4 je adresou instrukce ležící těsně ZA instrukcí skoku (nesmíme zapomenout na to, že se při provedení skoku ve skutečnosti již obsah registru PC stačil dvakrát zvýšit o hodnotu 4).
V dalších příkladech budeme často používat instrukci určenou pro výskok (resp. přesněji řečeno návrat) z podprogramu. Zajímavé je, že ve skutečnosti žádná speciální instrukce typu RET nebo RETURN na mikroprocesorech ARM neexistuje. Vše, co musí programátor udělat, je obnovit obsah registru R15/PC z registru R14, a to prostým přesunem dat:
mov PC, LR
U mikroprocesorů ARM podporujících instrukční sadu Thumb, popřípadě i technologii Jazelle (bajtkód JVM) existují i další typy skokových instrukcí. Pro přehlednost jsou všechny skokové instrukce vypsány v následující tabulce:
# | Instrukce | Význam |
---|---|---|
1 | B | prostý skok |
2 | BL | skok + uložení návratové hodnoty do link registru |
3 | BX | skok + přepnutí do režimu Thumb či zpět |
4 | BLX | kombinace instrukcí BL+BX |
5 | BXJ | skok + přepnutí do režimu Jazelle |
2. Kostra programu napsaného v assembleru se subrutinou main
Připomeňme si nejdříve, jak vlastně vypadala kostra toho nejjednoduššího programu napsaného v assembleru 32bitových mikroprocesorů ARM. Celý program se skládal pouze ze tří instrukcí, které nejprve naplnily pracovní registry r0 (návratový kód, který lze načíst v shellu) a r7 (číslo syscallu, tedy číslo funkce jádra, která se má zavolat) a následně se instrukcí SVC zavolalo jádro operačního systému. Tato sekvence instrukcí začínala na návěští se jménem _start. Toto jméno je velmi důležité, neboť právě podle něho linker pozná, kam má umístit vstupní bod do programu. Pro jistou si celou kostru aplikace ještě jednou ukažme (povšimněte si, že komentáře je nutné zapisovat znakem @, neboť znak # je vyhrazen pro zápis konstanty; určitou výjimkou jsou komentáře umístěné na samostatném řádku):
# asmsyntax=as
# Sablona pro zdrojovy kod Linuxoveho programu naprogramovaneho
# v assembleru GNU AS.
#
# Autor: Pavel Tisnovsky
# Linux kernel system call table
sys_exit=1
#-----------------------------------------------------------------------------
.section .data
#-----------------------------------------------------------------------------
.section .bss
#-----------------------------------------------------------------------------
.section .text
<strong>.global _start</strong> @ tento symbol ma byt dostupny i z linkeru
<strong>_start</strong>:
mov r7,$sys_exit @ cislo sycallu pro funkci "exit"
mov r0,#0 @ exit code = 0
svc 0 @ volani Linuxoveho kernelu
Podobně jako je tomu na architektuře x86-64, i na ARMech platí, že 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 prostého 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/subrutinu main, z níž se musí řízení programu na konci nějakým způsobem vrátit. Ukončení aplikace vypadá poměrně jednoduše: do registru r0 se uloží návratový kód a následně se řízení programu vrátí ze subrutiny main do volajícího kódu. Instrukce ret sice na ARMech neexistuje, to ovšem nevadí, jak již ostatně víme z předchozí kapitoly, protože ji můžeme jednoduše nahradit instrukcí mov PC, LR:
.section .text
<strong>.global main</strong> @ tento symbol ma byt dostupny i linkeru
<strong>main:</strong>
mov r0, #0 @ navratova hodnota (exit status)
mov pc, lr @ ukonceni aplikace (rizeni se vrati na adresu ulozenou v LR)
3. První demonstrační příklad – návrat ze subrutiny main s předáním návratového kódu
Pro úplnost si ukažme, jak tedy vypadá celá kostra demonstračního příkladu naprogramovaného pro 32bitové RISCové procesory ARM. Tento příklad je určen pro překlad s využitím nástroje gcc, nikoli kombinací utilit as+ld, proto je v něm deklarováno návěští main namísto _start:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - varianta urcena pro klasickou 32bitovou architekturu ARM
#
# Autor: Pavel Tisnovsky
#-----------------------------------------------------------------------------
.section .data
#-----------------------------------------------------------------------------
.section .text
.global main @ tento symbol ma byt dostupny i linkeru
main:
mov r0, #0 @ navratova hodnota (exit status)
mov pc, lr @ ukonceni aplikace (rizeni se vrati na adresu ulozenou v LR)
Porovnejme si, jak se tento program odlišuje od varianty určené pro procesory s architekturou x86-64 (nebo i i386, zde je to jedno):
Pro překlad a slinkování použijte jediný příkaz:
gcc test.s
Výsledkem by měl být spustitelný soubor, jehož velikost dosahuje na mém systému 5268 bajtů (ovšem s jinou verzí gcc či libc může být velikost nepatrně odlišná). Některé symboly je možné ze spustitelného souboru odstranit takto:
strip a.out
Na mém operačním systému (Raspbian) se velikost spustitelného souboru po tomto zásahu zmenší na 2824 bajtů. Jen pro připomenutí – překladem původní aplikace volající přímo jádro operačního systému vznikl spustitelný soubor o velikosti pouhých 312 bajtů.
Pro zajímavost se podívejme, jak vypadá přeložená funkce/subrutina main. Jedná se o pouhé dvě instrukce (to již ostatně víme), přičemž každá instrukce má konstantní šířku 32 bitů:
00008390 <main>:
8390: e3a00000 mov r0, #0
8394: e1a0f00e mov pc, lr
4. Alternativní způsob ukončení subrutiny main instrukcí bx
Pokud se podíváte na strojový kód, který vznikne překladem céčkovských zdrojových textů do assembleru, zjistíte, že se návrat z funkce/subrutiny main provádí poněkud odlišným způsobem, protože se namísto instrukce mov PC, LR používá instrukce bx LR (branch and exchange), tj. skok na adresu, která je uložena v pracovním registru LR (link register). Teoreticky by sice bylo možné použít i instrukci b (branch), ovšem tato instrukce neumožňuje, aby se namísto offsetu (24bitové konstanty) použil pracovní registr. Překladač programovacího jazyka C většinou vygeneruje tento strojový kód:
main:
mov r0, #0 @ navratova hodnota (exit status)
bx lr @ ukonceni aplikace (rizeni se vrati na adresu ulozenou v LR)
Poznámka: v některých případech může instrukce BX přepnout režim procesoru na základě nejnižších bitů adresy.
5. Druhý demonstrační příklad – použití instrukce bx
Ukažme si, jak by mohl vypadat zdrojový kód nejjednoduššího programu napsaného v assembleru, který je překládán pomocí nástroje gcc a tudíž obsahuje pouze funkci/subrutinu main. Program je tvořen pouhými dvěma instrukcemi, přičemž první instrukce nastavuje návratový kód a druhá instrukce ukončí provádění subrutiny main:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - varianta urcena pro klasickou 32bitovou architekturu ARM
#
# Autor: Pavel Tisnovsky
#-----------------------------------------------------------------------------
.section .data
#-----------------------------------------------------------------------------
.section .text
.global main @ tento symbol ma byt dostupny i linkeru
main:
mov r0, #0 @ navratova hodnota (exit status)
bx lr @ ukonceni aplikace (rizeni se vrati na adresu ulozenou v LR)
V objektovém kódu nalezneme tyto dvě instrukce. Další části jsou automaticky přidané při linkování a nemusí nás nyní zajímat:
00008390 <main>:
8390: e3a00000 mov r0, #0
8394: e12fff1e bx lr
Povšimněte si, že díky tomu, že všechny instrukce mají na původní RISCové architektuře ARM shodnou šířku 32bitů, má subrutina main po překladu stejnou velikost, jako u předchozího příkladu:
00008390 <main>:
8390: e3a00000 mov r0, #0
8394: e1a0f00e mov pc, lr
6. Uložení návratové adresy na zásobník
Pokud se v subrutině main budou volat další subrutiny a knihovní funkce, došlo by ke změně registru LR a tudíž by nebylo možné se jednoduše ze subrutiny main vrátit zpět do volajícího kódu. Z tohoto důvodu je vhodné, aby se obsah registru LR uložil na zásobník a těsně před návratem ze subrutiny main by se obsah tohoto registru opět obnovil. Pro tento účel je možné použít instrukce STM (store multiple) a LDM (load multiple). Jedná se o všestranně použitelné instrukce, u nichž lze specifikovat jak adresu, na kterou se budou ukládat operandy, tak i způsob případného zvýšení či snížení této adresy (právě automatická inkrementace a dekrementace simuluje práci zásobníku). Navíc tyto instrukce nemusí ukládat či načítat jen jeden operand, ale libovolný počet pracovních registrů (ten je zvolen pomocí bitového pole uvnitř instrukčního kódu):
Instrukce | Význam |
---|---|
STM R10, {R1} | uložení registru R1 na adresu specifikovanou v R10 |
STM R10, {R1, R2} | uložení registrů R1 a R2 na adresu specifikovanou v R10 a na adresu následující |
STMFD R10!, {R1} | R10 obsahuje adresu vrcholu zásobníku, na nějž je uložen registr R1 |
STMFD R10!, {R1,R2,R3} | R10 obsahuje adresu vrcholu zásobníku, na nějž jsou uloženy registry R1, R2 a R3 |
STMED R10!, {R1} | R10 obsahuje adresu vrcholu zásobníku, na nějž je uložen registr R1 |
STMED R10!, {R1,R2,R3} | R10 obsahuje adresu vrcholu zásobníku, na nějž jsou uloženy registry R1, R2 a R3 |
STMFA R10!, {R1} | jako STMFD, ovšem zásobník roste směrem k vyšším adresám |
STMFA R10!, {R1,R2,R3} | jako STMFD, ovšem zásobník roste směrem k vyšším adresám |
Rozdíl mezi STMFD a STMED spočívá v chování ukazatele na vrchol zásobníku. U varianty F vrchol ukazuje na poslední zapsaný (zaplněný) prvek, zatímco u varianty E ukazuje na první volný prvek.
7. Třetí demonstrační příklad – uložení návratové adresy na zásobník
Ve třetím demonstračním příkladu se ihned po vstupu do subrutiny main uloží link register na zásobník, následně se nastaví návratová hodnota do pracovního registru r0 a v posledním kroku se malým trikem obnoví hodnota uložená na zásobník (byl to obsah link registru), ovšem tentokrát se tato hodnota zapíše přímo do instrukčního čítače (program counter). Tím se vlastně de facto provede již dříve zmíněná instrukce mov PC, LR, resp. přesněji řečeno pseudoinstrukce mov PC, původní_hodnota_LR:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - varianta urcena pro klasickou 32bitovou architekturu ARM
#
# Autor: Pavel Tisnovsky
#-----------------------------------------------------------------------------
.section .data
#-----------------------------------------------------------------------------
.section .text
.global main @ tento symbol ma byt dostupny i linkeru
main:
stmfd sp!, {lr} @ ulozeni registru LR na zasobnik
mov r0, #42 @ navratova hodnota (exit status)
ldmfd sp!, {pc} @ ukonceni aplikace (rizeni se vrati na adresu ulozenou v LR)
I přesto, že jsou instrukce typu LDM a STM velmi univerzální a mohou provádět potenciálně složité operace (například uložení všech pracovních registrů atd.), jsou stále překládány do jediného 32bitového instrukčního slova, což je ostatně vidět z následujícího výpisu získaného disassemblerem:
00008390 <main>:
8390: e92d4000 stmfd sp!, {lr}
8394: e3a0002a mov r0, #42 ; 0x2a
8398: e8bd8000 ldmfd sp!, {pc}
8. Uložení návratové adresy i registrů, které se používají v subrutině
Vzhledem k tomu, že instrukce typu LDM a STM dokážou načíst či naopak uložit větší množství pracovních registrů, je možné na začátku subrutiny zajistit, aby se na zásobník uložily například registry R4, R5, R6 a LR. Pokud nedodržíte pořadí registrů, vypíše assembler varování, protože kvůli způsobu kódování musí být registry ukládány v pevně daném pořadí:
stmfd sp!, {r4, r5, r6, <strong>lr</strong>} @ ulozeni zvolenych registru na zasobnik
Návrat ze subrutiny je poté proveden následovně – celý trik spočívá v tom, že poslední registr, který se obnovuje, již není LR ale PC:
ldmfd sp!, {r4, r5, r6, <strong>pc</strong>} @ obnova registru, ukonceni aplikace (rizeni se vrati na adresu ulozenou v LR)
9. Čtvrtý demonstrační příklad – uložení a obnova obsahu pracovních registrů
Postup, který jsme si popsali v předchozí kapitole nyní použijeme ve čtvrtém demonstračním příkladu, jehož kostra vypadá následovně:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - varianta urcena pro klasickou 32bitovou architekturu ARM
#
# Autor: Pavel Tisnovsky
#-----------------------------------------------------------------------------
.section .data
#-----------------------------------------------------------------------------
.section .text
.global main @ tento symbol ma byt dostupny i linkeru
main:
stmfd sp!, {r4, r5, r6, lr} @ ulozeni zvolenych registru na zasobnik
mov r0, #42 @ navratova hodnota (exit status)
ldmfd sp!, {r4, r5, r6, pc} @ obnova registru, ukonceni aplikace (rizeni se vrati na adresu ulozenou v LR)
Po překladu se můžeme podívat na způsob zakódování instrukcí, tentokrát pro přehlednost s použitím mnemotechnických zkratek používaných firmou Intel:
00008390 <main>:
8390: e92d4070 push {r4, r5, r6, lr}
8394: e3a0002a mov r0, #42 ; 0x2a
8398: e8bd8070 pop {r4, r5, r6, pc}
Význam programu se samozřejmě nezměnil, disassembler pouze použil názvy push a pop, které přesně odpovídají tomu, co se v programu děje.
10. Volání knihovní funkce puts() z assembleru
Funkce, které jsou dostupné ve standardní céčkové knihovně, se volají velmi jednoduše instrukcí bl (branch and link), které se předá adresa funkce. Adresa se samozřejmě zjišťuje až při překladu; ve zdrojovém kódu se použije jméno (návěští funkce). Pokud by funkce neměla žádné parametry, vypadalo by volání následovně:
bl puts @ zavolani knihovni funkce puts()
Ve skutečnosti funkce puts očekává jeden parametr, kterým je adresa řetězce, který se má vytisknout. Tento řetězec může být umístěn v datovém segmentu a deklarován pomocí .asciiz (tím se za konec řetězce přidá ukončující nula):
.section .data
hello_world_message:
.asciz "Hello world!\n" @ zprava, ktera se ma vytisknout na standardni vystup
Parametry se předávají přes pracovní registry. V tomto konkrétním případě je nutné v registru r0 předat adresu řetězce (resp. adresu jeho prvního znaku). Nejprve tuto adresu načteme, a to pseudoinstrukcí ldr. Tato instrukce se většinou přeloží takovým způsobem, že konstantu (adresu) umístí před či za subrutinu a načtení je provedeno relativně k obsahu registru PC:
ldr r0, =hello_world_message @ adresa zpravy, ktera se ma vytisknout
Parametr máme inicializovaný a jiné parametry již funkce puts nepotřebuje, takže ji nyní skutečně můžeme zavolat:
bl puts @ zavolani knihovni funkce puts()
Povšimněte si, že zavoláním se přepíše obsah registru LR, což je jeden z důvodů, proč jsme si jeho obsah uschovali na zásobníku.
11. Pátý demonstrační příklad – zavolání funkce puts()
Celá sekvence volání knihovní funkce puts je použita v dnešním posledním demonstračním příkladu, jehož zdrojový kód je umístěn pod tento odstavec:
# asmsyntax=as
# Program pro otestovani volani funkci ze standardni knihovny jazyka C
# - volani funkce 'puts'
# - 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
bl puts @ zavolani knihovni funkce puts()
mov r0, #42 @ navratova hodnota (exit status)
ldmfd sp!, {pc} @ obnova registru, ukonceni aplikace (rizeni se vrati na adresu ulozenou v LR)
Opět se podívejme na porovnání tohoto programového kódu s jeho ekvivalentem, ovšem naprogramovaným pro mikroprocesory s architekturou x86-64. Manipulace se zásobníkem je na procesorech ARM kratší, a to z toho důvodu, že není nutné zajišťovat zarovnání vrcholu zásobníku na adresu dělitelnou šestnácti:
12. Spuštění příkladu v debuggeru
Na závěr si zkusme odkrokovat chování posledního programu v GNU debuggeru. Překlad proveďte s volbou -g a posléze spusťte debugger:
pi@raspberrypi $ <strong>gdb a.out</strong>
GNU gdb (GDB) 7.4.1-debian
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /mnt/home/pi/temp/44_stdc_puts/a.out...done.
Nastavíme breakpoint na vstup subrutiny main:
(gdb) <strong>break main</strong>
Breakpoint 1 at 0x83cc: file main_arm.s, line 24.
Nyní aplikaci spustíme. Měla by se zastavit na nastaveném breakpointu:
(gdb) <strong>run</strong>
Starting program: /mnt/home/pi/temp/44_stdc_puts/a.out
Breakpoint 1, main () at main_arm.s:24
24 ldr r0, =hello_world_message @ adresa zpravy, ktera se ma vytisknout
Můžeme se podívat na aktuální hodnotu všech pracovních registrů. Zajímat nás bude zejména registru r0 a pc:
(gdb) <strong>info registers</strong>
r0 <strong>0x1</strong> 1
r1 0xbefff724 3204445988
r2 0xbefff72c 3204445996
r3 0x83c8 33736
r4 0x0 0
r5 0x0 0
r6 0x831c 33564
r7 0x0 0
r8 0x0 0
r9 0x0 0
r10 0xb6fff000 3070226432
r11 0x0 0
r12 0xb6fbf000 3069964288
sp 0xbefff5d4 0xbefff5d4
lr 0xb6eaa82c -1226135508
pc <strong>0x83cc</strong> 0x83cc <main+4>
cpsr 0x60000010 1610612752
Taktéž si můžeme zobrazit, na jakém místě kódu se právě nacházíme:
(gdb) <strong>disassemble</strong>
Dump of assembler code for function main:
0x000083c8 <+0>: push {lr}
=> 0x000083cc <+4>: ldr r0, [pc, #8] ; 0x83dc <main+20>
0x000083d0 <+8>: bl 0x82ec
0x000083d4 <+12>: mov r0, #42 ; 0x2a
0x000083d8 <+16>: pop {pc}
0x000083dc <+20>: andeq r0, r1, r0, lsl #11
End of assembler dump.
Nyní řekneme debuggeru, aby vykonal další instrukci a ihned poté se opět zastavil:
(gdb) <strong>step</strong>
25 bl puts @ zavolani knihovni funkce puts()
Pohledem na disassemblovaný kód se ujistíme, že se řízení skutečně přesunulo pouze na další instrukci:
(gdb) <strong>disassemble </strong>
Dump of assembler code for function main:
0x000083c8 <+0>: push {lr}
0x000083cc <+4>: ldr r0, [pc, #8] ; 0x83dc <main+20>
=> 0x000083d0 <+8>: bl 0x82ec
0x000083d4 <+12>: mov r0, #42 ; 0x2a
0x000083d8 <+16>: pop {pc}
0x000083dc <+20>: andeq r0, r1, r0, lsl #11
End of assembler dump.
Současně se změnil obsah pracovního registru r0 (obsahuje adresu řetězce) a samozřejmě i obsah registru pc (obsahuje adresu další instrukce):
(gdb) info registers
r0 <strong>0x10580</strong> 66944
r1 0xbefff724 3204445988
r2 0xbefff72c 3204445996
r3 0x83c8 33736
r4 0x0 0
r5 0x0 0
r6 0x831c 33564
r7 0x0 0
r8 0x0 0
r9 0x0 0
r10 0xb6fff000 3070226432
r11 0x0 0
r12 0xb6fbf000 3069964288
sp 0xbefff5d4 0xbefff5d4
lr 0xb6eaa82c -1226135508
pc <strong>0x83d0</strong> 0x83d0 <main+8>
cpsr 0x60000010 1610612752
Po dalším kroku se na standardní výstup vytiskne zpráva, protože tímto krokem se volala funkce puts:
(gdb) <strong>step</strong>
Hello world!
26 mov r0, #42 @ navratova hodnota (exit status)
Program můžeme dokončit příkazem „continue“:
(gdb) <strong>cont</strong>
Continuing.
13. 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: šablona pro překlad nástrojem gcc
Poznámka: skript pro disassembling je shodný pro obě architektury mikroprocesorů.
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 | main_arm.s | hlavní program pro procesory ARM | https://github.com/tisnik/presentations/blob/master/assembler/44_stdc_puts/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/44_stdc_puts/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/44_stdc_puts/assemble_arm |
5 | disassemble | skript pro disassembling | https://github.com/tisnik/presentations/blob/master/assembler/44_stdc_puts/disassemble |
Poznámka: opět zde platí, že skript pro disassembling je shodný pro obě architektury mikroprocesorů.
14. 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