Was befindet sich in einer .wasm-Datei? Einführung in wasm-decompile

Wir verfügen über viele Compiler und andere Tools, um WASM-Dateien zu erstellen und damit zu arbeiten. Die Anzahl dieser Tools wächst ständig. Manchmal müssen Sie in die .wasm-Datei schauen und herausfinden, was sich darin befindet. Vielleicht sind Sie der Entwickler eines der Wasm-Tools, oder Sie sind ein Programmierer, der Code schreibt, der für die Konvertierung in Wasm entwickelt wurde, und der daran interessiert ist, wie er sich in den Code verwandelt. Ein solches Interesse kann beispielsweise durch Leistungsüberlegungen ausgelöst werden.



Das Problem ist, dass die .wasm-Dateien ziemlich einfachen Code enthalten, der dem echten Assembler-Code sehr ähnlich sieht. Insbesondere werden im Gegensatz zur JVM alle Datenstrukturen in Gruppen von Lade- / Speicheroperationen kompiliert und nicht in etwas, das eindeutige Klassen- und Feldnamen hat. Compiler wie LLVM können den Eingabecode so ändern, dass das, was sie erhalten, nicht in die Nähe kommt. 

Was ist mit demjenigen, der eine .wasm-Datei nehmen möchte, um herauszufinden, was darin passiert?

Zerlegen oder ... dekompilieren?


Sie können Tools wie wasm2wat verwenden, um .wasm-Dateien in .wat-Dateien zu konvertieren, die eine Standardtextdarstellung von Wasm-Code enthalten (dies ist Teil des WABT-Toolkits ). Die Ergebnisse dieser Konvertierung sind sehr genau, aber das Lesen des resultierenden Codes ist nicht besonders praktisch.

Hier ist zum Beispiel eine einfache Funktion, die in C geschrieben ist:

typedef struct { float x, y, z; } vec3;

float dot(const vec3 *a, const vec3 *b) {
    return a->x * b->x +
           a->y * b->y +
           a->z * b->z;
}

Der Code wird in einer Datei gespeichert dot.c.

Wir verwenden den folgenden Befehl:

clang dot.c -c -target wasm32 -O2

Um zu konvertieren, was mit einer .wat-Datei passiert ist, wenden wir den folgenden Befehl an:

wasm2wat -f dot.o

Folgendes wird es uns geben:

(func $dot (type 0) (param i32 i32) (result f32)
  (f32.add
    (f32.add
      (f32.mul
        (f32.load
          (local.get 0))
        (f32.load
          (local.get 1)))
      (f32.mul
        (f32.load offset=4
          (local.get 0))
        (f32.load offset=4
          (local.get 1))))
    (f32.mul
      (f32.load offset=8
        (local.get 0))
      (f32.load offset=8
        (local.get 1))))))

Der Code ist klein, aber aus vielen Gründen äußerst schwer zu lesen. Abgesehen von der Tatsache, dass Ausdrücke hier nicht verwendet werden und dass sie im Großen und Ganzen ziemlich ausführlich aussehen, ist es nicht einfach, die Datenstrukturen zu verstehen, die in Form von Befehlen zum Laden von Daten aus dem Speicher dargestellt werden. Stellen Sie sich nun vor, Sie müssten einen solchen Code mit einer viel größeren Größe analysieren. Eine solche Analyse wird eine sehr schwierige Aufgabe sein.

Lassen Sie uns versuchen, anstatt wasm2wat zu verwenden, den folgenden Befehl auszuführen:

wasm-decompile dot.o

Folgendes wird sie uns geben:

function dot(a:{ a:float, b:float, c:float },
             b:{ a:float, b:float, c:float }):float {
  return a.a * b.a + a.b * b.b + a.c * b.c
}

Es sieht schon viel besser aus. Der Dekompiler verwendet nicht nur Ausdrücke, die an eine Ihnen bereits bekannte Programmiersprache erinnern, sondern analysiert auch Befehle, die auf die Arbeit mit dem Speicher abzielen, und versucht, die durch diese Befehle dargestellten Datenstrukturen neu zu erstellen. Das System kommentiert dann jede Variable, die als Zeiger mit einer integrierten Strukturdeklaration verwendet wird. Der Dekompiler erstellt keine benannte Strukturdeklaration, da er nicht weiß, ob Strukturen, die jeweils 3 Gleitkommawerte verwenden, etwas gemeinsam haben.

