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

8

Решение

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

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

throw new exception;

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

throw exception();

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

1

Другие решения

На окнах необработанное исключение C ++ автоматически генерирует исключение SEH. SEH __Кроме блок позволяет прикрепить фильтр, который принимает _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 new 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);
}
1

По вопросам рекламы [email protected]