Mercurial > projects > ddmd
comparison dlib/CrashHandler.d @ 0:10317f0c89a5
Initial commit
author | korDen |
---|---|
date | Sat, 24 Oct 2009 08:42:06 +0400 |
parents | |
children | 2cc604139636 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:10317f0c89a5 |
---|---|
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); |