Win32 – Rückverfolgung von C-Code

Lesezeit: 8 Minuten

Benutzeravatar von Macmade
Macmade

Ich suche derzeit nach einer Möglichkeit, Backtrace-Informationen unter Windows aus C-Code (kein C++) abzurufen.

Ich baue eine plattformübergreifende C-Bibliothek mit Speicherverwaltung mit Referenzzählung. Es hat auch einen integrierten Speicherdebugger, der Informationen über Speicherfehler liefert (XEOS C Foundation-Bibliothek).

Wenn ein Fehler auftritt, wird der Debugger gestartet und liefert Informationen über den Fehler und den betroffenen Speicherdatensatz.

Geben Sie hier die Bildbeschreibung ein

Unter Linux oder Mac OS X kann ich suchen execinfo.h um die zu verwenden backtrace Funktion, damit ich mir zusätzliche Infos zum Speicherfehler anzeigen lassen kann.

Ich suche das gleiche unter Windows.

Ich habe gesehen Wie kann man in C einen Stack-Trace abrufen? auf Stapelüberlauf. Ich möchte keine Bibliothek eines Drittanbieters verwenden, also die CaptureStackBackTrace oder StackWalk Funktionen sieht gut aus.

Das einzige Problem ist, dass ich einfach nicht verstehe, wie man sie benutzt, selbst mit der Microsoft-Dokumentation.

Ich bin nicht an die Windows-Programmierung gewöhnt, da ich normalerweise auf POSIX-kompatiblen Systemen arbeite.

Was sind einige Erklärungen für diese Funktionen und vielleicht einige Beispiele?

BEARBEITEN

Ich überlege jetzt, die zu verwenden CaptureStackBackTrace Funktion ab DbgHelp.libwie es scheint, gibt es ein bisschen weniger Overhead …

Folgendes habe ich bisher versucht:

unsigned int   i;
void         * stack[ 100 ];
unsigned short frames;
SYMBOL_INFO    symbol;
HANDLE         process;

process = GetCurrentProcess();

SymInitialize( process, NULL, TRUE );

frames = CaptureStackBackTrace( 0, 100, stack, NULL );

for( i = 0; i < frames; i++ )
{
    SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, &symbol );

    printf( "%s\n", symbol.Name );
}

Ich bekomme nur Schrott. Ich denke, ich sollte etwas anderes als verwenden SymFromAddr.

  • Eigentlich der Artikel, der in Ihrem verlinkten Beitrag als Antwort gegeben wird (codeproject.com/KB/threads/StackWalker.aspx) kann auch als Leitfaden zum Erfassen des Stapels eines Threads verwendet werden. Es gibt Antworten auf alle Ihre Fragen. Außerdem gibt es einen Quellcode, den Sie verwenden können, um zu verstehen, wie man es selbst macht. Versuchen Sie, den Artikel nach unten zum Abschnitt “Sehenswürdigkeiten” zu scrollen.

    – bezmax

    17. April 2011 um 19:20 Uhr


  • Danke für den Kommentar 🙂 Immer noch kein Glück … stackoverflow.com/questions/5705650/…

    – Macmade

    19. April 2011 um 9:31 Uhr


Benutzeravatar von Macmade
Macmade

Okay, jetzt habe ich es. 🙂

Das Problem lag in der SYMBOL_INFO-Struktur. Es muss auf dem Heap zugewiesen werden, Platz für den Symbolnamen reservieren und ordnungsgemäß initialisiert werden.

Hier ist der endgültige Code:

void printStack( void );
void printStack( void )
{
     unsigned int   i;
     void         * stack[ 100 ];
     unsigned short frames;
     SYMBOL_INFO  * symbol;
     HANDLE         process;

     process = GetCurrentProcess();

     SymInitialize( process, NULL, TRUE );

     frames               = CaptureStackBackTrace( 0, 100, stack, NULL );
     symbol               = ( SYMBOL_INFO * )calloc( sizeof( SYMBOL_INFO ) + 256 * sizeof( char ), 1 );
     symbol->MaxNameLen   = 255;
     symbol->SizeOfStruct = sizeof( SYMBOL_INFO );

     for( i = 0; i < frames; i++ )
     {
         SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, symbol );

         printf( "%i: %s - 0x%0X\n", frames - i - 1, symbol->Name, symbol->Address );
     }

     free( symbol );
}

Ausgabe ist:

6: printStack - 0xD2430
5: wmain - 0xD28F0
4: __tmainCRTStartup - 0xE5010
3: wmainCRTStartup - 0xE4FF0
2: BaseThreadInitThunk - 0x75BE3665
1: RtlInitializeExceptionChain - 0x770F9D0F
0: RtlInitializeExceptionChain - 0x770F9D0F

  • Haben Sie eine Idee, wie Sie dieselbe Methode verwenden können, um die tatsächliche Zeilennummer zu erhalten? Wenn ja, antworten Sie bitte hier auf meine Frage: stackoverflow.com/questions/22465253/…

    – Alexandru

    17. März 2014 um 21:08 Uhr

  • Danke für den Code! Beachten Sie, dass SymFromAddr kann fehlschlagen (z. B. wenn die .pdb-Datei fehlt), während die Adresse von stack[i] könnte immer noch nützlich sein (und Sie drucken es nicht).

    – Paulus

    21. Februar 2015 um 16:21 Uhr

  • Es gibt nichts, was erfordert, dass die SYMBOL_INFO Struktur auf dem Haufen sein. Auf dem Stack funktioniert es einwandfrei.

    – legalisieren

    19. Juni 2016 um 17:11 Uhr

  • @legalize, es muss dynamisch zugewiesen werden, um dem Namensfeld zusätzliche Größe zuzuweisen (andernfalls wäre die maximale Länge des Namens 1).

    – Mark Ingram

    23. August 2017 um 10:59 Uhr

  • @AvivCohn #include und fügen Sie -lDbgHelp zu den Linker-Flags hinzu. Und ja, ich weiß, ich bin spät dran. 🙂

    – Refugnisches Eternium

    10. Januar 2020 um 11:11 Uhr

Benutzeravatar von Jon Bright
Jon Hell

Hier ist meine Super-Low-Fi-Alternative, wie sie zum Lesen von Stacks aus einer C++ Builder-App verwendet wird. Dieser Code wird innerhalb des Prozesses selbst ausgeführt, wenn er abstürzt und einen Stack in das cs-Array erhält.

    int cslev = 0;
    void* cs[300];
    void* it = <ebp at time of crash>;
    void* rm[2];
    while(it && cslev<300)
    {
            /* Could just memcpy instead of ReadProcessMemory, but who knows if 
               the stack's valid? If  it's invalid, memcpy could cause an AV, which is
               pretty much exactly what we don't want
            */
            err=ReadProcessMemory(GetCurrentProcess(),it,(LPVOID)rm,sizeof(rm),NULL);
            if(!err)
                    break;
            it=rm[0];
            cs[cslev++]=(void*)rm[1];
    }

AKTUALISIEREN

Sobald ich den Stack habe, mache ich mich daran, ihn in Namen zu übersetzen. Ich tue dies durch Querverweise mit dem .map Datei, die C++Builder ausgibt. Das Gleiche könnte mit einer Map-Datei von einem anderen Compiler gemacht werden, obwohl die Formatierung etwas anders wäre. Der folgende Code funktioniert für C++Builder-Maps. Dies ist wieder ziemlich Low-Fi und wahrscheinlich nicht die kanonische MS-Methode, aber es funktioniert in meiner Situation. Der folgende Code wird nicht an Endbenutzer geliefert.

char linbuf[300];
char *pars;
unsigned long coff,lngth,csect;
unsigned long thisa,sect;
char *fns[300];
unsigned int maxs[300];
FILE *map;