Wie Sie sehen können, erwiesen sich die Ergebnisse der Dekompilierung als verständlicher als die Ergebnisse der Demontage.

In welcher Sprache ist der vom Dekompiler geschriebene Code geschrieben?


Das wasm-decompile-Tool gibt den Code aus und versucht, diesen Code wie eine Art „durchschnittliche“ Programmiersprache aussehen zu lassen. Gleichzeitig versucht dieses Tool, nicht zu weit von Wasm entfernt zu sein.

Das erste Ziel von wasm-decompiler war es, lesbaren Code zu erstellen. Das heißt - ein solcher Code, mit dem der Leser leicht verstehen kann, was in der dekompilierten WASM-Datei geschieht. Der zweite Zweck dieses Tools besteht darin, die genaueste Darstellung der WASM-Datei bereitzustellen, indem Code generiert wird, der vollständig darstellt, was in der Quelldatei geschieht. Offensichtlich stimmen diese Ziele bei weitem nicht immer gut überein.

Was wasm-decompiler ausgibt, war ursprünglich nicht als Code gedacht, der eine echte Programmiersprache darstellt. Es gibt derzeit keine Möglichkeit, diesen Code in Wasm zu kompilieren.

Befehle zum Laden und Speichern von Daten


Wie oben gezeigt, sucht wasm-decompile nach Lade- und Speicherbefehlen, die einem bestimmten Zeiger zugeordnet sind. Wenn diese Befehle eine fortlaufende Sequenz bilden, zeigt der Dekompiler eine der "integrierten" Datenstrukturdeklarationen an.

Wenn nicht auf alle „Felder“ zugegriffen wurde, kann der Dekompiler die Struktur nicht zuverlässig von einer bestimmten Abfolge von Operationen mit Arbeit mit dem Speicher unterscheiden. In diesem Fall verwendet wasm-decompile die Fallback-Option und verwendet einfachere Typen wie float_ptr(wenn die Typen gleich sind) oder generiert im schlimmsten Fall Code, der veranschaulicht, wie mit einem Array gearbeitet wird, wie z o[2]:int. Ein solcher Code sagt uns, dass er oauf Elemente des Typs verweist int, und wir wenden uns dem dritten solchen Element zu.

Diese letzte Situation tritt viel häufiger auf, als Sie vielleicht denken, da sich lokale Wasm-Funktionen eher auf die Verwendung von Registern als auf Variablen konzentrieren. Infolgedessen kann im optimierten Code derselbe Zeiger verwendet werden, um mit völlig unabhängigen Objekten zu arbeiten.

Der Dekompiler sucht einen intelligenten Ansatz für die Indizierung und kann Muster wie identifizieren (base + (index << 2))[0]:int. Die Quelle solcher Muster sind die üblichen Indizierungsoperationen für C, beispielsweise base[index]wenn sie baseauf einen 4-Byte-Typ verweisen. Dies ist im Code sehr häufig, da Wasm beim Laden und Speichern von Datenbefehlen nur Offsets unterstützt, die als Konstanten definiert sind. In dem durch wasm-decompile generierten Code werden solche Konstrukte in type konvertiert base[index]:int.

Außerdem weiß der Dekompiler, wann absolute Adressen auf einen Datenabschnitt verweisen.

Programmablaufsteuerung


Wenn wir über Kontrollkonstrukte sprechen, ist das bekannteste unter ihnen das Wenn-Dann-Wasm-Konstrukt, das sich in if (cond) { A } else { B }die Tatsache verwandelt , dass ein solches Konstrukt in Wasm einen Wert zurückgeben kann, so dass es auch einen ternären Operator darstellen kann, wie cond ? A : Bes in einigen der Fall ist Sprachen.

