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