Artifact Content
Not logged in

Artifact 1456272a3238466c7f8ac0bb5a20083296dc60fc


     1  /*************************************************************
     2  Copyright (c) 2010
     3  
     4  Benjamin Thaut. All rights reserved.
     5  
     6  Redistribution and use in source and binary forms, with or without modification, are permitted
     7  provided that the following conditions are met:
     8  
     9    1. Redistributions of source code must retain the above copyright notice, this list of conditions
    10       and the following disclaimer.
    11  
    12    2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
    13       and the following disclaimer in the documentation and/or other materials provided with the distribution.
    14  
    15  THIS SOFTWARE IS PROVIDED BY BENJAMIN THAUT "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
    16  BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    17  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
    18  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    19  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    20  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
    21  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    22  
    23  web: http://3d.benjamin-thaut.de
    24  *************************************************************/
    25  module stacktrace;
    26  
    27  import std.c.windows.windows;
    28  import std.c.string;
    29  import std.string;
    30  import dbghelp;
    31  import core.runtime;
    32  import std.stdio;
    33  import std.c.stdlib;
    34  import std.demangle;
    35  import std.conv;
    36  
    37  extern(Windows){
    38  	DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR pBuffer, DWORD nSize);
    39  	void RtlCaptureContext(CONTEXT* ContextRecord);
    40  	typedef LONG function(void*) UnhandeledExceptionFilterFunc;
    41  	void* SetUnhandledExceptionFilter(void* handler);
    42  }
    43  
    44  class StackTrace {
    45  private:
    46  	enum : uint {
    47  		MAX_MODULE_NAME32 = 255,
    48  		TH32CS_SNAPMODULE = 0x00000008,
    49  		MAX_NAMELEN = 1024
    50  	};
    51  	
    52  	struct MODULEENTRY32 {
    53  		DWORD dwSize;
    54  		DWORD th32ModuleID;
    55  		DWORD th32ProcessID;
    56  		DWORD GlblcntUsage;
    57  		DWORD ProccntUsage;
    58  		BYTE* modBaseAddr;
    59  		DWORD modBaseSize;
    60  		HMODULE hModule;
    61  		CHAR[MAX_MODULE_NAME32 + 1] szModule;
    62  		CHAR[MAX_PATH] szExePath;
    63  	};
    64  	
    65  	string m_UserSymPath;
    66  	static bool isInit = false;
    67  	static bool modulesLoaded = false;
    68  	
    69  	extern(System){
    70  		typedef HANDLE function(DWORD dwFlags, DWORD th32ProcessID) CreateToolhelp32SnapshotFunc;
    71  		typedef BOOL function(HANDLE hSnapshot, MODULEENTRY32 *lpme) Module32FirstFunc;
    72  		typedef BOOL function(HANDLE hSnapshot, MODULEENTRY32 *lpme) Module32NextFunc;
    73  	}
    74  	
    75  	extern(Windows) static LONG UnhandeledExceptionFilterHandler(void* info){
    76  		printStackTrace();
    77  		return 0;
    78  	}
    79  	
    80  	static void printStackTrace(){
    81  		auto stack = TraceHandler(null);
    82  		foreach(char[] s;stack){
    83  			writefln("%s",s);
    84  		}
    85  	}
    86  	
    87  	bool LoadModules(HANDLE hProcess, DWORD pid){
    88  		if(modulesLoaded)
    89  			return true;
    90  		
    91  		CreateToolhelp32SnapshotFunc CreateToolhelp32Snapshot = null;
    92  		Module32FirstFunc Module32First = null;
    93  		Module32NextFunc Module32Next = null;
    94  			
    95  		HMODULE hDll = null;
    96  		
    97  		string[] searchDlls = [ "kernel32.dll", "tlhelp32.dll" ];
    98  		foreach(dll;searchDlls){
    99  			hDll = cast(HMODULE)Runtime.loadLibrary(dll);
   100  			if(hDll == null)
   101  				break;
   102  			CreateToolhelp32Snapshot = cast(CreateToolhelp32SnapshotFunc) GetProcAddress(hDll,"CreateToolhelp32Snapshot");
   103  			Module32First = cast(Module32FirstFunc) GetProcAddress(hDll,"Module32First");
   104  			Module32Next = cast(Module32NextFunc) GetProcAddress(hDll,"Module32Next");
   105  			if(CreateToolhelp32Snapshot != null && Module32First != null && Module32Next != null)
   106  				break;
   107  			Runtime.unloadLibrary(hDll);
   108  			hDll = null;
   109  		}
   110  		
   111  		if(hDll == null){
   112  			return false;
   113  		}
   114  		
   115  		HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
   116  		if(hSnap == cast(HANDLE) -1)
   117  			return false;
   118  		
   119  		MODULEENTRY32 ModuleEntry;
   120  		memset(&ModuleEntry,0,MODULEENTRY32.sizeof);
   121  		ModuleEntry.dwSize = MODULEENTRY32.sizeof;
   122  		
   123  		bool more = cast(bool)Module32First(hSnap,&ModuleEntry);
   124  		int count = 0;
   125  		while(more){
   126  			LoadModule(hProcess, ModuleEntry.szExePath.ptr, ModuleEntry.szModule.ptr, cast(Dbghelp.DWORD64)ModuleEntry.modBaseAddr, ModuleEntry.modBaseSize);
   127  			count++;
   128  			more = cast(bool)Module32Next(hSnap,&ModuleEntry);
   129  		}
   130  		
   131  		CloseHandle(hSnap);
   132  		Runtime.unloadLibrary(hDll);
   133  		
   134  		if(count <= 0)
   135  			return false;
   136  		
   137  		modulesLoaded = true;
   138  		return true;
   139  	}
   140  
   141  	void LoadModule(HANDLE hProcess, LPCSTR img, LPCSTR mod, Dbghelp.DWORD64 baseAddr, DWORD size){
   142  		char[] szImg = new char[strlen(img)];
   143  		char[] szMod = new char[strlen(mod)];
   144  		szImg[0..szImg.length] = img[0..(strlen(img))];
   145  		szMod[0..szMod.length] = mod[0..(strlen(mod))];
   146  		
   147  		Dbghelp.DWORD64 moduleAddr = Dbghelp.SymLoadModule64(hProcess,HANDLE.init,cast(Dbghelp.PCSTR)toStringz(szImg),cast(Dbghelp.PCSTR)toStringz(szMod),baseAddr,size);
   148  		if(moduleAddr == 0)
   149  			return;
   150  		
   151  		Dbghelp.IMAGEHLP_MODULE64 ModuleInfo;
   152  		memset(&ModuleInfo,0,typeof(ModuleInfo).sizeof);
   153  		ModuleInfo.SizeOfStruct = typeof(ModuleInfo).sizeof;
   154  		if(Dbghelp.SymGetModuleInfo64(hProcess,moduleAddr,&ModuleInfo) == TRUE){
   155  			if(ModuleInfo.SymType == Dbghelp.SYM_TYPE.SymNone){
   156  				Dbghelp.SymUnloadModule64(hProcess,moduleAddr);
   157  				moduleAddr = Dbghelp.SymLoadModule64(hProcess,HANDLE.init,cast(Dbghelp.PCSTR)toStringz(szImg),null,cast(Dbghelp.DWORD64)0,0);
   158  				if(moduleAddr == 0)
   159  					return;
   160  			}
   161  		}
   162  		
   163  		//writefln("Successfully loaded module %s",szImg);
   164  	}
   165  	
   166  	string GenereateSearchPath(){
   167  		string path;
   168  		if(m_UserSymPath.length){
   169  			path = m_UserSymPath ~ ";";
   170  		}
   171  		
   172  		char[1024] temp;
   173  		if(GetCurrentDirectoryA(temp.length,temp.ptr) > 0){
   174  			temp[temp.length-1] = 0;
   175  			path ~= temp ~ ";";
   176  		}
   177  		
   178  		if(GetModuleFileNameA(null,temp.ptr,temp.length) > 0){
   179  			temp[temp.length-1] = 0;
   180  			foreach_reverse(ref char e;temp){
   181  				if(e == '\\' || e == '/' || e == ':'){
   182  					e = 0;
   183  					break;
   184  				}
   185  			}
   186  			if(strlen(temp.ptr) > 0){
   187  				path ~= temp ~ ";";
   188  			}
   189  		}
   190  		
   191  		string[] systemVars = [ "_NT_SYMBOL_PATH", "_NT_ALTERNATE_SYMBOL_PATH", "SYSTEMROOT" ];
   192  		
   193  		foreach(e;systemVars){
   194  			if(GetEnvironmentVariableA(toStringz(e),temp.ptr,temp.length) > 0){
   195  				temp[temp.length-1] = 0;
   196  				path ~= temp ~ ";";
   197  			}
   198  		}
   199  		
   200  		return path;
   201  	}
   202  	
   203  	static class Callstack : Throwable.TraceInfo {
   204  	private:
   205  		string[] info = null;
   206  	public:		
   207  		int opApply(scope int delegate(ref char[]) dg){
   208  			int result = 0;
   209  			foreach(e;info){
   210  				char[] temp = to!(char[])(e);
   211  				result = dg(temp);
   212  				if(result)
   213  					break;
   214  			}
   215  			return result;
   216  		}
   217  		
   218  		override string toString(){
   219  			string result = "";
   220  			foreach(e;info){
   221  				result ~= e ~ "\n";
   222  			}
   223  			return result;
   224  		}
   225  		
   226  		void append(string str){
   227  			if(info is null){
   228  				info = new string[1];
   229  				info[0] = str;
   230  			}
   231  			else {
   232  				info.length = info.length + 1;
   233  				info[info.length-1] = str;
   234  			}
   235  		}
   236  	}
   237  	
   238  	static Throwable.TraceInfo TraceHandler(void* ptr){
   239  		// modified by k.inaba to avoid a throw inside std.demangle.demangle
   240  		// not quite thread safe
   241  		Runtime.traceHandler(&core.runtime.defaultTraceHandler);
   242  		scope(exit) Runtime.traceHandler(&TraceHandler);
   243  
   244  		StackTrace trace = new StackTrace();
   245  		return trace.GetCallstack();
   246  	}
   247  	
   248  public:
   249  	static this(){
   250  		Runtime.traceHandler(&TraceHandler);
   251  		SetUnhandledExceptionFilter(&UnhandeledExceptionFilterHandler);
   252  	}
   253  	
   254  	this(){
   255  		if(isInit)
   256  			return;
   257  		HANDLE hProcess = GetCurrentProcess();
   258  		DWORD pid = GetCurrentProcessId();
   259  		
   260  		Dbghelp.Init();
   261  		string symPath = GenereateSearchPath();
   262  		if(Dbghelp.SymInitialize(hProcess,cast(Dbghelp.PCSTR)toStringz(symPath),FALSE) != FALSE){
   263  			isInit = true;
   264  			
   265  			DWORD symOptions = Dbghelp.SymGetOptions();
   266  			symOptions |= Dbghelp.SYMOPT_LOAD_LINES;
   267  			symOptions |= Dbghelp.SYMOPT_FAIL_CRITICAL_ERRORS;
   268  			symOptions = Dbghelp.SymSetOptions(symOptions);
   269  			
   270  			LoadModules(hProcess,pid);
   271  		}
   272  	}
   273  	
   274  	Throwable.TraceInfo GetCallstack(){
   275  		if(!isInit){
   276  			writefln("Is not init!");
   277  			return null;
   278  		}
   279  		
   280  		HANDLE hThread = GetCurrentThread();
   281  		HANDLE hProcess = GetCurrentProcess();
   282  		
   283  		//Capture the current context
   284  		CONTEXT c;
   285  		memset(&c, 0, CONTEXT.sizeof);
   286  		c.ContextFlags = CONTEXT_FULL;
   287  		RtlCaptureContext(&c);
   288  		
   289  		Dbghelp.STACKFRAME64 stackframe;
   290  		memset(&stackframe,0,typeof(stackframe).sizeof);
   291  		DWORD imageType;
   292  		//x86
   293  		imageType = Dbghelp.IMAGE_FILE_MACHINE_I386;
   294  		stackframe.AddrPC.Offset = cast(Dbghelp.DWORD64)c.Eip;
   295  		stackframe.AddrPC.Mode = Dbghelp.ADDRESS_MODE.AddrModeFlat;
   296  		stackframe.AddrFrame.Offset = cast(Dbghelp.DWORD64)c.Ebp;
   297  		stackframe.AddrFrame.Mode = Dbghelp.ADDRESS_MODE.AddrModeFlat;
   298  		stackframe.AddrStack.Offset = cast(Dbghelp.DWORD64)c.Esp;
   299  		stackframe.AddrStack.Mode = Dbghelp.ADDRESS_MODE.AddrModeFlat;
   300  		
   301  		size_t SymbolSize = Dbghelp.IMAGEHLP_SYMBOL64.sizeof + MAX_NAMELEN;
   302  		Dbghelp.IMAGEHLP_SYMBOL64 *Symbol = cast(Dbghelp.IMAGEHLP_SYMBOL64*) malloc(SymbolSize);
   303  		memset(Symbol,0,SymbolSize);
   304  		Symbol.SizeOfStruct = SymbolSize;
   305  		Symbol.MaxNameLength = MAX_NAMELEN;
   306  		
   307  		Dbghelp.IMAGEHLP_LINE64 Line;
   308  		memset(&Line,0,typeof(Line).sizeof);
   309  		Line.SizeOfStruct = typeof(Line).sizeof;
   310  		
   311  		Dbghelp.IMAGEHLP_MODULE64 Module;
   312  		memset(&Module,0,typeof(Module).sizeof);
   313  		Module.SizeOfStruct  = typeof(Module).sizeof;
   314  		
   315  		auto stack = new Callstack();
   316  		
   317  		//writefln("Callstack:");
   318  		for(int frameNum=0;;frameNum++){
   319  			if(Dbghelp.StackWalk64(imageType, hProcess, hThread, 
   320  			                    &stackframe, &c, 
   321  			                    null,
   322  			                    cast(Dbghelp.FunctionTableAccessProc64)Dbghelp.SymFunctionTableAccess64,
   323  			                    cast(Dbghelp.GetModuleBaseProc64)Dbghelp.SymGetModuleBase64,
   324  			                    null) != TRUE )
   325  			{
   326  				//writefln("End of Callstack");
   327  				break;
   328  			}
   329  			
   330  			if(stackframe.AddrPC.Offset == stackframe.AddrReturn.Offset){
   331  				//writefln("Endless callstack");
   332  				stack.append("Endless callstack");
   333  				break;
   334  			}
   335  			
   336  			if(stackframe.AddrPC.Offset != 0){
   337  				string lineStr = "";
   338  				Dbghelp.DWORD64 offsetFromSymbol = cast(Dbghelp.DWORD64)0;
   339  				if( Dbghelp.SymGetSymFromAddr64(hProcess,stackframe.AddrPC.Offset,&offsetFromSymbol,Symbol) == TRUE){
   340  					char[] symName = new char[strlen(cast(const(char)*)Symbol.Name.ptr)+1];
   341  					memcpy(symName.ptr,Symbol.Name.ptr,symName.length);
   342  					string symString = "";
   343  					if(symName[0] == 'D')
   344  						symString = "_";
   345  					symString ~= symName;
   346  
   347  					string demangeledName = demangle(symString);
   348  					lineStr ~= demangeledName;
   349  
   350  					DWORD zeichen = 0;
   351  					if(Dbghelp.SymGetLineFromAddr64(hProcess,stackframe.AddrPC.Offset,&zeichen,&Line) == TRUE){
   352  						char[] fileName = new char[strlen(Line.FileName)];
   353  						fileName[] = Line.FileName[0..fileName.length];
   354  						lineStr = to!string(fileName ~ "::" ~ to!string(Line.LineNumber) ~ "(" ~ to!string(zeichen) ~ ") " ~ lineStr);
   355  					}
   356  				}
   357  				else {
   358  					lineStr = to!string(cast(ulong)stackframe.AddrPC.Offset);
   359  				}
   360  				lineStr = to!string(frameNum-2) ~ " " ~ lineStr;
   361  				if(frameNum-2 < 10)
   362  					lineStr = "0" ~ lineStr;
   363  				if(frameNum >= 2)
   364  					stack.append(lineStr);
   365  			}
   366  		}
   367  		
   368  		free(Symbol);
   369  		return stack;
   370  	}
   371   };
   372