Assembler pod Linuxem

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.

Programy pro práci s assemblerem

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.

GCC inline assembler

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

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.

NASM

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.

Linker

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

Struktura

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.

Volání služeb OS

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

Vytvoření spustitelného souboru

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".

Závěr

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 !!!



--- JOHNY.5 ---, chicky