1 /**
2  * ...
3  *
4  * Copyright: Copyright Benjamin Thaut 2010 - 2013.
5  * License: Distributed under the
6  *      $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
7  *    (See accompanying file LICENSE)
8  * Authors:   Benjamin Thaut, Sean Kelly
9  * Source:    $(DRUNTIMESRC core/sys/windows/_stacktrace.d)
10  */
11 
12 module core.sys.windows.stacktrace;
13 version (Windows):
14 @system:
15 
16 import core.demangle;
17 import core.stdc.stdlib;
18 import core.stdc.string;
19 import core.sys.windows.dbghelp;
20 import core.sys.windows.imagehlp /+: ADDRESS_MODE+/;
21 import core.sys.windows.winbase;
22 import core.sys.windows.windef;
23 
24 //debug=PRINTF;
25 debug(PRINTF) import core.stdc.stdio;
26 
27 
28 extern(Windows) void RtlCaptureContext(CONTEXT* ContextRecord);
29 extern(Windows) DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR pBuffer, DWORD nSize);
30 
31 extern(Windows) alias USHORT function(ULONG FramesToSkip, ULONG FramesToCapture, PVOID *BackTrace, PULONG BackTraceHash) RtlCaptureStackBackTraceFunc;
32 
33 private __gshared RtlCaptureStackBackTraceFunc RtlCaptureStackBackTrace;
34 private __gshared immutable bool initialized;
35 
36 
37 class StackTrace : Throwable.TraceInfo
38 {
39 public:
40     /**
41      * Constructor
42      * Params:
43      *  skip = The number of stack frames to skip.
44      *  context = The context to receive the stack trace from. Can be null.
45      */
this(size_t skip,CONTEXT * context)46     this(size_t skip, CONTEXT* context)
47     {
48         if (context is null)
49         {
50             version (Win64)
51                 static enum INTERNALFRAMES = 3;
52             else version (Win32)
53                 static enum INTERNALFRAMES = 2;
54 
55             skip += INTERNALFRAMES; //skip the stack frames within the StackTrace class
56         }
57         else
58         {
59             //When a exception context is given the first stack frame is repeated for some reason
60             version (Win64)
61                 static enum INTERNALFRAMES = 1;
62             else version (Win32)
63                 static enum INTERNALFRAMES = 1;
64 
65             skip += INTERNALFRAMES;
66         }
67         if ( initialized )
68             m_trace = trace(skip, context);
69     }
70 
opApply(scope int delegate (ref const (char[]))dg)71     int opApply( scope int delegate(ref const(char[])) dg ) const
72     {
73         return opApply( (ref size_t, ref const(char[]) buf)
74                         {
75                             return dg( buf );
76                         });
77     }
78 
79 
opApply(scope int delegate (ref size_t,ref const (char[]))dg)80     int opApply( scope int delegate(ref size_t, ref const(char[])) dg ) const
81     {
82         int result;
83         foreach ( i, e; resolve(m_trace) )
84         {
85             if ( (result = dg( i, e )) != 0 )
86                 break;
87         }
88         return result;
89     }
90 
91 
toString()92     @trusted override string toString() const
93     {
94         string result;
95 
96         foreach ( e; this )
97         {
98             result ~= e ~ "\n";
99         }
100         return result;
101     }
102 
103     /**
104      * Receive a stack trace in the form of an address list.
105      * Params:
106      *  skip = How many stack frames should be skipped.
107      *  context = The context that should be used. If null the current context is used.
108      * Returns:
109      *  A list of addresses that can be passed to resolve at a later point in time.
110      */
111     static ulong[] trace(size_t skip = 0, CONTEXT* context = null)
112     {
synchronized(typeid (StackTrace))113         synchronized( typeid(StackTrace) )
114         {
115             return traceNoSync(skip, context);
116         }
117     }
118 
119     /**
120      * Resolve a stack trace.
121      * Params:
122      *  addresses = A list of addresses to resolve.
123      * Returns:
124      *  An array of strings with the results.
125      */
resolve(const (ulong)[]addresses)126     @trusted static char[][] resolve(const(ulong)[] addresses)
127     {
128         synchronized( typeid(StackTrace) )
129         {
130             return resolveNoSync(addresses);
131         }
132     }
133 
134 private:
135     ulong[] m_trace;
136 
137 
traceNoSync(size_t skip,CONTEXT * context)138     static ulong[] traceNoSync(size_t skip, CONTEXT* context)
139     {
140         auto dbghelp  = DbgHelp.get();
141         if (dbghelp is null)
142             return []; // dbghelp.dll not available
143 
144         if (RtlCaptureStackBackTrace !is null && context is null)
145         {
146             size_t[63] buffer = void; // On windows xp the sum of "frames to skip" and "frames to capture" can't be greater then 63
147             auto backtraceLength = RtlCaptureStackBackTrace(cast(ULONG)skip, cast(ULONG)(buffer.length - skip), cast(void**)buffer.ptr, null);
148 
149             // If we get a backtrace and it does not have the maximum length use it.
150             // Otherwise rely on tracing through StackWalk64 which is slower but works when no frame pointers are available.
151             if (backtraceLength > 1 && backtraceLength < buffer.length - skip)
152             {
153                 debug(PRINTF) printf("Using result from RtlCaptureStackBackTrace\n");
154                 version (Win64)
155                 {
156                     return buffer[0..backtraceLength].dup;
157                 }
158                 else version (Win32)
159                 {
160                     auto result = new ulong[backtraceLength];
161                     foreach (i, ref e; result)
162                     {
163                         e = buffer[i];
164                     }
165                     return result;
166                 }
167             }
168         }
169 
170         HANDLE       hThread  = GetCurrentThread();
171         HANDLE       hProcess = GetCurrentProcess();
172         CONTEXT      ctxt;
173 
174         if (context is null)
175         {
176             ctxt.ContextFlags = CONTEXT_FULL;
177             RtlCaptureContext(&ctxt);
178         }
179         else
180         {
181             ctxt = *context;
182         }
183 
184         //x86
185         STACKFRAME64 stackframe;
186         with (stackframe)
187         {
188             version (X86)
189             {
190                 enum Flat = ADDRESS_MODE.AddrModeFlat;
191                 AddrPC.Offset    = ctxt.Eip;
192                 AddrPC.Mode      = Flat;
193                 AddrFrame.Offset = ctxt.Ebp;
194                 AddrFrame.Mode   = Flat;
195                 AddrStack.Offset = ctxt.Esp;
196                 AddrStack.Mode   = Flat;
197             }
198         else version (X86_64)
199             {
200                 enum Flat = ADDRESS_MODE.AddrModeFlat;
201                 AddrPC.Offset    = ctxt.Rip;
202                 AddrPC.Mode      = Flat;
203                 AddrFrame.Offset = ctxt.Rbp;
204                 AddrFrame.Mode   = Flat;
205                 AddrStack.Offset = ctxt.Rsp;
206                 AddrStack.Mode   = Flat;
207             }
208         }
209 
210         version (X86)         enum imageType = IMAGE_FILE_MACHINE_I386;
211         else version (X86_64) enum imageType = IMAGE_FILE_MACHINE_AMD64;
212         else                  static assert(0, "unimplemented");
213 
214         ulong[] result;
215         size_t frameNum = 0;
216 
217         // do ... while so that we don't skip the first stackframe
218         do
219         {
220             if (frameNum >= skip)
221             {
222                 result ~= stackframe.AddrPC.Offset;
223             }
224             frameNum++;
225         }
226         while (dbghelp.StackWalk64(imageType, hProcess, hThread, &stackframe,
227                                    &ctxt, null, null, null, null));
228         return result;
229     }
230 
resolveNoSync(const (ulong)[]addresses)231     static char[][] resolveNoSync(const(ulong)[] addresses)
232     {
233         auto dbghelp  = DbgHelp.get();
234         if (dbghelp is null)
235             return []; // dbghelp.dll not available
236 
237         HANDLE hProcess = GetCurrentProcess();
238 
239         static struct BufSymbol
240         {
241         align(1):
242             IMAGEHLP_SYMBOLA64 _base;
243             TCHAR[1024] _buf = void;
244         }
245         BufSymbol bufSymbol=void;
246         IMAGEHLP_SYMBOLA64* symbol = &bufSymbol._base;
247         symbol.SizeOfStruct = IMAGEHLP_SYMBOLA64.sizeof;
248         symbol.MaxNameLength = bufSymbol._buf.length;
249 
250         char[][] trace;
251         foreach (pc; addresses)
252         {
253             char[] res;
254             if (dbghelp.SymGetSymFromAddr64(hProcess, pc, null, symbol) &&
255                 *symbol.Name.ptr)
256             {
257                 DWORD disp;
258                 IMAGEHLP_LINEA64 line=void;
259                 line.SizeOfStruct = IMAGEHLP_LINEA64.sizeof;
260 
261                 if (dbghelp.SymGetLineFromAddr64(hProcess, pc, &disp, &line))
262                     res = formatStackFrame(cast(void*)pc, symbol.Name.ptr,
263                                            line.FileName, line.LineNumber);
264                 else
265                     res = formatStackFrame(cast(void*)pc, symbol.Name.ptr);
266             }
267             else
268                 res = formatStackFrame(cast(void*)pc);
269             trace ~= res;
270         }
271         return trace;
272     }
273 
formatStackFrame(void * pc)274     static char[] formatStackFrame(void* pc)
275     {
276         import core.stdc.stdio : snprintf;
277         char[2+2*size_t.sizeof+1] buf=void;
278 
279         immutable len = snprintf(buf.ptr, buf.length, "0x%p", pc);
280         cast(uint)len < buf.length || assert(0);
281         return buf[0 .. len].dup;
282     }
283 
formatStackFrame(void * pc,char * symName)284     static char[] formatStackFrame(void* pc, char* symName)
285     {
286         char[2048] demangleBuf=void;
287 
288         auto res = formatStackFrame(pc);
289         res ~= " in ";
290         const(char)[] tempSymName = symName[0 .. strlen(symName)];
291         //Deal with dmd mangling of long names
292         version (CRuntime_DigitalMars)
293         {
294             size_t decodeIndex = 0;
295             tempSymName = decodeDmdString(tempSymName, decodeIndex);
296         }
297         res ~= demangle(tempSymName, demangleBuf);
298         return res;
299     }
300 
formatStackFrame(void * pc,char * symName,const scope char * fileName,uint lineNum)301     static char[] formatStackFrame(void* pc, char* symName,
302                                    const scope char* fileName, uint lineNum)
303     {
304         import core.stdc.stdio : snprintf;
305         char[11] buf=void;
306 
307         auto res = formatStackFrame(pc, symName);
308         res ~= " at ";
309         res ~= fileName[0 .. strlen(fileName)];
310         res ~= "(";
311         immutable len = snprintf(buf.ptr, buf.length, "%u", lineNum);
312         cast(uint)len < buf.length || assert(0);
313         res ~= buf[0 .. len];
314         res ~= ")";
315         return res;
316     }
317 }
318 
319 
320 // Workaround OPTLINK bug (Bugzilla 8263)
FixupDebugHeader(HANDLE hProcess,ULONG ActionCode,ulong CallbackContext,ulong UserContext)321 extern(Windows) BOOL FixupDebugHeader(HANDLE hProcess, ULONG ActionCode,
322                                       ulong CallbackContext, ulong UserContext)
323 {
324     if (ActionCode == CBA_READ_MEMORY)
325     {
326         auto p = cast(IMAGEHLP_CBA_READ_MEMORY*)CallbackContext;
327         if (!(p.addr & 0xFF) && p.bytes == 0x1C &&
328             // IMAGE_DEBUG_DIRECTORY.PointerToRawData
329             (*cast(DWORD*)(p.addr + 24) & 0xFF) == 0x20)
330         {
331             immutable base = DbgHelp.get().SymGetModuleBase64(hProcess, p.addr);
332             // IMAGE_DEBUG_DIRECTORY.AddressOfRawData
333             if (base + *cast(DWORD*)(p.addr + 20) == p.addr + 0x1C &&
334                 *cast(DWORD*)(p.addr + 0x1C) == 0 &&
335                 *cast(DWORD*)(p.addr + 0x20) == ('N'|'B'<<8|'0'<<16|'9'<<24))
336             {
337                 debug(PRINTF) printf("fixup IMAGE_DEBUG_DIRECTORY.AddressOfRawData\n");
338                 memcpy(p.buf, cast(void*)p.addr, 0x1C);
339                 *cast(DWORD*)(p.buf + 20) = cast(DWORD)(p.addr - base) + 0x20;
340                 *p.bytesread = 0x1C;
341                 return TRUE;
342             }
343         }
344     }
345     return FALSE;
346 }
347 
generateSearchPath()348 private string generateSearchPath()
349 {
350     __gshared string[3] defaultPathList = ["_NT_SYMBOL_PATH",
351                                            "_NT_ALTERNATE_SYMBOL_PATH",
352                                            "SYSTEMROOT"];
353 
354     string path;
355     char[2048] temp = void;
356     DWORD len;
357 
358     foreach ( e; defaultPathList )
359     {
360         if ( (len = GetEnvironmentVariableA( e.ptr, temp.ptr, temp.length )) > 0 )
361         {
362             path ~= temp[0 .. len];
363             path ~= ";";
364         }
365     }
366     path ~= "\0";
367     return path;
368 }
369 
370 
this()371 shared static this()
372 {
373     auto dbghelp = DbgHelp.get();
374 
375     if ( dbghelp is null )
376         return; // dbghelp.dll not available
377 
378     auto kernel32Handle = LoadLibraryA( "kernel32.dll" );
379     if (kernel32Handle !is null)
380     {
381         RtlCaptureStackBackTrace = cast(RtlCaptureStackBackTraceFunc) GetProcAddress(kernel32Handle, "RtlCaptureStackBackTrace");
382         debug(PRINTF)
383         {
384             if (RtlCaptureStackBackTrace !is null)
385                 printf("Found RtlCaptureStackBackTrace\n");
386         }
387     }
388 
389     debug(PRINTF)
390     {
391         API_VERSION* dbghelpVersion = dbghelp.ImagehlpApiVersion();
392         printf("DbgHelp Version %d.%d.%d\n", dbghelpVersion.MajorVersion, dbghelpVersion.MinorVersion, dbghelpVersion.Revision);
393     }
394 
395     HANDLE hProcess = GetCurrentProcess();
396 
397     DWORD symOptions = dbghelp.SymGetOptions();
398     symOptions |= SYMOPT_LOAD_LINES;
399     symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
400     symOptions |= SYMOPT_DEFERRED_LOAD;
401     symOptions  = dbghelp.SymSetOptions( symOptions );
402 
403     debug(PRINTF) printf("Search paths: %s\n", generateSearchPath().ptr);
404 
405     if (!dbghelp.SymInitialize(hProcess, generateSearchPath().ptr, TRUE))
406         return;
407 
408     dbghelp.SymRegisterCallback64(hProcess, &FixupDebugHeader, 0);
409 
410     initialized = true;
411 }
412