Win32 - обратная трассировка из кода C - PullRequest
42 голосов
/ 17 апреля 2011

В настоящее время я ищу способ получения информации о трассировке в Windows из кода C (без C ++).

Я создаю кросс-платформенную библиотеку C с управлением памятью для подсчета ссылок.Он также имеет встроенный отладчик памяти, который предоставляет информацию об ошибках памяти ( XEOS C Foundation Library ).

При возникновении сбоя запускается отладчик, предоставляющий информацию о сбое изадействована запись памяти.

enter image description here

В Linux или Mac OS X я могу искать execinfo.h, чтобы использовать функцию backtrace, чтобы я мог отображать дополнительную информацию оошибка памяти.

Я ищу то же самое в Windows.

Я видел Как можно получить трассировку стека в C? при переполнении стека.Я не хочу использовать стороннюю библиотеку, поэтому функции CaptureStackBackTrace или StackWalk выглядят хорошо.

Единственная проблема в том, что я просто не понимаю, как их использовать, даже сдокументация Microsoft.

Я не привык к программированию Windows, так как обычно работаю на POSIX-совместимых системах.

Каковы некоторые объяснения этих функций и, возможно, некоторые примеры?

РЕДАКТИРОВАТЬ

Сейчас я рассматриваю возможность использования функции CaptureStackBackTrace из DbgHelp.lib, так как кажется, что издержки немного меньше ...

Вотто, что я пробовал до сих пор:

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

Я просто получаю мусор.Я думаю, что я должен использовать что-то еще, чем SymFromAddr.

Ответы [ 3 ]

47 голосов
/ 18 апреля 2011

Хорошо, теперь я понял. :)

Проблема была в структуре SYMBOL_INFO. Его нужно разместить в куче, зарезервировать место для имени символа и правильно инициализировать.

Вот окончательный код:

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

Вывод:

6: printStack - 0xD2430
5: wmain - 0xD28F0
4: __tmainCRTStartup - 0xE5010
3: wmainCRTStartup - 0xE4FF0
2: BaseThreadInitThunk - 0x75BE3665
1: RtlInitializeExceptionChain - 0x770F9D0F
0: RtlInitializeExceptionChain - 0x770F9D0F
3 голосов
/ 18 апреля 2011

Вот моя альтернатива супер-низкого fi, используемая для чтения стеков из приложения C ++ Builder.Этот код выполняется внутри самого процесса, когда он завершается сбоем и получает стек в массив 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];
    }

ОБНОВЛЕНИЕ

После получения стека я перехожу к его переводув имена.Я делаю это путем перекрестных ссылок с файлом .map, который выводит C ++ Builder.То же самое можно сделать с файлом карты из другого компилятора, хотя форматирование будет несколько другим.Следующий код работает для карт C ++ Builder.Это опять-таки довольно низкокачественный и, вероятно, не канонический способ MS делать вещи, но он работает в моей ситуации.Приведенный ниже код не доставляется конечным пользователям.

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

После запуска этого кода массив fns содержит наиболее подходящую функцию из файла .map.

В моемВ этой ситуации у меня фактически есть стек вызовов, созданный первым фрагментом кода, отправляемым в скрипт PHP - я делаю эквивалент кода C выше, используя фрагмент PHP.Этот первый бит анализирует файл карты (опять же, это работает с картами C ++ Builder, но его можно легко адаптировать к другим форматам файлов карт):

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

Затем этот бит переводит адрес (в $rowaddr) в данную функцию (а также смещение после функции):

                    $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";
                    }
2 голосов
/ 28 января 2014

@ Джон Брайт: Вы говорите «кто знает, допустим ли стек ...»: Ну, есть способ узнать, как известны адреса стека.Предполагая, что вам нужна трассировка в текущем потоке, конечно:

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

Мой "GetTEB ()" - это NtCurrentTeb () из NTDLL.DLL - и это не только Windows 7 и выше, как указано втекущий MSDN.М.С. пускает документацию.Это было там долгое время.Используя блок ThreadEnvironment (TEB), вам не нужен ReadProcessMemory (), поскольку вы знаете нижний и верхний предел стека.Я предполагаю, что это самый быстрый способ сделать это.

Используя компилятор MS, GetEBPForStackTrace () может быть

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

как простой способ получить EBP текущего потока (но вы можетепередать любой действительный EBP в этот цикл, если он используется для текущего потока).

Ограничение: Это действительно для x86 под Windows.

...