Funktionsrückgabe per Assembler in C++
-
Hio!
Ich habe folgenden C++ Code der mir den aktuellen StackPointer ausgeben soll, jedoch verstehe ich dabei nicht genau den verwendeten Assembler-Code:#include <iostream> //Der Code ist ungetestet und ich bin mir nicht sicher //ob der inline-Assembler-Code so korrekt ist, aber mir geht es auch eher um den //Assembler-Code ansich unsigned long int holeStackPointer() { asm{ movl %esp,%eax } } int main() { unsigned long int sp = holeStackPointer(); std :: cout << sp; return 0; }
In das Register eax wird der Inhalt von esp geladen, esp enthält den StackPointer und eax üblicherweise Rechenergebnisse oder?
Wenn ich nun mit C++ etwas per return zurückgebe wird das dann auch in eax abgelegt, sodass die später Zuweisung mit "sp = ..." möglich ist oder welche "Magie" ist da am Werk? Anders wüsste ich gerade nicht, wie ich das in Variable sp bekommen sollte.
Bzw. wo liegt 'sp' in diesem Fall? Stack oder Heap? Auf dem Stack oder, da 'sp' eine feste Größe hat oder?
-
#include <iostream> //#include <conio.h> unsigned long int holeStackPointer() { asm ( "movl %esp,%eax" ); } int main() { std::cout << (unsigned long) (-1) << std::endl; std::cout << std::hex << holeStackPointer() << std::endl; //getch(); }
4294967295
22ff48wo liegt 'sp' in diesem Fall? Stack oder Heap?
Stack.
-
Ich kann mich irren, aber wenn du
mov eax, esp
in einer Funktion ablegst, dann gibt dir das nicht den aktuellen Stack Pointer zurück in der Funktion, die den Call tätigt; vielmehr bekommst du so den Stack der Funktion, in welchem du den Assembly Code drin hast (der Stack ist im übrigen ungültig, lange bevor du inmain
SP speichern kannst). Ist dies, was du willst?Mit EBP gibts mehr Magie für dich:
#include <iostream> void *get_ebp() { __asm { mov eax, [ebp] } } int main() { int foo = 0; int *foo_ptr = (int *)get_ebp() - 1; *foo_ptr = 7; std::wcout << foo << std::endl; }
Und: Lies dies.
-
hm langsam wird es etwas kompliziert..
Naja danke erstmal für den Link, sieht ziemlich nach dem aus was ich suche.
Hm mit der Rückgabe des StackPointers hast du schon recht irgendwie, ich werde das mal ausprobieren - danke für den Tipp!Aber meine Frage vom Anfang ist immer noch nicht genau geklärt:
Wie bekomme ich beim Aufruf von "sp = holeStackPointer()" den StackPointer in 'sp'?
Denn "holeStackPointer()" gibt ja nichts via "return" zurück.
-
Das kann man ändern:
#include <iostream> #include <conio.h> unsigned long int holeStackPointer() { register unsigned long int eax asm("%eax"); asm volatile ( "movl %esp,%eax" ); return eax; } int main() { std::cout << (unsigned long) (-1) << std::endl; std::cout << std::hex << holeStackPointer() << std::endl; getch(); }
-
Hmm seeeehr interessant. Das ist sehr logisch muss ich sagen
Liege ich richtig, wenn ich die Funktionsweise der "holeStackPointer()" Funktion so beschreibe:
Zuerst wird die Variable eax deklariert und durch das Schlüsselwort "register" mit dem Register eax "verbunden"? (d.h. es funktioniert wie ein Pointer, der auf eax zeigt?)
Danach wird entsprechend esp in eax geschrieben, wobei 'volatile' dafür sorgt, dass der Compiler diese für ihn unter Umständen unnötige Zeile nicht einfach wegoptimiert?Erklärt aber immer noch nicht, wieso die von mir gezeigte Funktion auch funktionieren sollte:
unsigned long int holeStackPointer() { asm ( "movl %esp,%eax" ); }
-
das 'register' in
register int eax;
ist lediglich eine bitte an den compiler den wert von eax in einem register abzuspeichern. ob er es dann wirklich tut ist fraglich,vor allem, wenn der compiler optimiert. (zu verhindern mit volatile)
deine Funktion könnte funktionieren, da in eax ja, wie du gesagt hast, der rückgabewert drinnen sein sollte. aber ob das auch wirklich funktioniert, darauf würde ich es nicht anlegen.
Vor allem hat das ganze ja soweiso keinen sinn, da du mit einer funktion ja den stack veränderst, also ist in esp nicht der wert drinnen, den du möchtest....
aber egal. ich würde das ganze soweiso so machen, da dann die ganze diskussion um ausgabewerte nicht nötig ist:
inline unsigned long int holeStackPointer() { unsigned long int res; asm ("movl %%esp, %0" : "=a" (res) : ); return res; }
-
Ahh, nun sehe ich deine andere Frage erst (in den letzten paar Tagen werde ich beim Tippen immer von gewissen Dingen abgelenkt
;)). Warum also funktioniert folgender Code?
unsigned long int holeStackPointer() { asm ( "movl %esp,%eax" ); }
Die Antwort liegt in der Konvention, genauer der x86-
__cdecl
(das ist die C-Konvention auf x86er Systemen) calling convention, welche besagt, dass es so funktioniert. Du verschiebstesp
nacheax
;eax
wiederum ist per Konvention als Register für den Rückgabewert definiert. Wenn du also in C oder auch C++ einreturn foobar;
eingibst, dann resultiert das in einem Code äquivalent zumov eax, foobar
, gefolgt von einem Rücksprung. Diese Konvention wird bei deinem Compiler implizit eingehalten, weshalb er den Rückgabewert als gegeben ansieht, wenn dueax
mutierst. Darum kann man auch dasreturn
-statement weglassen. War das die Frage?
-
Ohne Angabe einer Speicherklasse bei der Deklaration einer Variablen ist diese "auto". Daher gilt für diese Variable der Gültigkeitsbereich des Blocks, in dem sie deklariert wurde. Bei der Deklaration wird die Variable angelegt und bei Blockende automatisch zerstört.
Die Speicherklasse "register" ist identisch mit "auto". Zusätzlich erhält der Compiler den Hinweis, dass diese Variable häufig verwendet wird und daher in einem Register des Prozessors gespeichert werden sollte. Der Compiler muss dieser Anweisung jedoch nicht Folge leisten. Moderne Compiler optimieren diesbezüglich sehr gut, so dass diese Anweisung heute nicht mehr notwendig ist.
Volatile Variablen sind Variablen, deren Inhalt auf eine vom Compiler nicht feststellbare Art und Weise jederzeit geändert werden kann. Beispiele für solche Einflüsse sind das Betriebssystem, die Hardware (interrupts) oder ein gleichzeitig laufender Thread. Durch den Typ-Qualifizierer volatile wird der Compiler angewiesen, keine Optimierungen durchzuführen. Er lässt die entsprechenden Werte bei jedem Zugriff neu aus dem Hauptspeicher laden und sorgt bei Veränderungen dafür, daß die neuen Werte ohne Verzögerung sofort im Hauptspeicher abgelegt werden.