Как вы можете использовать CaptureStackBackTrace для захвата стека исключений, а не стека вызовов?

Я разметил следующий код:

#include "stdafx.h"
#include <process.h>
#include <iostream>
#include <Windows.h>
#include "dbghelp.h"

using namespace std;

#define TRACE_MAX_STACK_FRAMES 1024
#define TRACE_MAX_FUNCTION_NAME_LENGTH 1024

int printStackTrace()
{
    void *stack[TRACE_MAX_STACK_FRAMES];
    HANDLE process = GetCurrentProcess();
    SymInitialize(process, NULL, TRUE);
    WORD numberOfFrames = CaptureStackBackTrace(0, TRACE_MAX_STACK_FRAMES, stack, NULL);
    SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO)+(TRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR));
    symbol->MaxNameLen = TRACE_MAX_FUNCTION_NAME_LENGTH;
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
    DWORD displacement;
    IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
    line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);
    for (int i = 0; i < numberOfFrames; i++)
    {
        DWORD64 address = (DWORD64)(stack[i]);
        SymFromAddr(process, address, NULL, symbol);
        if (SymGetLineFromAddr64(process, address, &displacement, line))
        {
            printf("\tat %s in %s: line: %lu: address: 0x%0X\n", symbol->Name, line->FileName, line->LineNumber, symbol->Address);
        }
        else
        {
            printf("\tSymGetLineFromAddr64 returned error code %lu.\n", GetLastError());
            printf("\tat %s, address 0x%0X.\n", symbol->Name, symbol->Address);
        }
    }
    return 0;
}

void function2()
{
    int a = 0;
    int b = 0;
    throw new exception;
}

void function1()
{
    int a = 0;
    function2();
}

void function0()
{
    function1();
}

static void threadFunction(void *param)
{
    try
    {
        function0();
    }
    catch (...)
    {
        printStackTrace();
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    _beginthread(threadFunction, 0, NULL);
    printf("Press any key to exit.\n");
    cin.get();
    return 0;
}

Что он делает, так это регистрирует трассировку стека, но проблема в том, что регистрируемая трассировка стека не дает мне нужных мне номеров строк. Я хочу, чтобы он регистрировал номера строк мест, вызвавших исключение, в стеке вызовов и вверх, вроде как в C#. Но что он на самом деле делает прямо сейчас, так это выводит следующее:

        at printStackTrace in c:\users\<yourusername>\documents\visual studio 2013\pr
ojects\stacktracing\stacktracing\stacktracing.cpp: line: 17: address: 0x10485C0
        at threadFunction in c:\users\<yourusername>\documents\visual studio 2013\pro
jects\stacktracing\stacktracing\stacktracing.cpp: line: 68: address: 0x10457C0
        SymGetLineFromAddr64 returned error code 487.
        at beginthread, address 0xF9431E0.
        SymGetLineFromAddr64 returned error code 487.
        at endthread, address 0xF9433E0.
        SymGetLineFromAddr64 returned error code 487.
        at BaseThreadInitThunk, address 0x7590494F.
        SymGetLineFromAddr64 returned error code 487.
        at RtlInitializeExceptionChain, address 0x7713986A.
        SymGetLineFromAddr64 returned error code 487.
        at RtlInitializeExceptionChain, address 0x7713986A.

Проблема, с которой я снова сталкиваюсь, заключается в том, что line: 68 в этой трассировке соответствует строке, которая вызывает метод printStackTrace();, в то время как я хотел бы, чтобы она дала мне строку номер 45, которая соответствует строке, которая выдает исключение: throw new exception;, а затем продолжайте дальше вверх по стеку.

Как я могу добиться такого поведения и проникнуть в этот поток именно тогда, когда он выдает это исключение, чтобы получить правильную трассировку стека?

PS Приведенный выше код был запущен для консольного приложения с использованием MSVC++ с включенным юникодом на компьютере с Windows 8.1 x64, при этом приложение выполнялось как приложение Win32 в режиме отладки.


person Alexandru    schedule 17.03.2014    source источник
comment
Вам, конечно, нужно пропустить кадры стека, которые являются частью вашего кода регистрации. Просто отсчитайте их, рекомендуется использовать __declspec(noinline).   -  person Hans Passant    schedule 18.03.2014
comment
@HansPassant Но тогда он просто пропустит printStackTrace и threadFunction ... оставив меня с beginthread, к которому, я думаю, у него нет доступа из дочернего потока ... видите мою дилемму? Я имею в виду, просто чтобы уточнить, вы подразумеваете передачу количества пропущенных кадров при вызове CaptureStackBackTrace(0, TRACE_MAX_STACK_FRAMES, stack, NULL); например, CaptureStackBackTrace(2, TRACE_MAX_STACK_FRAMES, stack, NULL); правильно? Это все еще не то, что мне нужно :P   -  person Alexandru    schedule 18.03.2014
comment
@HansPassant Скажем так, я хочу, чтобы моя трассировка стека включала функцию2, функцию1 и функцию0. Тем не менее, особенно function2 и строка, в которой выдается исключение.   -  person Alexandru    schedule 18.03.2014
comment
Это невозможно, когда вы используете catch(...), стек уже раскручен и исключение отклонено. Вы должны использовать SetUnhandledExceptionFilter() для перехвата необработанного исключения.   -  person Hans Passant    schedule 18.03.2014
comment
@HansPassant Должен быть способ, потому что даже если установить векторный обработчик исключений, он все равно не даст мне точную строку. Я работаю в компании, у которой нет трассировки стека в производстве (LOL), и мне нужно добавить ее (очевидно, что люди слишком ленивы, чтобы делать это самостоятельно, поэтому вся работа перекладывается на меня). Поэтому я ищу способ создать свою собственную библиотеку трассировки стека. Что-то маленькое, чтобы просто перехватить все исключения и вывести трассировку стека. Что ты порекомендуешь? И... почему, во имя бога, так сложно сделать это на C++?!   -  person Alexandru    schedule 18.03.2014
comment
PS Я не хочу использовать StackWalker. Я не писал его, и, насколько я могу судить, это почти то же самое с точки зрения функциональности, которую я хочу, единственное, что мне нужно найти способ ворваться в поток, когда выдается исключение, и получить след оттуда; не после исключения и не в каком-либо обработчике/потребителе исключений, потому что это не работает.   -  person Alexandru    schedule 18.03.2014
comment
stackoverflow.com/ вопросы/19656946/   -  person Alexandru    schedule 18.03.2014
comment
Возможно, мне придется использовать __try и __except и получить трассировку из контекста этого... как это делает Stack Walker со StackWalk64... Я действительно не хотел делать это так, поскольку __try и __except не будут работать, если обернутый вокруг других блоков try-catch...   -  person Alexandru    schedule 18.03.2014
comment
@HansPassant Spinoff, больше проблем ... такая головная боль. stackoverflow .com/questions/22481126/   -  person Alexandru    schedule 18.03.2014
comment
@HansPassant В конце концов мне пришлось украсить все функции __declspec (noinline). Это сработало, и я получил новое понимание встроенных методов. Я думаю, что когда я пытался изначально, я не украшал все свои методы директивой __declspec(noinline).   -  person Alexandru    schedule 20.03.2014
comment
Прошу прощения за то, что не полностью применил ваши концепции, @HansPassant. Ты чертовски хорош в том, что делаешь.   -  person Alexandru    schedule 20.03.2014


Ответы (3)


В Windows необработанное исключение C++ автоматически генерирует исключение SEH. Блок SEH __except позволяет подключить фильтр, принимающий в качестве параметра структуру _EXCEPTION_POINTERS, содержащую указатель на запись контекста процессора в момент возникновения исключения. Передача этого указателя в функцию StackWalk64 дает трассировку стека в момент исключения. Таким образом, эту проблему можно решить, используя обработку исключений в стиле SEH вместо стиля C++.

Пример кода:

#include <stdlib.h>
#include <locale.h>
#include <stdio.h>
#include <tchar.h>

#include <process.h>
#include <iostream>
#include <Windows.h>
#include "dbghelp.h"

using namespace std;

const int MaxNameLen = 256;

#pragma comment(lib,"Dbghelp.lib")

void printStack( CONTEXT* ctx ) //Prints stack trace based on context record
{
    BOOL    result;
    HANDLE  process;
    HANDLE  thread;
    HMODULE hModule;

    STACKFRAME64        stack;
    ULONG               frame;    
    DWORD64             displacement;

    DWORD disp;
    IMAGEHLP_LINE64 *line;

    char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
    char name[MaxNameLen];
    char module[MaxNameLen];
    PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;

    memset( &stack, 0, sizeof( STACKFRAME64 ) );

    process                = GetCurrentProcess();
    thread                 = GetCurrentThread();
    displacement           = 0;
#if !defined(_M_AMD64)
    stack.AddrPC.Offset    = (*ctx).Eip;
    stack.AddrPC.Mode      = AddrModeFlat;
    stack.AddrStack.Offset = (*ctx).Esp;
    stack.AddrStack.Mode   = AddrModeFlat;
    stack.AddrFrame.Offset = (*ctx).Ebp;
    stack.AddrFrame.Mode   = AddrModeFlat;
#endif

    SymInitialize( process, NULL, TRUE ); //load symbols

    for( frame = 0; ; frame++ )
    {
        //get next call from stack
        result = StackWalk64
        (
#if defined(_M_AMD64)
            IMAGE_FILE_MACHINE_AMD64
#else
            IMAGE_FILE_MACHINE_I386
#endif
            ,
            process,
            thread,
            &stack,
            ctx,
            NULL,
            SymFunctionTableAccess64,
            SymGetModuleBase64,
            NULL
        );

        if( !result ) break;        

        //get symbol name for address
        pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
        pSymbol->MaxNameLen = MAX_SYM_NAME;
        SymFromAddr(process, ( ULONG64 )stack.AddrPC.Offset, &displacement, pSymbol);

        line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64));
        line->SizeOfStruct = sizeof(IMAGEHLP_LINE64);       

        //try to get line
        if (SymGetLineFromAddr64(process, stack.AddrPC.Offset, &disp, line))
        {
            printf("\tat %s in %s: line: %lu: address: 0x%0X\n", pSymbol->Name, line->FileName, line->LineNumber, pSymbol->Address);
        }
        else
        { 
            //failed to get line
            printf("\tat %s, address 0x%0X.\n", pSymbol->Name, pSymbol->Address);
            hModule = NULL;
            lstrcpyA(module,"");        
            GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 
                (LPCTSTR)(stack.AddrPC.Offset), &hModule);

            //at least print module name
            if(hModule != NULL)GetModuleFileNameA(hModule,module,MaxNameLen);       

            printf ("in %s\n",module);
        }       

        free(line);
        line = NULL;
    }
}

