Das erste, was Sie brauchen, ist so etwas wie diese Datei . Dies ist die Anweisungsdatenbank für x86-Prozessoren, wie sie vom NASM-Assembler verwendet wird (die ich mitgeschrieben habe, obwohl nicht die Teile, die tatsächlich Anweisungen übersetzen). Lass uns eine beliebige Zeile aus der Datenbank auswählen:
ADD rm32,imm8 [mi: hle o32 83 /0 ib,s] 386,LOCK
Dies bedeutet, dass es die Anweisung beschreibt ADD
. Es gibt mehrere Varianten dieses Befehls, und die hier beschriebene Variante verwendet entweder ein 32-Bit-Register oder eine Speicheradresse und fügt einen unmittelbaren 8-Bit-Wert hinzu (dh eine Konstante, die direkt in dem Befehl enthalten ist). Eine beispielhafte Montageanleitung, die diese Version verwenden würde, lautet wie folgt:
add eax, 42
Nun müssen Sie Ihre Texteingabe in einzelne Anweisungen und Operanden zerlegen. Bei der obigen Anweisung würde dies wahrscheinlich zu einer Struktur führen, die die Anweisung ADD
und ein Array von Operanden enthält (eine Referenz auf das Register EAX
und den Wert 42
). Sobald Sie diese Struktur haben, durchlaufen Sie die Anweisungsdatenbank und suchen die Zeile, die sowohl dem Anweisungsnamen als auch den Typen der Operanden entspricht. Wenn Sie keine Übereinstimmung finden, ist dies ein Fehler, der dem Benutzer angezeigt werden muss ("unzulässige Kombination von Opcode und Operanden" oder ähnliches ist der übliche Text).
Sobald wir die Zeile aus der Datenbank erhalten haben, sehen wir uns die dritte Spalte an, die für diese Anweisung wie folgt lautet:
[mi: hle o32 83 /0 ib,s]
Dies ist eine Reihe von Anweisungen, die beschreiben, wie die erforderliche Maschinencodeanweisung generiert wird:
- Das
mi
ist eine Beschreibung der Operanden: ein modr/m
(Register- oder Speicher-) Operand (was bedeutet, dass wir ein modr/m
Byte an das Ende des Befehls anhängen müssen , auf das wir später noch eingehen werden) und ein sofortiger Befehl (der wird) in der Beschreibung der Anweisung verwendet werden).
- Weiter ist
hle
. Dies gibt an, wie wir mit dem Präfix "lock" umgehen. Wir haben "lock" nicht verwendet und ignorieren es daher.
- Weiter ist
o32
. Dies sagt uns, dass, wenn wir Code für ein 16-Bit-Ausgabeformat zusammenstellen, der Befehl ein Präfix zum Überschreiben der Operandengröße benötigt. Wenn wir eine 16-Bit-Ausgabe erzeugen würden, würden wir jetzt das Präfix ( 0x66
) erzeugen , aber ich gehe davon aus, dass wir es nicht sind, und mach weiter.
- Weiter ist
83
. Dies ist ein Literalbyte in hexadezimaler Schreibweise. Wir geben es aus.
Weiter ist /0
. Dies spezifiziert einige zusätzliche Bits, die wir im modr / m-Byte benötigen, und veranlasst uns, sie zu generieren. Das modr/m
Byte wird zum Codieren von Registern oder indirekten Speicherreferenzen verwendet. Wir haben einen einzigen solchen Operanden, ein Register. Das Register hat eine Nummer, die in einer anderen Datendatei angegeben ist :
eax REG_EAX reg32 0
Wir überprüfen, ob reg32
die erforderliche Größe der Anweisung aus der ursprünglichen Datenbank (wie sie ist) übereinstimmt. Das 0
ist die Nummer des Registers. Ein modr/m
Byte ist eine vom Prozessor festgelegte Datenstruktur, die folgendermaßen aussieht:
(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)
Da wir mit einem Register arbeiten, ist das mod
Feld 0b11
.
- Das
reg
Feld ist die Nummer des Registers, das wir verwenden,0b000
- Da diese Anweisung nur ein einziges Register enthält, müssen wir das
rm
Feld mit etwas ausfüllen . Das ist, wofür die zusätzlichen Daten spezifiziert /0
wurden, also setzen wir das in das rm
Feld 0b000
,.
- Das
modr/m
Byte ist also 0b11000000
oder 0xC0
. Wir geben dies aus.
- Weiter ist
ib,s
. Dies gibt ein vorzeichenbehaftetes Sofortbyte an. Wir sehen uns die Operanden an und stellen fest, dass wir einen sofort verfügbaren Wert haben. Wir konvertieren es in ein vorzeichenbehaftetes Byte und geben es aus ( 42
=> 0x2A
).
Die komplett montierte Anweisung lautet daher: 0x83 0xC0 0x2A
. Senden Sie es zusammen mit dem Hinweis, dass keines der Bytes eine Speicherreferenz darstellt, an Ihr Ausgabemodul (das Ausgabemodul muss möglicherweise wissen, ob dies der Fall ist).
Wiederholen Sie dies für jede Anweisung. Behalten Sie den Überblick über Beschriftungen, damit Sie wissen, was eingefügt werden muss, wenn auf sie verwiesen wird. Fügen Sie Funktionen für Makros und Anweisungen hinzu, die an die Ausgabemodule für Objektdateien übergeben werden. Und so funktioniert im Grunde ein Assembler.