Ich lerne gerade x86 asm, indem ich den vom Compiler generierten Binärcode untersuche.
Code, der mit dem C ++ - Compiler in Visual Studio 2010 Beta 2 kompiliert wurde.
%Vor%Der folgende beginnt am Einstiegspunkt.
%Vor%Dieser Teil stört mich:
%Vor%Sie können alles notieren und die Werte von EAX und ECX bleiben am Ende gleich. Also, was ist der Sinn dieser Anleitung?
Das Ganze
%Vor% steht für y /= 2
. Sie sehen, ein eigenständiges SAR
würde die Ganzzahl-Division mit Vorzeichen nicht so ausführen, wie es die Autoren des Compilers beabsichtigten. C ++ 98 Standard empfiehlt , dass eine Ganzzahldivision mit Vorzeichen das Ergebnis in Richtung 0 rundet, während SAR
allein auf die negative Unendlichkeit abgerundet wird. (Es ist erlaubt, in Richtung der negativen Unendlichkeit zu runden, die Wahl bleibt der Implementierung überlassen). Um eine Rundung auf 0 für negative Operanden zu implementieren, wird der obige Trick verwendet. Wenn Sie einen vorzeichenlosen Typ anstelle eines vorzeichenbehafteten Typs verwenden, generiert der Compiler nur eine einzige Schiebeanweisung, da das Problem mit der negativen Division nicht auftritt.
Der Trick ist ziemlich einfach: für negative y
Zeichenerweiterung wird ein Muster von 11111...1
in EDX
platziert, was tatsächlich -1
in der Zweierkomplementdarstellung ist. Das folgende SUB
addiert effektiv 1 zu EAX
, wenn der ursprüngliche y
Wert negativ war. Wenn das ursprüngliche y
positiv (oder 0) war, behält das EDX
0
nach der Vorzeichenerweiterung und EAX
bleibt unverändert.
Mit anderen Worten, wenn Sie y /= 2
mit signierten y
schreiben, generiert der Compiler den Code, der mehr wie folgt vorgeht:
oder, besser
%Vor%Beachten Sie, dass der C ++ - Standard nicht erfordert, dass das Ergebnis der Division auf Null gerundet wird, sodass der Compiler auch für signierte Typen nur eine einzige Verschiebung ausführen kann. Normalerweise folgen Compiler jedoch der Empfehlung , um auf Null zu runden (oder bieten eine Option, um das Verhalten zu steuern).
P.S. Ich weiß nicht genau, was der Zweck dieser Anweisung LEA
ist. Es ist in der Tat ein No-Op. Ich vermute jedoch, dass dies nur eine Platzhalteranweisung sein könnte, die in den Code für weiteres Patchen eingefügt wird. Wenn ich mich richtig erinnere, hat der MS-Compiler eine Option, die das Einfügen von Platzhalteranweisungen am Anfang und am Ende jeder Funktion erzwingt. In der Zukunft kann diese Anweisung vom Patcher mit einer Anweisung CALL
oder JMP
überschrieben werden, die den Patch-Code ausführt. Dieses spezifische LEA
wurde nur ausgewählt, weil es den a no-op-Platzhalterbefehl der richtigen Länge erzeugt. Natürlich könnte es etwas ganz anderes sein.
Das lea ebx,[ebx]
ist nur eine NOP-Operation. Sein Zweck ist es, den Anfang der Schleife im Speicher auszurichten, wodurch sie schneller wird. Wie Sie hier sehen können, beginnt der Anfang der Schleife bei der Adresse 0x00401010, die dank dieser Anweisung durch 16 teilbar ist.
Die Operationen CDQ
und SUB EAX,EDX
stellen sicher, dass die Division eine negative Zahl in Richtung Null aufrundet - andernfalls würde SAR sie abrunden und falsche Ergebnisse für negative Zahlen liefern.
Dies beantwortet die Frage nicht wirklich, ist aber ein hilfreicher Hinweis. Anstatt mit der OllyDbg.exe-Sache herumzualbern, können Sie Visual Studio veranlassen, die asm-Datei für Sie zu generieren, die den zusätzlichen Bonus hat, dass sie als Kommentare in den ursprünglichen Quellcode eingefügt werden kann. Dies ist keine große Sache für Ihr aktuelles kleines Projekt, aber wenn Ihr Projekt wächst, können Sie am Ende ziemlich viel Zeit damit verbringen, herauszufinden, welcher Assemblercode mit welchem Quellcode übereinstimmt.
Über die Befehlszeile möchten Sie die Optionen / FAs und / Fa ( MSDN ).
Hier ist ein Teil der Ausgabe für Ihren Beispielcode (ich habe Debugcode kompiliert, so dass die .asm länger ist, aber Sie können dasselbe für Ihren optimierten Code tun):
%Vor%Hoffe das hilft!