map = fopen(mapname, "r");
if (!map)
{
    ...Add error handling for missing map...
}

do
{
    fgets(linbuf,300,map);
} while (!strstr(linbuf,"CODE"));
csect=strtoul(linbuf,&pars,16); /* Find out code segment number */
pars++; /* Skip colon */
coff=strtoul(pars,&pars,16); /* Find out code offset */
lngth=strtoul(pars,NULL,16); /* Find out code length */
do
{
    fgets(linbuf,300,map);
} while (!strstr(linbuf,"Publics by Name"));

for(lop=0;lop!=cslev;lop++)
{
    fns[lop] = NULL;
    maxs[lop] = 0;
}
do
{
    fgets(linbuf,300,map);
    sect=strtoul(linbuf,&pars,16);
    if(sect!=csect)
        continue;
    pars++;
    thisa=strtoul(pars,&pars,16);
    for(lop=0;lop!=cslev;lop++)
    {
        if(cs[lop]<coff || cs[lop]>coff+lngth)
            continue;
        if(thisa<cs[lop]-coff && thisa>maxs[lop])
        {
            maxs[lop]=thisa;
            while(*pars==' ')
                pars++;
            fns[lop] = fnsbuf+(100*lop);
            fnlen = strlen(pars);
            if (fnlen>100)
                fnlen = 100;
            strncpy(fns[lop], pars, 99);
            fns[lop][fnlen-1]='\0';
        }
    }
} while (!feof(map));
fclose(map);

Nachdem Sie diesen Code ausgeführt haben, wird die fns Das Array enthält die am besten passende Funktion aus der .map-Datei.

In meiner Situation habe ich tatsächlich den Call-Stack, wie er durch das erste Stück Code erzeugt wird, das an ein PHP-Skript gesendet wird – ich mache das Äquivalent des obigen C-Codes mit einem Stück PHP. Dieses erste Bit analysiert die Kartendatei (Auch dies funktioniert mit C++Builder-Karten, könnte aber leicht an andere Kartendateiformate angepasst werden):

            $file = fopen($mapdir.$app."-".$appversion.".map","r");
            if (!$file)
                    ... Error handling for missing map ...
            do
            {
                    $mapline = fgets($file);
            } while (!strstr($mapline,"CODE"));
            $tokens = split("[[:space:]\:]", $mapline);
            $codeseg = $tokens[1];
            $codestart = intval($tokens[2],16);
            $codelen = intval($tokens[3],16);
            do
            {
                    $mapline = fgets($file);
            } while (!strstr($mapline,"Publics by Value"));
            fgets($file); // Blank
            $addrnum = 0;
            $lastaddr = 0;
            while (1)
            {
                    if (feof($file))
                            break;
                    $mapline = fgets($file);
                    $tokens = split("[[:space:]\:]", $mapline);
                    $thisseg = $tokens[1];
                    if ($thisseg!=$codeseg)
                            break;
                    $addrs[$addrnum] = intval($tokens[2],16);
                    if ($addrs[$addrnum]==$lastaddr)
                            continue;
                    $lastaddr = $addrs[$addrnum];
                    $funcs[$addrnum] = trim(substr($mapline, 16));
                    $addrnum++;
            }
            fclose($file);

