Frage zu erzeugtem assembler code



  • @Santos

    Wenn du Dich beim Lernen auch vom C bzw. C++ Code zum Assembler Code vohangeln willst, würde ich die Compileroptimierungen ausschalten und den Release Code analysieren. Welchen Compiler nimmst du?

    Bist du sicher, das du alles gepostet hast? Ich vermisse Irgendwo ein :
    mov XX, 0x0A

    , wo Dein "int temp = 10" initialisiert wird

    @masterofx32

    super Erklärung, das ist ja fast was für die FAQ.

    Aber wo kommen diese Aufrufe her:

    0040D7A9   push        ebx
    0040D7AA   push        esi
    0040D7AB   push        edi
    

    her? Ich hab das mir die gleiche Funktion auch mal im Disassembler angeschaut (VS, optimierung deactiviert, Release) und dort findet sich nichts dergleichen.
    vor allem bei -> push ebx : Der Inhalt von ebx ist doch an dieser Stelle undefiniert 😕

    Hast du einen Tip, wie man bei so excessivem Stackgebrauch noch durchsieht? Ich hab bei größeren Funktionen derzeit das Problem, das es doch ganz schön schwer ist noch durchzusehen, was gerade an welcher Stelle des Stacks liegt.


  • Mod

    TheBigW schrieb:

    Aber wo kommen diese Aufrufe her:

    0040D7A9   push        ebx
    0040D7AA   push        esi
    0040D7AB   push        edi
    

    her? Ich hab das mir die gleiche Funktion auch mal im Disassembler angeschaut (VS, optimierung deactiviert, Release) und dort findet sich nichts dergleichen.
    vor allem bei -> push ebx : Der Inhalt von ebx ist doch an dieser Stelle undefiniert 😕

    das ABI verlangt, dass ebx,esi,edi durch einen funktionsaufruf nicht verändert werden. falls die aufgerufene funktion diese register allerdings selbst benötigt, muss sie den alten registerinhalt nat. sichern um sie vor dem rücksprung wiederherstellen zu können.

    Hast du einen Tip, wie man bei so excessivem Stackgebrauch noch durchsieht? Ich hab bei größeren Funktionen derzeit das Problem, das es doch ganz schön schwer ist noch durchzusehen, was gerade an welcher Stelle des Stacks liegt.

    im watch-fenster zeigt er dir auch die adresse von variablen an. ausserdem kannst du z.b. im memory fenster direkt eine variable angeben, er wird dann direkt zu deren adresse springen.



  • Danke für die Erklärung, das macht einiges klar. Hätte ich mir die Funktion bis zu Ende angeschaut wäre mir sicher auch das pop ebx aufgefallen 🙂

    im watch-fenster zeigt er dir auch die adresse von variablen an. ausserdem kannst du z.b. im memory fenster direkt eine variable angeben, er wird dann direkt zu deren adresse springen.

    geht leider nicht: ich kann bei meinem derzeitigen Projekt nur "offline" disasembeln, d.h. ich kann mir zur Laufzeit nix anschauen 😞
    Wenn es Dich interessiert warum, kann ich Dich gerne anmailen. Keine Angst, ist nix verbotenes.

    [Edit]

    Na, dann gleich noch eine Frage. Folgendes müßte nach meinem Verständnis ja dann identisch sein (davon abgesehen, das ich den Stack nicht aufräume danach..) :

    void StackTest( int iValOne, int iValTwo, int iVal3, int iVal4 )
    {
    	int dummy = 0;
    }
    
    //(1)
    push 0x05
    push 0x10
    push 0x15
    push 0x20
    call StackTest
    //(2)
    sub esp, 0x10
    mov [esp], 0x05
    mov [esp+4], 0x10
    mov [esp+8], 0x15
    mov [esp+0x0C], 0x20
    call StackTest
    

    Variante 1 funktioniert (logisch), Variante 2 funktioniert irgendwie nur mit einem Argument...

    Hintergrund ist, das mir öfter schon Funktionen über den Weg gelaufen sind, wo die Anzahl der push's vor dem function-call nicht mit der Anzahl der Argumente, die die Funktion erwartet überein stimmt. Wie könnte ich mir das sonst erklären?

    [\Edit]



  • Hab es jetzt nochmal umgestrickt und es funktioniert so auch mit Variante 2

    sub esp, 0x10
    mov DWORD PTR[esp], 0x05
    mov DWORD PTR[esp+4], 0x10
    mov DWORD PTR[esp+8], 0x15
    mov DWORD PTR[esp+0x0C], 0x20
    call StackTest
    

    D.h., wenn ich Variablen auf dem Stack liegen lasse, kann ich sie problemlos nachfolgenden Funktionsaufrufen auch als Parameter zur Verfügung stellen, ohne nochmal pushen zu müssen.
    Das macht allerding das disassembeln nicht leichter - jetzt muß ich wohl wirklich mit Papier und Stift den Stack gedanklich mit malen 😞

    [Edit]
    Habe gerade nochmal weiter gemacht bei der Klärung der Funktionsaufrufe mit zu wenig Argumenten.
    Vor dem Funktionsaufruf passiert ein sub esp, 14h. Es werden also 20 BYTE auf dem Stack zur Verfügung gestellt. Diese werden aber nirgendwo vor dem Funktionsaufruf initialisiert. Ihr Inhalt ist doch dann undefiniert : was für einen Sinn macht das dann?
    [\Edit]



  • [quote="masterofx32"]

    hermes schrieb:

    allokieren möchte.
    Müsste man dann nicht eigentlich nicht zum Stackpointer 0x444 addieren anstatt zu subtrahieren?

    Nein, der Stack "wächst" negativ im Speicher also von den hohen Adressen zu den niedrigen Adressen.

    Genau von oben nach unten, ich habe 0x100 Bytes für den Stack reserviert und einen Stackpointer-Wert von 0xff, dann wäre bei 0x00 Schluss.
    Um zusätzlich 10 Byte Stack zu allocieren müsste ich doch zum Stackpointer
    10 Byte addieren um auf die gewünschten Stack von 0xff+10Byte zu kommen.
    Subtrahieren vom Stack würde den Stack doch verkleinern.
    Aber es werden doch 444Byte zusätzlich Stack benötigt.



  • TheBigW schrieb:

    Hab es jetzt nochmal umgestrickt und es funktioniert so auch mit Variante 2

    sub esp, 0x10
    mov DWORD PTR[esp], 0x05
    mov DWORD PTR[esp+4], 0x10
    mov DWORD PTR[esp+8], 0x15
    mov DWORD PTR[esp+0x0C], 0x20
    call StackTest
    

    Aber die Reihenfolge ist noch falsch. Das, was zuletzt gepusht wurde, befindet sich ja oben auf dem Stack also direkt am Stackpointer. Du müsstest die 0x20 also nach esp schieben, die 0x05 nach esp+0x0C, damit die Reihenfolge der von deiner Variante 1 entspricht. Die Parameterübergabe ist übrigens von rechts nach links. iVal4 wird also zuerst gepusht.

    Hast du für die Angelegenheit mit den nicht initialisierten Parametern auf dem Stack ein konkretes Beispiel mit Quellcode? Sind diese Argumente überhaupt erforderlich bei der Art, wie diese Funktion genutzt wird?



  • hermes schrieb:

    masterofx32 schrieb:

    hermes schrieb:

    allokieren möchte.
    Müsste man dann nicht eigentlich nicht zum Stackpointer 0x444 addieren anstatt zu subtrahieren?

    Nein, der Stack "wächst" negativ im Speicher also von den hohen Adressen zu den niedrigen Adressen.

    Genau von oben nach unten, ich habe 0x100 Bytes für den Stack reserviert und einen Stackpointer-Wert von 0xff, dann wäre bei 0x00 Schluss.
    Um zusätzlich 10 Byte Stack zu allocieren müsste ich doch zum Stackpointer
    10 Byte addieren um auf die gewünschten Stack von 0xff+10Byte zu kommen.
    Subtrahieren vom Stack würde den Stack doch verkleinern.
    Aber es werden doch 444Byte zusätzlich Stack benötigt.

    Wenn ich das richtig verstanden habe, sprichst du davon, die Größe des Stackspeichers zu ändern? Das wird normalerweise nicht gemacht und das Betriebssystem reserviert ihn beim Start des Threads mit ausreichender Größe.

    Bei deinem Stack mit einem ursprünglichen Stackpointer von 0xFF liegt noch nichts auf der gedachten Stack-Struktur. Wenn man jetzt etwas auf den Stack legt, verringert sich ja der Stackpointer und zeigt auf das obere Element des Stacks (das unterste im Speicher, weil er ja "auf dem Kopf" steht). Ist man bei einem Stackpointer von 0x00 angelangt und will etwas zusätzliches auf den Stack legen, erzeugt das einen Stack Overflow. Wenn man also vom Stackpointer subtrahiert, legt man virtuell ein Element beliebiger Größe auf den Stack, auf den man mit PUSH normalerweise nur DWORDs ablegen kann. Der übrige Stackspeicher, der vor dem Stackspeicher liegt enthält undefinierte Werte.



  • @masterofx32

    Hast du für die Angelegenheit mit den nicht initialisierten Parametern auf dem Stack ein konkretes Beispiel mit Quellcode? Sind diese Argumente überhaupt erforderlich bei der Art, wie diese Funktion genutzt wird?

    Vielleicht "veralbert" mich hier auch IDA. Ich hatte dort auch schon mehrfach, das mir IDA Funktionsargumente, die nachweißlich Ptr waren als int verkaufen wollte.

    Zu meinem Problem:

    push    ebp
    mov     ebp, esp
    and     esp, 0FFFFFFF8h
    sub     esp, 74h
    push    ebx
    push    esi
    mov     esi, ecx
    push    edi
    push    ecx
    mov     edx, [ebp+arg_0]
    mov     eax, esp
    mov     [esp+84h+var_70], esp
    push    40002716h
    mov     dword ptr [esi+720h], 2
    sub     esp, 14h
    mov     ecx, esp
    push    eax
    mov     [ecx], edx
    mov     edx, [ebp+arg_4]
    mov     [ecx+4], edx
    mov     edx, [ebp+arg_8]
    mov     [ecx+8], edx
    mov     edx, [ebp+arg_C]
    mov     [ecx+0Ch], edx
    mov     dx, [ebp+arg_10]
    mov     [ecx+10h], dx
    call    sub_10043F00 // sollte laut IDA 6 argumente haben..
    

    zu meiner Schande muß ich gestehen, das ich den mov - Block vor dem Aufruf bisher ignoriert habe, weil da ja nichts mit dem Stack passiert.
    Wenn ich hier die push - anweisungen Zähle, müßte die Funktion als 6. Parameter
    ebp bekommen. Was soll das?



  • Von dem "sub esp, 14h" kommen mal 5 Argumente, d.h. das 6. ist dann das "push eax", so wie ich das sehe.



  • Richtig, das ist aber der Funktionsanfang. D.h. push ebx und push esi greifen auf register zu, die IMO ja dann keinem dfinierten Wert haben. Das sind also nur push's, die dazu dienen den Inhalt der Register zu sichern (werdem am Ende zurückgeschrieben ).
    Was ich dort dann nicht verstehe, ist, dass zwischen dem push eax und dem call dann das sub esp, 14h kommt. D.h. doch dann in diesem Fall, das 5 Argumente auf den Stack kommen, die total zufällig belegt/undefiniert sind.


  • Mod

    TheBigW schrieb:

    D.h. doch dann in diesem Fall, das 5 Argumente auf den Stack kommen, die total zufällig belegt/undefiniert sind.

    die bleiben aber nicht lange undefiniert. Im prinzip kann man allerdings nicht sagen, wieviele parameter hier übergeben werden, ohne den quelltext zu kennen, der uns, wie üblich in diesem forum, vorenthalten wird... schliesslich muss nicht jedes einzelne dword tatsächlich ein eigenständiger parameter sein (wenn ich gar nichts weiss, würde ich auf 3 parameter schliessen, der zweite eine struktur von 20 byte).
    wozu diese diskussionen ins blaue hinein gut sein sollen, entzieht sich meinem verständnis.



  • der uns, wie üblich in diesem forum, vorenthalten wird...

    sorry, das war nicht meine Absicht, aber ich finde es auch immer etwas unhöflich, zig- Seiten Quelltext zu posten und zu erwarten, das einfach mal jemand die Mühe macht da durchzusteigen. Es ist halt 'ne ganze Menge code und da hab ich mich wohl oder übel auf den Aufruf der Funktion eingeschränkt.

    schliesslich muss nicht jedes einzelne dword tatsächlich ein eigenständiger parameter sein (wenn ich gar nichts weiss, würde ich auf 3 parameter schliessen, der zweite eine struktur von 20 byte).

    Das wußte ich z.B. schonmal nicht. Ich dachte immer, das strukturen dort wie Arrays behandelt werden und dort wird ja meist immer die Adresse des ersten Elements übergeben, da ja dort wie bei einer struct alle Elemente im Speicher nebeneinander liegen.
    Das kommt dabei raus, wenn man wie Ich Assembler vom Disassembler lernt... 🙄
    Wie gesagt, ich poste auch liebend gerne den code, aber das werden dann für die zwei Funktionen schon 3-4 Seiten.

    Trotzdem vielen Dank für Eure bisherige Hilfe


  • Mod

    es genügt ja schon der relevante teil des quelltextes, d.h. der betreffende funktionsaufruf, und die deklaration der aufgerufenen funktion (ggf. definition der parametertypen - eben alle definitionen dies auch der compiler haben will).



  • TheBigW schrieb:

    D.h. doch dann in diesem Fall, das 5 Argumente auf den Stack kommen, die total zufällig belegt/undefiniert sind.

    Schau dir doch mal an, was die darauffolgenden movs machen.



  • @Ringding

    Aua, da hast du wohl recht. Wenn ich das richtig verstehe werden dort die an die Funktion übergebenen argument in die vorher mit sub 14h bereitgestellten Speicherbereiche geschrieben und somit initialisiert.

    Danke das wars, jetzt ist es in etwa klar. Sorry, ich bin halt immer davon ausgegangen, das Argumente immer mit push auf den Stack gelegt werden und hab diesen mov - Block in meiner Betrachtung weggelassen. 🙄

    @camper

    in der aufgerufenen Funktion passiert nichts besonderes, dort wird wirklich nur auf besagte 6 einzelne Argumente zugegriffen und die liegen dort genauso, wie sie in dem Move-block vor dem Funktionsaufruf initialisiert werden. Z.B. ist das 5. Argument ein WORD, was super zu

    mov     dx, [ebp+arg_10]
    mov     [ecx+10h], dx
    

    passt. Es ist also wirklich nur ein "normaler" Funktionsaufruf und mein Weltbild ist wieder gerade.
    Tatsächlich wird so die Übergabe einer struct "By Value" realisiert. Ich hab es gerade noch einmal an einem Beispiel durchgespielt.

    Danke für Eure Hilfe, das Brett vor meinem Kopf ein bisschen abzuschrauben 🙂



  • hermes schrieb:

    allokieren möchte.
    Müsste man dann nicht eigentlich nicht zum Stackpointer 0x444 addieren anstatt zu subtrahieren?

    masterofx32 schrieb:

    Nein, der Stack "wächst" negativ im Speicher also von den hohen Adressen zu den niedrigen Adressen.

    ok,man will ja nicht mehr auf den Stack packen sonder für eigene Zwecke
    Stack-Speicher benutzen.

    Warum wird dem Programm dann nicht gleich genügend Speicher reserviert?


Anmelden zum Antworten