0
|
1 /**
|
|
2 A simple runtime crash handler which collects various informations about
|
|
3 the crash such as registers, stack traces, and loaded modules.
|
|
4
|
|
5 TODO:
|
|
6 * Threading support
|
|
7 * Stack dumps
|
|
8
|
|
9 Authors:
|
|
10 Jeremie Pelletier
|
|
11
|
|
12 License:
|
|
13 Public Domain
|
|
14 */
|
|
15 module dlib.CrashHandler;
|
|
16
|
|
17 debug = CrashHandler;
|
|
18
|
|
19 import std.c.stdio;
|
|
20 import dbg.Debug;
|
|
21 import dbg.ui.CrashWindow;
|
|
22
|
|
23 version(X86) {}
|
|
24 else static assert(0, "Unsupported architecture.");
|
|
25
|
|
26 version(DigitalMars) {
|
|
27 //import dlib.dmd.ErrorHandling;
|
|
28 }
|
|
29 else static assert(0, "Unsupported compiler.");
|
|
30
|
|
31 version(Windows) {
|
|
32 import win32.windows;
|
|
33 import win32.psapi;
|
|
34 //import sys.windows.Memory;
|
|
35 //import dlib.Module;
|
|
36 import dbg.image.PE;
|
|
37 import dbg.symbol.CodeView;
|
|
38 }
|
|
39 else version(Posix) {
|
|
40 import sys.posix.ucontext;
|
|
41 import std.c.signal;
|
|
42 import std.c.stdlib : free, exit, EXIT_FAILURE;
|
|
43 }
|
|
44 else static assert(0, "Unsupported platform.");
|
|
45
|
|
46 /**
|
|
47 Register the crash handler
|
|
48 */
|
|
49 void CrashHandlerInit() {
|
|
50 version(Windows) {
|
|
51 //SetErrorMode(SetErrorMode(0) | SEM_FAILCRITICALERRORS);
|
|
52 SetErrorMode(0);
|
|
53 SetUnhandledExceptionFilter(&UnhandledExceptionHandler);
|
|
54 }
|
|
55 else version(Posix) {
|
|
56 sigaction_t sa;
|
|
57 sa.sa_handler = cast(sighandler_t)&SignalHandler;
|
|
58 sigemptyset(&sa.sa_mask);
|
|
59 sa.sa_flags = SA_RESTART | SA_SIGINFO;
|
|
60
|
|
61 sigaction(SIGILL, &sa, null);
|
|
62 sigaction(SIGFPE, &sa, null);
|
|
63 sigaction(SIGSEGV, &sa, null);
|
|
64 }
|
|
65 else static assert(0);
|
|
66 }
|
|
67
|
|
68 /**
|
|
69 Information collected by the crash handler
|
|
70 */
|
|
71 struct CrashInfo {
|
|
72 struct Registers {
|
|
73 version(X86) {
|
|
74 uint EAX, EBX, ECX, EDX;
|
|
75 uint EDI, ESI;
|
|
76 uint EBP, ESP;
|
|
77 }
|
|
78 else static assert(0);
|
|
79 }
|
|
80
|
|
81 struct Module {
|
|
82 string fileName;
|
|
83 ushort[4] fileVersion;
|
|
84 }
|
|
85
|
|
86 Throwable error;
|
|
87 string moduleName;
|
|
88 Registers registers;
|
|
89 size_t[] backtrace;
|
|
90 Module[] modules;
|
|
91
|
|
92 void Dump() {
|
|
93 // TODO: support more dump methods
|
|
94 ShowCrashWindow(&this);
|
|
95 }
|
|
96
|
|
97 /**
|
|
98 Formats the crash info as plain-text
|
|
99 */
|
|
100 string toString() {
|
|
101 string text;
|
|
102 char[255] buffer = void;
|
|
103 uint len = void;
|
|
104
|
|
105 text ~= error.toString();
|
|
106 text ~= "\r\n\r\n";
|
|
107
|
|
108 version(X86) {
|
|
109 with(registers) len = snprintf(buffer.ptr, buffer.length,
|
|
110 " Registers:\r\n" ~
|
|
111 "========================================\r\n" ~
|
|
112 " EAX=0x%08X EBX=0x%08X ECX=0x%08X EDX=0x%08X\r\n" ~
|
|
113 " EDI=0x%08X ESI=0x%08X EBP=0x%08X ESP=0x%08X\r\n",
|
|
114 EAX, EBX, ECX, EDX, EDI, ESI, EBP, ESP
|
|
115 );
|
|
116 text ~= buffer[0 .. len] ~ "\r\n";
|
|
117 }
|
|
118 else static assert(0);
|
|
119
|
|
120 text ~= " Stack Trace:\r\n" ~
|
|
121 "========================================\r\n";
|
|
122
|
|
123 scope auto frames = new StackFrameInfo[backtrace.length];
|
|
124 ResolveStackFrames(frames);
|
|
125
|
|
126 foreach(ref frame; frames) {
|
|
127 //len = snprintf(buffer.ptr, buffer.length, "%p", frame.va);
|
|
128 //text ~= " " ~ buffer[0 .. len] ~ ": " ~ frame.moduleName ~ "\r\n";
|
|
129
|
|
130 //with(frame.symbol) if(name.length) {
|
|
131 // len = snprintf(buffer.ptr, buffer.length, "%X", offset);
|
|
132 // text ~= " " ~ name ~ " @ 0x" ~ buffer[0 .. len] ~ "\r\n";
|
|
133 //}
|
|
134
|
|
135 with(frame.fileLine) if(line) {
|
|
136 len = snprintf(buffer.ptr, buffer.length, "%u", line);
|
|
137 text ~= " " ~ file ~ ":" ~ buffer[0 .. len] ~ "\r\n";
|
|
138 }
|
|
139
|
|
140 //text ~= "\r\n";
|
|
141 }
|
|
142 /+
|
|
143 text ~= " Loaded Modules:\r\n" ~
|
|
144 "========================================\r\n";
|
|
145
|
|
146 foreach(mod; modules) {
|
|
147 len = snprintf(buffer.ptr, buffer.length, "%hu.%hu.%hu.%hu",
|
|
148 mod.fileVersion[0], mod.fileVersion[1],
|
|
149 mod.fileVersion[2], mod.fileVersion[3]);
|
|
150
|
|
151 text ~= " " ~ mod.fileName ~ "\r\n " ~ buffer[0 .. len] ~ "\r\n";
|
|
152 }
|
|
153 +/
|
|
154 text ~= '\0';
|
|
155
|
|
156 return text;
|
|
157 }
|
|
158
|
|
159 private:
|
|
160
|
|
161 struct StackFrameInfo {
|
|
162 size_t va;
|
|
163 string moduleName;
|
|
164 SymbolInfo symbol;
|
|
165 FileLineInfo fileLine;
|
|
166 }
|
|
167
|
|
168 struct DebugImage {
|
|
169 DebugImage* next;
|
|
170 string moduleName;
|
|
171 size_t baseAddress;
|
|
172 uint rvaOffset;
|
|
173 IExecutableImage exeModule;
|
|
174 ISymbolicDebugInfo debugInfo;
|
|
175 }
|
|
176
|
|
177 version(X86) {
|
|
178 void ResolveStackFrames(StackFrameInfo[] frames) const {
|
|
179 StackFrameInfo* frame = void;
|
|
180 DebugImage* imageList, image = void;
|
|
181 char[255] buffer = void;
|
|
182 uint len = void;
|
|
183 uint rva = void;
|
|
184
|
|
185 version(Windows) MEMORY_BASIC_INFORMATION mbi = void;
|
|
186
|
|
187 foreach(i, va; backtrace) {
|
|
188 frame = &frames[i];
|
|
189 frame.va = va;
|
|
190
|
|
191 version(Windows) {
|
|
192 // mbi.Allocation base is the handle to stack frame's module
|
|
193 VirtualQuery(cast(void*)va, &mbi, MEMORY_BASIC_INFORMATION.sizeof);
|
|
194 if(!mbi.AllocationBase) break;
|
|
195
|
|
196 image = imageList;
|
|
197 while(image) {
|
|
198 if(image.baseAddress == cast(size_t)mbi.AllocationBase) break;
|
|
199 image = image.next;
|
|
200 }
|
|
201
|
|
202 if(!image) {
|
|
203 image = new DebugImage;
|
|
204
|
|
205 with(*image) {
|
|
206 next = imageList;
|
|
207 imageList = image;
|
|
208 baseAddress = cast(size_t)mbi.AllocationBase;
|
|
209
|
|
210 len = GetModuleFileNameA(cast(HMODULE)baseAddress, buffer.ptr, buffer.length);
|
|
211 moduleName = buffer[0 .. len].idup;
|
|
212
|
|
213 exeModule = new PEImage(moduleName);
|
|
214 rvaOffset = baseAddress + exeModule.codeOffset;
|
|
215 debugInfo = exeModule.debugInfo;
|
|
216 }
|
|
217 }
|
|
218 }
|
|
219 else static assert(0);
|
|
220
|
|
221 frame.moduleName = image.moduleName;
|
|
222
|
|
223 if(!image.debugInfo) continue;
|
|
224
|
|
225 rva = va - image.rvaOffset;
|
|
226
|
|
227 with(image.debugInfo) {
|
|
228 frame.symbol = ResolveSymbol(rva);
|
|
229 frame.fileLine = ResolveFileLine(rva);
|
|
230 }
|
|
231 }
|
|
232
|
|
233 while(imageList) {
|
|
234 image = imageList.next;
|
|
235 delete imageList.debugInfo;
|
|
236 delete imageList.exeModule;
|
|
237 delete imageList;
|
|
238 imageList = image;
|
|
239 }
|
|
240 }
|
|
241 } // version(X86)
|
|
242 else static assert(0);
|
|
243 }
|
|
244
|
|
245 // ----------------------------------------------------------------------------
|
|
246 // W i n d o w s C r a s h H a n d l e r
|
|
247 // ----------------------------------------------------------------------------
|
|
248
|
|
249 version(Windows) {
|
|
250
|
|
251 extern(C) Throwable _d_translate_se_to_d_exception(EXCEPTION_RECORD* exception_record);
|
|
252
|
|
253 /**
|
|
254 D exceptions are built on top of Windows' SEH, a simple registered callback
|
|
255 will catch any exceptions unwinding past a thread's entry point.
|
|
256 */
|
|
257 extern(Windows)
|
|
258 int UnhandledExceptionHandler(EXCEPTION_POINTERS* e) {
|
|
259 CrashInfo crashInfo = void;
|
|
260 char[256] buffer = void;
|
|
261 uint len = void;
|
|
262 size_t ip = void, bp = void;
|
|
263
|
|
264 try {
|
|
265 with(crashInfo) {
|
|
266
|
|
267 version(DigitalMars)
|
|
268 error = _d_translate_se_to_d_exception(e.ExceptionRecord);
|
|
269 else
|
|
270 static assert(0);
|
|
271
|
|
272 len = GetModuleFileNameA(GetModuleHandle(null), buffer.ptr, buffer.length);
|
|
273 moduleName = buffer[0 .. len].idup;
|
|
274
|
|
275 version(X86) with(*e.ContextRecord) {
|
|
276 with(registers) {
|
|
277 EAX = Eax, EBX = Ebx, ECX = Ecx, EDX = Edx;
|
|
278 EDI = Edi, ESI = Esi;
|
|
279 EBP = Ebp, ESP = Esp;
|
|
280 }
|
|
281
|
|
282 ip = Eip;
|
|
283 bp = Ebp;
|
|
284 }
|
|
285 else static assert(0);
|
|
286
|
|
287 backtrace = null;
|
|
288 while(ip) {
|
|
289 backtrace ~= ip;
|
|
290
|
|
291 ip = cast(size_t)*(cast(void**)bp + 1);
|
|
292 bp = cast(size_t)*cast(void**)bp;
|
|
293 }
|
|
294
|
|
295 HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
|
|
296 0, GetCurrentProcessId());
|
|
297
|
|
298 if(process == INVALID_HANDLE_VALUE) SystemException();
|
|
299 scope(exit) if(!CloseHandle(process)) SystemException();
|
|
300
|
|
301 scope HMODULE[] hmodules = new HMODULE[64];
|
|
302 uint size = HMODULE.sizeof * hmodules.length;
|
|
303 uint sizeNeeded = void;
|
|
304 uint nModules = void;
|
|
305
|
|
306 GetModules:
|
|
307 if(!EnumProcessModules(process, hmodules.ptr, size, &sizeNeeded))
|
|
308 SystemException();
|
|
309
|
|
310 nModules = sizeNeeded / HMODULE.sizeof;
|
|
311
|
|
312 if(sizeNeeded > size) {
|
|
313 hmodules.length = nModules;
|
|
314 size = sizeNeeded;
|
|
315 goto GetModules;
|
|
316 }
|
|
317
|
|
318 Module mod = void;
|
|
319 char[] versionInfo;
|
|
320 VS_FIXEDFILEINFO* fixedVersionInfo = void;
|
|
321
|
|
322 modules = null;
|
|
323 foreach(i; 0 .. nModules) {
|
|
324 len = GetModuleFileNameA(hmodules[i], buffer.ptr, buffer.length);
|
|
325 mod.fileName = buffer[0 .. len].idup;
|
|
326
|
|
327 sizeNeeded = GetFileVersionInfoSizeA(buffer.ptr, &size);
|
|
328
|
|
329 if(sizeNeeded) {
|
|
330 if(versionInfo.length < sizeNeeded) versionInfo.length = sizeNeeded;
|
|
331
|
|
332 if(!GetFileVersionInfoA(buffer.ptr, 0, versionInfo.length, versionInfo.ptr))
|
|
333 SystemException();
|
|
334
|
|
335 if(!VerQueryValueA(versionInfo.ptr, cast(char*)"\\".ptr, cast(void**)&fixedVersionInfo, &size))
|
|
336 SystemException();
|
|
337
|
|
338 with(*fixedVersionInfo) with(mod) {
|
|
339 fileVersion[0] = HIWORD(dwProductVersionMS);
|
|
340 fileVersion[1] = LOWORD(dwProductVersionMS);
|
|
341 fileVersion[2] = HIWORD(dwProductVersionLS);
|
|
342 fileVersion[3] = LOWORD(dwProductVersionLS);
|
|
343 }
|
|
344 }
|
|
345 else {
|
|
346 mod.fileVersion[] = 0;
|
|
347 }
|
|
348
|
|
349 modules ~= mod;
|
|
350 }
|
|
351
|
|
352 } // with(crashInfo)
|
|
353
|
|
354 crashInfo.Dump();
|
|
355 }
|
|
356 catch(Throwable e) {
|
|
357 debug MessageBoxA(HWND.init, (e.toString ~ '\0').ptr, "Exception Handler Error!", MB_ICONERROR | MB_OK);
|
|
358 }
|
|
359
|
|
360 return EXCEPTION_EXECUTE_HANDLER;
|
|
361 }
|
|
362
|
|
363 } // version(Windows)
|
|
364
|
|
365 // ----------------------------------------------------------------------------
|
|
366 // P o s i x C r a s h H a n d l e r
|
|
367 // ----------------------------------------------------------------------------
|
|
368
|
|
369 else version(Posix) {
|
|
370
|
|
371 /**
|
|
372 This handler catches system signals and throws the appropriate D exception.
|
|
373 The exception will unwind down to the thread's entry point where it is catched
|
|
374 and sent to UnhandledExceptionHandler().
|
|
375 */
|
|
376 extern(C)
|
|
377 void SignalHandler(int signum, siginfo_t* siginfo, ucontext_t* ucontext) {
|
|
378 string msg = void;
|
|
379
|
|
380 switch(signum) {
|
|
381 case SIGILL: msg = "Illegal instruction"; break;
|
|
382 case SIGFPE: msg = "Floating-point exception"; break;
|
|
383 case SIGSEGV: msg = "Segmentation fault"; break;
|
|
384 default: msg = "Unknown signal";
|
|
385 }
|
|
386
|
|
387 SystemException e = new SystemException(msg);
|
|
388
|
|
389 e._context = ucontext;
|
|
390 e.GetBackTrace();
|
|
391
|
|
392 // The kernel fixed the stack frame to make us believe we called this
|
|
393 // routine ourselves, with the nasty side effect of losing the faulty
|
|
394 // routine's address. The undocumented parameter ucontext contains our
|
|
395 // lost EIP.
|
|
396 version(X86) {
|
|
397 // It should be the 3rd frame:
|
|
398 // SignalHandler() -> GetBackTrace() -> backtrace()
|
|
399 if(e._backtrace.length > 2)
|
|
400 e._backtrace[2] = cast(void*)ucontext.uc_mcontext.gregs[REG_EIP];
|
|
401 }
|
|
402 else static assert(0);
|
|
403
|
|
404 throw e;
|
|
405 }
|
|
406
|
|
407 /**
|
|
408 This handler is called when an exception unwinds down to the thread's entry
|
|
409 point, which should catch it and manually call this routine.
|
|
410 */
|
|
411 void UnhandledExceptionHandler(Throwable e) {
|
|
412 ErrorReport crashInfo = void;
|
|
413
|
|
414 with(crashInfo) {
|
|
415 assert(0);
|
|
416 /+error = e;
|
|
417
|
|
418 // Get the module filename
|
|
419 // TODO
|
|
420
|
|
421 // Dump the general purpose registers
|
|
422 if(e._context) {
|
|
423 gregset_t gregs = e._context.uc_mcontext.gregs;
|
|
424
|
|
425 version(X86) {
|
|
426 registers.Eax = gregs[REG_EAX];
|
|
427 registers.Ebx = gregs[REG_EBX];
|
|
428 registers.Ecx = gregs[REG_ECX];
|
|
429 registers.Edx = gregs[REG_EDX];
|
|
430 registers.Edi = gregs[REG_EDI];
|
|
431 registers.Esi = gregs[REG_ESI];
|
|
432 registers.Ebp = gregs[REG_EBP];
|
|
433 registers.Esp = gregs[REG_ESP];
|
|
434 }
|
|
435 else static assert(0);
|
|
436 }
|
|
437
|
|
438 // Dump stack backtrace
|
|
439 addresses = e._backtrace;
|
|
440
|
|
441 // Dump the loaded modules
|
|
442 // TODO+/
|
|
443
|
|
444 } // with(crashInfo)
|
|
445
|
|
446 crashInfo.Dump();
|
|
447 }
|
|
448
|
|
449 } // version(Posix)
|
|
450 else static assert(0);
|