Подтвердить что ты не робот

Как вы можете использовать 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;
}

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

        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 Этот код был запущен для консольного приложения с использованием MSVС++ с включенным unicode на компьютере с Windows 8.1 x64, при этом приложение запускалось как приложение Win32 в режиме отладки.

4b9b3361

Ответ 1

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

Ответ 2

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

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

throw new exception;

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

throw exception();

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