Prazdniny skoncily a ja pisu dalsi skolicku. Rozhodl jsem se popsat assembler v linuxu a gnu vubec. Presto, ze to nepovazuju za prilis dulezite, byl jsem mnohokrat tazan. Nemuzu rucit za 100% spravnost, protoze assembler nepatri k mym nejsilnejsim strankam(vetsi programy.. nad 20kb jsem v nem psal naposled na SHARPovi) Proc vypada tak prapodivne.. ============================ Tak za to muze AT&T syntaxe, ktera se dost lisi od "normalni" uznavane dosovymi programy. Zakladni body jsou: -opacne poradi parametru (source,destination) -konec kazdeho nazvu instrukce udava velikost operandu(b,w,l) -registry jsou uvozeny pomoci % -cisla pomoci & priklad: movw %bx,%ax (mov ax,bx) movw $0,%ax (mov ax,1) xorl %eax,%eax (xor eax,eax) movb $1,%ah (mov ah,byte ptr 1) jak vidite, da se na to zvyknout. Z teto syntaxe jsou ale vyjimky. A to je: movsx a movzx. Tady je treba udavat dve velikosti a tak se zapisuji treba: movsbl prevadi z byte na long dalsi instrukce "nestandardni instrukce": cbtw (cbw) cwtl (cwde) cwtd (cwd) cltd (cdq) lcall $S,$O (call far S:O) ljmp $S,$O (jump far S:O) lret $V (ret far V) Navic prfixy se pisou jako oddelene instrukce. Napriklad rep stosd musi byt dve radky, ktere jsou po sobe. Posledni odlisnost je v adresaci pameti. Intelacke: s:[a+b*c+d] je s:d(a,b,c) napriklad: movl 4(%ebp), %eax (mov eax, [ebp+4]) addl (%eax,%eax,4), %ecx (add ecx, [eax + eax*4]) movb $4, %fs:(%eax) (mov fs:eax, 4) movl _array(,%eax,4), %eax (mov eax, [4*eax + array]) movw _array(%ebx,%eax,4), %cx (mov cx, [ebx + 4*eax + array]) Snad je to vsechno..jeste mul a imul. Pri expanzi se pise pouze jeden operand. Takze imul ebx,ebx neda vysledek do edx:eax. To se zapisuje imul ebx Tyto prevody za vas udela i nekolik utilitek, videl jsem na to sed script a program ta2as ktery se sezene nekde u djgpp ( muzu poslat na pozadani) Externi assembler. ================== Tady je to celkem jednoduche. Nejlepsi kostru nam poskytne nejaky ceckovsky program jako treba: #include main() { printf("ahoj\n"); } gcc -O2 -S test.c .file "test.c" .version "01.01" gcc2_compiled.: .section .rodata .LC0: .string "ahoj\n" .text .align 16 .globl main .type main,@function main: pushl %ebp movl %esp,%ebp pushl $.LC0 call printf movl %ebp,%esp popl %ebp ret .Lfe1: .size main,.Lfe1-main .ident "GCC: (GNU) 2.7.2" To nam dava celkem slusnou kostru proframu: .file "myasm.S" .data somedata: .word 0 ... .text .globl __myasmfunc __myasmfunc: ... ret Za pomoci maker v include souboru linux/linkage si muzeme zivot zjednodusit. Definuji se tam dve uzitecna makra. ENTRY a SYMBOL_NAME. Muzete je budto ziskat takto: #define __ASSMEBLY__ #include nebo zjednodusene nadefinovat sami: #ifndef SYMBOL_NAME #define SYMBOL_NAME(name) _ ## name #endif #ifndef ENTRY #define ENTRY(name) .align 4; .globl _ ## name ## ; _ ## name ## : #endif Nyni muzete jednoduchy program napsat treba takto: .file moje.S .text ENTRY(mojefunc) pushl %ebp movl %esp,%ebp pushl %edi pushl %esi pushl %ebx pushl %ecx ....kod... popl %ecx popl %ebx popl %esi popl %edi popl %ebp ret .text ENTRY(mojefunc2) pushl %ebp movl %esp,%ebp pushl %edi pushl %esi pushl %ebx pushl %ecx ....kod2... popl %ecx popl %ebx popl %esi popl %edi popl %ebp ret Dalsi sada uzitecnych maker je tato(z djgpp) /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ #ifndef __dj_include_libc_asmdefs_h__ #define __dj_include_libc_asmdefs_h__ .file __BASE_FILE__ #ifdef USE_EBX #define PUSHL_EBX pushl %ebx; #define POPL_EBX popl %ebx; #else #define PUSHL_EBX #define POPL_EBX #endif #ifdef USE_ESI #define PUSHL_ESI pushl %esi; #define POPL_ESI popl %esi; #else #define PUSHL_ESI #define POPL_ESI #endif #ifdef USE_EDI #define PUSHL_EDI pushl %edi; #define POPL_EDI popl %edi; #else #define PUSHL_EDI #define POPL_EDI #endif #define FUNC(x) .globl x; x: #define ENTER pushl %ebp; movl %esp,%ebp; PUSHL_EBX PUSHL_ESI PUSHL_EDI #define LEAVE L_leave: POPL_EDI POPL_ESI POPL_EBX movl %ebp,%esp; popl %ebp; ret #define LEAVEP(x) L_leave: x; POPL_EDI POPL_ESI POPL_EBX movl %ebp,%esp; popl %ebp; ret #define RET jmp L_leave #define ARG1 8(%ebp) #define ARG1h 10(%ebp) #define ARG2 12(%ebp) #define ARG2h 14(%ebp) #define ARG3 16(%ebp) #define ARG4 20(%ebp) #define ARG5 24(%ebp) #define ARG6 28(%ebp) #define ARG7 32(%ebp) #define ARG8 36(%ebp) #endif /* __dj_include_libc_asmdefs_h__ */ Po zincludeni tohoto souboru(asmdefs.h) muzete funkce zapisovat asi takto: #include .file "myasm.S" .data .align 2 somedata: .word 0 ... .text .align 4 FUNC(__MyExternalAsmFunc) ENTER movl ARG1, %eax ... jmp mylabel ... mylabel: ... LEAVE Dale assmebler obsahuje mnoho directiv (.text,.align apod) fura zvlastnosti pro jednotlive formaty(a.out,coff,elf) a tak doporucuju precist info. GASP ==== Gasp je jakysy preprocesor specializovany pro psani vlastnich zdrojaku v assmebleru. Je soucasti binutils a tak ho snad ma kazdy. Moc s nim neumim a tak si prectete info, kdyz ho chcete pouzit. Umi hodne fajnovosti jako smycky a tak..Vypada ale dost silene INLINE ASSEMBLER ================ Tak k tomu jsem se snazil probojovat. Je to jeden z nejvetsich problemu pri prechodu na gcc. Hodne lidi pouziva inline assembler spatne a potom se divi ze jim programy v nove verzi kompileru padaji. Gnu c totiz kod optimalizuje a proto potrebuje o nevi vice informaci nez ostatni. Zakladny syntaxe je: __asm__(asm statements : outputs : inputs : registers-modified); asm statements: AT&T kod outputs: sklada se ze dvou casti, ktere objasnim pozdeji inputs: stejne.. registers modified: nazvy oddelene carkou(pro optimalizaci) Nemusite pouzivat vsechny casti, pokud je nepotrebujete a tak nejjednodussi kod je: __asm__(" pushl %eax\n movl $1, %eax\n popl %eax" ); A ted vstupni promene: int i = 0; __asm__(" pushl %%eax\n movl %0, %%eax\n addl $1, %%eax\n movl %%eax, %0\n popl %%eax" : : "g" (i) ); /* i++; */ Nevypada to priserne? no ale. Chceme promenou i zvetsit o jedna. Neni zadny vystup ani modifikovane registry. Nyni vysvetlim to "g" (i). Druha cast je ocividne vstupni promena. Prvni ("g") udava, kam ma kompiler promenou umistit. A rika mu, ze ji muze umistit, kam se mu hodi. To je asi nejlepsi, protoze kompiler muze pekne optimalizovat a tak vpodstate vsechny promene by mely byt "g". Jinak muzeme pouzit "r" pro registr a "a" (ax/eax), "b" (bx/ebx), "c" (cx/ecx), "d" (dx/edx), "D" (di/edi), "S" (si/esi) atd.. V assembleru se odkazujem na promenou jako %0 dva vstupy budou %0 a %1. Asi jako v shellu. Jeste si vsimnete, ze kdyz pouzivate jakoukoliv z vstupu, vystupu, nebo pouzitych registru, musite uvozovat registry pomoci %%, protoze % je uz prebrano ceckem a to ho sunda a vynada vam ze nevi co to je %a, protoze zna jenom %1. Takhle najde %% a to prevede na % a tak pro assembler je uz vsechno vporadku. dalsi dulezita vec je volatile: int i = 0, j = 1; __asm__ __volatile__ (" pushl %%eax\n movl %0, %%eax\n addl %1, %%eax\n movl %%eax, %0\n popl %%eax" : : "g" (i), "g" (j) ); /* i+=j; */ takhle to zabrani gcc, aby vas nadherni a promysleny kod optimalizovalo a pripadne zpomalilo. Ja to pouzivam skoro vsude. Ukazka vystupu: int i=0; __asm__ __volatile__(" pushl %%eax\n movl $1, %%eax\n movl %%eax, %0\n popl %%eax" : "=g" (i) ); /*i=1;*/ Je to stejne jako pri vstupech jenom pred pismeno se dava =. cislovani je potom: %0..%K pro vystupy %K+1..%N pro vstupy priklad: int i=0, j=1, k=0; __asm__ __volatile__(" pushl %%eax\n movl %1, %%eax\n addl %2, %%eax\n movl %%eax, %0\n popl %%eax" : "=g" (k) : "g" (i), "g" (j) ); No a ted nas uz ceka jenom posledni (registers modified). Misto abychom na zacatku vsechno pushali a na konmci popali muzeme to nechat na compileru, Ktery to za nas vyresi optimalneji: int i=0, j=1, k=0; __asm__ __volatile__(" movl %1, %%eax\n addl %2, %%eax\n movl %%eax, %0" : "=g" (k) : "g" (i), "g" (j) : "ax", "memory" ); /* k = i + j; */ Tady rikame, ze modifikujem eax(vynachava se e...to neni 16 bit registr!) a pamet. To memory by se melo psat opravdu dusledne(vsechny predchozi priklady byly spatne :) jinak je gcc schopne cely asm kod vyhodit pri optimalizaci. Labely se delaji lokalni a pouziva se b a f pro smer: __asm__ __volatile__(" 0:\n ... jmp 0b\n ... jmp 1f\n ... 1:\n ... ); A to je vse pratele...