view dlib/CrashHandler.d @ 0:10317f0c89a5

Initial commit
author korDen
date Sat, 24 Oct 2009 08:42:06 +0400
parents
children 2cc604139636
line wrap: on
line source

/**
 A simple runtime crash handler which collects various informations about
 the crash such as registers, stack traces, and loaded modules.

 TODO:
	* Threading support
	* Stack dumps

 Authors:
	Jeremie Pelletier

 License:
	Public Domain
*/
module dlib.CrashHandler;

debug = CrashHandler;

import std.c.stdio;
import dbg.Debug;
import dbg.ui.CrashWindow;

version(X86) {}
else static assert(0, "Unsupported architecture.");

version(DigitalMars) {
	//import dlib.dmd.ErrorHandling;
}
else static assert(0, "Unsupported compiler.");

version(Windows) {
	import win32.windows;
	import win32.psapi;
	//import sys.windows.Memory;
	//import dlib.Module;
	import dbg.image.PE;
	import dbg.symbol.CodeView;
}
else version(Posix) {
	import sys.posix.ucontext;
	import std.c.signal;
	import std.c.stdlib : free, exit, EXIT_FAILURE;
}
else static assert(0, "Unsupported platform.");

/**
 Register the crash handler
*/
void CrashHandlerInit() {
	version(Windows) {
		//SetErrorMode(SetErrorMode(0) | SEM_FAILCRITICALERRORS);
		SetErrorMode(0);
		SetUnhandledExceptionFilter(&UnhandledExceptionHandler);
	}
	else version(Posix) {
		sigaction_t sa;
		sa.sa_handler = cast(sighandler_t)&SignalHandler;
		sigemptyset(&sa.sa_mask);
		sa.sa_flags = SA_RESTART | SA_SIGINFO;

		sigaction(SIGILL, &sa, null);
		sigaction(SIGFPE, &sa, null);
		sigaction(SIGSEGV, &sa, null);
	}
	else static assert(0);
}

/**
 Information collected by the crash handler
*/
struct CrashInfo {
	struct Registers {
		version(X86) {
			uint EAX, EBX, ECX, EDX;
			uint EDI, ESI;
			uint EBP, ESP;
		}
		else static assert(0);
	}

	struct Module {
		string fileName;
		ushort[4] fileVersion;
	}

	Throwable	error;
	string		moduleName;
	Registers	registers;
	size_t[]	backtrace;
	Module[]	modules;

	void Dump() {
		// TODO: support more dump methods
		ShowCrashWindow(&this);
	}

	/**
	 Formats the crash info as plain-text
	*/
	string toString() {
		string text;
		char[255] buffer = void;
		uint len = void;

		text ~= error.toString();
		text ~= "\r\n\r\n";

		version(X86) {
			with(registers) len = snprintf(buffer.ptr, buffer.length,
				" Registers:\r\n" ~
				"========================================\r\n" ~
				"  EAX=0x%08X  EBX=0x%08X  ECX=0x%08X  EDX=0x%08X\r\n" ~
				"  EDI=0x%08X  ESI=0x%08X  EBP=0x%08X  ESP=0x%08X\r\n",
				EAX, EBX, ECX, EDX, EDI, ESI, EBP, ESP
			);
			text ~= buffer[0 .. len] ~ "\r\n";
		}
		else static assert(0);

		text ~= " Stack Trace:\r\n" ~
			"========================================\r\n";

		scope auto frames = new StackFrameInfo[backtrace.length];
		ResolveStackFrames(frames);

		foreach(ref frame; frames) {
			//len = snprintf(buffer.ptr, buffer.length, "%p", frame.va);
			//text ~= "  " ~ buffer[0 .. len] ~ ": " ~ frame.moduleName ~ "\r\n";

			//with(frame.symbol) if(name.length) {
			//	len = snprintf(buffer.ptr, buffer.length, "%X", offset);
			//	text ~= "    " ~ name ~ " @ 0x" ~ buffer[0 .. len] ~ "\r\n";
			//}

			with(frame.fileLine) if(line) {
				len = snprintf(buffer.ptr, buffer.length, "%u", line);
				text ~= "    " ~ file ~ ":" ~ buffer[0 .. len] ~ "\r\n";
			}

			//text ~= "\r\n";
		}
/+
		text ~= " Loaded Modules:\r\n" ~
			"========================================\r\n";

		foreach(mod; modules) {
			len = snprintf(buffer.ptr, buffer.length, "%hu.%hu.%hu.%hu",
				mod.fileVersion[0], mod.fileVersion[1],
				mod.fileVersion[2], mod.fileVersion[3]);

			text ~= "  " ~ mod.fileName ~ "\r\n    " ~ buffer[0 .. len] ~ "\r\n";
		}
+/
		text ~= '\0';

		return text;
	}

private:

