Artifact Content
Not logged in

Artifact 9e66870b7d120c423e793c480a528a134e2ee079


     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  import std.path;
    37  
    38  extern(Windows){
    39  	DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR pBuffer, DWORD nSize);
    40  	void RtlCaptureContext(CONTEXT* ContextRecord);
    41  	typedef LONG function(void*) UnhandeledExceptionFilterFunc;
    42  	void* SetUnhandledExceptionFilter(void* handler);
    43  }
    44  
    45  class StackTrace {
    46  private:
    47  	enum : uint {
    48  		MAX_MODULE_NAME32 = 255,
    49  		TH32CS_SNAPMODULE = 0x00000008,
    50  		MAX_NAMELEN = 1024
    51  	};
    52  	
    53  	struct MODULEENTRY32 {
    54  		DWORD dwSize;
    55  		DWORD th32ModuleID;
    56  		DWORD th32ProcessID;
    57  		DWORD GlblcntUsage;
    58  		DWORD ProccntUsage;
    59  		BYTE* modBaseAddr;
    60  		DWORD modBaseSize;
    61  		HMODULE hModule;
    62  		CHAR[MAX_MODULE_NAME32 + 1] szModule;
    63  		CHAR[MAX_PATH] szExePath;
    64  	};
    65  	
    66  	string m_UserSymPath;
    67  	static bool isInit = false;
    68  	static bool modulesLoaded = false;
    69  	
    70  	extern(System){
    71  		typedef HANDLE function(DWORD dwFlags, DWORD th32ProcessID) CreateToolhelp32SnapshotFunc;
    72  		typedef BOOL function(HANDLE hSnapshot, MODULEENTRY32 *lpme) Module32FirstFunc;
    73  		typedef BOOL function(HANDLE hSnapshot, MODULEENTRY32 *lpme) Module32NextFunc;
    74  	}
    75  	
    76  	extern(Windows) static LONG UnhandeledExceptionFilterHandler(void* info){
    77  		printStackTrace();
    78  		return 0;
    79  	}
    80  	
    81  	static void printStackTrace(){
    82  		auto stack = TraceHandler(null);
    83  		foreach(char[] s;stack){
    84  			writefln("%s",s);
    85  		}
    86  	}
    87  	
    88  	bool LoadModules(HANDLE hProcess, DWORD pid){
    89  		if(modulesLoaded)
    90  			return true;
    91  		
    92  		CreateToolhelp32SnapshotFunc CreateToolhelp32Snapshot = null;
    93  		Module32FirstFunc Module32First = null;
    94  		Module32NextFunc Module32Next = null;
    95  			
    96  		HMODULE hDll = null;
    97  		
    98  		string[] searchDlls = [ "kernel32.dll", "tlhelp32.dll" ];
    99  		foreach(dll;searchDlls){
   100  			hDll = cast(HMODULE)Runtime.loadLibrary(dll);
   101  			if(hDll == null)
   102  				break;
   103  			CreateToolhelp32Snapshot = cast(CreateToolhelp32SnapshotFunc) GetProcAddress(hDll,"CreateToolhelp32Snapshot");
   104  			Module32First = cast(Module32FirstFunc) GetProcAddress(hDll,"Module32First");
   105  			Module32Next = cast(Module32NextFunc) GetProcAddress(hDll,"Module32Next");
   106  			if(CreateToolhelp32Snapshot != null && Module32First != null && Module32Next != null)
   107  				break;
   108  			Runtime.unloadLibrary(hDll);
   109  			hDll = null;
   110  		}
   111  		
   112  		if(hDll == null){
   113  			return false;
   114  		}
   115  		
   116  		HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid);
   117  		if(hSnap == cast(HANDLE) -1)
   118  			return false;
   119  		
   120  		MODULEENTRY32 ModuleEntry;
   121  		memset(&ModuleEntry,0,MODULEENTRY32.sizeof);
   122  		ModuleEntry.dwSize = MODULEENTRY32.sizeof;
   123  		
   124  		bool more = cast(bool)Module32First(hSnap,&ModuleEntry);
   125  		int count = 0;
   126  		while(more){
   127  			LoadModule(hProcess, ModuleEntry.szExePath.ptr, ModuleEntry.szModule.ptr, cast(Dbghelp.DWORD64)ModuleEntry.modBaseAddr, ModuleEntry.modBaseSize);
   128  			count++;
   129  			more = cast(bool)Module32Next(hSnap,&ModuleEntry);
   130  		}
   131  		
   132  		CloseHandle(hSnap);
   133  		Runtime.unloadLibrary(hDll);
   134  		
   135  		if(count <= 0)
   136  			return false;
   137  		
   138  		modulesLoaded = true;
   139  		return true;
   140  	}
   141  
   142  	void LoadModule(HANDLE hProcess, LPCSTR img, LPCSTR mod, Dbghelp.DWORD64 baseAddr, DWORD size){
   143  		char[] szImg = new char[strlen(img)];
   144  		char[] szMod = new char[strlen(mod)];
   145  		szImg[0..szImg.length] = img[0..(strlen(img))];
   146  		szMod[0..szMod.length] = mod[0..(strlen(mod))];
   147  		
   148  		Dbghelp.DWORD64 moduleAddr = Dbghelp.SymLoadModule64(hProcess,HANDLE.init,cast(Dbghelp.PCSTR)toStringz(szImg),cast(Dbghelp.PCSTR)toStringz(szMod),baseAddr,size);
   149  		if(moduleAddr == 0)
   150  			return;
   151  		
   152  		Dbghelp.IMAGEHLP_MODULE64 ModuleInfo;
   153  		memset(&ModuleInfo,0,typeof(ModuleInfo).sizeof);
   154  		ModuleInfo.SizeOfStruct = typeof(ModuleInfo).sizeof;
   155  		if(Dbghelp.SymGetModuleInfo64(hProcess,moduleAddr,&ModuleInfo) == TRUE){
   156  			if(ModuleInfo.SymType == Dbghelp.SYM_TYPE.SymNone){
   157  				Dbghelp.SymUnloadModule64(hProcess,moduleAddr);
   158  				moduleAddr = Dbghelp.SymLoadModule64(hProcess,HANDLE.init,cast(Dbghelp.PCSTR)toStringz(szImg),null,cast(Dbghelp.DWORD64)0,0);
   159  				if(moduleAddr == 0)
   160  					return;
   161  			}
   162  		}
   163  		
   164  		//writefln("Successfully loaded module %s",szImg);
   165  	}
   166  	
   167  	string GenereateSearchPath(){
   168  		string path;
   169  		if(m_UserSymPath.length){
   170  			path = m_UserSymPath ~ ";";
   171  		}
   172  		
   173  		char[1024] temp;
   174  		if(GetCurrentDirectoryA(temp.length,temp.ptr) > 0){
   175  			temp[temp.length-1] = 0;
   176  			path ~= temp ~ ";";
   177  		}
   178  		
   179  		if(GetModuleFileNameA(null,temp.ptr,temp.length) > 0){
   180  			temp[temp.length-1] = 0;
   181  			foreach_reverse(ref char e;temp){
   182  				if(e == '\\' || e == '/' || e == ':'){
   183  					e = 0;
   184  					break;
   185  				}
   186  			}
   187  			if(strlen(temp.ptr) > 0){
   188  				path ~= temp ~ ";";
   189  			}
   190  		}
   191  		
   192  		string[] systemVars = [ "_NT_SYMBOL_PATH", "_NT_ALTERNATE_SYMBOL_PATH", "SYSTEMROOT" ];
   193  		
   194  		foreach(e;systemVars){
   195  			if(GetEnvironmentVariableA(toStringz(e),temp.ptr,temp.length) > 0){
   196  				temp[temp.length-1] = 0;
   197  				path ~= temp ~ ";";
   198  			}
   199  		}
   200  		
   201  		return path;
   202  	}
   203  	
   204  	static class Callstack : Throwable.TraceInfo {
   205  	private:
   206  		string[] info = null;
   207  	public:		
   208  		int opApply(scope int delegate(ref char[]) dg){
   209  			int result = 0;
   210  			foreach(e;info){
   211  				char[] temp = to!(char[])(e);
   212  				result = dg(temp);
   213  				if(result)
   214  					break;
   215  			}
   216  			return result;
   217  		}
   218  		
   219  		override string toString(){
   220  			string result = "";
   221  			foreach(e;info){
   222  				result ~= e ~ "\n";
   223  			}
   224  			return result;
   225  		}
   226  		
   227  		void append(string str){
   228  			if(info is null){
   229  				info = new string[1];
   230  				info[0] = str;
   231  			}
   232  			else {
   233  				info.length = info.length + 1;
   234  				info[info.length-1] = str;
   235  			}
   236  		}
   237  	}
   238  	
   239  	static Throwable.TraceInfo TraceHandler(void* ptr){
   240  		// modified by k.inaba to avoid a throw inside std.demangle.demangle
   241  		// not quite thread safe
   242  		Runtime.traceHandler(&core.runtime.defaultTraceHandler);
   243  		scope(exit) Runtime.traceHandler(&TraceHandler);
   244  
   245  		StackTrace trace = new StackTrace();
   246  		return trace.GetCallstack();
   247  	}
   248  	
   249  public:
   250  	static this(){
   251  		Runtime.traceHandler(&TraceHandler);
   252  		SetUnhandledExceptionFilter(&UnhandeledExceptionFilterHandler);
   253  	}
   254  	
   255  	this(){
   256  		if(isInit)
   257  			return;
   258  		HANDLE hProcess = GetCurrentProcess();
   259  		DWORD pid = GetCurrentProcessId();
   260  		
   261  		Dbghelp.Init();
   262  		string symPath = GenereateSearchPath();
   263  		if(Dbghelp.SymInitialize(hProcess,cast(Dbghelp.PCSTR)toStringz(symPath),FALSE) != FALSE){
   264  			isInit = true;
   265  			
   266  			DWORD symOptions = Dbghelp.SymGetOptions();
   267  			symOptions |= Dbghelp.SYMOPT_LOAD_LINES;
   268  			symOptions |= Dbghelp.SYMOPT_FAIL_CRITICAL_ERRORS;
   269  			symOptions = Dbghelp.SymSetOptions(symOptions);
   270  			
   271  			LoadModules(hProcess,pid);
   272  		}
   273  	}
   274  	
   275  	Throwable.TraceInfo GetCallstack(){
   276  		if(!isInit){
   277  			writefln("Is not init!");
   278  			return null;
   279  		}
   280  		
   281  		HANDLE hThread = GetCurrentThread();
   282  		HANDLE hProcess = GetCurrentProcess();
   283  		
   284  		//Capture the current context
   285  		CONTEXT c;
   286  		memset(&c, 0, CONTEXT.sizeof);
   287  		c.ContextFlags = CONTEXT_FULL;
   288  		RtlCaptureContext(&c);
   289  		
   290  		Dbghelp.STACKFRAME64 stackframe;
   291  		memset(&stackframe,0,typeof(stackframe).sizeof);
   292  		DWORD imageType;
   293  		//x86
   294  		imageType = Dbghelp.IMAGE_FILE_MACHINE_I386;
   295  		stackframe.AddrPC.Offset = cast(Dbghelp.DWORD64)c.Eip;
   296  		stackframe.AddrPC.Mode = Dbghelp.ADDRESS_MODE.AddrModeFlat;
   297  		stackframe.AddrFrame.Offset = cast(Dbghelp.DWORD64)c.Ebp;
   298  		stackframe.AddrFrame.Mode = Dbghelp.ADDRESS_MODE.AddrModeFlat;
   299  		stackframe.AddrStack.Offset = cast(Dbghelp.DWORD64)c.Esp;
   300  		stackframe.AddrStack.Mode = Dbghelp.ADDRESS_MODE.AddrModeFlat;
   301  		
   302  		size_t SymbolSize = Dbghelp.IMAGEHLP_SYMBOL64.sizeof + MAX_NAMELEN;
   303  		Dbghelp.IMAGEHLP_SYMBOL64 *Symbol = cast(Dbghelp.IMAGEHLP_SYMBOL64*) malloc(SymbolSize);
   304  		memset(Symbol,0,SymbolSize);
   305  		Symbol.SizeOfStruct = SymbolSize;
   306  		Symbol.MaxNameLength = MAX_NAMELEN;
   307  		
   308  		Dbghelp.IMAGEHLP_LINE64 Line;
   309  		memset(&Line,0,typeof(Line).sizeof);
   310  		Line.SizeOfStruct = typeof(Line).sizeof;
   311  		
   312  		Dbghelp.IMAGEHLP_MODULE64 Module;
   313  		memset(&Module,0,typeof(Module).sizeof);
   314  		Module.SizeOfStruct  = typeof(Module).sizeof;
   315  		
   316  		auto stack = new Callstack();
   317  		
   318  		//writefln("Callstack:");
   319  		for(int frameNum=0;;frameNum++){
   320  			if(Dbghelp.StackWalk64(imageType, hProcess, hThread, 
   321  			                    &stackframe, &c, 
   322  			                    null,
   323  			                    cast(Dbghelp.FunctionTableAccessProc64)Dbghelp.SymFunctionTableAccess64,
   324  			                    cast(Dbghelp.GetModuleBaseProc64)Dbghelp.SymGetModuleBase64,
   325  			                    null) != TRUE )
   326  			{
   327  				//writefln("End of Callstack");
   328  				break;
   329  			}
   330  			
   331  			if(stackframe.AddrPC.Offset == stackframe.AddrReturn.Offset){
   332  				//writefln("Endless callstack");
   333  				stack.append("Endless callstack");
   334  				break;
   335  			}
   336  			
   337  			if(stackframe.AddrPC.Offset != 0){
   338  				string lineStr = "";
   339  				Dbghelp.DWORD64 offsetFromSymbol = cast(Dbghelp.DWORD64)0;
   340  				if( Dbghelp.SymGetSymFromAddr64(hProcess,stackframe.AddrPC.Offset,&offsetFromSymbol,Symbol) == TRUE){
   341  					char[] symName = new char[strlen(cast(const(char)*)Symbol.Name.ptr)];
   342  					memcpy(symName.ptr,Symbol.Name.ptr,symName.length);
   343  					string symString = "";
   344  					if(symName[0] == 'D')
   345  						symString = "_";
   346  					symString ~= symName;
   347  
   348  					string demangledName = demangle(symString);
   349  					bool isOK = true;
   350  					for(int i=0; i<demangledName.length; ++i)
   351  						if( demangledName[i] >= 0x80 )
   352  							isOK = false;
   353  					if(isOK)
   354  						lineStr ~= demangledName;
   355  
   356  					DWORD zeichen = 0;
   357  					if(Dbghelp.SymGetLineFromAddr64(hProcess,stackframe.AddrPC.Offset,&zeichen,&Line) == TRUE){
   358  						char[] fileName = new char[strlen(Line.FileName)];
   359  						fileName = std.path.basename( Line.FileName[0..fileName.length] );
   360  						lineStr = text(fileName ~ "::" ~ text(Line.LineNumber) ~ "(" ~ text(zeichen) ~ ") " ~ lineStr);
   361  					}
   362  				}
   363  				else {
   364  					lineStr = text(cast(ulong)stackframe.AddrPC.Offset);
   365  				}
   366  				lineStr = text(frameNum-2) ~ " " ~ lineStr;
   367  				if(frameNum-2 < 10)
   368  					lineStr = "0" ~ lineStr;
   369  				if(frameNum >= 2)
   370  					stack.append(lineStr);
   371  			}
   372  		}
   373  		
   374  		free(Symbol);
   375  		return stack;
   376  	}
   377   };
   378