Dalla sintassi Intel alla sintassi At&t Provenendo dall' asm sotto sistemi DOS/Windows, nei quali la sintassi Ideal o derivate spadroneggiano, generalmente la prima impressione esaminando un sorgente At&t è quella di dover reimparare l' assembler; questo è parzialmente vero nella misura in cui le mnemonic sono una convenzione per rappresentare in maniera più leggibile quello che altrimenti sarebbe visibile in termini di 'EB 54 12 90 C3' (questo nella miglior ipotesi di usare un editor esadecimale). Sotto sistemi *nix-like tradizionalmente la sintassi intel non è uno standard canonicamente accettato (motivi storici fanno si che i sistemi *nix non siano propriamente nati su architetture 80x86) quindi risulta conveniente adattarsi allo standard pre-esistente nato in quel dell' At&t bells (sarebbe utile parlare inglese se si fa una gita di pochi giorni in Francia nella stessa misura in cui sarebbe stupido parlare inglese a vita se ci si trasferisce a Parigi). A questo scopo presenterò brevemente la sintasi At&t prendendo come riferimento l' architettura 80386 con os Linux, compilatore as (GNU assembler) e linker ld (sempre GNU) delineando anche alcune delle differenze di base che si possono incontrare proveniendo da DOS/Windows. Principali differenze sintattiche. La sintassi di gas per alcuni versi risulta machine dependent, qui tratterò le differenze tra la sintassi Intel e quella At&t non considerando alcuna distinzione tra platform-specific e generiche At&t. * Gli operandi immediati sono preceduti da una '$' (la sintassi Intel non li marca in alcun modo). esempio: pushl $4. * Gli operandi registri sono preceduti da una '%' (per chiarezza, lasciano così la possibilità di avere simboli con nomi di registri) esempio: pushl %eax * Gli operandi di jump e call assoluti sono preceduti da '*'. Le architetture pc-like offrono anche la possibilità di jump e call relativi, il marcarli come assoluti quando necessario migliora la chiarezza. * La sintassi Intel e quella At&t usando un ordine inverso per gli operandi sorgente e destinazione per mantenere compatibiltà con precedenti compilatori sotto Unix. Instruzioni con più di un operando sorgente non usano l' ordine inverso. esempio: addl $4, %eax * La dimensione degli operatori di memoria è indicata con una 'b' (byte), 'w' (word), 'l' (dword, analogamente al long del c), 'q' (quad) postfissata al mnemonico, a differenza della sintassi At&t che usa 'byte/word/dword ptr'. Mnemonici di estensione sel segno che richiedono operandi di dimensioni differenti sono postfissati in ordine dalla dimensione di origine e da quella di destinazione esempio: movb FOO, %al ;sotto Intel l' equivalente sarebbe 'mov al, byte ptr ;FOO) cbw ;%al viene convertito in %eax estendendo il byte di segno * jump e call long immediati diventano lcall/ljmp $SEZIONE,$OFFSET (l' equivalente sotto sintassi Intel sarebbe call/jump far SECTION:OFFSET). Analogamente il ret long diventa lret $STACK-ADJUST (dove sotto sintassi Intel si avrebbe ret far STACK-ADJUST). * La sintassi At&t non supporta sezioni multiple (effettivamente è Unix in generale che si aspetta una sola sezione) * Le reference a memoria cambiano radicalmente di aspetto: SECTION:[BASE + INDEX*SCALE + DISP] diventa SECTION:DISP(BASE, INDEX, SCALE) DISP è opzionale, SCALE quando non esplicitato viene assunto come 1. SECTION quando viene esplicitato deve essere preceduto da '%'; se, seppur esplicitato, coincide con il registro di default as lo omette nell' output. esemi: ----------------------------------- At&t Intel ----------------------------------- -4(%ebp) [ebp -4] foo(,%eax,4) [foo + eax*4] foo(,1) [foo] %gs:foo gs:foo (quoting+traduzione della infopage di gas) * Per rendere globale un simbolo (ossia renderlo visibile al linker) si usa .global (o .globl) SYMBOL * Il simbolo = viene utilizzato per associare ad un simbolo un dato valore (analogamente ad EQU per sintassi Intel) * Il simbolo '.' rappresenta la posizione attuale nel compilato. una direttiva ORG MEMPOS è analoga ad un .= MEMPOS. * I numeri esadecimali vengono rappresentati prefissando '0x', quelli binari postfissando al numero una 'b', gli ottali prefissando uno `0'. Per informazioni più dettagliate credo sia opportuno guardare la documentazione del compilatore. Le differenze poveniendo da DOS si fanno rilevanti anche quando si parla di servizi offerti dal sistema. Sotto *nix vengono poste dietro ad una comune interfaccia chiamata 'system call' (sotto linux mappata sull' int $0x80), quindi le alternative sono 2: 1) ci si appoggia alle librerie C e si usa i simboli che queste esportano 2) si chiama direttamente le systemcall ecco in sostanza i due metodi: ------------------------------------------------------------ /* hello.s */ .data histr: .asciz "Hello world\n" .text .global main main: leal histr,%eax pushl %eax call printf popl %eax ret ------------------------------------------------------------ .data e .text è faclimente intuibile cosa siano .asciz è una direttiva atta a definire una costante. di seguito le principali: .byte i valori seguenti sono trattati come byte .ascii stringa costante non terminante con 0 .asciz stringa costante (viene aggiunto un byte 0 alla fine) .octa bignum .float non credo ci sai molto da dire .global main serve ad indicare al linker di trattare main come l' equivalente main() in c, quindi ne fa l' entry point. ------------------------------------------------------------ /* hello.s */ .data histr: .asciz "Hello world\n" .text .global main main: pusha movl $4,%eax movl $1,%ebx leal histr, %ecx movl $12,%edx int $0x80 popa ret ------------------------------------------------------------ La system call sotto linux, come già detto, è acessibile via la gateway dell' int $0x80, in eax viene messo il numero della system call (una lista delle quali è presente in /usr/include/asm/unistd.h), i parametri vengono messi quindi nei registri (in ordine: ebc, ecx, edx, esi, edi). Il valore di ritorno è in eax. Da notare il pusha ed il popa per lasciare i registri consistenti al termine dell'esecuzione di main, visto che il linker sucessivamente eseguirà del codice per terminare il programma. questo ovviamente può essere ovviato tramite la system call exit. Per sapere il tipo ed il numero dei parametri baterà consultare le man page della syscall corrispondente (man 2 exit ad esempio) anche se un minimo di conoscienza di c è richiesto. Alro fatto da notare è l' 1 in %ebx: sotto sistemi *nix like esistono tre file descriptor standard: standard input 0 standard output 1 standard error 2 Per ulteriori informazioni ritengo sia necessario leggere qualche testo che tratti di programmazione sotto sistemi *nix. ralph