	struct StackFrameInfo {
		size_t			va;
		string			moduleName;
		SymbolInfo		symbol;
		FileLineInfo	fileLine;
	}

	struct DebugImage {
		DebugImage*			next;
		string				moduleName;
		size_t				baseAddress;
		uint				rvaOffset;
		IExecutableImage	exeModule;
		ISymbolicDebugInfo	debugInfo;
	}

	version(X86) {
	void ResolveStackFrames(StackFrameInfo[] frames) const {
		StackFrameInfo* frame = void;
		DebugImage* imageList, image = void;
		char[255] buffer = void;
		uint len = void;
		uint rva = void;

		version(Windows) MEMORY_BASIC_INFORMATION mbi = void;

		foreach(i, va; backtrace) {
			frame = &frames[i];
			frame.va = va;

			version(Windows) {
				// mbi.Allocation base is the handle to stack frame's module
				VirtualQuery(cast(void*)va, &mbi, MEMORY_BASIC_INFORMATION.sizeof);
				if(!mbi.AllocationBase) break;

				image = imageList;
				while(image) {
					if(image.baseAddress == cast(size_t)mbi.AllocationBase) break;
					image = image.next;
				}

				if(!image) {
					image = new DebugImage;

					with(*image) {
						next = imageList;
						imageList = image;
						baseAddress = cast(size_t)mbi.AllocationBase;

						len = GetModuleFileNameA(cast(HMODULE)baseAddress, buffer.ptr, buffer.length);
						moduleName = buffer[0 .. len].idup;

						exeModule = new PEImage(moduleName);
						rvaOffset = baseAddress + exeModule.codeOffset;
						debugInfo = exeModule.debugInfo;
					}
				}
			}
			else static assert(0);

			frame.moduleName = image.moduleName;

			if(!image.debugInfo) continue;

			rva = va - image.rvaOffset;

			with(image.debugInfo) {
				frame.symbol = ResolveSymbol(rva);
				frame.fileLine = ResolveFileLine(rva);
			}
		}

		while(imageList) {
			image = imageList.next;
			delete imageList.debugInfo;
			delete imageList.exeModule;
			delete imageList;
			imageList = image;
		}
	}
	} // version(X86)
	else static assert(0);
}

// ----------------------------------------------------------------------------
// W i n d o w s  C r a s h  H a n d l e r
// ----------------------------------------------------------------------------

