Verwenden Sie dieses Beispiel aus Wikipedia, in dem DrawSquare () DrawLine () aufruft.
(Beachten Sie, dass dieses Diagramm unten hohe Adressen und oben niedrige Adressen enthält.)
Könnte mir jemand erklären was ebp
und esp
in diesem Zusammenhang sind?
Nach allem, was ich sehe, würde der Stapelzeiger immer auf den oberen Rand des Stapels und der Basiszeiger auf den Anfang der aktuellen Funktion zeigen. Oder was?
edit: Ich meine das im Kontext von Windows-Programmen
edit2: Und wie funktioniert das eip
auch?
edit3: Ich habe den folgenden Code von MSVC ++:
var_C= dword ptr -0Ch
var_8= dword ptr -8
var_4= dword ptr -4
hInstance= dword ptr 8
hPrevInstance= dword ptr 0Ch
lpCmdLine= dword ptr 10h
nShowCmd= dword ptr 14h
Alle scheinen Dwords zu sein und benötigen jeweils 4 Bytes. Ich kann also sehen, dass es eine Lücke von hInstance zu var_4 von 4 Bytes gibt. Was sind Sie? Ich nehme an, es ist die Absenderadresse, wie auf dem Bild von Wikipedia zu sehen ist.
(Anmerkung des Herausgebers: Ein langes Zitat aus Michaels Antwort wurde entfernt, das nicht in die Frage gehört, aber eine Folgefrage wurde in bearbeitet.):
Dies liegt daran, dass der Ablauf des Funktionsaufrufs wie folgt ist:
* Push parameters (hInstance, etc.)
* Call function, which pushes return address
* Push ebp
* Allocate space for locals
Meine Frage (zuletzt hoffe ich!) Ist nun, was genau passiert, sobald ich die Argumente der Funktion, die ich aufrufen möchte, bis zum Ende des Prologs platziere. Ich möchte wissen, wie sich das Ebbe, insbesondere in diesen Momenten entwickelt (ich habe bereits verstanden, wie der Prolog funktioniert, ich möchte nur wissen, was passiert, nachdem ich die Argumente auf den Stapel geschoben habe und vor dem Prolog).
Antworten:
esp
ist, wie Sie sagen, die Spitze des Stapels.ebp
wird normalerweise zuesp
Beginn der Funktion eingestellt. Auf Funktionsparameter und lokale Variablen wird zugegriffen, indem ein konstanter Versatz von addiert bzw. subtrahiert wirdebp
. Alle x86-Aufrufkonventionen geltenebp
als über Funktionsaufrufe hinweg erhalten.ebp
selbst zeigt tatsächlich auf den Basiszeiger des vorherigen Frames, wodurch das Stapeln in einem Debugger und das Anzeigen der lokalen Variablen anderer Frames funktioniert.Die meisten Funktionsprologe sehen ungefähr so aus:
Dann später in der Funktion können Sie Code wie haben (vorausgesetzt, beide lokalen Variablen sind 4 Bytes)
Die FPO- oder Frame-Pointer-Auslassungsoptimierung, die Sie aktivieren können, beseitigt dies tatsächlich und verwendet sie
ebp
als ein anderes Register und greift direkt auf Lokale zuesp
. Dies erschwert jedoch das Debuggen etwas, da der Debugger nicht mehr direkt auf die Stack-Frames früherer Funktionsaufrufe zugreifen kann.BEARBEITEN:
Für Ihre aktualisierte Frage fehlen zwei Einträge im Stapel:
Dies liegt daran, dass der Ablauf des Funktionsaufrufs wie folgt ist:
hInstance
usw.)ebp
quelle
ESP ist der aktuelle Stapelzeiger, der sich jedes Mal ändert, wenn ein Wort oder eine Adresse auf den Stapel geschoben oder von diesem entfernt wird. EBP ist eine bequemere Möglichkeit für den Compiler, die Parameter und lokalen Variablen einer Funktion zu verfolgen, als das ESP direkt zu verwenden.
Im Allgemeinen (und dies kann von Compiler zu Compiler variieren) werden alle Argumente für eine aufgerufene Funktion von der aufrufenden Funktion auf den Stapel verschoben (normalerweise in umgekehrter Reihenfolge, in der sie im Funktionsprototyp deklariert sind, dies variiert jedoch). . Dann wird die Funktion aufgerufen, die die Rücksprungadresse (EIP) auf den Stapel schiebt.
Beim Aufrufen der Funktion wird der alte EBP-Wert auf den Stapel verschoben und EBP auf den Wert von ESP gesetzt. Dann wird der ESP dekrementiert (weil der Stapel im Speicher nach unten wächst), um Platz für die lokalen Variablen und Temporäre der Funktion zuzuweisen. Ab diesem Zeitpunkt befinden sich während der Ausführung der Funktion die Argumente für die Funktion bei positiven Offsets von EBP auf dem Stapel (da sie vor dem Funktionsaufruf verschoben wurden), und die lokalen Variablen befinden sich bei negativen Offsets von EBP (weil sie nach dem Funktionseintrag auf dem Stapel zugeordnet wurden). Aus diesem Grund wird das EBP als Frame-Zeiger bezeichnet , da es auf die Mitte des Funktionsaufruf-Frames zeigt .
Beim Beenden muss die Funktion lediglich ESP auf den Wert von EBP setzen (wodurch die lokalen Variablen vom Stapel freigegeben werden und der Eintrag EBP oben auf dem Stapel verfügbar gemacht wird) und dann der alte EBP-Wert vom Stapel entfernt werden. und dann kehrt die Funktion zurück (die Rücksprungadresse wird in EIP eingefügt).
Bei der Rückkehr zur aufrufenden Funktion kann ESP erhöht werden, um die Funktionsargumente zu entfernen, die unmittelbar vor dem Aufrufen der anderen Funktion auf den Stapel übertragen wurden. Zu diesem Zeitpunkt befindet sich der Stapel wieder in dem Zustand, in dem er sich vor dem Aufrufen der aufgerufenen Funktion befand.
quelle
Du hast es richtig. Der Stapelzeiger zeigt auf das oberste Element auf dem Stapel, und der Basiszeiger zeigt auf die "vorherige" Oberseite des Stapels, bevor die Funktion aufgerufen wurde.
Wenn Sie eine Funktion aufrufen, wird jede lokale Variable auf dem Stapel gespeichert und der Stapelzeiger wird inkrementiert. Wenn Sie von der Funktion zurückkehren, verlassen alle lokalen Variablen auf dem Stapel den Gültigkeitsbereich. Sie tun dies, indem Sie den Stapelzeiger auf den Basiszeiger zurücksetzen (der vor dem Funktionsaufruf die "vorherige" Spitze war).
Die Speicherzuweisung auf diese Weise ist sehr , sehr schnell und effizient.
quelle
BEARBEITEN: Eine bessere Beschreibung finden Sie unter x86-Demontage / -Funktionen und Stapelrahmen in einem WikiBook über x86-Assembly. Ich versuche, einige Informationen hinzuzufügen, die Sie möglicherweise für die Verwendung von Visual Studio interessieren.
Das Speichern des Aufrufer-EBP als erste lokale Variable wird als Standard-Stack-Frame bezeichnet und kann für fast alle Aufrufkonventionen unter Windows verwendet werden. Es gibt Unterschiede, ob der Aufrufer oder der Angerufene die übergebenen Parameter freigeben und welche Parameter in Registern übergeben werden, diese sind jedoch orthogonal zum Standard-Stapelrahmenproblem.
Wenn Sie über Windows-Programme sprechen, verwenden Sie möglicherweise Visual Studio, um Ihren C ++ - Code zu kompilieren. Beachten Sie, dass Microsoft eine Optimierung namens Frame Pointer Omission verwendet, die es nahezu unmöglich macht, den Stapel zu durchlaufen, ohne die dbghlp-Bibliothek und die PDB-Datei für die ausführbare Datei zu verwenden.
Diese Frame Pointer Omission bedeutet, dass der Compiler das alte EBP nicht an einem Standardspeicherort speichert und das EBP-Register für etwas anderes verwendet. Daher fällt es Ihnen schwer, die EIP des Aufrufers zu finden, ohne zu wissen, wie viel Speicherplatz die lokalen Variablen für eine bestimmte Funktion benötigen. Natürlich bietet Microsoft eine API, mit der Sie auch in diesem Fall Stack-Walks durchführen können. Das Nachschlagen der Symboltabellendatenbank in PDB-Dateien dauert jedoch für einige Anwendungsfälle zu lange.
Um FPO in Ihren Kompilierungseinheiten zu vermeiden, müssen Sie die Verwendung von / O2 vermeiden oder / Oy- explizit zu den C ++ - Kompilierungsflags in Ihren Projekten hinzufügen. Sie verknüpfen wahrscheinlich mit der C- oder C ++ - Laufzeit, die FPO in der Release-Konfiguration verwendet, sodass Sie Schwierigkeiten haben, Stack-Walks ohne die Datei dbghlp.dll durchzuführen.
quelle
Zunächst zeigt der Stapelzeiger auf den unteren Rand des Stapels, da x86-Stapel von hohen Adresswerten zu niedrigeren Adresswerten aufgebaut werden. Der Stapelzeiger ist der Punkt, an dem der nächste Aufruf (oder Aufruf) den nächsten Wert platziert. Die Operation entspricht der C / C ++ - Anweisung:
Der Basiszeiger befindet sich oben im aktuellen Frame. ebp verweist in der Regel auf Ihre Absenderadresse. ebp + 4 zeigt auf den ersten Parameter Ihrer Funktion (oder auf diesen Wert einer Klassenmethode). ebp-4 zeigt auf die erste lokale Variable Ihrer Funktion, normalerweise den alten Wert von ebp, damit Sie den vorherigen Frame-Zeiger wiederherstellen können.
quelle
Es ist lange her, dass ich Assembly-Programmierung durchgeführt habe, aber dieser Link könnte nützlich sein ...
Der Prozessor verfügt über eine Sammlung von Registern, in denen Daten gespeichert werden. Einige davon sind direkte Werte, während andere auf einen Bereich im RAM verweisen. Register werden in der Regel für bestimmte Aktionen verwendet, und jeder Operand in der Assembly benötigt eine bestimmte Datenmenge in bestimmten Registern.
Der Stapelzeiger wird meistens verwendet, wenn Sie andere Prozeduren aufrufen. Bei modernen Compilern wird eine Reihe von Daten zuerst auf dem Stapel abgelegt, gefolgt von der Rücksprungadresse, damit das System weiß, wohin es zurückkehren soll, sobald es aufgefordert wird, zurückzukehren. Der Stapelzeiger zeigt auf die nächste Stelle, an der neue Daten auf den Stapel übertragen werden können, wo sie verbleiben, bis sie wieder zurückspringen.
Basisregister oder Segmentregister zeigen nur auf den Adressraum einer großen Datenmenge. In Kombination mit einem zweiten Regiser teilt der Basiszeiger den Speicher in große Blöcke auf, während das zweite Register auf ein Element in diesem Block zeigt. Basiszeiger zeigen daher auf die Basis von Datenblöcken.
Beachten Sie, dass Assembly sehr CPU-spezifisch ist. Die Seite, auf die ich verlinkt habe, enthält Informationen zu verschiedenen CPU-Typen.
quelle
Bearbeiten Ja, das ist meistens falsch. Es beschreibt etwas ganz anderes, falls jemand interessiert ist :)
Ja, der Stapelzeiger zeigt auf die Oberseite des Stapels (ob dies die erste leere oder die letzte volle Stapelposition ist, bei der ich mir nicht sicher bin). Der Basiszeiger zeigt auf den Speicherort des Befehls, der ausgeführt wird. Dies ist auf der Ebene der Opcodes - die grundlegendste Anweisung, die Sie auf einem Computer erhalten können. Jeder Opcode und seine Parameter werden an einem Speicherort gespeichert. Eine C- oder C ++ - oder C # -Zeile kann in einen Opcode oder eine Folge von zwei oder mehr übersetzt werden, je nachdem, wie komplex sie ist. Diese werden nacheinander in den Programmspeicher geschrieben und ausgeführt. Unter normalen Umständen wird der Basiszeiger um einen Befehl erhöht. Für die Programmsteuerung (GOTO, IF usw.) kann sie mehrmals erhöht oder einfach durch die nächste Speicheradresse ersetzt werden.
In diesem Zusammenhang werden die Funktionen unter einer bestimmten Adresse im Programmspeicher gespeichert. Wenn die Funktion aufgerufen wird, werden bestimmte Informationen auf den Stapel übertragen, mit denen das Programm feststellen kann, wo sich die Funktion befindet, sowie die Parameter für die Funktion. Anschließend wird die Adresse der Funktion im Programmspeicher in den Speicher verschoben Basiszeiger. Beim nächsten Taktzyklus beginnt der Computer, Anweisungen von dieser Speicheradresse auszuführen. Dann kehrt es irgendwann nach der Anweisung, die die Funktion aufgerufen hat, zum Speicherort zurück und fährt von dort fort.
quelle
esp steht für "Extended Stack Pointer" ..... ebp für "Something Base Pointer" .... und eip für "Something Instruction Pointer" ...... Der Stack Pointer zeigt auf die Offset-Adresse des Stack-Segments . Der Basiszeiger zeigt auf die Versatzadresse des zusätzlichen Segments. Der Anweisungszeiger zeigt auf die Versatzadresse des Codesegments. Nun zu den Segmenten ... es handelt sich um kleine 64-KB-Bereiche des Prozessorspeicherbereichs ..... Dieser Prozess wird als Speichersegmentierung bezeichnet. Ich hoffe dieser Beitrag war hilfreich.
quelle