Win32 - Отслеживание кода C

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

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

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

введите описание изображения здесь

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

Ищу то же самое в винде.

Я видел Как можно получить трассировку стека на C? в Stack Overflow. Я не хочу использовать стороннюю библиотеку, поэтому функции 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.


person Macmade    schedule 17.04.2011    source источник
comment
Фактически статья, которая дается в качестве ответа в вашем связанном сообщении (codeproject.com/KB/ thread / StackWalker.aspx) также можно использовать в качестве руководства по захвату стека потока. Он дает ответы на все ваши вопросы. Более того, есть исходный код, который вы можете использовать, чтобы понять, как это сделать самому. Попробуйте прокрутить статью до раздела "Интересные места".   -  person bezmax    schedule 17.04.2011
comment
Спасибо за комментарий :) По-прежнему не повезло ... stackoverflow.com/questions/5705650/   -  person Macmade    schedule 19.04.2011


Ответы (3)


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

Проблема была в структуре 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
person Macmade    schedule 18.04.2011
comment
Есть идеи, как использовать ту же методологию для получения фактического номера строки? Если да, ответьте на мой вопрос здесь: stackoverflow.com/questions / 22465253 / - person Alexandru; 18.03.2014
comment
Спасибо за код! Обратите внимание, что SymFromAddr может выйти из строя (например, если файл .pdb отсутствует), в то время как адрес stack[i] может быть полезен (и вы не распечатываете его). - person Paul; 21.02.2015
comment
@Paul Спасибо за информацию :) - person Macmade; 21.02.2015
comment
Нет ничего, что требовало бы, чтобы структура SYMBOL_INFO была в куче. Он отлично работает в стеке. - person legalize; 19.06.2016
comment
@legalize, его необходимо выделять динамически, чтобы выделить дополнительный размер для поля имени (в противном случае максимальная длина имени была бы 1). - person Mark Ingram; 23.08.2017
comment
Я знаю, что это старый, но думаю, что это может мне помочь. Какой файл заголовка мне нужно включить для компиляции? Спасибо :) - person Aviv Cohn; 24.08.2019
comment
@AvivCohn #include ‹dbghelp.h› и добавьте -lDbgHelp к флагам компоновщика. И да, я знаю, что опаздываю. :) - person Refugnic Eternium; 10.01.2020

Вот моя супернизкая альтернатива, используемая для чтения стеков из приложения 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";
                    }
person Jon Bright    schedule 18.04.2011
comment
Тогда как я могу получить имена функций из этой информации? Стоит ли использовать функцию SymGetSym()? - person Macmade; 18.04.2011
comment
Спасибо за очень красивое редактирование :) Но что тогда, если нет файла карты? - person Macmade; 18.04.2011
comment
Macmade, вообще говоря, с моим методом вы облажались. Возможно, мне следовало объяснить, что я делаю это для вещей, которые фактически распространяю, в которые я не хочу включать отладочную информацию. Поскольку исполняемый файл не содержит отладочной информации, вам нужна какая-то внешняя ссылка. Это то, для чего нужен файл карты. Все это говорит о том, что нет причин не использовать файл карты, если вы уже декодируете свои собственные следы. Каждый компилятор, который я знаю, может его создать. В C ++ Builder в параметрах проекта есть флажок. С помощью gcc организуйте передачу --print-map в ld. VC, он находится в Linker / Debugging. - person Jon Bright; 18.04.2011
comment
Спасибо за объяснение. Проблема в том, что я распространяю статическую библиотеку, которая содержит отладчик встроенной памяти. Все работает нормально, кроме трассировки в Windows. - person Macmade; 18.04.2011
comment
Нашел способ с CaptureStackBackTrace. Спасибо за помощь : ) - person Macmade; 19.04.2011

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

    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. MS копает документацию. Это было давно. Используя блок ThreadEnvironment (TEB), вам не нужен ReadProcessMemory (), поскольку вы знаете нижний и верхний предел стека. Я полагаю, это самый быстрый способ сделать это.

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

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

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

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

person chksr    schedule 28.01.2014