Ich habe es versucht:
;# As you have already noticed, the compiler wants to align the stack
;# pointer on a 16 byte boundary before it pushes anything. That's
;# because certain instructions' memory access needs to be aligned
;# that way.
;# So in order to first save the original offset of esp (+4), it
;# executes the first instruction:
lea ecx,[esp+0x4]
;# Now alignment can happen. Without the previous insn the next one
;# would have made the original esp unrecoverable:
and esp,0xfffffff0
;# Next it pushes the return addresss and creates a stack frame. I
;# assume it now wants to make the stack look like a normal
;# subroutine call:
push DWORD PTR [ecx-0x4]
push ebp
mov ebp,esp
;# Remember that ecx is still the only value that can restore the
;# original esp. Since ecx may be garbled by any subroutine calls,
;# it has to save it somewhere:
push ecx
Dies geschieht, um den Stapel an einer 16-Byte-Grenze ausgerichtet zu halten. Einige Anweisungen erfordern, dass bestimmte Datentypen an einer 16-Byte-Grenze ausgerichtet werden. Um diese Anforderung zu erfüllen, stellt GCC sicher, dass der Stack anfänglich auf 16 Byte ausgerichtet ist, und weist Stack-Speicherplatz in Vielfachen von 16 Byte zu. Dies kann mit der Option -mpreferred-stack-boundary=num gesteuert werden . Wenn Sie -mpreferred-stack-boundary=2 (für ein 2=4-Byte-Alignment) verwenden, wird dieser Alignment-Code nicht generiert, da der Stack immer mindestens 4-Byte-aligned ist. Allerdings könnten Sie dann Probleme bekommen, wenn Ihr Programm Datentypen verwendet, die eine stärkere Ausrichtung erfordern.
Laut gcc-Handbuch:
Auf Pentium und PentiumPro sollten Double- und Long-Double-Werte an einer 8-Byte-Grenze ausgerichtet werden (siehe -malign-double) oder erhebliche Einbußen bei der Laufzeitleistung erleiden. Auf Pentium III funktioniert der Datentyp __m128 der Streaming SIMD Extension (SSE) möglicherweise nicht richtig, wenn er nicht auf 16 Byte ausgerichtet ist.
Um eine ordnungsgemäße Ausrichtung dieser Werte auf dem Stack sicherzustellen, muss die Stack-Grenze so ausgerichtet sein, wie es für alle auf dem Stack gespeicherten Werte erforderlich ist. Außerdem muss jede Funktion so generiert werden, dass sie den Stack ausgerichtet hält. Daher wird das Aufrufen einer Funktion, die mit einer höheren bevorzugten Stapelgrenze kompiliert wurde, von einer Funktion, die mit einer niedrigeren bevorzugten Stapelgrenze kompiliert wurde, höchstwahrscheinlich den Stapel falsch ausrichten. Es wird empfohlen, dass Bibliotheken, die Rückrufe verwenden, immer die Standardeinstellung verwenden.
Diese zusätzliche Ausrichtung verbraucht zusätzlichen Stapelplatz und erhöht im Allgemeinen die Codegröße. Code, der empfindlich auf Stapelplatznutzung reagiert, wie eingebettete Systeme und Betriebssystemkernel, sollte die bevorzugte Ausrichtung möglicherweise auf -mpreferred-stack-boundary=2 reduzieren.
Der lea
lädt den ursprünglichen Stapelzeiger (von vor dem Aufruf von main
) in ecx
, da der Stapelzeiger gerade geändert wird. Dies wird für zwei Zwecke verwendet:
- um auf die Argumente für
main
zuzugreifen Funktion, da sie relativ zum ursprünglichen Stapelzeiger sind - um den Stapelzeiger auf seinen ursprünglichen Wert zurückzusetzen, wenn er von
main
zurückkehrt
lea ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of the main...why ?
and esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ???
push DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ?
push ebp
mov ebp,esp
push ecx ;why is ecx pushed too ??
Selbst wenn jede Anweisung trotz willkürlich ausgerichteter Operanden ohne Geschwindigkeitseinbußen perfekt funktionierte, würde die Ausrichtung die Leistung dennoch steigern. Stellen Sie sich eine Schleife vor, die auf eine 16-Byte-Menge verweist, die gerade zwei Cache-Zeilen überlappt. Um dieses kleine Wchar in den Cache zu laden, müssen zwei ganze Cache-Zeilen entfernt werden, und was ist, wenn Sie sie in derselben Schleife benötigen? Der Cache ist so enorm schneller als RAM, dass die Cache-Leistung immer kritisch ist.
Außerdem gibt es normalerweise einen Geschwindigkeitsnachteil, wenn falsch ausgerichtete Operanden in die Register verschoben werden. Da der Stack neu ausgerichtet wird, müssen wir natürlich die alte Ausrichtung speichern, um Stack-Frames für Parameter zu durchlaufen und zurückzukehren.
ecx ist ein temporäres Register und muss gespeichert werden. Je nach Optimierungsstufe können auch einige der Rahmenverknüpfungsoperationen, die für die Ausführung des Programms nicht unbedingt erforderlich zu sein scheinen, wichtig sein, um eine nachverfolgungsfähige Kette von Rahmen aufzubauen.