La prima cosa di cui hai bisogno è qualcosa come questo file . Questo è il database di istruzioni per processori x86 usato dall'assemblatore NASM (che ho aiutato a scrivere, sebbene non le parti che traducono effettivamente le istruzioni). Consente di selezionare una linea arbitraria dal database:
ADD rm32,imm8 [mi: hle o32 83 /0 ib,s] 386,LOCK
Ciò significa che descrive le istruzioni ADD
. Esistono più varianti di questa istruzione e quella specifica qui descritta è la variante che accetta un registro a 32 bit o un indirizzo di memoria e aggiunge un valore immediato a 8 bit (ovvero una costante inclusa direttamente nell'istruzione). Un'istruzione di assemblaggio di esempio che userebbe questa versione è questa:
add eax, 42
Ora, devi prendere il tuo input di testo e analizzarlo in singole istruzioni e operandi. Per l'istruzione sopra, ciò comporterebbe probabilmente una struttura che contiene l'istruzione ADD
e una matrice di operandi (un riferimento al registro EAX
e al valore 42
). Una volta che hai questa struttura, corri attraverso il database delle istruzioni e trovi la riga che corrisponde sia al nome dell'istruzione che ai tipi degli operandi. Se non trovi una corrispondenza, è un errore che deve essere presentato all'utente ("combinazione illegale di opcode e operandi" o simile è il solito testo).
Una volta ottenuta la linea dal database, esaminiamo la terza colonna, che per questa istruzione è:
[mi: hle o32 83 /0 ib,s]
Questa è una serie di istruzioni che descrivono come generare l'istruzione del codice macchina richiesta:
- La
mi
è una descriptiuon degli operandi: una al modr/m
(register o memoria) operando (i quali mezzi dovremo aggiungere un modr/m
byte alla fine della corrispondente istruzione, che arriveremo in seguito) e un'un'istruzione immediata (che sarà essere usato nella descrizione dell'istruzione).
- Il prossimo è
hle
. Questo identifica come gestiamo il prefisso "lock". Non abbiamo usato "blocco", 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 sostituzione della dimensione dell'operando. Se producessimo un output a 16 bit, produrremmo il prefisso ora ( 0x66
), ma suppongo che non lo siamo e andiamo avanti.
- Il prossimo è
83
. Questo è un byte letterale in esadecimale. Lo abbiamo prodotto.
Il prossimo è /0
. Questo specifica alcuni bit extra di cui avremo bisogno nel modr / m bytem e ci fa generare. Il modr/m
byte viene utilizzato per codificare registri o riferimenti di memoria indiretta. Abbiamo un solo di questi operandi, un registro. Il registro ha un numero, che è specificato in un altro file di dati :
eax REG_EAX reg32 0
Controlliamo che sia d' reg32
accordo con la dimensione richiesta dell'istruzione dal database originale (lo fa). Il 0
è il numero del registro. Un modr/m
byte è una struttura di 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)
Perché stiamo lavorando con un registro, il mod
campo è 0b11
.
- Il
reg
campo è il numero del registro che stiamo usando,0b000
- Poiché in questa istruzione esiste un solo registro, è necessario compilare il
rm
campo con qualcosa. Ecco a cosa servivano i dati extra specificati in /0
, quindi li inseriamo nel rm
campo 0b000
.
- Il
modr/m
byte è quindi 0b11000000
o 0xC0
. Abbiamo prodotto questo.
- Il prossimo è
ib,s
. Questo specifica un byte immediato con segno. Osserviamo gli operandi e notiamo che abbiamo un valore immediato disponibile. Lo convertiamo in un byte con segno e lo emettiamo ( 42
=> 0x2A
).
L'istruzione assemblato completo è pertanto: 0x83 0xC0 0x2A
. Invialo al tuo modulo di output, insieme a una nota che nessuno dei byte costituisce un riferimento di memoria (potrebbe essere necessario che il modulo di output lo sappia).
Ripetere l'operazione per ogni istruzione. Tieni traccia delle etichette in modo da sapere cosa inserire quando sono referenziate. Aggiungi funzionalità per macro e direttive che vengono passate ai moduli di output del file oggetto. E questo è fondamentalmente come funziona un assemblatore.