Andere wasm Kontrollstrukturen sind blockbasierten blockund loopsowie Übergänge br, br_ifund br_table. Der Dekompiler versucht, diesen Strukturen so nahe wie möglich zu kommen. Er versucht nicht, Konstrukte neu zu erstellen, während / für / wechseln, die als Grundlage für sie dienen könnten. Tatsache ist, dass sich dieser Ansatz bei der Verarbeitung von optimiertem Code besser zeigt. Zum Beispiel ein herkömmliches Designloop könnte in dem von wasm-decompile zurückgegebenen Code folgendermaßen aussehen:

loop A {
  //    .
  if (cond) continue A;
}

Hier Aist eine Bezeichnung, mit der Sie verschachtelte Strukturen ineinander erstellen können loop. Die Tatsache , dass es Befehle ifund continueverwendet , um den Zyklus zu steuern aliens aussehen kann etwas an , während Schleifen, aber sie entsprechen die wasm-Konstruktion br_if.

Blöcke werden auf ähnliche Weise erstellt, aber hier stehen die Bedingungen am Anfang und nicht am Ende:

block {
  if (cond) break;
  //    .
}

Das Ergebnis der Dekompilierung des if-then-Konstrukts wird hier gezeigt. In zukünftigen Versionen des Dekompilers wird wahrscheinlich anstelle eines solchen Codes, soweit möglich, ein vertrauteres Wenn-Dann-Konstrukt gebildet.

Das ungewöhnlichste Wasm-Tool zur Steuerung des Programmflusses ist br_table. Dieses Tool ist eine Art switch-Anweisung, außer dass es Inline-Blöcke verwendet. All dies erschwert das Lesen von Code. Der Dekompiler vereinfacht die Struktur solcher Strukturen und bemüht sich, ihre Wahrnehmung ein wenig zu erleichtern:

br_table[A, B, C, ..D](a);
label A:
return 0;
label B:
return 1;
label C:
return 2;
label D:

Dies erinnert an die Verwendung switchfür die Analyse, awenn die Standardoption ist D.

Andere interessante Funktionen


Hier sind einige weitere Funktionen von wasm-decompile:

  • , . C++-.
  • , , , . . , .
  • .
  • Wasm-, . , wasm-decompile , , , .
  • , ( , C- ).

 


Das Dekompilieren von Wasm-Code ist eine Aufgabe, die viel komplizierter ist als beispielsweise das Dekompilieren von JVM-Bytecode.

Der Bytecode ist nicht optimiert, dh er gibt die Struktur des Quellcodes ziemlich genau wieder. Gleichzeitig verwendet der Bytecode Verweise auf eindeutige Klassen und nicht auf Speicherbereiche, obwohl dieser Code möglicherweise nicht die ursprünglichen Namen enthält.

Im Gegensatz zum JVM-Bytecode wird der Code, der in WASM-Dateien gelangt, von LLVM stark optimiert. Infolgedessen verliert ein solcher Code häufig den größten Teil seiner ursprünglichen Struktur. Der Ausgabecode unterscheidet sich stark von dem, was der Programmierer schreiben würde. Dies erschwert die Dekompilierung von Wasm-Code erheblich mit der Ausgabe von Ergebnissen, die Programmierern echte Vorteile bringen können. Dies bedeutet jedoch nicht, dass wir uns nicht bemühen sollten, dieses Problem zu lösen!

Zusammenfassung


Wenn Sie sich für das Thema Wasm-Code-Dekompilierung interessieren, können Sie dieses Thema am besten verstehen, indem Sie Ihr eigenes .wasm-Projekt erstellen und dekompilieren! Außerdem finden Sie hier detailliertere Anleitungen zum Wasm-Dekompilieren. Der Dekompilercode befindet sich in den Dateien dieses Repositorys, deren Namen mit beginnen decompile(wenn Sie möchten, nehmen Sie an der Arbeit am Dekompiler teil). Hier finden Sie Tests, die zusätzliche Beispiele für Unterschiede zwischen .wat-Dateien und Dekompilierungsergebnissen zeigen.

Und mit welchen Tools recherchieren Sie .wasm-Dateien?

, , iPhone. , .


All Articles