Dann übersetzt dieses Bit eine Adresse (in $rowaddr) in eine gegebene Funktion (sowie den Offset nach der Funktion):

                    $thisaddr = intval($rowaddr,16);
                    $thisaddr -= $codestart;
                    if ($thisaddr>=0 && $thisaddr<=$codelen)
                    {
                            for ($lop=0; $lop!=$addrnum; $lop++)
                                    if ($thisaddr<$addrs[$lop])
                                            break;
                    }
                    else
                            $lop = $addrnum;
                    if ($lop!=$addrnum)
                    {
                            $lop--;
                            $lines[$ix] = substr($line,0,13).$rowaddr." : ".$funcs[$lop]." (+".sprintf("%04X",$thisaddr-$addrs[$lop]).")";
                            $stack .= $rowaddr;
                    }
                    else
                    {
                            $lines[$ix] = substr($line,0,13).$rowaddr." : external";
                    }

  • Wie bekomme ich dann die Funktionsnamen aus diesen Infos? Soll ich die verwenden SymGetSym() Funktion?

    – Macmade

    18. April 2011 um 10:15 Uhr

  • Danke für die sehr schöne Bearbeitung 🙂 Aber was ist, wenn es keine Kartendatei gibt?

    – Macmade

    18. April 2011 um 15:19 Uhr


  • Macmade, grob gesagt, mit meiner Methode sind Sie am Arsch. Ich hätte vielleicht erklären sollen, dass ich dies für Dinge mache, die ich eigentlich verteile, in die ich keine Debug-Informationen aufnehmen möchte. Da die ausführbare Datei keine Debug-Informationen enthält, benötigen Sie eine Art externe Referenz. Dafür ist die Map-Datei da. Alles in allem gibt es keinen Grund, keine Map-Datei zu verwenden, wenn Sie bereits Ihre eigenen Backtraces decodieren. Jeder Compiler, den ich kenne, kann einen erzeugen. In C++Builder gibt es ein Kontrollkästchen in den Projektoptionen. Sorgen Sie mit gcc dafür, dass –print-map an ld übergeben wird. VC, es ist unter Linker/Debugging.

    – Jon Bright

    18. April 2011 um 15:35 Uhr

  • Danke für die Erklärung. Das Problem ist, dass ich eine statische Bibliothek verteile, die einen integrierten Speicher-Debugger enthält. Alles funktioniert gut, außer dem Backtrace unter Windows.

    – Macmade

    18. April 2011 um 15:52 Uhr


  • Weg gefunden mit CaptureStackBackTrace. Danke für die Hilfe : )

    – Macmade

    19. April 2011 um 12:55 Uhr

Benutzeravatar von chksr
chksr

@ Jon Bright: Sie sagen “wer wusste, ob der Stapel gültig ist …”: Nun, es gibt eine Möglichkeit, dies herauszufinden, da die Stapeladressen bekannt sind. Angenommen, Sie benötigen natürlich einen Trace im aktuellen Thread:

    NT_TIB*     pTEB = GetTEB();
    UINT_PTR    ebp = GetEBPForStackTrace();
    HANDLE      hCurProc = ::GetCurrentProcess();

    while (
        ((ebp & 3) == 0) &&
        ebp + 2*sizeof(VOID*) < (UINT_PTR)pTEB->StackBase &&
        ebp >= (UINT_PTR)pTEB->StackLimit &&
        nAddresses < nTraceBuffers)
        {
        pTraces[nAddresses++]._EIP = ((UINT_PTR*)ebp)[1];
        ebp = ((UINT_PTR*)ebp)[0];
        }

Mein “GetTEB()” ist NtCurrentTeb() von NTDLL.DLL – und es ist nicht nur Windows 7 und höher, wie in der aktuellen MSDN angegeben. MS verschrottet die Dokumentation. Es war lange da. Wenn Sie den ThreadEnvironment Block (TEB) verwenden, benötigen Sie ReadProcessMemory() nicht, da Sie die Unter- und Obergrenze des Stacks kennen. Ich nehme an, das ist der schnellste Weg, es zu tun.

Mit dem MS-Compiler kann GetEBPForStackTrace() sein

inline __declspec(naked) UINT_PTR GetEBPForStackTrace()
{
    __asm
        {
        mov eax, ebp
        ret
        }
}

als einfache Möglichkeit, EBP des aktuellen Threads zu erhalten (aber Sie können jedes gültige EBP an diese Schleife übergeben, solange es für den aktuellen Thread gilt).

Einschränkung: Dies gilt für x86 unter Windows.

1408760cookie-checkWin32 – Rückverfolgung von C-Code

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy