Обработка исключений — это всегда скучно и многословно, никому не хочется загромождать свой код вездесущими try/catch. Кроме того, в D обработка исключений на уровне языка не работает с SEH-исключениями под x86_64, что сильно усложняет отлов багов, связанных с доступом по невалидному указателю — приложение просто падает молча, что не соответствует принципу «fail loudly». Но оказалось, что есть относительно простой способ решения этой проблемы.

Windows позволяет навесить обработчик для любых необработанных исключений следующим образом:

version(Windows)
{
    import core.sys.windows.windows;
    import core.sys.windows.dbghelp;

    extern(Windows) LONG crashHandler(EXCEPTION_POINTERS* info)
    {
        auto record = info.ExceptionRecord;
        writefln("Unhandled exception: 0x%08X", record.ExceptionCode);
        return EXCEPTION_EXECUTE_HANDLER;
    }
}

void main()
{
    version(Windows)
    {
        SetUnhandledExceptionFilter(&crashHandler);
    }
}

Уже неплохо, но можно лучше — добавим трассировку стека, в чем нам помогут функция WinAPI StackWalk64 и модуль core.sys.windows.stacktrace:

import core.sys.windows.stacktrace;

extern(Windows) nothrow
{
    alias PREAD_PROCESS_MEMORY_ROUTINE64 = BOOL function(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead);
    alias PFUNCTION_TABLE_ACCESS_ROUTINE64 = PVOID function(HANDLE ahProcess, DWORD64 AddrBase);
    alias PGET_MODULE_BASE_ROUTINE64 = DWORD64 function(HANDLE hProcess, DWORD64 Address);
    alias PTRANSLATE_ADDRESS_ROUTINE64 = DWORD function(HANDLE hProcess, HANDLE hThread, LPADDRESS lpaddr);

    BOOL StackWalk64(
        DWORD MachineType,
        HANDLE hProcess,
        HANDLE hThread,
        STACKFRAME64* StackFrame,
        PVOID ContextRecord,
        PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
        PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
        PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
        PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
    );

    PVOID SymFunctionTableAccess64(HANDLE hProcess, DWORD64 AddrBase);
    DWORD64 SymGetModuleBase64(HANDLE hProcess, DWORD64 qwAddr);
}

private enum MAX_FRAMES = 64;
private __gshared ulong[MAX_FRAMES] addresses;

private ulong[] traceContext(CONTEXT* ctx)
{
    STACKFRAME64 frame;
    frame.AddrPC.Offset    = ctx.Rip;
    frame.AddrFrame.Offset = ctx.Rbp;
    frame.AddrStack.Offset = ctx.Rsp;
    frame.AddrPC.Mode    = ADDRESS_MODE.AddrModeFlat;
    frame.AddrFrame.Mode = ADDRESS_MODE.AddrModeFlat;
    frame.AddrStack.Mode = ADDRESS_MODE.AddrModeFlat;

    auto process = GetCurrentProcess();
    auto thread  = GetCurrentThread();

    int numFrames = 0;
    for (numFrames = 0; numFrames < MAX_FRAMES; numFrames++)
    {
        if (!StackWalk64(
            IMAGE_FILE_MACHINE_AMD64,
            process,
            thread,
            &frame,
            ctx,
            null,
            &SymFunctionTableAccess64,
            &SymGetModuleBase64,
            null))
        {
            break;
        }

        if (frame.AddrPC.Offset == 0)
            break;

        addresses[numFrames] = frame.AddrPC.Offset;
    }

    return addresses[0..numFrames];
}

extern(Windows) LONG crashHandler(EXCEPTION_POINTERS* info)
{
    auto record = info.ExceptionRecord;
    debug version(X86_64)
    {
        ulong[] addresses = traceContext(info.ContextRecord);
        char[][] symbols = StackTrace.resolve(addresses);

        writeln("Stack trace:");
        foreach (i, symbol; symbols)
        {
            writefln("  %s", symbol);
        }
    }

    writefln("Unhandled exception: 0x%08X", record.ExceptionCode);

    return EXCEPTION_EXECUTE_HANDLER;
}

Теперь при падении будет выведено информативное сообщение типа такого:

Unhandled exception 0xC0000005
Stack trace:
  0x00007FF6E7351532 in app.D main at E:\dlang\test\source\app.d(109)
  0x00007FF6E73952E1 in void rt.dmain2._d_run_main2(char[][], ulong, extern (C) int function(char[][])*).runAll()
  0x00007FF6E7394F8B in d_run_main2
  0x00007FF6E7395246 in d_wrun_main
  0x00007FF6E7351564 in app.wmain at C:\Users\gecko\Documents\Software\dlang\ldc2-1.41.0\import\core\internal\entrypoint.d(32)
  0x00007FF6E73BA1D8 in __scrt_common_main_seh at D:\a01\_work\9\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288)
  0x00007FFF8EFA7374 in BaseThreadInitThunk
  0x00007FFF8F85CC91 in RtlUserThreadStart

Оставить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *