Wie gehen wir von der Baugruppe zum Maschinencode (Codegenerierung)?

Gibt es eine einfache Möglichkeit, den Schritt zwischen dem Zusammenstellen von Code und Maschinencode zu visualisieren? P. >

Wenn Sie beispielsweise eine Binärdatei im Editor öffnen, wird eine textformatierte Darstellung des Maschinencodes angezeigt. Ich gehe davon aus, dass jedes Byte (Symbol), das Sie sehen, das entsprechende ASCII-Zeichen für den Binärwert ist.

Aber wie gehen wir von Assembly zu Binär, was passiert hinter den Kulissen? / p>

Antwort

In der Dokumentation zum Befehlssatz finden Sie Einträge wie diesen unter ein Bild-Mikrocontroller für jeden Befehl:

Beispiel für einen zusätzlichen Befehl

Die Zeile „encoding“ gibt Auskunft Wie diese Anweisung in Binärform aussieht. In diesem Fall beginnt es immer mit 5 Einsen, dann einem Bit „egal“ (das entweder eins oder null sein kann), dann stehen die „k“ für das Literal, das Sie hinzufügen.

Die Die ersten paar Bits werden als „Opcode“ bezeichnet. Sie sind für jeden Befehl eindeutig. Die CPU überprüft im Grunde den Opcode, um festzustellen, um welchen Befehl es sich handelt, und weiß dann, dass sie die „k“ als hinzuzufügende Zahl dekodieren muss.

Es ist langweilig, aber nicht so schwer zu codieren und zu decodieren. Ich hatte eine Undergrad-Klasse, in der wir es in Prüfungen von Hand machen mussten.

Um eine vollständige ausführbare Datei zu erstellen, müssen Sie auch Dinge wie Speicher zuweisen, Verzweigungsversätze berechnen und in eine ablegen Format wie ELF , abhängig von Ihrem Betriebssystem.

Antwort

Baugruppen-Opcodes haben größtenteils eine Eins-zu-Eins-Entsprechung mit den zugrunde liegenden Maschinenanweisungen. Sie müssen also nur jeden Opcode in der Assemblersprache identifizieren, ihn der entsprechenden Maschinenanweisung zuordnen und die Maschinenanweisung zusammen mit den entsprechenden Parametern (falls vorhanden) in eine Datei schreiben. Anschließend wiederholen Sie den Vorgang für jeden zusätzlichen Opcode in der Quelldatei.

Natürlich ist mehr als das erforderlich, um eine ausführbare Datei zu erstellen, die ordnungsgemäß geladen und auf einem Betriebssystem ausgeführt wird, und die meisten anständigen Assembler tun dies Sie verfügen über einige zusätzliche Funktionen, die über die einfache Zuordnung von Opcodes zu Maschinenanweisungen (z. B. Makros) hinausgehen.

Antwort

Die 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 Anweisungen tatsächlich übersetzen). Wählen wir eine beliebige Zeile aus der Datenbank aus:

 ADD rm32,imm8 [mi: hle o32 83 /0 ib,s] 386,LOCK  

Dies bedeutet Folgendes Es beschreibt die Anweisung ADD. Es gibt mehrere Varianten dieses Befehls, und die spezifische, die hier beschrieben wird, ist die Variante, die entweder ein 32-Bit-Register oder eine Speicheradresse verwendet und einen sofortigen 8-Bit-Wert hinzufügt (d. H. Eine Konstante, die direkt in dem Befehl enthalten ist). Eine beispielhafte Assemblyanweisung, die diese Version verwenden würde, lautet wie folgt:

 add eax, 42  

