Ich habe das schon oft gemacht und mache das auch weiterhin. In diesem Fall, in dem Ihr primäres Ziel darin besteht, Assembler zu lesen und nicht zu schreiben, gilt dies meines Erachtens.
Schreiben Sie Ihren eigenen Disassembler. Nicht für den Zweck, den nächstgrößeren Disassembler herzustellen, ist dieser ausschließlich für Sie. Ziel ist es, den Befehlssatz zu lernen. Ob ich Assembler auf einer neuen Plattform lerne, mich an Assembler für eine Plattform erinnere, die ich einmal kannte. Beginnen Sie mit nur wenigen Codezeilen, fügen Sie beispielsweise Register hinzu und pingen Sie zwischen dem Zerlegen des Binärausgangs und dem Hinzufügen immer komplizierterer Anweisungen auf der Eingabeseite.
1) Lernen Sie den Befehlssatz für den jeweiligen Prozessor
2) Lernen Sie die Nuancen des Schreibens von Code in Assembler für diesen Prozessor, so dass Sie jedes Opcode-Bit in jedem Befehl wackeln können
3) Sie lernen den Befehlssatz besser als die meisten Ingenieure, die diesen Befehlssatz verwenden, um ihren Lebensunterhalt zu verdienen
In Ihrem Fall gibt es ein paar Probleme. Normalerweise empfehle ich zunächst den ARM-Befehlssatz. Heute werden mehr ARM-basierte Produkte ausgeliefert als alle anderen (einschließlich x86-Computer). Die Wahrscheinlichkeit, dass Sie ARM jetzt verwenden und nicht genügend Assembler kennen, um Startcode oder andere Routinen zu schreiben, die ARM kennen, kann jedoch helfen oder auch nicht, was Sie versuchen. Der zweite und wichtigere Grund für ARM ist, dass die Befehlslängen eine feste Größe haben und ausgerichtet sind. Das Zerlegen von Anweisungen mit variabler Länge wie x86 kann als erstes Projekt ein Albtraum sein. Ziel ist es, den Befehlssatz zu erlernen, um kein Forschungsprojekt zu erstellen. Der dritte ARM ist ein gut gemachter Befehlssatz, Register werden gleich erstellt und haben keine individuellen Spezialnuancen.
Sie müssen also herausfinden, mit welchem Prozessor Sie beginnen möchten. Ich schlage zuerst den msp430 oder ARM vor, dann zuerst den ARM oder dann das Chaos von x86. Unabhängig von der Plattform verfügt jede Plattform, die es wert ist, verwendet zu werden, über Datenblätter oder Referenzhandbücher für Programmierer, die vom Hersteller frei sind und den Befehlssatz sowie die Codierung der Opcodes (die Bits und Bytes der Maschinensprache) enthalten. Um zu lernen, was der Compiler tut und wie man Code schreibt, mit dem der Compiler nicht zu kämpfen hat, ist es gut, einige Befehlssätze zu kennen und zu sehen, wie bei jedem Optimierungssatz mit jedem Compiler bei jeder Optimierung derselbe Code auf hoher Ebene implementiert wird Rahmen. Sie möchten Ihren Code nicht nur optimieren, um festzustellen, dass Sie ihn für einen Compiler / eine Plattform besser, für jeden anderen jedoch viel schlechter gemacht haben.
Oh, zum Zerlegen von Befehlssätzen mit variabler Länge, anstatt einfach am Anfang zu beginnen und jedes 4-Byte-Wort linear durch den Speicher zu zerlegen, wie Sie es mit dem ARM oder alle zwei Bytes wie beim msp430 tun würden (Der msp430 verfügt über Befehle mit variabler Länge, aber Sie können trotzdem durchkommen linear durch den Speicher gehen, wenn Sie an den Einstiegspunkten aus der Interrupt-Vektortabelle beginnen). Für eine variable Länge möchten Sie einen Einstiegspunkt finden, der auf einer Vektortabelle oder dem Wissen darüber basiert, wie der Prozessor startet, und dem Code in der Ausführungsreihenfolge folgen. Sie müssen jeden Befehl vollständig dekodieren, um zu wissen, wie viele Bytes verwendet werden. Wenn der Befehl kein bedingungsloser Zweig ist, nehmen Sie an, dass das nächste Byte nach diesem Befehl ein anderer Befehl ist. Sie müssen auch alle möglichen Zweigadressen speichern und davon ausgehen, dass dies die Startbyte-Adressen für weitere Anweisungen sind. Als ich einmal erfolgreich war, habe ich mehrere Durchgänge durch die Binärdatei gemacht. Beginnend am Einstiegspunkt habe ich dieses Byte als Beginn eines Befehls markiert und dann linear durch den Speicher dekodiert, bis ich einen bedingungslosen Zweig getroffen habe. Alle Verzweigungsziele wurden als Startadressen einer Anweisung markiert. Ich habe mehrere Durchgänge durch die Binärdatei gemacht, bis ich keine neuen Verzweigungsziele gefunden hatte. Wenn Sie zu irgendeinem Zeitpunkt beispielsweise eine 3-Byte-Anweisung finden, aber aus irgendeinem Grund das zweite Byte als Beginn einer Anweisung markiert haben, liegt ein Problem vor. Wenn der Code von einem High-Level-Compiler generiert wurde, sollte dies nur geschehen, wenn der Compiler etwas Böses tut. Wenn der Code einen handgeschriebenen Assembler enthält (wie beispielsweise ein altes Arcade-Spiel), ist es durchaus möglich, dass es bedingte Verzweigungen gibt, die niemals wie r0 = 0 auftreten können, gefolgt von einem Sprung, wenn nicht Null. Möglicherweise müssen Sie diese aus der Binärdatei heraus bearbeiten, um fortzufahren. Für Ihre unmittelbaren Ziele, von denen ich annehme, dass sie auf x86 liegen, glaube ich nicht, dass Sie ein Problem haben werden.
Ich empfehle die gcc-Tools. Mingw32 ist eine einfache Möglichkeit, gcc-Tools unter Windows zu verwenden, wenn x86 Ihr Ziel ist. Wenn nicht, ist mingw32 plus msys eine hervorragende Plattform zum Generieren eines Cross-Compilers aus binutils und gcc-Quellen (im Allgemeinen ziemlich einfach). mingw32 hat einige Vorteile gegenüber Cygwin, wie deutlich schnellere Programme und Sie vermeiden die Hölle der Cygwin-DLL. Mit gcc und binutils können Sie in C oder Assembler schreiben und Ihren Code zerlegen. Es gibt mehr Webseiten als Sie lesen können, die Ihnen zeigen, wie Sie eine oder alle drei Methoden ausführen. Wenn Sie dies mit einem Befehlssatz variabler Länge tun, empfehle ich dringend, einen Werkzeugsatz zu verwenden, der einen Disassembler enthält. Ein Disassembler von Drittanbietern für x86 wird beispielsweise eine Herausforderung sein, da Sie nie wirklich wissen, ob er korrekt zerlegt wurde. Einiges davon hängt auch vom Betriebssystem ab. Ziel ist es, die Module in ein Binärformat zu kompilieren, das Anweisungen zum Markieren von Informationen aus Daten enthält, damit der Disassembler eine genauere Arbeit leisten kann. Ihre andere Wahl für dieses primäre Ziel ist es, ein Tool zu haben, das direkt zu Assembler für Ihre Inspektion kompiliert werden kann, und dann zu hoffen, dass es beim Kompilieren in ein Binärformat dieselben Anweisungen erstellt.
Die kurze (okay etwas kürzere) Antwort auf Ihre Frage. Schreiben Sie einen Disassembler, um einen Befehlssatz zu lernen. Ich würde mit etwas RISCY beginnen und leicht zu lernen wie ARM. Sobald Sie einen Befehlssatz kennen, lassen sich andere, oft in wenigen Stunden, viel einfacher erlernen. Mit dem dritten Befehlssatz können Sie fast sofort mit dem Schreiben von Code beginnen, indem Sie das Datenblatt / Referenzhandbuch für die Syntax verwenden. Alle Prozessoren, die es wert sind, verwendet zu werden, verfügen über ein Datenblatt oder ein Referenzhandbuch, das die Anweisungen bis auf die Bits und Bytes der Opcodes beschreibt. Lernen Sie einen RISC-Prozessor wie ARM und einen CISC wie x86 genug, um ein Gefühl für die Unterschiede zu bekommen, z. B. Register für alles durchlaufen zu müssen oder Operationen direkt im Speicher mit weniger oder keinen Registern ausführen zu können. Drei Operandenanweisungen gegen zwei usw. Während Sie Ihren High-Level-Code optimieren, Kompilieren Sie für mehr als einen Prozessor und vergleichen Sie die Ausgabe. Das Wichtigste, was Sie lernen werden, ist, dass unabhängig davon, wie gut der Code auf hoher Ebene geschrieben ist, die Qualität des Compilers und die getroffenen Optimierungsentscheidungen einen großen Unterschied in den tatsächlichen Anweisungen bewirken. Ich empfehle llvm und gcc (mit binutils), keine produzierenToller Code, aber sie sind Multi-Plattform und Multi-Target und beide haben Optimierer. Und beide sind kostenlos und Sie können problemlos Cross-Compiler aus Quellen für verschiedene Zielprozessoren erstellen.