Fragen zur SystemV AMD64 ABI



  • Hi,
    ich probiere hier gerade rum und ich verstehe ein paar Sachen nicht:

    1.) -- Geklärt --

    2.) Ich versuche eine kleine Bibliothek für Function Hooks zu schreiben und muss für meinen Einsatzweck der Hookfunktion einen Pointer unterjubeln. Das versuche ich auf dem Stack. Ich überscchreibe eine Funktion mit dem folgenden Code:

    ; 8 Bytes Pointer, 8 Bytes Funktionsaddresse, 8 Bytes ??? (Siehe Frage 1)
    sub rsp, 24
    
    ; Den Zeiger auf den Stack legen.
    mov [rsp + 20], PTR_HI
    mov [rsp + 16], PTR_LO
    
    ; Die Hook-Funktionsaddresse ganz oben auf den Stack legen.
    mov [rsp + 4], HOOK_HI
    mov [rsp + 0], HOOK_LO
    
    ; Return missbrauchen um die Funktion anzuspringen.
    ret
    
    ; Den Stack wieder auf den alten Zustand bringen und zum Caller zurückkehren.
    add rsp, 16
    ret
    

    Was mache ich hier falsch? Ich lande in der Hook-Funktion, habe auch Zugriff auf den Pointer auf dem Stack - beim Zurückkehren knallt es aber immer und leider lässt sich das Gehacke reichlich schlecht mit dem GDB debuggen.

    3.) Wenn ich die Referenz richtig verstehe dann müsste ich wegen der Red Zone 128 + n Bytes aus dem Stack allozieren? Wäre es nicht theoretisch sonst möglich dass ich nach der Subtraktion des Stackpointers immer noch voll in den lokalen Variablen der aufrufenden Funktion stecke?

    Hoffe wirklich dass jemand mir helfenmn kann!
    Danke und Grüße,
    Ethon


  • Mod

    Ethon schrieb:

    Was mache ich hier falsch?

    Zeigerarumente gehören zur INTEGER-Klasse und werden per Register übergeben (3.2.3).

    Ethon schrieb:

    Wäre es nicht theoretisch sonst möglich dass ich nach der Subtraktion des Stackpointers immer noch voll in den lokalen Variablen der aufrufenden Funktion stecke?

    nein (3.2.2).



  • camper schrieb:

    Ethon schrieb:

    Was mache ich hier falsch?

    Zeigerarumente gehören zur INTEGER-Klasse und werden per Register übergeben (3.2.3).

    Nur die ersten 6. 🙂
    Ich teste gerade nur Funktionen mit höchstens 6 Integerargumenten und da fülle ich die Hook-Funktion mit Integerargumenten auf bis der Fakeparameter als letzter Parameter verwendbar ist.

    camper schrieb:

    {quote="Ethon"]Wäre es nicht theoretisch sonst möglich dass ich nach der Subtraktion des Stackpointers immer noch voll in den lokalen Variablen der aufrufenden Funktion stecke?

    nein (3.2.2).[/quote]

    Dankeschön, muss wohl blind sein.

    ----

    Grr, je mehr ich alles durchdenke desto mehr Fallstricke kommen auf.
    Hast du/jemand sonst einen Vorschlag wie man möglichst problemlos eine Addresse in eine Funktion schmuggeln könnte?



  • Okay, jetzt bin ich zu Tode verwirrt.

    push r14
    push r15
    
    mov r14, HOOK_FUNCTION
    mov r15, USERDATA
    call r14
    
    pop r15
    pop r14
    

    Das würde funktionieren, oder? Soweit ich das verstehe wird immer über rbp auf die etwaigen Stackargumente der Funktion zugegriffen, dh. ich kann mit rsp spielen so viel ich mag bevor es in die Funktion geht?


  • Mod

    rsp bestimmt, wo sich der Stackrahmen befindet; der übergebene Inhalt von rbp ist für die aufgerufenene Funktion dagegen bedeutungslos.

    Dein Code ist mir zu unvollständig, um ihn nachzuvollziehen.



  • Danke ... Es wird immer schlimmer mit meiner Verwirrung. 😕
    rsp bestimmt also den Stackrahmen?

    Ich habe zu Testzwecken mal versucht den Stack zu "walken" um ein besseres Verständnis dafür zu bekommen wie das mit den Stackframes nun läuft:

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <iterator>
    #include <cassert>
    #include <execinfo.h>
    using namespace std;
    
    #define GET_CURRENT_STACK_FRAME(dest) asm volatile ("movq %%rbp, %0" : "=r"(dest))
    #define GET_NEXT_STACK_FRAME(bp) *((void**)(bp))
    #define GET_RETURN_ADDRESS(bp) *((void**)(((Byte*)(bp) + 8)))
    
    typedef char Byte;
    
    vector<void*> stacktrace()
    {
        // Retrieve the current stack frame.
        void* curFrame;
        GET_CURRENT_STACK_FRAME(curFrame);
    
        // Iterate the stack.
        vector<void*> result;
        while(curFrame)
        {
            // Get the current return address & store it.
            void* returnAddress = GET_RETURN_ADDRESS(curFrame);
            result.push_back(returnAddress);
    
            // Get the next stack frame.
            void* next = GET_NEXT_STACK_FRAME(curFrame);
            if(!next)
                break;
    
            // Advance to the next stack frame.
            curFrame = next;
        }
    
        return result;
    }
    
    void print(vector<void*> const& st)
    {
        cout << "TRACE: ";
        copy(st.begin(), st.end(), ostream_iterator<void*>(cerr, " "));
        cout << endl;
    }
    
    void a()
    {
        print(stacktrace());
    
        void* buf[20];
        print(vector<void*>(buf, buf + backtrace(buf, 20)));
    }
    
    void b()
    {
        a();
    }
    
    void c()
    {
        b();
    }
    
    int main()
    {
        c();
    }
    

    (Jaja, das ist kein ASM, hat aber auch nichts mehr mit vernünftigem C++ zu tun :p )

    Ich hangele mich also an den Basepointern entlang - dummerweise versagt meine Lösung im Debugmodus teilweise (__libc_start_main bekomme ich noch in den Stacktrace, _start nicht mehr) und im optimierten Build komplett.

    Die fertige Lösung backtrace() hingegen funktioniert tadellos.

    Also müsste ich rsp verwenden um die älteren Stackrahmen bzw. Rücksprungaddressen zu bekommen? Wie soll das gehen - zwischen rsp und der Rücksprungaddresse auf dem Stack befindet sich doch eine unspezifizierte Anzahl Bytes?


  • Mod

    Das "walken" des Stacks funktioniert nat. nur, wenn bestimmte Konventionen eingehalten werden, insebesondere eben die Adresse des jeweilige Stackrahmen in bestimmter Weise gespeichert werden.
    Typischerweise wird hierfür rbp eingesetzt, das ist aber bloß Konvention, keine Vorschrift und darf entsprechend auch - gerade wenn optimiert wird - unterbleiben. Betrifft das eine der aufrufende Funktion, wird diese beim Stackwalk einfach übersprungen, sofern rbp von der Funktion nicht tatsächlich verändert wird, ansonsten landet man an irgendeiner Stelle im Speicher.

    Hält sich eine Funktion an die Konvention, beginnt der Funktionsprolog immer mit einem

    push rbp
    mov rbp, rsp
    

    (oder etwas dazu äquivalentem. Die Adresse des Stackrahmens der aufrufenden Funktion befindet sich also unmittelbar unterhalb der Rücksprungadresse. Diese wiederum befindet sich laut ABI direkt unterhalb des Bereiches, indem der erste über den Stack übergebene Parameter gespeichert wird. Am einfachsten dürfte es daher sein, einfach einen Dummyparameter zu verwenden, und aus dessen Adresse heraus die Adresse zu ermitteln, an der Ort des übergeordneten Stackrahmens zu finden ist. So geht es dann auch ohne Assembler

    struct dummy { char x[40]; }; // zu groß für Übergabe per Register
    
    vector<void*> stacktrace(dummy arg = dummy())
    {
        // Retrieve the current stack frame.
        char* curFrame = arg.x-16; // sizeof(void*) wäre falsch bei x32-ABI
    
        // Iterate the stack.
        vector<void*> result;
        while(curFrame)
        {
            // Get the current return address & store it.
            void* returnAddress = *(void**)(curFrame+8);
            result.push_back(returnAddress);
    
            // Get the next stack frame.
            char* next = *(char**)curFrame;
            if(!next)
                break;
    
            // Advance to the next stack frame.
            curFrame = next;
        }
    
        return result;
    }
    

Anmelden zum Antworten