//******************************************************************************

void function2()
{
    int a = 0;
    int b = 0;
    throw exception();
}

void function1()
{
    int a = 0;
    function2();
}

void function0()
{
    function1();
}

int seh_filter(_EXCEPTION_POINTERS* ex)
{
    printf("*** Exception 0x%x occured ***\n\n",ex->ExceptionRecord->ExceptionCode);    
    printStack(ex->ContextRecord);

    return EXCEPTION_EXECUTE_HANDLER;
}

static void threadFunction(void *param)
{    

    __try
    {
         function0();
    }
    __except(seh_filter(GetExceptionInformation()))
    {       
        printf("Exception \n");         
    }
}

int _tmain(int argc, _TCHAR* argv[])
{   
    _beginthread(threadFunction, 0, NULL);
    printf("Press any key to exit.\n");
    cin.get();
    return 0;
}

Пример вывода (первые две записи являются шумом, но остальные правильно отражают функции, вызвавшие исключение):

*** Exception 0xe06d7363 occured ***

        at RaiseException, address 0xFD3F9E20.
in C:\Windows\system32\KERNELBASE.dll
        at CxxThrowException, address 0xDBB5A520.
in C:\Windows\system32\MSVCR110D.dll
        at function2 in c:\work\projects\test\test.cpp: line: 146: address: 0x3F9C6C00
        at function1 in c:\work\projects\test\test.cpp: line: 153: address: 0x3F9C6CB0
        at function0 in c:\work\projects\test\test.cpp: line: 158: address: 0x3F9C6CE0
        at threadFunction in c:\work\projects\test\test.cpp: line: 174: address: 0x3F9C6D70
        at beginthread, address 0xDBA66C60.
in C:\Windows\system32\MSVCR110D.dll
        at endthread, address 0xDBA66E90.
in C:\Windows\system32\MSVCR110D.dll
        at BaseThreadInitThunk, address 0x773C6520.
in C:\Windows\system32\kernel32.dll
        at RtlUserThreadStart, address 0x775FC520.
in C:\Windows\SYSTEM32\ntdll.dll

Другой вариант — создать пользовательский класс исключений, который фиксирует контекст в конструкторе и использовать его (или производные классы) для создания исключений:

class MyException{
public:
    CONTEXT Context;

    MyException(){
        RtlCaptureContext(&Context);        
    }
};

void function2()
{    
    throw MyException();    
}

//...   

try
{
     function0();
}
catch (MyException& e)
{       
    printf("Exception \n");     
    printStack(&e.Context);                 
}
person MSDN.WhiteKnight    schedule 07.05.2018
comment
Работает, хотя печатает номер строки следующей строки, которая выполняется после возврата текущей функции (например, если вызов находится в строке 10, будет напечатана следующая строка с кодом, который может быть 15) - person Top-Master; 18.11.2018
comment
также, как только *.pdb находится не там, где была собрана программа, у нас возникли некоторые проблемы, см. - person Top-Master; 18.11.2018

Если вы хотите зафиксировать трассировку стека в точке, где код выдал исключение, вы должны захватить трассировку стека в ctor объекта исключения и сохранить ее в объекте исключения. Следовательно, часть, вызывающая CaptureStackBackTrace(), должна быть перемещена в конструктор объекта исключения, который также должен предоставлять методы для его извлечения либо в виде вектора адресов, либо в виде вектора символов. Именно так работают Throwable в Java и Exception в C#.

Наконец, пожалуйста, не пишите:

throw new exception;

в C++, как в C# или Java. Это отличный способ как для создания утечек памяти, так и для предотвращения перехвата исключений по типам (поскольку вы выбрасываете указатели на эти типы). Скорее используйте:

throw exception();

Я знаю, что это старый вопрос, но люди (включая меня) все еще находят его.

person stari prdec    schedule 06.11.2017

вы пропускаете вызов ниже? SymInitialize(процесс, NULL, TRUE); SymSetOptions(SYMOPT_LOAD_LINES);

person Jacky Wang    schedule 28.05.2020