rideau@ens.fr
Copyright © 1996,1997 by François-René Rideau. This document may be distributed under the terms set forth in the LDP license at http://sunsite.unc.edu/LDP/COPYRIGHT.html.
C'è da aspettarsi che questo sia l'ultimo rilascio di questo documento da parte mia.
C'è un candidato al ruolo di curatore, ma finché l'HOWTO non diventa ufficialmente suo, accetterò suggerimenti e critiche.
Siete in particolare invitati a porre domande, a rispondere alle domande, a correggere le risposte date, ad aggiungere nuove risposte alle FAQ, a fornire riferimenti ad altro software, ad indicare errori o lacune nelle pagine al responsabile attuale. Se qualcuno di voi è motivato, potrebbe perfino DIVENTARE IL RESPONSABILE DELLE FAQ. In una parola, contribuite!
Per contribuire, per favore contattate chiunque sembri curare l'Assembly-HOWTO. I curatori attuali sono: François-René Rideau ed ora Paul Anderson.
Questo documento intende rispondere alle domande più frequenti delle persone che programmano o vogliono programmare in assembly a 32 bit per x86 utilizzando assemblatori liberi, specialmente sotto il sistema operativo Linux. Potrebbe inoltre rimandare ad altri documenti circa assemblatori non liberi, non per x86 o non a 32 bit, anche se questo non è il suo scopo principale.
Poiché l'interesse principale della programmazione in assembly consiste nel realizzare le viscere dei sistemi operativi, degli interpreti, dei compilatori e dei giochi, laddove un compilatore C non riesce a fornire l'espressività richiesta (è abbastanza raro che si tratti di una questione di prestazioni), ci interesseremo principalmente di questo tipo di software.
Questo documento contiene risposte ad alcune domande poste di frequente. In molte occasioni, vengono forniti URL di alcuni archivi di software o documentazione. Per favore, notate che gli archivi di maggiore utilità hanno dei mirror e che accedendo ad un mirror più vicino da un lato evitate ad Internet traffico non necessario e dall'altro risparmiate tempo prezioso. In particolare, ci sono dei grandi depositi sparsi per tutto il mondo che fanno il mirror anche di altri archivi di pubblico interesse.
Dovreste imparare ad annotarvi quali sono questi posti vicino a voi (dal punto di vista della rete).
Talvolta, la lista dei mirror si trova in un file o in un messaggio di login. Siete pregati di seguire i consigli. Altrimenti, dovreste interrogare archie circa il software di cui siete alla ricerca...
La versione più recente di questi documenti risiede ad
http://www.eleves.ens.fr:8080/home/rideau/Assembly-HOWTO oppure http://www.eleves.ens.fr:8080/home/rideau/Assembly-HOWTO.sgml
ma anche ciò che si trova negli archivi degli HOWTO di Linux dovrebbe essere abbastanza aggiornato (io non ho modo di saperlo):
ftp://sunsite.unc.edu/pub/Linux/docs/HOWTO/ (?)
Una traduzione in francese di questo HOWTO può essere trovata dalle parti di:
ftp://ftp.ibp.fr/pub/linux/french/HOWTO/
COPYING
,
con una versione per le librerie in un file chiamato COPYING.LIB
.
Anche qualche pubblicazione della FSF (free software foundation)
potrebbe esservi d'aiuto.
Ogni versione contiene alcune correzioni e rettifiche di poco conto che non è necessario menzionare ogni volta.
Francois-Rene «Faré» Rideau <rideau@ens.fr> crea e pubblica il primo mini-HOWTO, perché «Non ne posso più di rispondere sempre alle stesse domande su comp.lang.asm.x86»
*
*
Trovata l'opzione -fasm per abilitare l'assemblatore inline di GCC senza le ottimizzazioni -O
Creata la storia del documento. Aggiunti i riferimenti nella sezione sulla compilazione incrociata. Aggiunta la sezione circa la programmazione dell'I/O sotto Linux (video in particolare).
maggiori informazioni sulla compilazione incrociata. Vedere devel/msdos su sunsite.
NASM sta diventando molto affidabile.
Riferimento alla traduzione in francese.
Cosa? Mi ero dimenticato di fare riferimento a Terse???
*
Il mini-HOWTO in formato testo viene trasformato in un completo HOWTO linuxdoc-sgml, per vedere come sono gli SGML tools.
Primo rilascio dell'HOWTO come tale.
Aggiunta la sezione «ringraziamenti».
Spostato NASM: ora è prima di AS86
Aggiunta la sezione «avete bisogno dell'assembly?»
Annuncio prematuro di un nuovo responsabile dell'Assembly-HOWTO.
Rilascio per DrLinux
*
*
ancora aggiunte a «come NON usare l'assembly»; aggiornamenti su NASM, GAS.
informazioni sull'accesso al modo a 16 bit da Linux.
*
*
rilascio per LSL, sesta edizione.
Questo è ancora un altro ultimo-rilascio-di-Faré-prima-che-un-altro-curatore-gli-subentri (?)
Vorrei ringraziare le seguenti persone, in ordine di apparizione:
Beh, non vorrei interferire con ciò che state facendo, ma ecco alcuni consigli derivanti da una esperienza ottenuta faticosamente.
L'assembly può esprimere cose molto a basso livello:
L'assembly è un linguaggio molto a basso livello (più in basso c'è solo la codifica a mano delle istruzioni in codice binario).
Ciò significa:
Tutto sommato, potreste notare che nonostante l'uso dell'assembly sia talvolta necessario (o semplicemente utile, in alcuni casi), sarà il caso che:
Anche nei casi in cui l'assembly è necessario (ad esempio, nello sviluppo di sistemi operativi), scoprirete che non ne serve poi molto e che i principi precedenti continuano a valere.
A questo riguardo, date un'occhiata ai sorgenti del kernel di Linux: poco assembly, giusto lo stretto necessario, il che ha come risultato un sistema operativo veloce, affidabile, portabile e mantenibile. Anche un gioco di successo come DOOM è stato scritto quasi completamente in C, con solo una minuscola parte scritta in assembly per renderlo più veloce.
Come dice Charles Fiterman su comp.compilers circa il confronto tra codice assembly generato a mano o automaticamente,
«L'uomo dovrebbe sempre vincere, ed eccone i motivi:
L'uomo vince perché sa usare la macchina.»
I linguaggi quali ObjectiveCAML, SML, CommonLISP, Scheme, ADA, Pascal, C, C++, tra gli altri, dispongono di compilatori ottimizzanti liberi che ottimizzeranno il grosso dei vostri programmi (e spesso otterranno risultati migliori rispetto all'assembly manuale anche per cicli stretti), permettendovi nel frattempo di concentrarvi su dettagli più ad alto livello, il tutto senza vietarvi di ottenere qualche punto percentuale di prestazioni in più nella maniera espressa sopra, una volta che il vostro progetto avrà raggiunto un'impostazione stabile.
Certo, ci sono anche compilatori ottimizzanti commerciali per la maggior parte di quei linguaggi!
Alcuni linguaggi hanno compilatori che producono codice C, che può essere ulteriormente ottimizzato da un compilatore C. LISP, Scheme, Perl e molti altri fanno parte di questa categoria. La velocità è abbastanza buona.
Per quanto riguarda l'accelerazione del vostro codice, dovreste restringerla alle parti di un programma che uno strumento di profiling ha decisamente identificato come un collo di bottiglia.
Perciò, se identificate qualche porzione di codice come troppo lenta, dovreste:
Come ultima cosa, prima che vi riduciate a scrivere assembly, dovreste ispezionare il codice generato, per controllare che il problema risieda proprio nella cattiva generazione del codice, visto che potrebbe anche non essere così: il codice generato dal compilatore potrebbe essere migliore di quanto avreste potuto fare voi, specialmente sulle moderne architetture multi-pipelined! Le parti lente di un programma potrebbero essere intrinsecamente tali. I più grossi problemi sulle architetture moderne con processori veloci sono dovuti a ritardi di accesso alla memoria, cache-miss, TLB miss, e page fault; l'ottimizzazione sui registri diventa inutile, ed otterrete risultati migliori riprogettando le strutture dati ed il threading per ottenere una miglior località nell'accesso alla memoria. Potrebbe forse essere d'aiuto un approccio completamente diverso al problema.
Ci sono molte ragioni per ispezionare il codice assembly generato dal compilatore. Ecco cosa potete fare con tale codice:
Il modo canonico per far generare codice assembly
è invocare con il flag -S
il vostro compilatore.
Ciò funziona con la maggior parte dei compilatori UNIX,
compreso il compilatore C di GNU (GCC), ma nel vostro caso le cose potrebbero
andare diversamente.
Nel caso di GCC, con l'opzione -fverbose-asm
verrà prodotto
codice assembly più comprensibile.
Certo, se volete ottenere buon codice assembly, non dimenticate di dare
i soliti consigli e le solite opzioni per l'ottimizzazione!
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.
La programmazione in assembly è una scocciatura, tranne che per sezioni critiche dei programmi.
Dovreste usare gli strumenti appropriati per il compito giusto, quindi non scegliete l'assembly quando non è adatto; C, OCAML, perl, Scheme potrebbero essere una scelta migliore per la maggior parte della vostra programmazione.
Comunque, ci sono casi in cui questi strumenti non consentono un controllo sufficientemente accurato della macchina e l'assembly risulta utile o addirittura necessario. In questi casi, vi sarà utile un sistema di macro e metaprogrammazione che permetterà di ridurre ogni struttura ricorrente in una definizione riutilizzabile molte volte, il che consente una programmazione più sicura, la propagazione automatica delle modifiche alla struttura stessa, ecc.
Un assemblatore «liscio» è spesso insufficiente, anche quando si sviluppano semplicemente piccole routine per un linking con il C.
Sì, lo so che questa sezione non contiene molte informazioni aggiornate. Sentitevi liberi di contribuire ciò che scoprite sulla vostra pelle...
GCC permette (e richiede) che voi specifichiate vincoli sui registri nel vostro codice assembly inline cosicché l'ottimizzatore ne è sempre al corrente; perciò il codice assembly inline, in realtà, non è per forza costituito da codice esatto, ma da strutture.
Potete poi inserire il vostro assembly in macro di CPP ed in funzioni C
inline, così si può usare come una qualsiasi macro o funzione C.
Le funzioni inline assomigliano molto alle macro, ma il loro uso
risulta talvolta più pulito.
Badate che in tutti questi casi si avrà una duplicazione di codice,
quindi in questo codice asm dovrebbero essere definite solo etichette
locali (del tipo 1:
).
Comunque, una macro permetterebbe che il nome di una etichetta non definita
localmente venga passato come parametro (oppure utilizzate metodi aggiuntivi
di metaprogrammazione).
Inoltre, propagare codice asm inline diffonderà potenziali bug
presenti in esso, state quindi doppiamente attenti ai vincoli sui registri
in situazioni simili.
Infine, il linguaggio C stesso può essere considerato una buona astrazione rispetto al linguaggio assembly, il che vi solleva dalla maggior parte dei problemi legati all'assembling.
Fate attenzione: alcune ottimizzazioni riguardanti il passaggio di argomenti a funzioni tramite registri possono rendere dette funzioni inadatte ad essere chiamate da routine esterne (ed in particolare da quelle scritte a mano in assembly) nel modo standard; l'attributo «asmlinkage» potrebbe impedire ad una routine di preoccuparsi di tale flag di ottimizzazione; guardatevi i sorgenti del kernel per gli esempi.
GAS fornisce del supporto per le macro, come è spiegato in dettaglio nella documentazione texinfo. GCC, oltra a riconoscere i file .s come assembly «grezzo» da inviare a GAS, riconosce anche i file .S come file da passare attraverso CPP prima di mandarli a GAS. Ancora una volta, guardate i sorgenti di Linux per gli esempi.
Aggiunge tutti i tipici trucchetti delle macro a GAS. Consultate la sua documentazione texinfo.
Anche NASM ha del supporto per le macro. Date un'occhiata alla documentazione relativa. Se avete qualche idea brillante, potreste voler contattare gli autori, dal momento che lo stanno sviluppando attivamente. Nel frattempo, date un'occhiata ai filtri esterni presentati in seguito.
Ha un po' di semplice supporto per le macro, ma non sono riuscito a trovare della documentazione. I sorgenti sono molto chiari perciò se siete interessati dovreste capirli facilmente. Se le capacità di base non vi bastano, dovreste usare un filtro esterno (vedere in seguito).
Qualunque sia il supporto per le macro del vostro assemblatore, o qualunque linguaggio utilizziate (perfino il C!), se secondo voi il linguaggio non è sufficientemente espressivo, potete far passare i file attraverso un filtro esterno con una regola come questa nel Makefile:
%.s: %.S altre_dipendenze $(FILTRO) $(OPZIONI_DEL_FILTRO) < $< > $@
CPP non è molto espressivo, ma è sufficiente per le cose facili, è standard ed è chiamato in modo trasparente da GCC.
Per fare un esempio delle sue limitazioni, non si possono dichiarare oggetti in modo tale che i distruttori vengano chiamati automaticamente al termine del blocco di dichiarazione; non avete diversion o regole di visibilità (scoping), ecc.
CPP si trova assieme ad ogni compilatore C. Se ve la siete cavata senza averne uno, non preoccupatevi di procurarvi GCC (anche se mi chiedo come abbiate fatto).
M4 vi dà tutta la potenza delle macro, con un linguaggio Turing-equivalente, ricorsione, espressioni regolari, ecc. Potete farci tutto ciò che CPP non riesce a fare.
Date un'occhiata a macro4th/This4th da ftp://ftp.forth.org/pub/Forth/ in Reviewed/ ANS/ (?), o le sorgenti di Tunes 0.0.0.25 come esempi di un utilizzo avanzato delle macro con m4. Comunque, il suo scomodo sistema di protezione dall'espansione vi obbliga ad utilizzare per le macro uno stile basato sulla ricorsione in coda con passaggio di continuazione esplicito (explicit continuation-passing) se volete una uso avanzato delle macro (il che ricorda TeX. A proposito, qualcuno ha provato ad usare TeX per le macro di qualcosa di diverso dalla composizione tipografica?). Questo comunque NON è peggio di CPP, il quale non permette né il quoting né la ricorsione. La versione giusta di m4 da procurarsi è GNU m4 1.4 (o successiva, se esiste), la quale ha il maggior numero di funzionalità ed il minor numero di bug o limitazioni rispetto alle altre. m4 è progettato per essere lento per tutto tranne gli utilizzi più semplici, il che potrebbe andare ancora bene per la maggior parte dei programmi assembly (non state scrivendo programmi in assembly da milioni di righe, vero?).
Potete scrivervi dei semplici filtri per l'espansione delle macro con i soliti strumenti: perl, awk, sed, ecc. Si fa in fretta ed avete il controllo su tutto. Ma ovviamente, la potenza nella gestione delle macro bisogna guadagnarsela con fatica.
Invece di usare un filtro esterno che espande le macro, un modo alternativo di procedere è quello di scrivere programmi che scrivono parti di altri programmi (o interi programmi).
Ad esempio, potreste utilizzare un programma che dia in uscita codice sorgente
Pensateci!
Compilatori come SML/NJ, Objective CAML, MIT-Scheme, ecc. hanno il loro backend generico per gli assemblatori che potreste o meno voler usare se intendete generare semiautomaticamente del codice partendo dai linguaggi relativi.
C'è un progetto per costruire, usando il linguaggio di programmazione Icon, una base per produrre codice che manipola l'assembly. Date un'occhiata dalle parti di http://www.cs.virginia.edu/~nr/toolkit/
Il progetto Tunes OS sta sviluppando il suo assemblatore come un'estensione al linguaggio Scheme, come parte del suo processo di sviluppo. Al momento non è ancora in grado di girare, tuttavia ogni aiuto è bene accetto.
L'assemblatore manipola alberi di sintassi simbolici, così può ugualmente servire come base per un traduttore di sintassi assembly, un disassemblatore, un backend comune per assemblatore/compilatore, ecc. Inoltre tutta la potenza di un vero linguaggio, Scheme, lo rende insuperato per quanto riguarda le macro e la metaprogrammazione.
http://www.eleves.ens.fr:8080/home/rideau/Tunes/
È il modo preferito.
Controllate la documentazione di GCC e gli esempi dai file .S
del kernel di Linux che passano attraverso gas (non quelli che passano
attraverso as86).
Gli argomenti a 32 bit vengono posti sullo stack (push) in ordine inverso
rispetto alla sintassi (per cui vi si accede prelevandoli
nell'ordine corretto) sopra l'indirizzo di ritorno a 32 bit.
%ebp
, %esi
,
%edi
, %ebx
sono salvati dalla funzione chiamata,
gli altri dalla funzione chiamante;
per contenere il risultato si usa %eax
,
oppure %edx:%eax
per risultati a 64 bit.
Stack FP: non ne sono certo, ma penso che il risultato vada
in st(0)
e che l'intero stack sia salvato dalla funzione chiamante.
Notate che GCC ha delle opzioni per modificare le convenzioni di chiamata
riservando registri, per mettere argomenti nei registri, per non
fare supposizioni sulla presenza dell'FPU, ecc.
Controllate le pagine .info i386 .
Fate attenzione: in questo caso dovete dichiarare l'attributo decl
per una funzione che seguirà le convenzioni di chiamata
standard di GCC (non so cosa faccia con le convenzioni di chiamata modificate).
Leggete le pagine info di GCC nella sezione:
C Extensions::Extended Asm::
Alcuni compilatori C antepongono un underscore prima di ogni simbolo, mentre altri non lo fanno.
In particolare, GCC a.out per Linux effettua questa anteposizione, mentre GCC ELF per Linux no.
Se avete bisogno di gestire insieme entrambi i comportamenti, guardate come fanno i pacchetti esistenti. Ad esempio, procuratevi dei vecchi sorgenti di Linux, Elk, qthreads, o OCAML...
Potete inoltre far ignorare la rinominazione implicita C->
asm
inserendo istruzioni come
void pippo asm("pluto") (void);
Notate che il programma di utilità objcopy
, dal pacchetto
binutils
, dovrebbe permettervi di trasformare i vostri oggetti
a.out in oggetti ELF e forse anche viceversa, in alcuni casi.
Più in generale, effettuerà un gran numero di conversioni
di formato dei file.
Ciò è espressamente NON consigliato,
poiché le convenzioni cambiano di tanto in tanto
o tra varianti del kernel (vedere L4Linux),
inoltre si perde la portabilità, comporta un lavoro
di scrittura massiccio, si ha ridondanza con gli sforzi di libc ED INOLTRE
preclude estensioni e correzioni apportate a libc come, ad esempio,
il pacchetto zlibc
, che provvede ad una trasparente decompressione
«al volo» di file compressi con gzip.
Il metodo convenzionale e consigliato per chiamare i servizi di sistema
di Linux è, e rimarrà, quello di passare attraverso la libc.
Gli oggetti condivisi dovrebbero mantenere la vostra roba entro
dimensioni contenute.
E se proprio volete binari più piccoli, utilizzate #!
e scaricate sull'interprete il fardello che volete togliere dai vostri
binari.
Ora, se per qualche ragione non volete fare un link alla libc, procuratevi la libc stessa e capite come funziona! Dopotutto, aspirate a sostituirla, no?
Potreste inoltre dare un'occhiata a come il mio eforth 1.0c lo fa.
Anche i sorgenti di Linux tornano utili, in particolare l'header file asm/unistd.h, che descrive come effettuare le chiamate di sistema...
Fondamentalmente, generate un int $0x80
,
con il numero associato a __NR_
nomedellasyscall
(da asm/unistd.h
) in %eax
,
ed i parametri (fino a cinque) in
%ebx
, %ecx
, %edx
,
%esi
, %edi
rispettivamente.
Il risultato viene restituito in %eax
,
dove un numero negativo è un errore
il cui opposto è ciò che libc metterebbe in errno.
Lo stack utente non viene toccato,
così non è necessario averne uno valido quando effettuate
una chiamata di sistema.
Se volete effettuare dell'I/O sotto Linux,
o si tratta di qualcosa di molto semplice che non richiede l'arbitraggio
del sistema operativo, e allora dovreste consultare
l'IO-Port-Programming mini-HOWTO
,
oppure ha bisogno di un driver di periferica nel kernel, nel qual
caso dovreste provare ad approfondire le vostre conoscenze sull'hacking
del kernel, sullo sviluppo di driver di periferica, sui moduli del
kernel, ecc., per i quali ci sono altri
eccellenti HOWTO e documenti del LDP.
In particolare, se ciò che vi interessa è la programmazione della grafica, allora partecipate al progetto GGI: http://synergy.caltech.edu/~ggi/ http://sunserver1.rz.uni-duesseldorf.de/~becka/doc/scrdrv.html
Comunque, in tutti questi casi, fareste meglio ad usare l'assembly inline di GCC con le macro da linux/asm/*.h piuttosto che scrivere file sorgenti completamente in assembly.
Ciò è teoricamente possibile (dimostrazione: guardate come DOSEMU riesca a garantire ai programmi un accesso a porte hardware in modo selettivo), ed ho anche sentito voci secondo le quali qualcuno da qualche parte ci sarebbe di fatto riuscito (nel driver PCI? Roba per l'accesso VESA? ISA PnP? Non so). Se avete informazioni più precise a riguardo, siate i benvenuti. Comunque, buoni posti in cui cercare maggiori informazioni sono i sorgenti del kernel di Linux, i sorgenti di DOSEMU (ed altri programmi nel DOSEMU repository), e sorgenti di vari programmi a basso livello sotto Linux... (forse GGI se supporta VESA). Fondamentalmente, dovete usare la modalità protetta a 16 bit o il modo vm86.
Il primo è più semplice da mettere in piedi, ma funziona solamente con codice «educato» (well-behaved) che non utilizza l'aritmetica dei segmenti o indirizzamento assoluto degli stessi (segmento 0 in particolare), a meno che non ci si trovi nel caso in cui tutti i segmenti utilizzati possano essere preparati in anticipo nella LDT.
Il secondo permette più «compatibilità» con i comuni ambienti a 16 bit, ma richiede una gestione più complessa.
In entrambi i casi, prima di poter saltare al codice a 16 bit, dovete
Ancora una volta, leggete con cura i sorgenti dei contributi al DOSEMU repository menzionato sopra, in particolare quei mini-emulatori per far girare ELKS e/o semplici programmi .COM sotto Linux/i386.
La maggior parte dei DOS extender viene fornita con dell'interfacciamento
a servizi DOS.
Leggete la loro documentazione a riguardo, ma spesso si limitano a simulare
int $0x21
e simili, così vi comportate «come se»
foste in modo reale
(dubito che abbiano qualcosa di più di stub
ed estensioni per lavorare con operandi a 32 bit;
molto probabilmente si limiteranno a riportare l'interrupt
nel gestore del modo reale o del vm86).
Documentazione su DPMI e affini (e molto più) può essere trovata in ftp://x2ftp.oulu.fi/pub/msdos/programming/
Anche DJGPP viene fornito con il proprio derivato/sottoinsieme/sostituto di glibc.
È possibile la compilazione incrociata da Linux a DOS, guardate nella directory devel/msdos/ del vostro mirror locale dell'area FTP di sunsite.unc.edu Date anche un'occhiata al DOS extender «MOSS» dal progetto Flux in Utah.
Altri documenti e FAQ sono molto DOS-centrici. Noi non consigliamo di sviluppare per DOS.
Ehi, questo documento riguarda solo il software libero. Telefonatemi quando Winzozz diventa libero, o ci si possono usare strumenti di sviluppo liberi!
Beh, dopotutto ci sono: Cygnus Solutions ha sviluppato la libreria cygwin32.dll per far girare i programmi GNU su piattaforme Micrashoft. Così potete usare GCC, GAS, tutti gli strumenti di GNU e molte altre applicazioni UNIX. Date un'occhiata alla loro homepage. Io (Faré) non intendo dilungarmi sulla programmazione di WinnaNanna ma sono certo che potete trovare un sacco di documentazione a riguardo praticamente ovunque...
Essendo il controllo ciò che attrae molti programmatori all'assembly, il desiderio di sviluppare sistemi operativi è spesso ciò che li porta all'(o deriva dall') hacking in assembly. Va notato che ogni sistema che permette lo sviluppo di se stesso potrebbe essere considerato un «sistema operativo», anche se magari gira «sopra» ad un sistema sottostante che fornisce multitasking o I/O (in modo molto simile a Linux sopra Mach o OpenGenera sopra UNIX), ecc.
Quindi, per rendere più facile il debugging, potreste voler sviluppare il vostro «sistema operativo» prima come processo che gira sopra a Linux (nonostante la lentezza), quindi usare il Flux OS kit (che consente l'utilizzo di driver di Linux e BSD nel vostro OS) per renderlo autonomo. Quando il vostro sistema è stabile, resta ancora del tempo per scrivere dei driver per l'hardware tutti vostri, se la cosa vi fa proprio piacere.
Questo HOWTO non si occuperà di argomenti quali il codice per il boot loader ed entrare nel modo a 32 bit, gestire gli interrupt, i fondamenti degli orrori intel «modo protetto» o «V86/R86», la definizione di un vostro formato per i file oggetto e le convenzioni di chiamata. Il luogo principale in cui trovare informazioni attendibili a riguardo sono i sorgenti di sistemi operativi o bootloader già esistenti. Ci sono un sacco di riferimenti in questa pagina WWW: http://www.eleves.ens.fr:8080/home/rideau/Tunes/Review/OSes.html
.sig dell'autore:
-- , , _ v ~ ^ -- -- Fare -- rideau@clipper.ens.fr -- Francois-Rene Rideau -- +)ang-Vu Ban -- -- ' / . -- Join the TUNES project for a computing system based on computing freedom ! TUNES is a Useful, Not Expedient System WWW page at URL: http://www.eleves.ens.fr:8080/home/rideau/Tunes/