72 Stimmen

Wie schreibt man einen Disassembler?

Ich bin daran interessiert, einen x86-Dissembler für ein Bildungsprojekt zu schreiben.

Die einzige wirkliche Quelle, die ich gefunden habe, ist Spiral Space's, " Wie man einen Disassembler schreibt ". Dies gibt zwar eine gute Beschreibung der verschiedenen Komponenten eines Disassemblers, aber ich bin an detaillierteren Ressourcen interessiert. Ich habe auch einen kurzen Blick geworfen auf NASMs Quellcode, aber das ist ein ziemliches Schwergewicht, von dem man lernen kann.

Mir ist klar, dass eine der größten Herausforderungen bei diesem Projekt der ziemlich große x86-Befehlssatz ist, den ich zu bewältigen habe. Ich bin auch an der grundlegenden Struktur, grundlegenden Disassembler-Links usw. interessiert.

Kann mir jemand detaillierte Ressourcen zum Schreiben eines x86-Disassemblers zeigen?

0 Stimmen

Nicht eine Antwort, sondern die Antwort in stackoverflow.com/questions/82432/ ist auch eine gute Lektüre für diejenigen, die gerade anfangen.

69voto

Adam Rosenfield Punkte 373807

Werfen Sie einen Blick auf Abschnitt 17.2 de la 80386 Referenzhandbuch für Programmierer . Ein Disassembler ist eigentlich nur eine verherrlichte Endliche Maschine . Die Schritte der Demontage sind:

  1. Prüfen, ob das aktuelle Byte ein Befehlspräfixbyte ist ( F3 , F2 , oder F0 ); wenn ja, dann haben Sie eine REP / REPE / REPNE / LOCK Präfix. Weiterschalten zum nächsten Byte.
  2. Prüfen, ob das aktuelle Byte ein Adressgrößenbyte ist ( 67 ). In diesem Fall werden die Adressen im Rest des Befehls im 16-Bit-Modus dekodiert, wenn sie sich im 32-Bit-Modus befinden, oder sie werden im 32-Bit-Modus dekodiert, wenn sie sich im 16-Bit-Modus befinden
  3. Prüfen, ob das aktuelle Byte ein Byte mit Operandengröße ist ( 66 ). Wenn ja, dekodieren Sie unmittelbare Operanden im 16-Bit-Modus, wenn Sie sich im 32-Bit-Modus befinden, oder dekodieren Sie unmittelbare Operanden im 32-Bit-Modus, wenn Sie sich im 16-Bit-Modus befinden
  4. Prüfen, ob das aktuelle Byte ein Segmentüberschreibungsbyte ist ( 2E , 36 , 3E , 26 , 64 , oder 65 ). Wenn dies der Fall ist, verwenden Sie das entsprechende Segmentregister für die Dekodierung von Adressen anstelle des Standard-Segmentregisters.
  5. Das nächste Byte ist der Opcode. Wenn der Opcode 0F dann ist es ein erweiterter Opcode, und das nächste Byte wird als erweiterter Opcode gelesen.
  6. Lesen und dekodieren Sie je nach dem jeweiligen Opcode ein Mod R/M-Byte, ein SIB-Byte (Scale Index Base), eine Verschiebung (0, 1, 2 oder 4 Byte) und/oder einen Sofortwert (0, 1, 2 oder 4 Byte). Die Größe dieser Felder hängt von dem zuvor dekodierten Opcode, der Adressgrößenüberschreibung und der Überschreibung der Operandengröße ab.

Der Opcode gibt Aufschluss über die durchgeführte Operation. Die Argumente des Opcodes können aus den Werten von Mod R/M, SIB, Verschiebung und Sofortwert dekodiert werden. Aufgrund der komplexen Natur von x86 gibt es eine Vielzahl von Möglichkeiten und Sonderfällen. Unter den obigen Links finden Sie eine ausführlichere Erklärung.

25voto

hannson Punkte 4427

Ich würde empfehlen, einige Open-Source-Disassembler auszuprobieren, vorzugsweise pfänden und insbesondere "disOps (Instructions Sets DataBase)" (ctrl+find it on the page).

Die Dokumentation selbst ist voll von interessanten Informationen über Opcodes und Anweisungen.

