Ловим необработанные исключения под Windows
Обработка исключений — это всегда скучно и многословно, никому не хочется загромождать свой код вездесущими 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