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