Il noto compilatore GNU C/C++ (GCC), un compilatore ottimizzante a 32-bit alla base del progetto GNU, supporta l'architettura x86 abbastanza bene, e fornisce la possibilità di inserire codice assembly nei programmi C, in modo tale che l'allocazione dei registri può essere o specificata o lasciata a GCC. GCC funziona sulla maggior parte delle piattaforme disponibili, tra le quali sono degne di nota Linux, *BSD, VSTa, OS/2, *DOS, Win*, ecc.
Il sito originale di GCC è il sito FTP di GNU ftp://prep.ai.mit.edu/pub/gnu/ in cui si trova anche tutto il software applicativo del progetto GNU che è stato rilasciato.
Versioni configurate e precompilate per Linux possono essere trovate in ftp://sunsite.unc.edu/pub/Linux/GCC/. Esistono un sacco di mirror FTP di entrambi i siti in tutte le parti del mondo, così come copie su CD-ROM.
Recentemente, lo sviluppo di GCC si è biforcato. Maggiori notizie sulla versione sperimentale, egcs, presso http://www.cygnus.com/egcs/.
Dovreste trovare dei sorgenti adattati per il vostro sistema operativo preferito e dei binari precompilati ai soliti siti FTP.
La versione più comune per DOS si chiama DJGPP e può essere trovata nelle directory con questo nome nei siti FTP. Vedere:
C'è anche una versione di GCC per OS/2 chiamata EMX, che funziona anche sotto DOS ed include molte routine di libreria per l'emulazione di UNIX. Date un'occhiata dalle parti di:
http://www.leo.org/pub/comp/os/os2/gnu/emx+gcc/
http://warp.eecs.berkeley.edu/os2/software/shareware/emx.html
ftp://ftp-os2.cdrom.com/pub/os2/emx09c/
La documentazione di GCC include file di documentazione in formato texinfo. Potete compilarli con TeX e poi stampare il risultato, oppure convertirli in .info e sfogliarli con emacs, oppure ancora convertirli in .html o (con gli strumenti appropriati) in tutto ciò che volete, oppure semplicemente leggerli così come sono.
Di solito i file .info si trovano in ogni buona installazione di GCC.
La sezione corretta da cercare è:
C Extensions::Extended Asm::
La sezione
Invoking GCC::Submodel Options::i386 Options::
potrebbe anch'essa rivelarsi utile.
In particolare, dà i vincoli sui nomi dei registri
specifici per l'i386:
abcdSDB corrispondono rispettivamente a:
%eax
, %ebx
, %ecx
, %edx
,
%esi
, %edi
, %ebp
(nessuna lettera per %esp
).
La DJGPP Games resource (non solo per hacker dei giochi) ha questa pagina apposta per l'assembly:
http://www.rt66.com/~brennan/djgpp/djgpp_asm.html
Infine, c'è una pagina web chiamata «DJGPP Quick ASM Programming Guide» che tratta URL, FAQ, sintassi asm AT&T per x86, alcune informazioni sull'asm inline e la conversione dei file .obj/.lib:
http://remus.rutgers.edu/~avly/djasm.html
GCC dipende da GAS per l'assembling e segue la sua sintassi (vedere in seguito); se usate l'asm inline, badate bene: è necessario che i caratteri «percento» siano protetti dall'espansione per poter essere passati a GAS. Vedere la sezione su GAS in seguito.
Potete trovare un sacco di esempi utili nella
sottodirectory linux/include/asm-i386/
dei sorgenti
del kernel di Linux.
Assicuratevi di invocare GCC con il
flag -O
( oppure -O2
, -O3
, ecc.) per
abilitare le ottimizzazioni e l'assembly inline.
Se non lo fate, il vostro codice potrebbe venire compilato ma non essere
eseguito correttamente!!!
In realtà (lodi smisurate a Tim Potter, timbo@mohpit.air.net.au)
è sufficiente utilizzare il flag -fasm
(e
forse -finline-functions
)
che attiva solo una parte di tutte le funzionalità abilitate
da -O
.
Così, se avete problemi a causa di bug nelle ottimizzazioni
della vostra particolare versione/implementazione
di GCC, potete comunque usare l'asm inline.
In maniera analoga, usate -fno-asm per disabilitare l'assembly inline
(perché dovreste?).
Più in generale, buoni flag di compilazione per GCC sulla piattaforma x86 sono
gcc -O2 -fomit-frame-pointer -m386 -Wall
-O2
è il giusto livello di ottimizzazione. Ottimizzare
oltre produce codice che è parecchio più grande, ma
solo di poco più veloce;
tale sovraottimizzazione potrebbe essere utile solo per cicli stretti (se
ce ne sono), che potreste comunque realizzare in assembly;
se ne sentite la necessità, fatelo solo per le poche routine che ne
hanno bisogno.
-fomit-frame-pointer
consente al codice generato di evitare la stupida
gestione del frame pointer, il che rende il codice più piccolo e veloce
e libera un registro per ulteriori ottimizzazioni.
Ciò preclude il comodo utilizzo degli strumenti per il debugging (gdb
),
ma nel momento in cui usate questi strumenti, non è che vi importi
poi molto delle dimensioni e della velocità.
-m386
produce codice più compatto, senza alcun rallentamento
misurabile (notate che codice piccolo significa anche meno I/O per il disco
ed esecuzione più rapida), ma forse sui suddetti cicli stretti
potreste apprezzare -mpentium
per il GCC speciale che ottimizza
per pentium (sempre che abbiate come obiettivo
proprio una piattaforma pentium).
-Wall
abilita tutti gli avvisi e vi aiuta a scovare errori stupidi
ed ovvii.
Per ottimizzare ancora di più, l'opzione -mregparm=2
e/o il
corrispondente attributo per le funzioni vi potrebbero essere utili<,
ma potrebbero porre un sacco di problemi qualora doveste fare un link con
codice estraneo...
Notate che potete rendere questi flag quelli predefiniti modificando il
file
/usr/lib/gcc-lib/i486-linux/2.7.2.2/specs
o dovunque esso si trovi nel vostro sistema (meglio non aggiungere
-Wall in quella sede, comunque).
GAS è l'assemblatore GNU, su cui fa affidamento GCC.
Lo trovate nello stesso posto dove avete trovato GCC, in un pacchetto denominato binutils.
Poiché GAS è stato concepito per supportare un compilatore UNIX a 32 bit, esso utilizza la notazione standard «AT&T», che assomiglia molto alla sintassi degli assemblatori standard per m68k ed è standard nel mondo UNIX. Questa sintassi non è né peggiore né migliore della sintassi «Intel». È semplicemente diversa. Una volta che ci si è abituati, la si trova molto più regolare della sintassi Intel, anche se un po' noiosa.
Ecco le cose a cui prestare maggiore attenzione quando si ha a che fare con la sintassi di GAS:
%
come prefisso, cosicché
i registri sono %eax
, %dl
e così via
invece di solo eax
, dl
, ecc.
Ciò fa sì che sia possibile includere simboli C esterni
direttamente nel sorgente assembly, senza alcun rischio di confusione
e senza alcun bisogno di orribili underscore anteposti.mov ax,dx
(carica il contenuto del registro dx
nel
registro ax
) diventerà
mov %dx, %ax
nella sintassi AT&T.
b
per byte (8 bit), w
per word (16 bit)
e l
per long (32 bit). Ad esempio, la sintassi corretta per
l'istruzione menzionata poco fa sarebbe stata movw %dx,%ax
.
Comunque, gas non richiede una sintassi AT&T rigorosa,
quindi il suffisso è opzionale quando la lunghezza può essere
ricavata dai registri usati come operandi, altrimenti viene posta a 32 bit per
default (con un avviso).$
,
come in addl $5,%eax
(somma il valore long 5 al registro %eax
).movl $pippo,%eax
mette l'indirizzo
della variabile pippo
nel registro %eax
,
mentre movl pippo,%eax
mette il contenuto
della variabile
pippo
nel registro %eax
.testb $0x80,17(%ebp)
(esegue un test sul bit più alto del valore byte all'offset 17 dalla
cella puntata da %ebp
).Esiste un programma per aiutarvi a convertire programmi dalla sintassi TASM alla sintassi AT&T. Date un'occhiata a
ftp://x2ftp.oulu.fi/pub/msdos/programming/convert/ta2asv08.zip
GAS ha una documentazione esauriente in formato TeXinfo,
che trovate nella distribuzione dei sorgenti (e forse altrove).
Potete sfogliare le pagine .info estratte con emacs o con ciò che
più vi aggrada.
C'era un file chiamato gas.doc o as.doc dalle parti del pacchetto sorgente
di GAS, ma è stato incorporato nella documentazione in TeXinfo.
Certo, in caso di dubbio, la documentazione definitiva
sono i sorgenti stessi!
Una sezione che vi interesserà particolarmente è
Machine Dependencies::i386-Dependent::
Ancora, i sorgenti di Linux (il kernel del sistema operativo) si rivelano buoni esempi; date un'occhiata ai seguenti file sotto linux/arch/i386:
kernel/*.S, boot/compressed/*.S, mathemu/*.S
Se state scrivendo qualcosa tipo un linguaggio, un pacchetto per i thread, ecc. potreste anche guardare come si comportano altri linguaggi (OCaml, gforth, ecc.) o pacchetti per i thread (QuickThreads, MIT pthreads, LinuxThreads, etc), o quel che è.
Infine, limitarsi a compilare un programma C in assembly potrebbe mostrarvi la sintassi del genere di istruzioni che vi interessano. Vedere la precedente sezione Avete bisogno dell'assembly?.
GAS è un assemblatore a 32 bit, il suo compito è quello di supportare un compilatore a 32 bit. Attualmente ha solo un supporto limitato per il modo a 16 bit, che consiste nell'anteporre i prefissi per i 32 bit alle istruzioni, cosicché scrivete codice a 32 bit che gira nel modo a 16 bit su una CPU a 32 bit. In entrambi i modi supporta l'uso dei registri a 16 bit, ma non l'indirizzamento a 16 bit.
Utilizzate le direttive .code16
e .code32
per passare da un
modo all'altro.
Notate che un'istruzione di assembly inline
asm(".code16\n")
consentirà a GCC di produrre codice a 32 bit che girerà in
real mode!
Mi è stato detto che la maggior parte del codice necessario per supportare pienamente la programmazione nel modo a 16 bit è stata aggiunta a GAS da Bryan Ford (si prega di confermare), tuttavia non si trova in nessuna delle distribuzioni che ho provato, fino a binutils-2.8.1.x ... sarebbero gradite maggiori informazioni su questo argomento.
Una soluzione economica è quella di definire macro (vedere in
seguito) che in qualche modo producono la codifica binaria (con .byte
)
solo per le istruzioni del modo a 16 bit di cui avete bisogno (quasi nessuna
se usate il codice a 16 bit come sopra e se potete supporre con certezza
che il codice girerà su una CPU x86 in grado di gestire i 32 bit).
Per trovare la codifica corretta, potete ispirarvi ai sorgenti degli
assemblatori in grado di gestire i 16 bit.
GASP (GAS Preprocessor) è il Preprocessore per GAS. Aggiunge macro e dei costrutti sintattici carini a GAS.
GASP si trova assieme a GAS nell'archivio binutils di GNU.
Funziona come un filtro, in modo molto simile a cpp e programmi analoghi. Non ho alcuna idea sui dettagli, ma assieme ad esso trovate documentazione relativa in texinfo, perciò limitatevi a sfogliarla (in .info), stamparla, sviscerarla. GAS con GASP mi sembra un comune assemblatore con macro.
Il progetto Netwide Assembler sta producendo un ulteriore assemblatore, scritto in C, che dovrebbe essere abbastanza modulare per supportare eventualmente tutte le sintassi ed i formati di oggetto conosciuti.
Le release binarie, nel vostro solito mirror di sunsite, sotto
devel/lang/asm/
.
Dovrebbero inoltre essere disponibili come .rpm o .deb
nei contrib delle vostre distribuzioni RedHat/Debian.
Nel momento in cui questo HOWTO viene scritto, la versione corrente di NASM è 0.96.
La sintassi è in stile Intel.
È integrato del supporto per le macro.
I formati di file oggetto supportati sono
bin
, aout
, coff
, elf
, as86
,
(DOS) obj
, win32
, rdf
(il loro formato specifico).
NASM può essere usato come backend per il compilatore libero LCC (sono inclusi i file di supporto).
Di certo NASM si evolve troppo rapidamente perché questo HOWTO possa essere aggiornato. A meno che voi stiate usando BCC come compilatore a 16 bit (il che esula dagli scopi di questo HOWTO sulla programmazione a 32 bit), dovreste usare NASM invece di, ad esempio, ASM o MASM, perché è attivamente supportato online e gira su tutte le piattaforme.
Nota: con NASM trovate anche un disassemblatore, NDISASM.
Il suo parser scritto a mano lo rende molto più veloce di GAS anche se, ovviamente, non supporta tre fantastiliardi di architetture differenti. Se volete generare codice per x86, dovrebbe essere l'assemblatore da scegliere.
AS86 è un assemblatore 80x86 a 16 e 32 bit, parte del compilatore C di Bruce Evans (BCC). Segue fondamentalmente la sintassi Intel, anche se ne discosta leggermente per quanto riguarda le modalità di indirizzamento.
Una versione decisamente superata di AS86 è distribuita da HJLu semplicemente per compilare il kernel di Linux, in un pacchetto chiamato bin86 (versione corrente: 0.4), disponibile in ogni archivio di GCC per Linux. Tuttavia non consiglio a nessuno di usarlo per qualcosa che non sia compilare Linux. Questa versione supporta solo una versione modificata del formato per file oggetto di minix, che non è supportata dalle binutils GNU o altro e che ha qualche bug nel modo a 32 bit, quindi fareste proprio meglio a tenerla solo per compilare Linux.
Le versioni più recenti realizzate da Bruce Evans (bde@zeta.org.au) sono pubblicate assieme alla distribuzione FreeBSD. Beh, lo erano: non sono riuscito a trovare i sorgenti dalla distribuzione 2.1 in poi :( Quindi, metto i sorgenti da me:
http:///www.eleves.ens.fr:8080/home/rideau/files/bcc-95.3.12.src.tgz
Il progetto Linux/8086 (conosciuto anche come ELKS) sta in qualche modo mantenendo bcc (anche se non credo che abbiano incluso le patch per i 32 bit). Date un'occhiata dalle parti di http://www.linux.org.uk/Linux8086.html ftp://linux.mit.edu/.
Tra l'altro, queste versione più recenti, contrariamente a quelle di HJLu, supportano il formato GNU a.out per Linux, cosicché è possibile il linking tra il vostro codice ed i programmi Linux e/o l'utilizzo dei soliti strumenti dal pacchetto GNU binutil per manipolare i vostri dati. Questa versione può coesistere senza alcun danno con quella precedente (vedere la domanda relativa in seguito).
BCC, versione del 12 marzo 1995 e precedenti, esegue i push ed i pop di segmenti soltanto a 16 bit, il che è non poco seccante quando si programma nel modo a 32 bit.
Una patch è disponibile nel progetto Tunes
http://www.eleves.ens.fr:8080/home/rideau/Tunes/
alla sottopagina
files/tgz/tunes.0.0.0.25.src.tgz
nella directory decompressa
LLL/i386/
La patch dovrebbe anche essere disponibile direttamente da
http://www.eleves.ens.fr:8080/home/rideau/files/as86.bcc.patch.gz
Bruce Evans ha accettato questa patch, così se un giorno ci sarà
da qualche parte una versione più recente di bcc,
la patch dovrebbe essere stata inclusa.
Ecco la voce nel Makefile di GNU per usare bcc allo scopo di
trasformare asm .s
in oggetto .o
ed ottenere un listato .l
:
%.o %.l: %.s bcc -3 -G -c -A-d -A-l -A$*.l -o $*.o $<
Togliete %.l
, -A-l
e -A$*.l
se non volete alcun listato.
Se volete qualcos'altro invece di un a.out GNU,
potete consultare la documentazione di bcc circa gli altri formati supportati
e/o utilizzare objcopy dal pacchetto delle GNU binutils.
La documentazione è quella che è inclusa nel pacchetto bcc. Da qualche parte nel sito di FreeBSD sono inoltre disponibili le pagine di manuale. In caso di dubbi, i sorgenti stessi sono spesso una buona documentazione: non è molto ben commentata, ma lo stile di programmazione è chiaro. Potreste provare a vedere come as86 è utilizzato in Tunes 0.0.0.25...
Linus è sepolto vivo nella posta e la mia patch per compilare Linux con as86 a.out per Linux non ce l'ha fatta ad arrivargli (!). Ora, questo non dovrebbe avere importanza: limitatevi a tenere il vostro as86 dal pacchetto bin86 in /usr/bin e lasciate che bcc installi l'as86 buono come /usr/local/libexec/i386/bcc/as dove dovrebbe risiedere. Non avrete mai bisogno di chiamare esplicitamente questo as86 «buono», perché bcc fa tutto come si deve, compresa la conversione dal formato a.out di Linux quando viene invocato con le opzioni corrette; limitatevi perciò ad assemblare usando bcc come frontend, non fatelo direttamente con as86.
Queste sono altre scelte possibili, non convenzionali, nel caso in cui le precedenti non vi abbiano soddisfatto (perché?). Non le consiglio nei casi comuni (?), ma potrebbero rivelarsi molto utili se l'assemblatore deve essere integrato nel software che state progettando (ad esempio un sistema operativo o un ambiente di sviluppo).
Win32Forth è un sistema ANS FORTH a 32 bit libero che gira sotto Win32s, Win95, Win NT. Include un assemblatore libero a 32 bit (con sintassi prefissa o postfissa) integrato nel linguaggio FORTH. La gestione delle macro è realizzata con la piena potenza del linguaggio FORTH; comunque, l'unico contesto di input e di output supportato è Win32Forth stesso (nessuna creazione di file .obj ; certo, potreste aggiungerla voi stessi). Lo trovate qui: ftp://ftp.forth.org/pub/Forth/win32for/
Terse è uno strumento di programmazione con LA sintassi dell'assemblatore più compatta per la famiglia x86!
Vedere http://www.terse.com. Si dice che ce ne sia un clone libero da qualche parte. Sarebbe stato abbandonato in seguito a pretese infondate secondo le quali la sintassi apparterrebbe all'autore originale. Vi invito a continuarne lo sviluppo, nel caso la sintassi vi interessi.
Potete trovare più informazioni a riguardo, assieme alle basi della programmazione assembly per x86, nelle FAQ di Raymond Moon per comp.lang.asm.x86 http://www2.dgsys.com/~raymoon/faq/asmfaq.zip
Va notato che tutti gli assemblatori che si basano sul DOS dovrebbero funzionare sotto l'emulatore di DOS per Linux ed altri emulatori analoghi cosicché, se ne possedete già uno, potete continuare ad usarlo in un vero sistema operativo. Alcuni assemblatori recenti per DOS supportano anche COFF e/o altri formati di file oggetto che sono supportati dalla libreria GNU BFD, così potete usarli insieme ai vostri strumenti liberi a 32 bit, magari usando GNU objcopy (che fa parte delle binutils) come filtro di conversione.