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를 출력하는 것이 좋을 것 같다.