Obsah
1. Framework Capstone aneb univerzální disassembler
2. Základní způsob použití frameworku Capstone: disassembling obsahu binárního souboru
4. Výpis operačních kódů instrukcí
6. Explicitní specifikace bázové adresy kódu (org)
8. Binární kód uložený v souboru od zadaného nenulového offsetu
9. Demonstrační příklady: korektní a nekorektní varianty
10. Disassembling binárního souboru s nativním kódem pro odlišnou architekturu CPU
11. Kombinace Keystone+Capstone
12. Kód v assembleru určený pro platformu x86–64
13. Explicitní změna bázové adresy
14. Kód v assembleru určený pro 32bitovou platformu ARM
15. Využití instrukční sady ARM Thumb
16. Přepis příkladů pro architekturu AArch64
19. Repositář s demonstračními příklady
1. Framework Capstone aneb univerzální disassembler
V dnešním článku, jenž navazuje na předchozí článek o frameworku Keystone, se seznámíme s frameworkem nazvaným Capstone. Zatímco výše zmíněný framework Keystone je univerzálním assemblerem, jedná se v případě frameworku Capstone o univerzální disassembler, který podporuje všechny v současnosti mainstreamové architektury (x86, x86–64, 32bitový ARM, AArch64 atd.) a je implementován formou nativní knihovny, kterou je možné volat z různých programovacích jazyků. Oba zmíněné frameworky, tedy jak Capstone, tak i Keystone, lze přitom používat společně. Příkladem mohou být systémy, které nejprve provedou disassembling vybraného binárního bloku dat s úpravou kódu v assembleru a s jeho následným překladem zpět do nativního binárního kódu.
Možný a podporovaný je pochopitelně i opačný přístup, tj. překlad kódu napsaného v assembleru do nativního kódu s jeho následným zpětným překladem do assembleru (s doplněním informací o způsobu zakódování jednotlivých strojových instrukcí). Tento přístup je možné použít například při výuce a ukážeme si ho na několika demonstračních příkladech, kde porovnáme podobný kód (dvojice vnořených programových smyček) zapsaný pro architekturu x86(64), klasický 32bitový ARM, 32bitový ARM s instrukční sadou Thumb a konečně 64bitovou architekturu AArch64.
2. Základní způsob použití frameworku Capstone: disassembling obsahu binárního souboru
Podívejme se nejprve na základní způsob využití frameworku Capstone. Budeme se snažit o zpětný překlad (disassembling) nativního binárního kódu, který vznikl překladem zdrojového kódu v assembleru pro platformu x86–64 (resp. přesněji řečeno pro platformu x86 v 64bitovém režimu). Konkrétně se jedná o sekvenci strojových instrukcí, které realizují dvojici vnořených programových smyček. V assembleru vypadá zápis těchto programových smyček následovně:
MOV EBX, 10 OUTER_LOOP: MOV EAX, 100 INNER_LOOP: DEC EAX JNZ INNER_LOOP DEC EBX JNZ OUTER_LOOP
Překladem (konkrétně překladem s využitím minule popsaného assembleru Keystone) vznikl binární soubor nazvaný „loops.bin“ o délce osmnácti bajtů. Obsah tohoto souboru je následující:
$ od -tx1 loops.bin 0000000 bb 0a 00 00 00 b8 64 00 00 00 ff c8 75 fc ff cb 0000020 75 f3 0000022
3. Demonstrační příklad
A právě obsah binárního souboru představeného v rámci předchozí kapitoly se budeme snažit zpětně přeložit do assembleru s využitím frameworku Capstone. K tomuto účelu poslouží následující skript, jenž obsah binárního souboru načte a následně se pokusí o zpětný překlad. Povšimněte si, že při zpětném překladu musíme (vcelku logicky) určit architekturu a taktéž režim – jinými slovy jak instrukční sadu, tak i způsob kódování instrukcí (tyto informace není možné nijak automaticky odvodit):
# import všech symbolů použitých ve skriptu from capstone import Cs, CS_ARCH_X86, CS_MODE_64 # otevřít binární soubor a přečíst jeho obsah with open("loops.bin", "rb") as fin: code = fin.read() # disassembling obsahu binárního souboru md = Cs(CS_ARCH_X86, CS_MODE_64) # vypsat podrobnější informace o každé instrukci for i in md.disasm(code, 0x0000): # výpis informací o instrukci print("0x{:02x}:\t{:s}\t{:s}".format(i.address, i.mnemonic, i.op_str))
Z výsledků vypsaných výše uvedeným skriptem je patrné, že máme k dispozici prakticky všechny důležité informace o strojových instrukcích – jejich adresu, jméno instrukce i seznam operandů (pokud se jedná o instrukci s operandem či s operandy):
0x00: mov ebx, 0xa 0x05: mov eax, 0x64 0x0a: dec eax 0x0c: jne 0xa 0x0e: dec ebx 0x10: jne 5
4. Výpis operačních kódů instrukcí
Naprostá většina v současnosti používaných disassemblerů dokáže kromě jména instrukce a seznamu operandů vypsat i bajty obsahující operační kód instrukce i zakódované operandy. Příkladem může být nástroj objdump, s nímž jsme se již setkali minule a jenž dokáže provést například tuto operaci:
$ objdump -b binary -D -m i386:x86-64 -M intel loops.bin > loops_dump.asm loops.bin: file format binary Disassembly of section .data: 0000000000000000 <.data>: 0: bb 0a 00 00 00 mov ebx,0xa 5: b8 64 00 00 00 mov eax,0x64 a: ff c8 dec eax c: 75 fc jne 0xa e: ff cb dec ebx 10: 75 f3 jne 0x5
I tyto informace nám pochopitelně framework Capstone dává k dispozici, protože při procházení jednotlivými instrukcemi jsou v atributu bytes uložené jednotlivé bajty reprezentující instrukci v nativním kódu. Hodnoty těchto bajtů je pouze nutné převést do čitelné podoby. Je zvykem hodnoty těchto bajtů vypisovat v hexadecimálním tvaru, takže použijeme standardní metodu hex(), která převod sekvence bajtů do řetězce s hexadecimálními hodnotami provede za nás, a to i s využitím oddělovače:
dump = i.bytes.hex(" ")
5. Demonstrační příklad
Skript ze třetí kapitoly upravíme takovým způsobem, aby se kromě adresy, mnemotechnické zkratky instrukce a operandů instrukce vypsaly i hodnoty bajtů reprezentujících instrukci v nativním kódu. Délka instrukce v nativním strojovém kódu sice není obecně omezena, ovšem skript kvůli jednoduchosti počítá s tím, že se bude jednat o maximálně šest bajtů (i delší instrukce se pochopitelně vypíše, ovšem jednotlivé sloupce již v tomto případě nebudou zarovnány pěkně pod sebou):
# import všech symbolů použitých ve skriptu from capstone import Cs, CS_ARCH_X86, CS_MODE_64 # otevřít binární soubor a přečíst jeho obsah with open("loops.bin", "rb") as fin: code = fin.read() # disassembling obsahu binárního souboru md = Cs(CS_ARCH_X86, CS_MODE_64) # vypsat podrobnější informace o každé instrukci for i in md.disasm(code, 0x0000): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str))
Získaný výsledek již vypadá (alespoň podle mého skromného názoru) značně profesionálně:
0x00: bb 0a 00 00 00 mov ebx, 0xa 0x05: b8 64 00 00 00 mov eax, 0x64 0x0a: ff c8 dec eax 0x0c: 75 fc jne 0xa 0x0e: ff cb dec ebx 0x10: 75 f3 jne 5
6. Explicitní specifikace bázové adresy kódu (org)
V obou předchozích demonstračních příkladech jste si mohli povšimnout, že při volání metody md.disassemble() se kromě sekvence bajtů představujících nativní (strojový) kód do této metody ve druhém parametru předává i celočíselná konstanta 0×0000. Modifikací této konstanty je možné změnit počáteční adresu celého bloku instrukcí; jedná se tedy o (i když ne zcela přesnou) obdobu direktivy .org, kterou známe z běžných assemblerů, například z GNU Assembleru (viz například https://ftp.gnu.org/old-gnu/Manuals/gas-2.9.1/html_chapter/as7.html#SEC112). V případě, že hodnotu této konstanty změníme, pochopitelně dojde jak ke změně adres instrukcí zobrazených v prvním sloupci, tak i k modifikaci adres relativních skoků (a podmíněné skoky se typicky překládají právě na relativní skoky, což mj. umožňuje generování přemístitelného strojového kódu).
7. Demonstrační příklad
Následující skript se od skriptu předchozího liší jen v maličkosti – namísto offsetu 0×0000 metody Cs.disasm() se této metodě předá offset 0×1234:
# import všech symbolů použitých ve skriptu from capstone import Cs, CS_ARCH_X86, CS_MODE_64 # otevřít binární soubor a přečíst jeho obsah with open("loops.bin", "rb") as fin: code = fin.read() # disassembling obsahu binárního souboru md = Cs(CS_ARCH_X86, CS_MODE_64) # vypsat podrobnější informace o každé instrukci for i in md.disasm(code, 0x1234): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str))
Výsledek bude ovšem v tomto případě zcela odlišný, protože se budou odlišovat například i cílové adresy (relativních) skoků:
0x1234: bb 0a 00 00 00 mov ebx, 0xa 0x1239: b8 64 00 00 00 mov eax, 0x64 0x123e: ff c8 dec eax 0x1240: 75 fc jne 0x123e 0x1242: ff cb dec ebx 0x1244: 75 f3 jne 0x1239
8. Binární kód uložený v souboru od zadaného nenulového offsetu
Nyní si (poněkud uměle) připravíme soubor s binárním obsahem, který bude nejprve obsahovat obecně náhodná data (resp. v našem případě nuly, ale může se jednat i o jiné hodnoty) a teprve poté v něm bude umístěn kód nativních instrukcí vzniklých překladem assemblerem Capstone. Jak lze takový soubor vytvořit? Cest je mnoho, můžeme například použít například základní nástroje Linuxu. Nejdříve si necháme vygenerovat binární soubor obsahující tisíc nulových hodnot:
$ truncate -s 1000 zeros
Výsledkem předchozího příkazu bude soubor nazvaný „zeros“ o délce přesně 1000 bajtů:
$ ls -l zeros -rw-rw-r-- 1 ptisnovs ptisnovs 1000 Dec 31 11:07 zeros
Následně jednoduše spojíme soubor „zeros“ s binárním souborem „loops.bin“, který byl analyzován v rámci předchozích kapitol:
$ cat zeros loops.bin > loops_x.bin
Výsledkem by měl být soubor „loops_x.bin“ o délce 1018 bajtů:
$ ls -l loops_x.bin -rw-rw-r-- 1 ptisnovs ptisnovs 1018 Dec 27 09:43 loops_x.bin
9. Demonstrační příklady: korektní a nekorektní varianty
Nejprve se pokusme takto vzniklý binární soubor přeložit běžným způsobem, tedy i s ignorováním faktu, že prvních tisíc bajtů ve skutečnosti žádné instrukce neobsahuje:
# import všech symbolů použitých ve skriptu from capstone import Cs, CS_ARCH_X86, CS_MODE_64 # otevřít binární soubor a přečíst jeho obsah with open("loops_x.bin", "rb") as fin: code = fin.read() # disassembling obsahu binárního souboru md = Cs(CS_ARCH_X86, CS_MODE_64) # vypsat podrobnější informace o každé instrukci for i in md.disasm(code, 0x0000): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str))
Výsledek bude pochopitelně obsahovat nekorektní informace a až na konci nalezneme korektní sekvenci instrukcí:
0x00: 00 00 add byte ptr [rax], al 0x02: 00 00 add byte ptr [rax], al 0x04: 00 00 add byte ptr [rax], al 0x06: 00 00 add byte ptr [rax], al 0x08: 00 00 add byte ptr [rax], al 0x0a: 00 00 add byte ptr [rax], al 0x0c: 00 00 add byte ptr [rax], al 0x0e: 00 00 add byte ptr [rax], al 0x10: 00 00 add byte ptr [rax], al 0x12: 00 00 add byte ptr [rax], al 0x14: 00 00 add byte ptr [rax], al ... ... ... 0x3e6: 00 00 add byte ptr [rax], al 0x3e8: bb 0a 00 00 00 mov ebx, 0xa 0x3ed: b8 64 00 00 00 mov eax, 0x64 0x3f2: ff c8 dec eax 0x3f4: 75 fc jne 0x3f2 0x3f6: ff cb dec ebx 0x3f8: 75 f3 jne 0x3ed
Řešení tohoto problémku je ve skutečnosti jednoduché – oněch 1000 bajtů musíme explicitně přeskočit a disassembleru předat pouze vybranou podsekvenci bajtů. Přeskočení lze realizovat buď operací seek nad souborem, nebo (což je méně efektivní) načtením celého binárního souboru následovaného operací slice:
# import všech symbolů použitých ve skriptu from capstone import Cs, CS_ARCH_X86, CS_MODE_64 # pozice v binárním souboru, od níž jsou instrukce uloženy OFFSET = 1000 # otevřít binární soubor a přečíst jeho obsah with open("loops_x.bin", "rb") as fin: code = fin.read() code = code[OFFSET:] # disassembling obsahu binárního souboru md = Cs(CS_ARCH_X86, CS_MODE_64) # vypsat podrobnější informace o každé instrukci for i in md.disasm(code, 0x0000): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str))
Výsledek:
0x00: bb 0a 00 00 00 mov ebx, 0xa 0x05: b8 64 00 00 00 mov eax, 0x64 0x0a: ff c8 dec eax 0x0c: 75 fc jne 0xa 0x0e: ff cb dec ebx 0x10: 75 f3 jne 5
popř. s korektním offsetem (začátkem instrukcí) nastaveným na 1000:
0x3e8: bb 0a 00 00 00 mov ebx, 0xa 0x3ed: b8 64 00 00 00 mov eax, 0x64 0x3f2: ff c8 dec eax 0x3f4: 75 fc jne 0x3f2 0x3f6: ff cb dec ebx 0x3f8: 75 f3 jne 0x3ed
10. Disassembling binárního souboru s nativním kódem pro odlišnou architekturu CPU
Vzhledem k tomu, že Capstone je (či měl by být) univerzálním disassemblerem, dokáže zpětně překládat i nativní kód určený pro odlišné architektury mikroprocesorů. Podívejme se na velmi jednoduchý příklad. Překladem následujícího céčkového kódu:
int add(int x, int y) { return x+y; }
na 32bitové platformě MIPS získáme tento (zcela neoptimalizovaný) kód:
add: addiu sp,sp,-8 sw s8,4(sp) move s8,sp sw a0,8(s8) sw a1,12(s8) lw v1,8(s8) lw v0,12(s8) nop addu v0,v1,v0 move sp,s8 lw s8,4(sp) addiu sp,sp,8 jr ra nop
Nativní kód je uložen do binárního souboru nazvaného „mips.bin“ o délce šedesáti bajtů:
$ od -tx1 mips.bin 0000000 f8 ff bd 27 04 00 be af 25 f0 a0 03 08 00 c4 af 0000020 0c 00 c5 af 08 00 c3 8f 0c 00 c2 8f 00 00 00 00 0000040 21 10 62 00 25 e8 c0 03 04 00 be 8f 08 00 bd 27 0000060 08 00 e0 03 00 00 00 00 00 00 00 00 0000074
Pokusme se nyní o zpětný překlad. Povšimněte si, že je opět nutné specifikovat jak architekturu CPU, tak i režim:
# import všech symbolů použitých ve skriptu from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS32 # otevřít binární soubor a přečíst jeho obsah with open("mips.bin", "rb") as fin: code = fin.read() # disassembling obsahu binárního souboru md = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32) # vypsat podrobnější informace o každé instrukci for i in md.disasm(code, 0x0000): # výpis informací o instrukci print("0x{:02x}:\t{:s}\t{:s}".format(i.address, i.mnemonic, i.op_str))
Výsledek bude vypadat následovně – skutečně jsme tedy dokázali provést zpětný překlad i na zcela odlišné platformě, a to bez nutnosti instalace dalších nástrojů:
0x00: addiu $sp, $sp, -8 0x04: sw $fp, 4($sp) 0x08: move $fp, $sp 0x0c: sw $a0, 8($fp) 0x10: sw $a1, 0xc($fp) 0x14: lw $v1, 8($fp) 0x18: lw $v0, 0xc($fp) 0x1c: nop 0x20: addu $v0, $v1, $v0 0x24: move $sp, $fp 0x28: lw $fp, 4($sp) 0x2c: addiu $sp, $sp, 8 0x30: jr $ra 0x34: nop 0x38: nop
11. Kombinace Keystone+Capstone
Frameworky Keystone a Capstone, tedy assembler a disassembler, jsou navrženy takovým způsobem, aby se používaly společně, například v rámci jediné aplikace. Příkladem může být nástroj pro tvorbu patchů, který nejprve část binárního nativního kódu zpětně přeloží do assembleru, umožní uživateli provést všechny potřebné změny a následně provede opětovný překlad do nativního binárního kódu. Ovšem samozřejmě je možné použít i opačný postup, kdy je na začátku soubor či text napsaný v assembleru, jenž je frameworkem Keystone přeložen do nativního binárního kódu a posléze se použije Capstone jak pro zpětný překlad, tak i pro získání sekvence bajtů reprezentujících jednotlivé instrukce. Tento postup je možné využít například pro výuku, typicky pro ukázku rozdílů mezi CISCovými a RISCovými instrukčními sadami nebo pro naznačení rozdílů mezi ARM32 a „komprimovanou“ instrukční sadou Thumb a Thumb-2.
12. Kód v assembleru určený pro platformu x86–64
Kombinaci Capstone+Keystone si můžeme vyzkoušet velmi snadno na následujícím demonstračním příkladu, v němž se nejdříve provede překlad kódu v assembleru, v němž je realizována dvojice vnořených programových smyček. Výsledkem překladu je sekvence bajtů, která se posléze zpětně přeložena zpět do čitelného kódu, a to včetně kódování jednotlivých instrukcí. Celý příklad využívá instrukční sadu x86–64 (64bitový režim):
# import všech symbolů assembleru použitých ve skriptu from keystone import Ks, KS_ARCH_X86, KS_MODE_64, KsError # import všech symbolů disassembleru použitých ve skriptu from capstone import Cs, CS_ARCH_X86, CS_MODE_64 # instrukce, které se mají přeložit assemblerem CODE = """ MOV EBX, 10 OUTER_LOOP: MOV EAX, 100 INNER_LOOP: DEC EAX JNZ INNER_LOOP DEC EBX JNZ OUTER_LOOP """ try: # inicializace assembleru se specifikací architektury a popř. i režimu ks = Ks(KS_ARCH_X86, KS_MODE_64) # vlastní překlad (assembling) encoding, count = ks.asm(CODE) # disassembling binární sekvence kódů md = Cs(CS_ARCH_X86, CS_MODE_64) # vypsat podrobnější informace o každé instrukci for i in md.disasm(bytes(encoding), 0x0000): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str)) except KsError as e: print("ERROR: %s" % e)
Po spuštění tohoto skriptu by se měl provést překlad (assembling) do binárního kódu následovaný zpětným překladem (disassembling) zpět do čitelné podoby, pochopitelně společně s binární reprezentací jednotlivých instrukcí:
0x00: bb 0a 00 00 00 mov ebx, 0xa 0x05: b8 64 00 00 00 mov eax, 0x64 0x0a: ff c8 dec eax 0x0c: 75 fc jne 0xa 0x0e: ff cb dec ebx 0x10: 75 f3 jne 5
13. Explicitní změna bázové adresy
Vzhledem k tomu, že původní kód v assembleru lze přesunout (realokovat) na prakticky libovolnou adresu, můžeme při zpětném překladu (disassemblingu) změnit bázovou adresu (offset, origin) zpětně překládaného kódu – viz též zvýrazněnou hodnotu v nepatrně upraveném zdrojovém kódu:
# import všech symbolů assembleru použitých ve skriptu from keystone import Ks, KS_ARCH_X86, KS_MODE_64, KsError # import všech symbolů disassembleru použitých ve skriptu from capstone import Cs, CS_ARCH_X86, CS_MODE_64 # instrukce, které se mají přeložit assemblerem CODE = """ MOV EBX, 10 OUTER_LOOP: MOV EAX, 100 INNER_LOOP: DEC EAX JNZ INNER_LOOP DEC EBX JNZ OUTER_LOOP """ try: # inicializace assembleru se specifikací architektury a popř. i režimu ks = Ks(KS_ARCH_X86, KS_MODE_64) # vlastní překlad (assembling) encoding, count = ks.asm(CODE) # disassembling binární sekvence kódů md = Cs(CS_ARCH_X86, CS_MODE_64) # vypsat podrobnější informace o každé instrukci for i in md.disasm(bytes(encoding), 0x1234): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str)) except KsError as e: print("ERROR: %s" % e)
Výsledek zpětného překladu se v tomto případě bude pochopitelně lišit:
0x1234: bb 0a 00 00 00 mov ebx, 0xa 0x1239: b8 64 00 00 00 mov eax, 0x64 0x123e: ff c8 dec eax 0x1240: 75 fc jne 0x123e 0x1242: ff cb dec ebx 0x1244: 75 f3 jne 0x1239
oproti původnímu výsledku:
0x00: bb 0a 00 00 00 mov ebx, 0xa 0x05: b8 64 00 00 00 mov eax, 0x64 0x0a: ff c8 dec eax 0x0c: 75 fc jne 0xa 0x0e: ff cb dec ebx 0x10: 75 f3 jne 5
14. Kód v assembleru pro 32bitovou platformu ARM
Capstone a Keystone podporují i 32bitovou platformu ARM, která se stále používá, i když v segmentu výkonných čipů je nahrazována 64bitovou architekturou AArch64. Čipy ARM jsou založeny na pipeline s proměnným počtem řezů (podle generace atd.). Interní i externí datová sběrnice má šířku 32 bitů a i všechny instrukce v instrukční sadě mají tutéž šířku 32 bitů (což ostatně uvidíme na příkladu). Díky tomu se zjednodušila řídicí logika v čipu, navíc bylo možné měnit obsah čítače instrukcí PC (r15) již ve chvíli, kdy se vykonávala fáze decode (výjimku samozřejmě tvořily skoky). Vzhledem k tomu, že mikroprocesory s architekturou RISC používají při přístupu do operační paměti pouze instrukce typu Load a Store, musí být tyto čipy vybaveny poměrně velkým počtem pracovních registrů. V případě ARMu je na čipu umístěno celkem 37 registrů (opět se může lišit podle generace), z nichž každý má pevnou šířku 32 bitů. Některé registry jsou pracovní, jiné mají speciální význam, například jsou v nich uloženy různé bitové příznaky. Zajímavé je, že namísto registrových oken použitých u jiných procesorů typu RISC se v případě ARMu objevuje rozdělení registrů do skupin, podle toho, v jakém stavu se mikroprocesor v daném okamžiku nachází.
Zkusme si nyní předchozí dvojici skriptů upravit do takové podoby, že se bude pracovat s jednoduchým prográmkem psaným v assembleru čipů ARM, v němž je opět realizována dvojice vnořených smyček. Povšimněte si, že je (pochopitelně) použita odlišná sada instrukcí a i názvy pracovních registrů se od platformy x86(64) liší. V samotném skriptu jsou použity odlišné konstanty KS_ARCH_ARM, KS_MODE_ARM, CS_ARCH_ARM a CS_MODE_ARM:
# import všech symbolů assembleru použitých ve skriptu from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KsError # import všech symbolů disassembleru použitých ve skriptu from capstone import Cs, CS_ARCH_ARM, CS_MODE_ARM # instrukce, které se mají přeložit assemblerem CODE = """ MOV R0, 10 OUTER_LOOP: MOV R1, 100 INNER_LOOP: SUBS R1, R1, 1 BNE INNER_LOOP SUBS R0, R0, 1 BNE OUTER_LOOP """ try: # inicializace assembleru se specifikací architektury a popř. i režimu ks = Ks(KS_ARCH_ARM, KS_MODE_ARM) # vlastní překlad (assembling) encoding, count = ks.asm(CODE) # disassembling binární sekvence kódů md = Cs(CS_ARCH_ARM, CS_MODE_ARM) # vypsat podrobnější informace o každé instrukci for i in md.disasm(bytes(encoding), 0x0000): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str)) except KsError as e: print("ERROR: %s" % e)
Po spuštění tohoto skriptu získáme mj. i sekvence bajtů se zakódovanými instrukcemi. Povšimněte si, že všechny instrukce, nezávisle na jejich interní složitosti a počtu operandů, mají na platformě ARM jednotnou šířku čtyř bajtů:
0x00: 0a 00 a0 e3 mov r0, #0xa 0x04: 64 10 a0 e3 mov r1, #0x64 0x08: 01 10 51 e2 subs r1, r1, #1 0x0c: fd ff ff 1a bne #8 0x10: 01 00 50 e2 subs r0, r0, #1 0x14: fa ff ff 1a bne #4
15. Využití instrukční sady ARM Thumb
Mikroprocesory ARM, které byly původně navrženy pro využití v osobních počítačích, se postupně začaly používat i v jiných oblastech, především ve vestavěných (embedded) systémech, kde vládnou poněkud jiné požadavky. Ve vestavěných systémech je totiž kromě nízké spotřeby, popř. rychlé reakce na přerušení, kladen i velký důraz na to, aby binární obrazy programů byly co nejmenší, protože programy jsou ukládány do pamětí ROM/EPROM/EEPROM/Flash s relativně vysokou cenou za jeden bit a nikoli na vysokokapacitních paměťových médiích (pevné disky) tak, jak je tomu na osobních počítačích. Navíc došlo k postupné změně i v oblasti osobních počítačů – rychlosti procesorů rostly větším tempem, než rychlost pamětí DRAM – což s sebou přinášelo nutnost použití drahých vyrovnávacích pamětí založených na technologii SRAM. V jeden okamžik se dokonce zdálo, že tento vývoj bude znamenat konec procesorů typu RISC, které jsou mj. typické i tím, že používají instrukční sady s instrukcemi pevné šířky.
Odpovědí společnosti ARM na obě nové skutečnosti, tedy na požadavek na menší velikost binárních obrazů programů a zvyšující se rozdíl v rychlosti CPU a DRAM, bylo zavedení nové alternativní instrukční sady nazvané Thumb, v níž mají všechny instrukce šířku jen šestnáct bitů, což znamená, že v paměti určité kapacity lze uložit přibližně dvakrát tolik instrukcí Thumb, než původních RISCových instrukcí (slovo „přibližně“ je zde použito především z toho důvodu, že se v kódu vyskytují i 32bitové konstanty, nezávisle na použité instrukční sadě). Ovšem menší šířka instrukcí znamenala i určitá omezení. Zcela zmizely podmínkové kódy, které zůstaly zachovány jen u instrukce podmíněného skoku. Také se možnost použití barrel shifteru omezila jen na určitou skupinu instrukcí. Ovšem asi největší změnou bylo to, že se sada pracovních registrů R0-R15 rozdělila na spodní polovinu R0-R7 (Lo registers) a horní polovinu R8-R15 (Hi registers), přičemž většina instrukcí dokáže pracovat pouze s prvními osmi registry, zatímco některé registry z horní skupiny mají speciální význam (čítač instrukcí, ukazatel na vrchol zásobníku atd.).
Výpočetní výkon dosahovaný v reálných aplikacích však byl u sady Thumb v některých případech menší, protože mnohé operace musely být provedeny pomocí většího množství instrukcí – instrukce Thumb totiž vždy vykonávaly jen jednu operaci, na rozdíl od instrukcí ARM, které obsahovaly jak podmínkové bity, tak i v mnoha případech „podoperaci“ pro bitový posun či rotaci jednoho z operandů vstupujícího do aritmeticko-logické jednotky. Proto později vznikla instrukční sada Thumb-2, která kombinuje instrukce s délkou dvou bajtů a čtyř bajtů. A právě Thumb i Thumb-2 jsou frameworky Capstone a Keystone podporovány. Podívejme se na příklad, jehož kód v assembleru se nijak neliší od předchozího příkladu – liší se jen způsob překladu:
# import všech symbolů assembleru použitých ve skriptu from keystone import Ks, KS_ARCH_ARM, KS_MODE_THUMB, KsError # import všech symbolů disassembleru použitých ve skriptu from capstone import Cs, CS_ARCH_ARM, CS_MODE_THUMB # instrukce, které se mají přeložit assemblerem CODE = """ MOV R0, 10 OUTER_LOOP: MOV R1, 100 INNER_LOOP: SUBS R1, R1, 1 BNE INNER_LOOP SUBS R0, R0, 1 BNE OUTER_LOOP """ try: # inicializace assembleru se specifikací architektury a popř. i režimu ks = Ks(KS_ARCH_ARM, KS_MODE_THUMB) # vlastní překlad (assembling) encoding, count = ks.asm(CODE) # disassembling binární sekvence kódů md = Cs(CS_ARCH_ARM, CS_MODE_THUMB) # vypsat podrobnější informace o každé instrukci for i in md.disasm(bytes(encoding), 0x0000): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str)) except KsError as e: print("ERROR: %s" % e)
Z výsledků je patrné, že přeložené instrukce skutečně mají délku buď dvou bajtů nebo čtyř bajtů, tj. hustota kódu je vyšší:
0x00: 4f f0 0a 00 mov.w r0, #0xa 0x04: 4f f0 64 01 mov.w r1, #0x64 0x08: 01 39 subs r1, #1 0x0a: fc d1 bne #8 0x0c: 01 38 subs r0, #1 0x10: f7 d1 bne #4
Čistě ARMovská varianta přitom vypadala takto:
0x00: 0a 00 a0 e3 mov r0, #0xa 0x04: 64 10 a0 e3 mov r1, #0x64 0x08: 01 10 41 e2 subs r1, r1, #1 0x0c: fd ff ff 1a bne #8 0x10: 01 00 40 e2 subs r0, r0, #1 0x14: fa ff ff 1a bne #4
16. Přepis příkladů pro architekturu AArch64
V poměrně velkém množství moderních elektronických zařízení, ať již se jedná o tablety, smartphony či o jednodeskové mikropočítače (RPi), se můžeme setkat s mikroprocesory založenými na 64bitové architektuře AArch64 vyvinuté ve společnosti ARM. Tato architektura je sice do jisté míry odvozena od původní „klasické“ 32bitové RISCové architektury ARM (dnes pro odlišení nazývané ARM32 či dokonce jen A32), ovšem při přechodu na 64bitový systém došlo k mnoha podstatným změnám, které se týkají jak počtu a funkce pracovních registrů, tak i instrukční sady, jež se v některých ohledech od původní RISCové sady dosti podstatným způsobem odlišuje.
Z pohledu programátora pracujícího v assembleru je architektura AArch64 v několika ohledech odlišná od původní 32bitové architektury. Především se zvýšil počet pracovních registrů z patnácti na 31. Všechny registry se navíc rozšířily z třiceti dvou bitů na 64 bitů. Ovšem mnohé operace stále podporují i 32bitové operandy – v tomto případě se použije ta samá skupina registrů, z nichž se ovšem využije jen spodních 32 bitů (nedochází zde tedy k rozdvojení každého 64bitového registru na dva registry 32bitové, jak to známe z jiných typů procesorů).
Pokusme se tedy původní vnořenou smyčku, která na ARM32 vypadala takto:
MOV R0, 10 OUTER_LOOP: MOV R1, 100 INNER_LOOP: SUBS R1, R1, 1 BNE INNER_LOOP SUBS R0, R0, 1 BNE OUTER_LOOP
realizovat pro AArch64. Kód bude prakticky stejný, pouze použijeme odlišná jména registrů:
MOV W0, 10 OUTER_LOOP: MOV W1, 100 INNER_LOOP: SUBS W1, W1, 1 BNE INNER_LOOP SUBS W0, W0, 1 BNE OUTER_LOOP
Opět provedeme překlad tohoto kódu do nativního binárního kódu, po němž bude následovat zpětné dekódování instrukcí:
# import všech symbolů assembleru použitých ve skriptu from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN, KsError # import všech symbolů disassembleru použitých ve skriptu from capstone import Cs, CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN # instrukce, které se mají přeložit assemblerem CODE = """ MOV W0, 10 OUTER_LOOP: MOV W1, 100 INNER_LOOP: SUBS W1, W1, 1 BNE INNER_LOOP SUBS W0, W0, 1 BNE OUTER_LOOP """ try: # inicializace assembleru se specifikací architektury a popř. i režimu ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) # vlastní překlad (assembling) encoding, count = ks.asm(CODE) # disassembling binární sekvence kódů md = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN) # vypsat podrobnější informace o každé instrukci for i in md.disasm(bytes(encoding), 0x0000): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str)) except KsError as e: print("ERROR: %s" % e)
Kódování instrukcí je v tomto případě zcela odlišné od ARM32, i když „RISCový“ charakter (instrukce shodné délky) zůstal zachován:
0x00: 40 01 80 52 movz w0, #0xa 0x04: 81 0c 80 52 movz w1, #0x64 0x08: 21 04 00 71 sub w1, w1, #1 0x0c: e1 ff ff 54 b.ne #8 0x10: 00 04 00 71 sub w0, w0, #1 0x14: 81 ff ff 54 b.ne #4
17. RISCová architektura MIPS
Poslední procesorovou architekturou, s níž se v dnešním článku v souvislosti s frameworky Capstone a Keystone setkáme, je architektura MIPS, a to jak 32bitová, tak i 64bitová varianta (navíc se rozlišuje kódování little endian a big endian). Začneme kódem v assembleru, který vznikl překladem následujícího céčkového kódu (a to s vypnutím optimalizací):
int add(int x, int y) { return x+y; }
Překlad do assembleru MIPSu vypadá takto (včetně zarovnání na konci):
addiu $sp,$sp,-8 sw $fp,4($sp) move $fp,$sp sw $4,8($fp) sw $5,12($fp) lw $3,8($fp) lw $2,12($fp) nop addu $2,$3,$2 move $sp,$fp lw $fp,4($sp) addiu $sp,$sp,8 jr $31 nop
Pokusme se nyní o překlad takto zapsaného kódu v assembleru do nativního strojového kódu a zpět, a to pro původní 32bitovou architekturu MIPS:
# import všech symbolů assembleru použitých ve skriptu from keystone import Ks, KS_ARCH_MIPS, KS_MODE_MIPS32, KsError # import všech symbolů disassembleru použitých ve skriptu from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS32 # instrukce, které se mají přeložit assemblerem CODE = """ addiu $sp,$sp,-8 sw $fp,4($sp) move $fp,$sp sw $4,8($fp) sw $5,12($fp) lw $3,8($fp) lw $2,12($fp) nop addu $2,$3,$2 move $sp,$fp lw $fp,4($sp) addiu $sp,$sp,8 jr $31 nop """ try: # inicializace assembleru se specifikací architektury a popř. i režimu ks = Ks(KS_ARCH_MIPS, KS_MODE_MIPS32) # vlastní překlad (assembling) encoding, count = ks.asm(CODE) # disassembling binární sekvence kódů md = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32) # vypsat podrobnější informace o každé instrukci for i in md.disasm(bytes(encoding), 0x0000): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str)) except KsError as e: print("ERROR: %s" % e)
Z výsledku je patrné, že MIPS patří mezi klasické RISCové architektury, v níž mají všechny instrukce shodnou délku čtyř bajtů, podobně jako u ARM32 či AArch64:
0x00: f8 ff bd 27 addiu $sp, $sp, -8 0x04: 04 00 be af sw $fp, 4($sp) 0x08: 25 f0 a0 03 move $fp, $sp 0x0c: 08 00 c4 af sw $a0, 8($fp) 0x10: 0c 00 c5 af sw $a1, 0xc($fp) 0x14: 08 00 c3 8f lw $v1, 8($fp) 0x18: 0c 00 c2 8f lw $v0, 0xc($fp) 0x1c: 00 00 00 00 nop 0x20: 21 10 62 00 addu $v0, $v1, $v0 0x24: 25 e8 c0 03 move $sp, $fp 0x28: 04 00 be 8f lw $fp, 4($sp) 0x2c: 08 00 bd 27 addiu $sp, $sp, 8 0x30: 08 00 e0 03 jr $ra 0x34: 00 00 00 00 nop 0x38: 00 00 00 00 nop
Pro zajímavost se nyní pokusme přepnout na 64bitovou variantu MIPSu, a to bez změny překládaného kódu:
# import všech symbolů assembleru použitých ve skriptu from keystone import Ks, KS_ARCH_MIPS, KS_MODE_MIPS64, KsError # import všech symbolů disassembleru použitých ve skriptu from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS64 # instrukce, které se mají přeložit assemblerem CODE = """ addiu $sp,$sp,-8 sw $fp,4($sp) move $fp,$sp sw $4,8($fp) sw $5,12($fp) lw $3,8($fp) lw $2,12($fp) nop addu $2,$3,$2 move $sp,$fp lw $fp,4($sp) addiu $sp,$sp,8 jr $31 nop """ try: # inicializace assembleru se specifikací architektury a popř. i režimu ks = Ks(KS_ARCH_MIPS, KS_MODE_MIPS64) # vlastní překlad (assembling) encoding, count = ks.asm(CODE) # disassembling binární sekvence kódů md = Cs(CS_ARCH_MIPS, CS_MODE_MIPS64) # vypsat podrobnější informace o každé instrukci for i in md.disasm(bytes(encoding), 0x0000): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str)) except KsError as e: print("ERROR: %s" % e)
Výsledek je v tomto případě totožný s předchozím kódem:
0x00: f8 ff bd 27 addiu $sp, $sp, -8 0x04: 04 00 be af sw $fp, 4($sp) 0x08: 25 f0 a0 03 move $fp, $sp 0x0c: 08 00 c4 af sw $a0, 8($fp) 0x10: 0c 00 c5 af sw $a1, 0xc($fp) 0x14: 08 00 c3 8f lw $v1, 8($fp) 0x18: 0c 00 c2 8f lw $v0, 0xc($fp) 0x1c: 00 00 00 00 nop 0x20: 21 10 62 00 addu $v0, $v1, $v0 0x24: 25 e8 c0 03 move $sp, $fp 0x28: 04 00 be 8f lw $fp, 4($sp) 0x2c: 08 00 bd 27 addiu $sp, $sp, 8 0x30: 08 00 e0 03 jr $ra 0x34: 00 00 00 00 nop 0x38: 00 00 00 00 nop
Některé mikroprocesory s architekturou MIPS používají kódování little endian zatímco jiné big endian. Jedná se o způsob fyzického uložení kódů instrukcí (čtyři bajty) popř. dat do operační paměti – může se začínat bajtem s nejnižší váhou nebo naopak bajtem s váhou nejvyšší (další možné kombinace nejsou využity – což je ostatně jen dobře). Frameworky Capstone a Keystone tato odlišná kódování plně podporují, což je ostatně patrné i z následujícího příkladu, v němž explicitně vyžadujeme kódování big endian:
# import všech symbolů assembleru použitých ve skriptu from keystone import Ks, KS_ARCH_MIPS, KS_MODE_MIPS64, KS_MODE_BIG_ENDIAN, KsError # import všech symbolů disassembleru použitých ve skriptu from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS64, CS_MODE_BIG_ENDIAN # instrukce, které se mají přeložit assemblerem CODE = """ addiu $sp,$sp,-8 sw $fp,4($sp) move $fp,$sp sw $4,8($fp) sw $5,12($fp) lw $3,8($fp) lw $2,12($fp) nop addu $2,$3,$2 move $sp,$fp lw $fp,4($sp) addiu $sp,$sp,8 jr $31 nop """ try: # inicializace assembleru se specifikací architektury a popř. i režimu ks = Ks(KS_ARCH_MIPS, KS_MODE_MIPS64 + KS_MODE_BIG_ENDIAN) # vlastní překlad (assembling) encoding, count = ks.asm(CODE) # disassembling binární sekvence kódů md = Cs(CS_ARCH_MIPS, CS_MODE_MIPS64 + CS_MODE_BIG_ENDIAN) # vypsat podrobnější informace o každé instrukci for i in md.disasm(bytes(encoding), 0x0000): # převod pole bajtů na řetězec s hexa hodnotami dump = i.bytes.hex(" ") # výpis informací o instrukci print("0x{:02x}:\t{:20s}\t{:s}\t{:s}".format(i.address, dump, i.mnemonic, i.op_str)) except KsError as e: print("ERROR: %s" % e)
Povšimněte si, že hodnoty bajtů, do nichž jsou instrukce zakódovány, jsou skutečně oproti předchozí dvojici demonstračních příkladů prohozeny:
0x00: 27 bd ff f8 addiu $sp, $sp, -8 0x04: af be 00 04 sw $fp, 4($sp) 0x08: 03 a0 f0 25 move $fp, $sp 0x0c: af c4 00 08 sw $a0, 8($fp) 0x10: af c5 00 0c sw $a1, 0xc($fp) 0x14: 8f c3 00 08 lw $v1, 8($fp) 0x18: 8f c2 00 0c lw $v0, 0xc($fp) 0x1c: 00 00 00 00 nop 0x20: 00 62 10 21 addu $v0, $v1, $v0 0x24: 03 c0 e8 25 move $sp, $fp 0x28: 8f be 00 04 lw $fp, 4($sp) 0x2c: 27 bd 00 08 addiu $sp, $sp, 8 0x30: 03 e0 00 08 jr $ra 0x34: 00 00 00 00 nop 0x38: 00 00 00 00 nop
Pro porovnání – takto vypadá varianta little endian:
0x00: f8 ff bd 27 addiu $sp, $sp, -8 0x04: 04 00 be af sw $fp, 4($sp) 0x08: 25 f0 a0 03 move $fp, $sp 0x0c: 08 00 c4 af sw $a0, 8($fp) 0x10: 0c 00 c5 af sw $a1, 0xc($fp) 0x14: 08 00 c3 8f lw $v1, 8($fp) 0x18: 0c 00 c2 8f lw $v0, 0xc($fp) 0x1c: 00 00 00 00 nop 0x20: 21 10 62 00 addu $v0, $v1, $v0 0x24: 25 e8 c0 03 move $sp, $fp 0x28: 04 00 be 8f lw $fp, 4($sp) 0x2c: 08 00 bd 27 addiu $sp, $sp, 8 0x30: 08 00 e0 03 jr $ra 0x34: 00 00 00 00 nop 0x38: 00 00 00 00 nop
18. Závěrečné zhodnocení
Autoři frameworků Keystone a Capstone si vytyčili poměrně velký úkol: vytvoření jádra univerzálního assembleru a disassembleru, které by bylo možné využít jak v nástrojích volaných přes příkazovou řádku, tak i například přímo v rámci IDE či nějakého specializovaného nástroje. Nutno říci, že tohoto cíle prozatím nebylo plně dosaženo, protože v praxi je zapotřebí pracovat s kódem v assembleru rozděleným do sekcí (segmentů), generovat objektový kód pro linker atd. Ovšem už současná použitelnost obou frameworků je poměrně dobrá, protože jsou podporovány všechny mainstreamové architektury a jejich varianty.
Navíc už dnes existuje rozhraní mezi Capstone a Keystone pro mnoho programovacích jazyků (nejenom C/C++, ostatně dnes jsme používali Python) a samotný assembler programátorům poskytuje podobné funkce jako například GNU Assembler. Zlepšit je však nutné detekci a hlášení chyb a možná by bylo zajímavé samotná rozhraní pro Capstone a Keystone vylepšit tak, aby bylo umožněno přímé spuštění přeloženého kódu (což ovšem s sebou nese bezpečnostní rizika).
19. Repositář s demonstračními příklady
Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3 (nikoli ovšem pro starší verze Pythonu 2!) byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
Pro úplnost si uveďme i odkazy na demonstrační příklady založené na frameworku Keystone, které byly popsané minule:
20. Odkazy na Internetu
- Keystone Engine na GitHubu
https://github.com/keystone-engine/keystone - Keystone: The Ultimate Assembler
https://www.keystone-engine.org/ - The Ultimate Disassembler
http://www.capstone-engine.org/ - Tutorial for Keystone
https://www.keystone-engine.org/docs/tutorial.html - Rozhraní pro Capstone na PyPi
https://pypi.org/project/capstone/ - Rozhraní pro Keystone na PyPi
https://pypi.org/project/keystone-engine/ - KEYSTONE: Next Generation Assembler Framework
https://www.keystone-engine.org/docs/BHUSA2016-keystone.pdf - AT&T Syntax versus Intel Syntax
http://web.mit.edu/rhel-doc/3/rhel-as-en-3/i386-syntax.html - AT&T assembly syntax and IA-32 instructions
https://gist.github.com/mishurov/6bcf04df329973c15044 - ARM GCC Inline Assembler Cookbook
http://www.ethernut.de/en/documents/arm-inline-asm.html - Extended Asm – Assembler Instructions with C Expression Operands
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html - ARM inline asm secrets
http://hardwarebug.org/2010/07/06/arm-inline-asm-secrets/ - How to Use Inline Assembly Language in C Code
https://gcc.gnu.org/onlinedocs/gcc/Using-Assembly-Language-with-C.html#Using-Assembly-Language-with-C - GCC-Inline-Assembly-HOWTO
http://ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html - A Brief Tutorial on GCC inline asm (x86 biased)
http://www.osdever.net/tutorials/view/a-brief-tutorial-on-gcc-inline-asm - GCC Inline ASM
http://locklessinc.com/articles/gcc_asm/ - GNU Assembler Examples
http://cs.lmu.edu/~ray/notes/gasexamples/ - X86 Assembly/Arithmetic
https://en.wikibooks.org/wiki/X86_Assembly/Arithmetic - Art of Assembly – Arithmetic Instructions
http://oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter6/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 - 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 - Online x86 / x64 Assembler and Disassembler
https://defuse.ca/online-x86-assembler.htm#disassembly