Win32 - Backtrace à partir du code C

je suis actuellement à la recherche d'un moyen pour obtenir des informations backtrace sous Windows, à partir du code C (no C++).

je construis une bibliothèque multiplateformes C, avec gestion de la mémoire de comptage de références. Il dispose également d'un débogueur mémoire intégré qui fournit des informations sur les erreurs de mémoire ( XEOS C Foundation Library ).

Lorsqu'une défaillance se produit, le débogueur est lancé, fournissant des informations sur la défaillance, et le dossier de mémoire impliqués.

enter image description here

sur Linux ou Mac OS X, je peux chercher execinfo.h pour utiliser la fonction backtrace , donc je peux afficher des informations supplémentaires sur la faille de mémoire.

Je cherche la même chose sur Windows.

j'ai vu Comment peut-on saisir une trace de la pile en C? sur un Débordement de Pile. Je ne veux pas utiliser une bibliothèque tierce, donc les fonctions CaptureStackBackTrace ou StackWalk semblent bonnes.

le seul problème est que je ne comprends pas comment les utiliser, même avec la documentation de Microsoft.

Je ne suis pas habitué à la programmation Windows, car je travaille habituellement sur des systèmes conformes à POSIX.

quelles sont quelques explications pour ces fonctions, et peut-être quelques exemples?

MODIFIER

j'envisage maintenant d'utiliser la fonction CaptureStackBackTrace de DbgHelp.lib , car il semble qu'il y ait un peu moins de frais généraux...

voici ce que j'ai essayé jusqu'à présent:

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( "%sn", symbol.Name );
}

je prends juste de la camelote. Je suppose que je devrais utiliser autre chose que SymFromAddr .

39
demandé sur Community 2011-04-17 15:19:07

3 réponses

très bien, maintenant je l'ai. :)

le problème était dans la structure SYMBOL_INFO. Il doit être attribué sur le tas, en réservant de l'espace pour le nom du symbole, et initialisé correctement.

voici le code final:

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 );
}

sortie:

6: printStack - 0xD2430
5: wmain - 0xD28F0
4: __tmainCRTStartup - 0xE5010
3: wmainCRTStartup - 0xE4FF0
2: BaseThreadInitThunk - 0x75BE3665
1: RtlInitializeExceptionChain - 0x770F9D0F
0: RtlInitializeExceptionChain - 0x770F9D0F
44
répondu Macmade 2011-04-19 12:15:26

Voici mon alternative super-low-fi, utilisée pour lire les piles d'une application C++ Builder. Ce code est exécuté dans le processus lui-même quand il s'écrase et obtient une pile dans le tableau cs.

    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];
    }

UPDATE

une fois que j'ai la pile, je vais la traduire en noms. Je le fais en croisant avec le fichier .map que C++Builder sort. La même chose pourrait être faite avec un fichier de carte d'un autre compilateur, bien que le formatage serait quelque peu différent. Le code suivant fonctionne pour C++Builder maps. Encore une fois, il s'agit d'une façon assez discrète et probablement pas la façon canonique de faire les choses, mais cela fonctionne dans ma situation. Le code ci-dessous n'est pas livré aux utilisateurs finaux.

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]='"151910920"';
        }
    }
} while (!feof(map));
fclose(map);

après l'exécution de ce code, le tableau fns contient la fonction la mieux adaptée de la .fichier cartographique.

dans ma situation, j'ai en fait la pile d'appels comme produit par le premier morceau de code soumis à un script PHP - je fais l'équivalent du code C ci-dessus en utilisant un morceau de PHP. Ce premier bit analyse le fichier map (encore une fois, cela fonctionne avec C++Builder maps mais pourrait être facilement adapté à d'autres formats de fichier map):

            $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);

puis ce bit traduit une adresse (dans $rowaddr ) en une fonction donnée (ainsi que l'offset après la fonction):

                    $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";
                    }
3
répondu Jon Bright 2011-04-18 10:38:56

@Jon Bright: vous dites "qui savait si la pile est valide...": Eh bien, il y a un moyen de le savoir, car les adresses de la pile sont connues. En supposant que vous avez besoin d'une trace dans le fil courant, bien sûr:

    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];
        }

Mon "GetTEB()" est NtCurrentTeb() à partir de NTDLL.DLL-et il n'est pas seulement Windows 7 et plus comme indiqué dans le MSDN actuel. Mme junks up la documentation. Il était là pour une longue période de temps. En utilisant le bloc Threadenvironnement (TEB), vous n'avez pas besoin ReadProcessMemory () comme vous connaissez les limites inférieure et supérieure de la pile. Je suppose que c'est le moyen le plus rapide de le faire.

en utilisant le compilateur MS, GetEBPForStackTrace () peut être

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

comme moyen facile d'obtenir EBP du thread courant (mais vous pouvez passer n'importe quel EBP valide à cette boucle aussi longtemps qu'il est pour le thread courant).

Limitation: ceci est valable pour x86 sous Windows.

2
répondu chksr 2014-01-28 09:01:54