Ist es zulässig, eine x64-Funktion mit einem Ein-Byte-Befehl zu beginnen?

8

Laut masm's macamd64.inc, rex_push_reg ,

  

... rex_push_reg muss anstelle von push_reg verwendet werden, wenn es als angezeigt wird   die erste Anweisung in einer Funktion, wie der aufrufende Standard vorschreibt   Diese Funktionen dürfen nicht mit einem einzelnen Byte-Befehl beginnen.

Ich konnte jedoch keine Dokumentation finden, die das ausdrückt. Ist das wahr? Wo ist es dokumentiert? Warum ist das der Fall?

    
tenfour 15.06.2017, 09:12
quelle

1 Antwort

6

Der operative Teil dieser Behauptung scheint der "Rufstandard" zu sein - welcher Aufrufstandard? Der Witz mag alt sein, aber er bleibt angemessen: Das Tolle an Standards ist, dass es so viele gibt, aus denen man wählen kann.

Da Sie in diesem Fall von MASM sprechen, können wir davon ausgehen, dass die Zielplattform Windows ist, also Windows 64-bit Aufrufkonvention würde angenommen, anstatt etwas in der offiziellen AMD64-Spezifikation. Aber wie Sie, kann ich dort nichts finden, was zu dieser Anforderung passt.

Allerdings denke ich , worauf sich dieser Kommentar bezieht, ist der interne Standard von Microsoft, der das Hot-Patching von Systembinärdateien ermöglicht. Mit "Hot Patching" ist die Fähigkeit gemeint, Binärdateien im Speicher dynamisch zu reparieren - z. B. , um ein Systemupdate anzuwenden - ohne dass ein Neustart erforderlich ist.

Die Mindestvoraussetzung dafür ist, dass am Anfang jeder Funktion Platz für eine 2 Byte kurze JMP -Anweisung ist. (Beachten Sie, dass ein kurzer Sprung nur die Ausführung von -128 bis +127 Bytes vom aktuellen Befehlszeiger zulässt, aber das reicht, um zu einem langen Sprung zu verzweigen, der dann zur gepatchten Funktion verzweigt Das wird in der Praxis durch den Befehl "long jump" in das Padding zwischen den Funktionen gepatcht.)

Daher kann eine Funktion nicht mit einem 1-Byte-Befehl beginnen, da ein Hot-Patch möglicherweise dazu führen kann, dass der Befehlszeiger in die Mitte eines Befehls zeigt. (Denken Sie an Multi-Threading-Race-Bedingungen.) Wenn Sie also eine Funktion mit einer Prolog-Anweisung wie PUSH RBP beginnen wollen, die normalerweise nur 1 Byte lang ist, müssen Sie ein 1-Byte-REX-Präfix hinzufügen. Dieses unnötige REX-Präfix wird von der CPU ignoriert und funktioniert im Wesentlichen als 1-Byte-NOP.

In 32-Bit-Builds wurde das Hot-Patching durch den 2-Byte-Befehl MOV EDI, EDI bereitgestellt. Dies kopiert das EDI -Register zu sich selbst, ohne die Flags zu beeinflussen, also ist es effektiv ein NOP.

Bei 32-Bit-Builds müssen Sie den /hotpatch -Schalter ausdrücklich an übergeben der Compiler, um diese Anweisung einzufügen. Bei 64-Bit-Builds verhält sich der Compiler jedoch immer so, als wäre /hotpatch angegeben worden. Daher wird diese Anforderung, dass die erste Anweisung 2 Byte lang ist, effektiv Teil des Plattformstandards.

Also, warum diese komplizierte Regel machen, anstatt nur den Compiler zu Beginn jeder Funktion einen 2-Byte-NOP einfügen zu lassen, wie es bei 32-Bit-Builds der Fall ist? Nun, ich kann es nicht mit Sicherheit sagen, aber ich kann spekulieren. Ein Problem ist, dass MOV EDI, EDI nicht ein NOP auf x64 ist, weil es implizit die oberen 32 Bits des RDI -Registers auf Null setzt. Sie müssen eine andere Anweisung als NOP auswählen, und wenn Sie das einmal getan haben, können Sie das gesamte Geschäft überdenken. Zweitens gibt es (geringe) Performance-Kosten, die Sie für das Vorhandensein dieses NOPs bezahlen, und da die meisten Anweisungen im Long-Modus mindestens 2 Byte lang sind, lohnt es sich kaum, einen sinnlosen NOP-Befehl zu verlangen wenn der Befehl, der normalerweise dort wäre, mit nur wenigen Ausnahmen ausreicht.

    
Cody Gray 15.06.2017, 10:29
quelle

Tags und Links