Tento článek vám má nastínit, jak vytvořit program (proceduru) v assembleru s použitím FREE programovacích prostředků pod Linuxem pro architekturu i386.
Tato problematika není autorům tohoto textu zcela známá, protože pod Linuxem není potřeba programování v assembleru (snad kromě nějakých brutálních optimalizací v kritických místech aplikace, nebo ve speciálních případech vývoje jádra), viz. slova Alana Coxe:
"C is nothing more than portable assembler"Pokud tedy opravdu chcete dělat něco s assemblerem pod Linuxem, pak jako absolutní základ si přečtěte tento článek, dále potom podrobnější informace najdete v Linux Assembly HOWTO a manuálových stránkách zde uvedených programů, ale asi nejvíc informací o assembleru pod Linuxem najdete na linuxassembly.org.
V assembleru nemusíte psát celý program od začátku do konce, a ani se to
nedělá (snad jen když chcete mít program extrémě rychlý a/nebo extrémě malý, např. GAG - sqělý grafický bootmanager, celý napsaný v assembleru). Program napíšete v nějakém vyšším prog. jazyce (př. C/C++) a v assembleru jen ty procedury, které musí být hodně rychlé. No a assemblerovský kód můžete napsat buď přímo do céčkovského zdrojáku (inline assembler) nebo do samostatného souboru, který pak přilinkujete k zbytku programu.
A protože pravidla zápisu inline assembleru nějak nechápu (pokud někdo z vás ANO, tak o tom může něco napsat, poslat mi to, viz. mail na konci, a já to sem doplním :-)), tak se dále dočtete hlavně o psaní assemblerovského kódu do samostatného souboru.
GNU C/C++ Compiler (GCC) umožňuje vložit assemblerovský kód v programech psaných v jazyce C/C++. Pro kompilaci assembleru se využívá GAS (viz. dále).
Používá se k tomu jakási "šablona" tvaru asm ("insrukce":výstupy:vstupy), kterou nechápu ani tak, abych ji uměl použít a napsat nějakou funkci v assembleru, natož abych to tady mohl vysvětlovat :->. Istrukce se zapisují v syntaxi AT&T (viz. GAS dále) a specifikují se vstupní a výstupní proměnné, použité v instrukcích.
Jako příklad uvedu funkci ze zdrojáků jádra:
static inline unsigned short ip_fast_csum(unsigned char * iph, unsigned int ihl) { unsigned int sum; __asm__ __volatile__(" movl (%1), %0 subl $4, %2 jbe 2f addl 4(%1), %0 adcl 8(%1), %0 adcl 12(%1), %0 1: adcl 16(%1), %0 lea 4(%1), %1 decl %2 jne 1b adcl $0, %0 movl %0, %2 shrl $16, %0 addw %w2, %w0 adcl $0, %0 notl %0 2: " : "=r" (sum), "=r" (iph), "=r" (ihl) : "1" (iph), "2" (ihl)); return(sum); }
Koho by to náhodou zajímalo, tak mu můžu doporučit gcc info ('info gcc', sekce C Extensions::Extended Asm::) a zdrojáky jádra Linuxu (include/asm-i386/).
Zdrojáky s inline assemblerem se musí kompilovat s parametrem -O (popř. -O2, -O3, ...), protože jsou kompilovány do externích inline funkcí.
Inline assembler se zakazuje pomocí -fno-asm a znova povoluje pomocí -fasm.
GCC obsahuje každá (tedy každá, se kterou lze něco rozumného dělat) distribuce Linuxu a je to program gcc pro C nebo g++ pro C++.
GAS je GNU Assembler, který GCC používá. Je to "nativní" assembler pro Linux.
Protože byl GAS vyvinut pro 32-bitové UNIXové kompilátory, používá standardní AT&T syntaxi. Tato syntaxe není ani horší ani lepší než Intelovská syntaxe, jen je jiná.
Nejdůležitější prvky AT&T syntaxe:
Samozřejmě existují konvertory obou syntaxí oběma směry, např. Intel2gas (Intel -> AT&T) a A2I (AT&T -> Intel).
Dokumentaci pro GAS lze najít např. v gas info ('info as', sekce Machine Dependencies::i386-Dependent::) nebo se můžete opět inspirovat zdrojáky jádra (arch/i386/).
Běžný způsob zjistit, jak vypadá něco v assembleru, je napsat to v céčku a nechat si to převést do assembleru.
Zdrojáky pro GAS mají příponu .s a GCC při kompilaci soubory s touto příponou rovnou posílá GASu. A .S soubory nejdříve přefiltruje přes CPP a pak je předá GASu (můžeme zde použít příkazy pro preprocesor CPP jako #include, #define, #ifdef, ...).
GAS je 32-bitový assembler. Má ale i podporu pro 16-bitový mód (registy i adresování). K přepínání mezi módy jsou direktivy .code16 a .code32.
Stejně jako pro GCC existuje preprocesor CPP, tak pro GAS existuje preprocesor GASP (dokumentace - 'info gasp').
Pod Linuxem je GAS reprezentován programem as a obsahují jej (téměř) všechny distribuce Linuxu.
Netwide Assembler je sqělý assembler a je určený přímo pro platformu i386. Je napsaný v C a má Intelovskou syntaxi. Podporuje spoustu formátů objektového souboru, př. elf, (DOS) obj, win32, aout a další, jeho vlastní formát je rdf. Ale protože je od počátku psán jako modulární, bude jej možné použít pro jakoukoliv syntaxi a objektový formát.
K tomuto assembleru existuje i disassembler NDISASM.
NASM, NDISASM, dokumentaci a vše ostatní najdete na jeho domovské stránce.
Protože všechny tyto programy generují objektový kód, musíme mít ještě spojovací program - linker - program ld, pomocí kterého vytvoříme spustitelný soubor.. Tento program naleznete v každé distribuci (stejně jako GCC).
Assemblerovský zdroják má zhruba tuto strukturu:
.file "jmeno_souboru" /* zaciname assemblerovsky soubor */ .data /* data */ .bss /* nedefinovaná data */ .text /* kod */
Program musí obsahovat aspoň sekci .text. Mezi /* a */ jsou komentáře.
Pro GAS se proměnné (v sekci .data) píší takto:
jmeno_promenne: .word 0 /* alokovany prostor 2 byty */ jmeno_promenne: .byte 1 /* dalsi napr. : long, ascii, ... */
a kód (v sekci .text) takto:
.global _start /* exportujeme globalni symbol pro linker */ _start: instrukce /* instrukce jsou v AT&T syntaxi ! */ ... jmeno_navesti: /* tady je nejake navesti */ instrukce
Pro NASM se stejná věc udělá podobně:
Proměnné:
jmeno_promenne dw 0 jmeno_promenne db 1
Kód:
global _start _start: instrukce /* instrukce jsou v Intelovske syntaxi ! */ ... jmeno_navesti: instrukce
Ve zdrojáku pro NASM se před jméno sekce (př. .text) uvede ještě slovo section.
Pro volání služeb OS Linux je vyhrazeno přerušení 80H. Toto přerušení se vyvolává s číslem systémové služby (jejich seznam je v hlavičkovém souboru unistd.h, který je např. ve zdrojácích jádra v include/asm-i386/) v registru eax a parametry služby (max. 5) v registrech ebx, ecx, edx, esi a edi. Výsledek služby je vrácen v registru eax (záporná hodnota je chyba).
Zásobník programu systémové služby nepoužívají (k čemu :-)).
Pokud píšete celý program jen v assembleru, nesmíte zapomenout na korektní ukončení programu:
movl $1,%eax ;cislo systemove sluzby sys_exit xorl %ebx,%ebx ;navratovy kod programu int $0x80 ;volani jadra
Nejdříve musíme zdrojový soubor program.s (program.asm) zkompilovat:
as -o program.o program.s
nebo
nasm -f elf -o program.o program.asm
Dostaneme objektový soubor program.o, který předáme linkeru:
ld -s -o program program.o
a máme požadovaný spustitelný ELF soubor "program".
Možná jste si všimli, že v tomto článku není (skoro) nic o tom, jak programovat v assembleru. K tomu, jak psát kód do zdrojového souboru (práce s registry, instrukcemi, pamětí, ...), jsou určeny přednášky a cvičení kurzu Operační systémy/Systémové programování a cílem tohoto článku nebylo a ani nemůže být je nahrazovat. Pouze jsme se snažili je doplnit ve věcech týkajících se Linuxu.
Teď už byste měli vědět, jak pod Linuxem napsat něco v assembleru a co s tím udělat, aby to šlo spustit.
Takže ... HAPPY CODING !!!