Zitat von https://code.google.com/p/distorm/wiki/x86_x64_Machine_Code

80x86-Befehl:

Ein 80x86-Befehl wird geteilt in eine Anzahl von Elementen unterteilt:

  1. Befehls-Präfixe, beeinflusst das Verhalten des Befehls in der Operation.
  2. Obligatorisches Präfix, das als Opcode-Byte für SSE-Befehle verwendet wird.
  3. Opcode-Bytes, kann ein oder mehrere Bytes sein (bis zu 3 ganze Bytes).
  4. ModR/M-Byte ist optional und kann manchmal einen Teil der Opcode selbst enthalten.
  5. Das SIB-Byte ist optional und steht für eine komplexe Speicherumleitung Formen.
  6. Die Verdrängung ist fakultativ und ist ein Wert mit einer variablen Größe von Bytes (Byte, Word, Long) und wird als Versatz.
  7. Immediate ist optional und wird als allgemeiner Zahlenwert verwendet, der aus einer variablen Größe von Bytes(byte, word, long).

Das Format sieht wie folgt aus:

/-------------------------------------------------------------------------------------------------------------------------------------------\
|*Prefixes | *Mandatory Prefix | *REX Prefix | Opcode Bytes | *ModR/M | *SIB | *Displacement (1,2 or 4 bytes) | *Immediate (1,2 or 4 bytes) |
\-------------------------------------------------------------------------------------------------------------------------------------------/
* means the element is optional.

Die Datenstrukturen und die Dekodierungsphasen werden erläutert in https://code.google.com/p/distorm/wiki/diStorm_Internals

Zitat:

Dekodierungsphasen

  1. [Präfixe]
  2. [Fetch Opcode]
  3. [Filter-Opcode]
  4. [Operand(en) extrahieren]
  5. [Textformatierung]
  6. [Hex Dump]
  7. [Decodierte Anweisung]

Jeder Schritt wird auch erklärt.


Die ursprünglichen Links werden aus historischen Gründen beibehalten:

http://code.google.com/p/distorm/wiki/x86_x64_Machine_Code et http://code.google.com/p/distorm/wiki/diStorm_Internals

6voto

Charlie Martin Punkte 106684

Beginnen Sie mit einem kleinen Programm, das bereits assembliert wurde und das Ihnen sowohl den generierten Code als auch die Anweisungen liefert. Besorgen Sie sich eine Referenz mit dem Befehlsarchitektur und arbeiten Sie einige der generierten Codes mit der Architekturreferenz von Hand durch. Sie werden feststellen, dass die Anweisungen eine sehr stereotype Struktur haben, nämlich inst op op op mit unterschiedlicher Anzahl von Operanden. Sie müssen lediglich die Hexadezimal- oder Oktal-Darstellung des Codes übersetzen, damit er zu den Anweisungen passt; ein wenig Herumspielen wird dies zeigen.

Dieser automatisierte Prozess ist der Kern eines Disassemblers. Im Idealfall werden Sie wahrscheinlich intern (oder extern, wenn das Programm sehr groß ist) ein n-Array von Befehlsstrukturen erstellen wollen. Dieses Array können Sie dann in die Anweisungen im Assemblerformat übersetzen.

4voto

Joshua Punkte 37898

Sie benötigen eine Tabelle mit Opcodes, aus der Sie laden können.

Die grundlegende Datenstruktur für das Nachschlagen ist ein Trie, aber eine Tabelle reicht auch aus, wenn Sie nicht viel Wert auf Geschwindigkeit legen.

Um den Basis-Opcode-Typ zu ermitteln, beginnt man mit match in der Tabelle.

Es gibt einige Standardverfahren zur Dekodierung von Registerargumenten; es gibt jedoch so viele Sonderfälle, dass die meisten von ihnen einzeln implementiert werden müssen.

Da es sich um eine Bildungsmaßnahme handelt, schauen Sie sich ndisasm an.

2voto

Kasse objdump Quellen - es ist ein großartiges Werkzeug, es enthält viele Opcode-Tabellen und seine Quellen können eine gute Basis für die Erstellung eines eigenen Disassemblers sein.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X