La prima cosa di cui hai bisogno è qualcosa come questo file . Questo è il database delle istruzioni per i processori x86 utilizzato dall'assemblatore NASM (che ho aiutato a scrivere, sebbene non le parti che effettivamente traducono le istruzioni). Selezioniamo una riga arbitraria dal database:
ADD rm32,imm8 [mi: hle o32 83 /0 ib,s] 386,LOCK
Ciò significa che descrive l'istruzione ADD
. Ci sono più varianti di questa istruzione, e quella specifica che viene descritta qui è la variante che accetta un registro a 32 bit o un indirizzo di memoria e aggiunge un valore immediato a 8 bit (cioè una costante direttamente inclusa nell'istruzione). Un esempio di istruzione di assemblaggio che userebbe questa versione è questa:
add eax, 42
Ora, devi inserire il tuo testo e analizzarlo in singole istruzioni e operandi. Per l'istruzione di cui sopra, probabilmente si otterrebbe una struttura che contiene l'istruzione ADD
e una matrice di operandi (un riferimento al registro EAX
e il valore 42
). Una volta che si dispone di questa struttura, si esegue il database delle istruzioni e si trova la riga che corrisponde sia al nome dell'istruzione che ai tipi di operandi. Se non trovi una corrispondenza, è un errore che deve essere presentato all'utente ("la combinazione illegale di opcode e operandi" o simile è il solito testo).
Una volta che abbiamo preso la linea dal database, guardiamo la terza colonna, che per questa istruzione è:
[mi: hle o32 83 /0 ib,s]
Questa è una serie di istruzioni che descrivono come generare le istruzioni del codice macchina richieste:
- Il
mi
è una descrizione degli operandi: uno a modr/m
(registro o memoria) operando (il che significa che dovremo aggiungere un modr/m
byte alla fine dell'istruzione, che sarà venire a più tardi) e uno un'istruzione immediata (che sarà usata nella descrizione dell'istruzione).
- Il prossimo è
hle
. Questo identifica come gestiamo il prefisso "lock". Non abbiamo usato "lock", quindi lo ignoriamo.
- Il prossimo è
o32
. Questo ci dice che se stiamo assemblando il codice per un formato di output a 16 bit, l'istruzione ha bisogno di un prefisso di override della dimensione dell'operando. Se producessimo un output a 16 bit, produrremmo il prefisso ora ( 0x66
), ma assumerò che non lo siamo e proseguiamo.
- Il prossimo è
83
. Questo è un byte letterale in esadecimale. Lo pubblichiamo.
-
Il prossimo è /0
. Questo specifica alcuni bit aggiuntivi che ci serviranno nel file modr / m, e ci induce a generarlo. Il byte modr/m
viene utilizzato per codificare registri o riferimenti di memoria indiretti. Abbiamo un unico operando di questo tipo, un registro. Il registro ha un numero, che è specificato in un altro file di dati :
eax REG_EAX reg32 0
-
Controlliamo che reg32
concorda con la dimensione richiesta dell'istruzione dal database originale (lo fa). Il 0
è il numero del registro. Un modr/m
byte è una struttura dati specificata dal processore, che assomiglia a questo:
(most significant bit)
2 bits mod - 00 => indirect, e.g. [eax]
01 => indirect plus byte offset
10 => indirect plus word offset
11 => register
3 bits reg - identifies register
3 bits rm - identifies second register or additional data
(least significant bit)
-
Poiché stiamo lavorando con un registro, il campo mod
è 0b11
.
- Il campo
reg
è il numero del registro che stiamo utilizzando, 0b000
- Poiché in questa istruzione esiste un solo registro, è necessario compilare il campo
rm
con qualcosa. Questo è quanto previsto per i dati extra in /0
, quindi lo abbiamo inserito nel campo rm
, 0b000
.
- Il
modr/m
byte è quindi 0b11000000
o 0xC0
. Emettiamo questo.
- Il prossimo è
ib,s
. Questo specifica un byte immediato firmato. Guardiamo gli operandi e notiamo che abbiamo un valore immediato disponibile. Lo convertiamo in un byte firmato e lo emettiamo ( 42
= > 0x2A
).
L'istruzione assemblata completa è quindi: 0x83 0xC0 0x2A
. Mandalo al tuo modulo di output, insieme a una nota che nessuno dei byte costituisce un riferimento alla memoria (il modulo di output potrebbe aver bisogno di sapere se lo fa).
Ripeti per ogni istruzione. Tieni traccia delle etichette in modo da sapere cosa inserire quando vengono referenziate. Aggiungi funzionalità per macro e direttive che vengono passate ai moduli di output del file oggetto. E questo è fondamentalmente come funziona un assemblatore.