Nun, Sie müssen Ihre Texteingabe in einzelne Anweisungen und Operanden zerlegen. Für den obigen Befehl würde dies wahrscheinlich zu einer Struktur führen, die den Befehl ADD und ein Array von Operanden enthält (ein Verweis auf das Register EAX und der 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 („illegale Kombination von Opcode und Operanden“ oder ähnlichem ist der übliche Text).

Sobald wir „ve“ haben Nachdem wir die Zeile aus der Datenbank erhalten haben, sehen wir uns die dritte Spalte an, die für diese Anweisung lautet:

 [mi: hle o32 83 /0 ib,s]  

Dies ist eine Reihe von Anweisungen, die beschreiben, wie die erforderliche Maschinencode-Anweisung generiert wird:

  • Die mi lautet eine Beschreibung der Operanden: ein Operand modr/m (Register oder Speicher) (was bedeutet, dass ein Byte modr/m an angehängt werden muss) das Ende der Anweisung, auf das wir später noch eingehen werden) und eine sofortige Anweisung (die in der Beschreibung der Anweisung verwendet wird).
  • Als nächstes kommt hle. Dies gibt an, wie wir mit dem Präfix „Sperre“ umgehen. Wir haben „lock“ nicht verwendet, daher ignorieren wir es.
  • Als nächstes folgt o32. Dies sagt uns, dass, wenn wir Code für einen 16- zusammenstellen. Im Bitausgabeformat benötigt der Befehl ein Überschreibungspräfix in Operandengröße.Wenn wir eine 16-Bit-Ausgabe erzeugen würden, würden wir jetzt das Präfix erzeugen (0x66), aber ich gehe davon aus, dass wir es nicht tun und weitermachen.
  • Weiter ist 83. Dies ist ein Hexadezimalbyte in Hexadezimalzahl. Wir geben es aus.
  • Weiter ist /0. Dies gibt einige zusätzliche Bits an, die wir im modr / m-Bytem 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 mit übereinstimmt Die erforderliche Größe des Befehls aus der Originaldatenbank (dies ist der Fall). Die 0 ist die Nummer des Registers. Ein modr/m Byte ist eine vom Prozessor angegebene 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, lautet das Feld mod 0b11.

  • Das Feld reg ist die Nummer des Registers, das wir verwenden, 0b000
  • Da diese Anweisung nur ein Register enthält, müssen wir das Feld rm mit etwas ausfüllen. Dafür waren die in /0 angegebenen zusätzlichen Daten bestimmt. Deshalb haben wir diese in das Feld rm .
  • Das modr/m Byte ist daher 0b11000000 oder 0xC0. Wir geben dies aus.
  • Als nächstes folgt ib,s. Dies gibt ein vorzeichenbehaftetes Sofortbyte an. Wir sehen uns die Operanden an und stellen fest, dass wir ein Sofortbyte haben Wert verfügbar. Wir konvertieren ihn in ein vorzeichenbehaftetes Byte und geben ihn aus (42 => 0x2A).

Die vollständig zusammengestellte Anweisung lautet daher: 0x83 0xC0 0x2A. Senden Sie sie zusammen mit dem Hinweis an Ihr Ausgabemodul, dass keines der Bytes Speicherreferenzen darstellt (das Ausgabemodul muss dies möglicherweise wissen Wenn dies der Fall ist).

Wiederholen Sie diesen Vorgang für jede Anweisung. Behalten Sie die Beschriftungen im Auge, damit Sie wissen, was einzufügen ist, wenn auf sie verwiesen wird. Fügen Sie Funktionen für Makros und Anweisungen hinzu, die an die Ausgabemodule Ihrer Objektdatei übergeben werden. Und so funktioniert ein Assembler im Grunde.

Kommentare

  • Vielen Dank. Gute Erklärung, sollte aber nicht ‚ “ 0x83 0xC0 0x2A “ statt “ 0x83 0xB0 0x2A “ weil 0b11000000 = 0xC0
  • @Kamran – $ cat > test.asm bits 32 add eax,42 $ nasm -f bin test.asm -o test.bin $ od -t x1 test.bin 0000000 83 c0 2a 0000003 … ja, Sie ‚ haben ganz recht. 🙂

Antwort

In der Praxis ein Assembler produzieren normalerweise nicht direkt eine binäre ausführbare Datei , sondern einige Objektdatei (wird später an den -Linker weitergeleitet). Es gibt jedoch Ausnahmen (Sie können einige Assembler verwenden, um direkt eine ausführbare Binärdatei zu erstellen ; sie sind ungewöhnlich).

Beachten Sie zunächst, dass viele Assembler heutzutage freie Software sind. Laden Sie also die Quelle herunter und kompilieren Sie sie auf Ihren Computer Code von GNU als (ein Teil von binutils ) und von nasm . Dann studieren Sie den Quellcode. Übrigens empfehle ich die Verwendung von Linux für diesen Zweck (es ist ein sehr entwicklerfreundliches und für freie Software geeignetes Betriebssystem).

Die von einem Assembler erstellte Objektdatei enthält insbesondere ein -Codesegment und Verschiebungsanweisungen . Es ist in einem gut dokumentierten Dateiformat organisiert, das vom Betriebssystem abhängt. Unter Linux lautet dieses Format (das für Objektdateien, gemeinsam genutzte Bibliotheken, Core-Dumps und ausführbare Dateien verwendet wird) ELF . Diese Objektdatei wird später in den Linker eingegeben (der schließlich eine ausführbare Datei erzeugt). Umzüge werden durch den ABI angegeben (z. B. x86-64 ABI ). Weitere Informationen finden Sie in Levines Buch Linker und Loader .

Das Codesegment in einer solchen Objektdatei enthält Maschinencode mit Löchern (vom Linker mithilfe von Verschiebungsinformationen zu füllen). Der von einem Assembler generierte (verschiebbare) Maschinencode ist offensichtlich spezifisch für einen -Befehlssatz Architektur .Die ISAs x86 oder x86-64 (werden in den meisten Laptop- oder Desktop-Prozessoren verwendet) sind schrecklich komplex in ihren Details. Zu Lehrzwecken wurde jedoch eine vereinfachte Teilmenge mit den Namen y86 oder y86-64 erfunden. Lesen Sie Folien darauf. Andere Antworten auf diese Frage erklären auch ein bisschen davon. Vielleicht möchten Sie ein gutes Buch über Computerarchitektur lesen.

Die meisten Assembler arbeiten in zwei Durchgänge , wobei der zweite eine Verlagerung ausgibt oder einen Teil der Ausgabe des ersten Durchgangs korrigiert. Sie verwenden jetzt übliche Parsing-Techniken (lesen Sie also vielleicht The Dragon Book ). P. >

Wie eine ausführbare Datei vom Betriebssystem gestartet wird Kernel (z. B. wie der Systemaufruf execve unter Linux funktioniert ) ist eine andere (und komplexe) Frage. Normalerweise wird ein virtueller Adressraum eingerichtet (im -Prozess , der dies tut execve (2) …) initialisiert dann den internen Prozessstatus neu (einschließlich der Register Benutzermodus ). Ein dynamischer Linker , wie ld-linux.so (8) unter Linux zur Laufzeit beteiligt sein. Lesen Sie ein gutes Buch wie Betriebssystem: Drei einfache Teile . Das Wiki OSDEV enthält ebenfalls nützliche Informationen.

PS. Ihre Frage ist so weit gefasst, dass Sie mehrere Bücher darüber lesen müssen. Ich habe einige (sehr unvollständige) Referenzen gegeben. Sie sollten mehr davon finden.

Kommentare

  • In Bezug auf Objektdateiformate I ‚ Ich empfehle, das von NASM produzierte RDOFF-Format zu betrachten. Dies wurde absichtlich so einfach wie möglich gestaltet und funktioniert dennoch in einer Vielzahl von Situationen. Die NASM-Quelle enthält einen Linker und einen Loader für das Format. (Vollständige Offenlegung – ich habe all dies entworfen und geschrieben)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.