windows - Win32 - Backtrace fra C kode

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg søger for øjeblikket en måde at få backtrace information under Windows, fra C-kode (nej C ++).


Jeg bygger en C-bibliotek på tværs af platformen med referencestyret hukommelsesstyring. Det har også en integreret hukommelsesfejlfinding, der indeholder informationer om hukommelsesfejl (XEOS C Foundation Library). [20]


Når en fejl opstår, startes debuggeren, der giver information om fejlen og den hukommelseskort, der er involveret.


Indtast billedbeskrivelse her


På Linux eller Mac OS X kan jeg søge efter execinfo.h for at bruge funktionen backtrace, så jeg kan vise yderligere oplysninger om hukommelsesfejl.


Jeg leder efter det samme på Windows.


Jeg har set Hvordan kan man gribe et stakkespor i C? på Stack Overflow. Jeg vil ikke bruge et tredjepartsbibliotek, så CaptureStackBackTrace eller StackWalk]] funktioner ser godt ud.


Det eneste problem er, at jeg bare ikke får brug for dem, selv med Microsoft-dokumentationen.


Jeg er ikke vant til Windows-programmering, da jeg normalt arbejder med POSIX-kompatible systemer.


Hvad er nogle forklaringer til disse funktioner, og måske nogle eksempler?


EDIT


Jeg overvejer nu at bruge funktionen CaptureStackBackTrace fra DbgHelp.lib, som det synes, er der lidt mindre overhead ...


Her er hvad jeg har prøvet hidtil:


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


Jeg bliver bare junk. Jeg tror jeg skulle bruge noget andet end SymFromAddr.

Bedste reference


Okay, nu har jeg det. :)


Problemet var i SYMBOL\_INFO strukturen. Det skal tildeles på bunken, reservere plads til symbolnavnet og initialiseres korrekt.


Her er den endelige kode:


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
", frames - i - 1, symbol->Name, symbol->Address );
     }

     free( symbol );
}


Output er:


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

Andre referencer 1


Her er mit super-lav-fi-alternativ, som bruges til læsning af stakke fra en C ++ Builder-app. Denne kode udføres inden for selve processen, når den styrter og får en stak i cs-arrayet.


    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


Når jeg har fået stakken, går jeg så om at oversætte den til navne. Det gør jeg ved at krydshenvise med .map filen, som C ++ Builder output. Det samme kunne gøres med en kortfil fra en anden compiler, selv om formateringen ville være lidt anderledes.Koden nedenfor virker for C ++ Builder-kort. Dette er igen ret lav-fi og sandsynligvis ikke den kanoniske MS måde at gøre ting på, men det fungerer i min situation. Koden nedenfor er ikke leveret til slutbrugerne.


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


Efter at have kørt denne kode indeholder fns -systemet den bedst matchende funktion fra .map-filen.


I min situation har jeg faktisk opkaldsstakken som produceret af det første stykke kode, der sender til et PHP script - jeg svarer til C-koden ovenfor ved hjælp af et stykke PHP. Denne første bit analyserer kortfilen (Igen fungerer dette med C ++ Builder-kort, men det kan nemt tilpasses til andre kortfilformater):


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


Så oversætter denne bit en adresse (i $rowaddr) til en given funktion (såvel som forskydningen efter funktionen):


                    $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";
                    }

Andre referencer 2


@Jon Bright: Du siger 'hvem vidste om stakken er gyldig ...': Nå er der en måde at finde ud af, da stakken adresser er kendt. Forudsat at du har brug for et spor i den aktuelle tråd, selvfølgelig:


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


Min 'GetTEB ()' er NtCurrentTeb () fra NTDLL.DLL - og det er ikke kun Windows 7 og nyere som angivet i den nuværende MSDN. MS forstyrrer dokumentationen. Det var der i lang tid. Ved brug af ThreadEnvironment Block (TEB) behøver du ikke ReadProcessMemory () som du kender stakken s nederste og øvre grænse. Jeg antager, at dette er den hurtigste måde at gøre det på.


Brug af MS compiler, GetEBPForStackTrace () kan være


inline \_\_declspec(naked) UINT\_PTR GetEBPForStackTrace()
{
    \_\_asm
        {
        mov eax, ebp
        ret
        }
}


så nem måde at få EBP til den aktuelle tråd (men du kan overføre enhver gyldig EBP til denne loop, så længe den er for den aktuelle tråd).


Begrænsning: Dette gælder for x86 under Windows.