version(Windows) {

extern(C) Throwable _d_translate_se_to_d_exception(EXCEPTION_RECORD* exception_record);

/**
 D exceptions are built on top of Windows' SEH, a simple registered callback
 will catch any exceptions unwinding past a thread's entry point.
*/
extern(Windows)
int UnhandledExceptionHandler(EXCEPTION_POINTERS* e) {
	CrashInfo crashInfo = void;
	char[256] buffer = void;
	uint len = void;
	size_t ip = void, bp = void;

	try {
		with(crashInfo) {

		version(DigitalMars)
			error = _d_translate_se_to_d_exception(e.ExceptionRecord);
		else
			static assert(0);

		len = GetModuleFileNameA(GetModuleHandle(null), buffer.ptr, buffer.length);
		moduleName = buffer[0 .. len].idup;

		version(X86) with(*e.ContextRecord) {
			with(registers) {
				EAX = Eax, EBX = Ebx, ECX = Ecx, EDX = Edx;
				EDI = Edi, ESI = Esi;
				EBP = Ebp, ESP = Esp;
			}

			ip = Eip;
			bp = Ebp;
		}
		else static assert(0);

		backtrace = null;
		while(ip) {
			backtrace ~= ip;

			ip = cast(size_t)*(cast(void**)bp + 1);
			bp = cast(size_t)*cast(void**)bp;
		}

		HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
			0, GetCurrentProcessId());

		if(process == INVALID_HANDLE_VALUE) SystemException();
		scope(exit) if(!CloseHandle(process)) SystemException();

		scope HMODULE[] hmodules = new HMODULE[64];
		uint size = HMODULE.sizeof * hmodules.length;
		uint sizeNeeded = void;
		uint nModules = void;

	GetModules:
		if(!EnumProcessModules(process, hmodules.ptr, size, &sizeNeeded))
			SystemException();

		nModules = sizeNeeded / HMODULE.sizeof;

		if(sizeNeeded > size) {
			hmodules.length = nModules;
			size = sizeNeeded;
			goto GetModules;
		}

		Module mod = void;
		char[] versionInfo;
		VS_FIXEDFILEINFO* fixedVersionInfo = void;

		modules = null;
		foreach(i; 0 .. nModules) {
			len = GetModuleFileNameA(hmodules[i], buffer.ptr, buffer.length);
			mod.fileName = buffer[0 .. len].idup;

			sizeNeeded = GetFileVersionInfoSizeA(buffer.ptr, &size);

			if(sizeNeeded) {
				if(versionInfo.length < sizeNeeded) versionInfo.length = sizeNeeded;

				if(!GetFileVersionInfoA(buffer.ptr, 0, versionInfo.length, versionInfo.ptr))
					SystemException();

				if(!VerQueryValueA(versionInfo.ptr, cast(char*)"\\".ptr, cast(void**)&fixedVersionInfo, &size))
					SystemException();

				with(*fixedVersionInfo) with(mod) {
					fileVersion[0] = HIWORD(dwProductVersionMS);
					fileVersion[1] = LOWORD(dwProductVersionMS);
					fileVersion[2] = HIWORD(dwProductVersionLS);
					fileVersion[3] = LOWORD(dwProductVersionLS);
				}
			}
			else {
				mod.fileVersion[] = 0;
			}

			modules ~= mod;
		}

		} // with(crashInfo)

		crashInfo.Dump();
	}
	catch(Throwable e) {
		debug MessageBoxA(HWND.init, (e.toString ~ '\0').ptr, "Exception Handler Error!", MB_ICONERROR | MB_OK);
	}

	return EXCEPTION_EXECUTE_HANDLER;
}

} // version(Windows)

// ----------------------------------------------------------------------------
// P o s i x  C r a s h  H a n d l e r
// ----------------------------------------------------------------------------

else version(Posix) {

/**
 This handler catches system signals and throws the appropriate D exception.
 The exception will unwind down to the thread's entry point where it is catched
 and sent to UnhandledExceptionHandler().
*/
extern(C)
void SignalHandler(int signum, siginfo_t* siginfo, ucontext_t* ucontext) {
	string msg = void;

	switch(signum) {
	case SIGILL:	msg = "Illegal instruction";		break;
	case SIGFPE:	msg = "Floating-point exception";	break;
	case SIGSEGV:	msg = "Segmentation fault";			break;
	default:		msg = "Unknown signal";
	}

	SystemException e = new SystemException(msg);

	e._context = ucontext;
	e.GetBackTrace();

	// The kernel fixed the stack frame to make us believe we called this
	// routine ourselves, with the nasty side effect of losing the faulty
	// routine's address. The undocumented parameter ucontext contains our
	// lost EIP.
	version(X86) {
		// It should be the 3rd frame: 
		//	SignalHandler() -> GetBackTrace() -> backtrace()
		if(e._backtrace.length > 2)
			e._backtrace[2] = cast(void*)ucontext.uc_mcontext.gregs[REG_EIP];
	}
	else static assert(0);

	throw e;
}

/**
 This handler is called when an exception unwinds down to the thread's entry
 point, which should catch it and manually call this routine.
*/
void UnhandledExceptionHandler(Throwable e) {
	ErrorReport crashInfo = void;

	with(crashInfo) {
assert(0);
	/+error = e;

	// Get the module filename
	// TODO

	// Dump the general purpose registers
	if(e._context) {
		gregset_t gregs = e._context.uc_mcontext.gregs;

		version(X86) {
			registers.Eax = gregs[REG_EAX];
			registers.Ebx = gregs[REG_EBX];
			registers.Ecx = gregs[REG_ECX];
			registers.Edx = gregs[REG_EDX];
			registers.Edi = gregs[REG_EDI];
			registers.Esi = gregs[REG_ESI];
			registers.Ebp = gregs[REG_EBP];
			registers.Esp = gregs[REG_ESP];
		}
		else static assert(0);
	}

	// Dump stack backtrace
	addresses = e._backtrace;

	// Dump the loaded modules
	// TODO+/

	} // with(crashInfo)

	crashInfo.Dump();
}

} // version(Posix)
else static assert(0);