Programming/C++ & Unreal
StackWalk64가 context에 의하여 Access violation를 유발
장형이
2023. 10. 23. 04:49
#include <iostream>
#include <windows.h>
#include <dbghelp.h>
BOOL InitSymHandler(HANDLE hProcess) {
if (SymInitialize(hProcess, NULL, TRUE)) {
SymSetOptions(SYMOPT_LOAD_LINES);
return TRUE;
}
return FALSE;
}
LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exceptionInfo) {
CONTEXT* context = exceptionInfo->ContextRecord;
STACKFRAME64 stackFrame;
memset(&stackFrame, 0, sizeof(STACKFRAME64));
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrPC.Offset = context->Rip;
stackFrame.AddrStack.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = context->Rsp;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = context->Rbp;
while (StackWalk64(
IMAGE_FILE_MACHINE_AMD64,
GetCurrentProcess(),
GetCurrentThread(),
&stackFrame,
context,
NULL,
SymFunctionTableAccess64,
SymGetModuleBase64,
NULL)) {
DWORD64 address = stackFrame.AddrPC.Offset;
CHAR buffer[sizeof(SYMBOL_INFO) + 256] = { 0 };
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = 255;
if (SymFromAddr(GetCurrentProcess(), address, NULL, pSymbol)) {
std::string functionName = pSymbol->Name;
std::cout << "Function: " << functionName << " at 0x" << std::hex << address << std::endl;
}
else {
std::cout << "SymFromAddr failed, error: " << GetLastError() << std::endl;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
int main()
{
HANDLE hProcess = GetCurrentProcess();
InitSymHandler(hProcess);
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
char* a = 0;
*a = 5;
}
/*
출력 :
Function: main at 0x7ff6bd774587
Function: invoke_main at 0x7ff6bd7768f9
Function: __scrt_common_main_seh at 0x7ff6bd77679e
Function: __scrt_common_main at 0x7ff6bd77665e
Function: mainCRTStartup at 0x7ff6bd77698e
Function: BaseThreadInitThunk at 0x7ff8c79b7344
Function: RtlUserThreadStart at 0x7ff8c7b026b1
*/
흔히 사용하는 StackWalk64를 사용하여 stackTrace를 찍는 예제.
이 함수를 그대로 써서 현재 StackTrace를 찍어보고 싶어 개조해보았다.
LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS* exceptionInfo) {
CONTEXT* context = exceptionInfo->ContextRecord;
STACKFRAME64 stackFrame;
memset(&stackFrame, 0, sizeof(STACKFRAME64));
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrPC.Offset = context->Rip;
stackFrame.AddrStack.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = context->Rsp;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = context->Rbp;
while (StackWalk64(
IMAGE_FILE_MACHINE_AMD64,
GetCurrentProcess(),
GetCurrentThread(),
&stackFrame,
context,
NULL,
SymFunctionTableAccess64,
SymGetModuleBase64,
NULL)) {
DWORD64 address = stackFrame.AddrPC.Offset;
CHAR buffer[sizeof(SYMBOL_INFO) + 256] = { 0 };
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = 255;
if (SymFromAddr(GetCurrentProcess(), address, NULL, pSymbol)) {
std::string functionName = pSymbol->Name;
std::cout << "Function: " << functionName << " at 0x" << std::hex << address << std::endl;
}
else {
std::cout << "SymFromAddr failed, error: " << GetLastError() << std::endl;
}
}
return EXCEPTION_EXECUTE_HANDLER;
}
void PrintStackTrace() {
__try {
RaiseException(1, 0, 0, NULL);
} // StackTrace 출력 후 여기서 Access violation이 발생한다.
__except (ExceptionHandler(GetExceptionInformation())) {
}
}
int main()
{
HANDLE hProcess = GetCurrentProcess();
InitSymHandler(hProcess);
PrintStackTrace();
}
이렇게 강제로 Exception을 만들어서 StackTrace만 찍고 돌아가려고 했는데, stack unwind하는 과정에서 Access violation exception이 발생한다.
LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS* exceptionInfo) {
CONTEXT context;
memcpy(&context, exceptionInfo->ContextRecord, sizeof(CONTEXT));
STACKFRAME64 stackFrame;
memset(&stackFrame, 0, sizeof(STACKFRAME64));
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrPC.Offset = context.Rip;
stackFrame.AddrStack.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = context.Rsp;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = context.Rbp;
while (StackWalk64(
IMAGE_FILE_MACHINE_AMD64,
GetCurrentProcess(),
GetCurrentThread(),
&stackFrame,
&context,
NULL,
SymFunctionTableAccess64,
SymGetModuleBase64,
NULL)) {
DWORD64 address = stackFrame.AddrPC.Offset;
CHAR buffer[sizeof(SYMBOL_INFO) + 256] = { 0 };
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = 255;
if (SymFromAddr(GetCurrentProcess(), address, NULL, pSymbol)) {
std::string functionName = pSymbol->Name;
std::cout << "Function: " << functionName << " at 0x" << std::hex << address << std::endl;
}
else {
std::cout << "SymFromAddr failed, error: " << GetLastError() << std::endl;
}
}
return EXCEPTION_EXECUTE_HANDLER;
}
void PrintStackTrace() {
__try {
RaiseException(1, 0, 0, NULL);
}
__except (ExceptionHandler(GetExceptionInformation())) {
}
}
int main()
{
HANDLE hProcess = GetCurrentProcess();
InitSymHandler(hProcess);
PrintStackTrace();
}
이렇게 Context를 copy하여 사용하면 문제가 발생하지 않는다.
StackWalk64 함수가 Context를 훼손시키는데, StackTrace 출력 이후 로직에서 ContextRecord를 다시 사용하기 때문에 Exception이 발생하는 것이다.
어찌되었건 이런 방식은 너무 이상하고 느리기 때문에 boost::stacktrace나 더 좋은 도구들을 사용하여서 stackTrace를 출력하는 것이 좋을 것 같다.