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 
15 import core.demangle;
16 import core.stdc.stdlib;
17 import core.stdc.string;
18 import core.sys.windows.dbghelp;
19 import core.sys.windows.imagehlp /+: ADDRESS_MODE+/;
20 import core.sys.windows.winbase;
21 import core.sys.windows.windef;
22 
23 //debug=PRINTF;
24 debug(PRINTF) import core.stdc.stdio;
25 
26 
27 extern(Windows) void RtlCaptureContext(CONTEXT* ContextRecord) @nogc;
28 extern(Windows) DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR pBuffer, DWORD nSize);
29 
30 extern(Windows) alias USHORT function(ULONG FramesToSkip, ULONG FramesToCapture, PVOID *BackTrace, PULONG BackTraceHash) @nogc RtlCaptureStackBackTraceFunc;
31 
32 private __gshared RtlCaptureStackBackTraceFunc RtlCaptureStackBackTrace;
33 private __gshared CRITICAL_SECTION mutex; // cannot use core.sync.mutex.Mutex unfortunately (cyclic dependency...)
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      */
46     this(size_t skip, CONTEXT* context) @nogc
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(tracebuf[], skip, context);
69     }
70 
71     override 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 
80     override 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 
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. One form accepts
105      * an allocated buffer, the other form automatically allocates the buffer.
106      *
107      * Params:
108      *  skip = How many stack frames should be skipped.
109      *  context = The context that should be used. If null the current context is used.
110      *  buffer = The buffer to use for the trace. This should be at least 63 elements.
111      * Returns:
112      *  A list of addresses that can be passed to resolve at a later point in time.
113      */
114     static ulong[] trace(size_t skip = 0, CONTEXT* context = null)
115     {
116         return trace(new ulong[63], skip, context);
117     }
118 
119     /// ditto
120     static ulong[] trace(ulong[] buffer, size_t skip = 0, CONTEXT* context = null) @nogc
121     {
122         EnterCriticalSection(&mutex);
123         scope(exit) LeaveCriticalSection(&mutex);
124 
125         return traceNoSync(buffer, skip, context);
126     }
127 
128     /**
129      * Resolve a stack trace.
130      * Params:
131      *  addresses = A list of addresses to resolve.
132      * Returns:
133      *  An array of strings with the results.
134      */
135     @trusted static char[][] resolve(const(ulong)[] addresses)
136     {
137         // FIXME: make @nogc to avoid having to disable resolution within finalizers
138         import core.memory : GC;
139         if (GC.inFinalizer)
140             return null;
141 
142         EnterCriticalSection(&mutex);
143         scope(exit) LeaveCriticalSection(&mutex);
144 
145         return resolveNoSync(addresses);
146     }
147 
148 private:
149     ulong[128] tracebuf;
150     ulong[] m_trace;
151 
152 
153     static ulong[] traceNoSync(ulong[] buffer, size_t skip, CONTEXT* context) @nogc
154     {
155         auto dbghelp  = DbgHelp.get();
156         if (dbghelp is null)
157             return []; // dbghelp.dll not available
158 
159         if (buffer.length >= 63 && RtlCaptureStackBackTrace !is null &&
160             context is null)
161         {
162             version (Win64)
163             {
164                 auto bufptr = cast(void**)buffer.ptr;
165             }
166             version (Win32)
167             {
168                 size_t[63] bufstorage = void; // On windows xp the sum of "frames to skip" and "frames to capture" can't be greater then 63
169                 auto bufptr = cast(void**)bufstorage.ptr;
170             }
171             auto backtraceLength = RtlCaptureStackBackTrace(cast(ULONG)skip, cast(ULONG)(63 - skip), bufptr, null);
172 
173             // If we get a backtrace and it does not have the maximum length use it.
174             // Otherwise rely on tracing through StackWalk64 which is slower but works when no frame pointers are available.
175             if (backtraceLength > 1 && backtraceLength < 63 - skip)
176             {
177                 debug(PRINTF) printf("Using result from RtlCaptureStackBackTrace\n");
178                 version (Win32)
179                 {
180                     foreach (i, ref e; buffer[0 .. backtraceLength])
181                     {
182                         e = bufstorage[i];
183                     }
184                 }
185                 return buffer[0..backtraceLength];
186             }
187         }
188 
189         HANDLE       hThread  = GetCurrentThread();
190         HANDLE       hProcess = GetCurrentProcess();
191         CONTEXT      ctxt;
192 
193         if (context is null)
194         {
195             ctxt.ContextFlags = CONTEXT_FULL;
196             RtlCaptureContext(&ctxt);
197         }
198         else
199         {
200             ctxt = *context;
201         }
202 
203         //x86
204         STACKFRAME64 stackframe;
205         with (stackframe)
206         {
207             version (X86)
208             {
209                 enum Flat = ADDRESS_MODE.AddrModeFlat;
210                 AddrPC.Offset    = ctxt.Eip;
211                 AddrPC.Mode      = Flat;
212                 AddrFrame.Offset = ctxt.Ebp;
213                 AddrFrame.Mode   = Flat;
214                 AddrStack.Offset = ctxt.Esp;
215                 AddrStack.Mode   = Flat;
216             }
217         else version (X86_64)
218             {
219                 enum Flat = ADDRESS_MODE.AddrModeFlat;
220                 AddrPC.Offset    = ctxt.Rip;
221                 AddrPC.Mode      = Flat;
222                 AddrFrame.Offset = ctxt.Rbp;
223                 AddrFrame.Mode   = Flat;
224                 AddrStack.Offset = ctxt.Rsp;
225                 AddrStack.Mode   = Flat;
226             }
227         }
228 
229         version (X86)         enum imageType = IMAGE_FILE_MACHINE_I386;
230         else version (X86_64) enum imageType = IMAGE_FILE_MACHINE_AMD64;
231         else                  static assert(0, "unimplemented");
232 
233         size_t frameNum = 0;
234         size_t nframes = 0;
235 
236         // do ... while so that we don't skip the first stackframe
237         do
238         {
239             if (frameNum >= skip)
240             {
241                 buffer[nframes++] = stackframe.AddrPC.Offset;
242                 if (nframes >= buffer.length)
243                     break;
244             }
245             frameNum++;
246         }
247         while (dbghelp.StackWalk64(imageType, hProcess, hThread, &stackframe,
248                                    &ctxt, null, null, null, null));
249         return buffer[0 .. nframes];
250     }
251 
252     static char[][] resolveNoSync(const(ulong)[] addresses)
253     {
254         auto dbghelp  = DbgHelp.get();
255         if (dbghelp is null)
256             return []; // dbghelp.dll not available
257 
258         HANDLE hProcess = GetCurrentProcess();
259 
260         static struct BufSymbol
261         {
262         align(1):
263             IMAGEHLP_SYMBOLA64 _base;
264             TCHAR[1024] _buf = void;
265         }
266         BufSymbol bufSymbol=void;
267         IMAGEHLP_SYMBOLA64* symbol = &bufSymbol._base;
268         symbol.SizeOfStruct = IMAGEHLP_SYMBOLA64.sizeof;
269         symbol.MaxNameLength = bufSymbol._buf.length;
270 
271         char[][] trace;
272         foreach (pc; addresses)
273         {
274             char[] res;
275             if (dbghelp.SymGetSymFromAddr64(hProcess, pc, null, symbol) &&
276                 *symbol.Name.ptr)
277             {
278                 DWORD disp;
279                 IMAGEHLP_LINEA64 line=void;
280                 line.SizeOfStruct = IMAGEHLP_LINEA64.sizeof;
281 
282                 if (dbghelp.SymGetLineFromAddr64(hProcess, pc, &disp, &line))
283                     res = formatStackFrame(cast(void*)pc, symbol.Name.ptr,
284                                            line.FileName, line.LineNumber);
285                 else
286                     res = formatStackFrame(cast(void*)pc, symbol.Name.ptr);
287             }
288             else
289                 res = formatStackFrame(cast(void*)pc);
290             trace ~= res;
291         }
292         return trace;
293     }
294 
295     static char[] formatStackFrame(void* pc)
296     {
297         import core.stdc.stdio : snprintf;
298         char[2+2*size_t.sizeof+1] buf=void;
299 
300         immutable len = snprintf(buf.ptr, buf.length, "0x%p", pc);
301         cast(uint)len < buf.length || assert(0);
302         return buf[0 .. len].dup;
303     }
304 
305     static char[] formatStackFrame(void* pc, char* symName)
306     {
307         char[2048] demangleBuf=void;
308 
309         auto res = formatStackFrame(pc);
310         res ~= " in ";
311         const(char)[] tempSymName = symName[0 .. strlen(symName)];
312         // Deal with dmd mangling of long names for OMF 32 bits builds
313         // Note that `target.d` only defines `CRuntime_DigitalMars` for OMF builds
314         version (CRuntime_DigitalMars)
315         {
316             size_t decodeIndex = 0;
317             tempSymName = decodeDmdString(tempSymName, decodeIndex);
318         }
319         res ~= demangle(tempSymName, demangleBuf);
320         return res;
321     }
322 
323     static char[] formatStackFrame(void* pc, char* symName,
324                                    const scope char* fileName, uint lineNum)
325     {
326         import core.stdc.stdio : snprintf;
327         char[11] buf=void;
328 
329         auto res = formatStackFrame(pc, symName);
330         res ~= " at ";
331         res ~= fileName[0 .. strlen(fileName)];
332         res ~= "(";
333         immutable len = snprintf(buf.ptr, buf.length, "%u", lineNum);
334         cast(uint)len < buf.length || assert(0);
335         res ~= buf[0 .. len];
336         res ~= ")";
337         return res;
338     }
339 }
340 
341 
342 // Workaround OPTLINK bug (Bugzilla 8263)
343 extern(Windows) BOOL FixupDebugHeader(HANDLE hProcess, ULONG ActionCode,
344                                       ulong CallbackContext, ulong UserContext)
345 {
346     if (ActionCode == CBA_READ_MEMORY)
347     {
348         auto p = cast(IMAGEHLP_CBA_READ_MEMORY*)CallbackContext;
349         if (!(p.addr & 0xFF) && p.bytes == 0x1C &&
350             // IMAGE_DEBUG_DIRECTORY.PointerToRawData
351             (*cast(DWORD*)(p.addr + 24) & 0xFF) == 0x20)
352         {
353             immutable base = DbgHelp.get().SymGetModuleBase64(hProcess, p.addr);
354             // IMAGE_DEBUG_DIRECTORY.AddressOfRawData
355             if (base + *cast(DWORD*)(p.addr + 20) == p.addr + 0x1C &&
356                 *cast(DWORD*)(p.addr + 0x1C) == 0 &&
357                 *cast(DWORD*)(p.addr + 0x20) == ('N'|'B'<<8|'0'<<16|'9'<<24))
358             {
359                 debug(PRINTF) printf("fixup IMAGE_DEBUG_DIRECTORY.AddressOfRawData\n");
360                 memcpy(p.buf, cast(void*)p.addr, 0x1C);
361                 *cast(DWORD*)(p.buf + 20) = cast(DWORD)(p.addr - base) + 0x20;
362                 *p.bytesread = 0x1C;
363                 return TRUE;
364             }
365         }
366     }
367     return FALSE;
368 }
369 
370 private string generateSearchPath()
371 {
372     __gshared string[3] defaultPathList = ["_NT_SYMBOL_PATH",
373                                            "_NT_ALTERNATE_SYMBOL_PATH",
374                                            "SYSTEMROOT"];
375 
376     string path;
377     char[2048] temp = void;
378     DWORD len;
379 
380     foreach ( e; defaultPathList )
381     {
382         if ( (len = GetEnvironmentVariableA( e.ptr, temp.ptr, temp.length )) > 0 )
383         {
384             path ~= temp[0 .. len];
385             path ~= ";";
386         }
387     }
388     path ~= "\0";
389     return path;
390 }
391 
392 
393 shared static this()
394 {
395     auto dbghelp = DbgHelp.get();
396 
397     if ( dbghelp is null )
398         return; // dbghelp.dll not available
399 
400     auto kernel32Handle = LoadLibraryA( "kernel32.dll" );
401     if (kernel32Handle !is null)
402     {
403         RtlCaptureStackBackTrace = cast(RtlCaptureStackBackTraceFunc) GetProcAddress(kernel32Handle, "RtlCaptureStackBackTrace");
404         debug(PRINTF)
405         {
406             if (RtlCaptureStackBackTrace !is null)
407                 printf("Found RtlCaptureStackBackTrace\n");
408         }
409     }
410 
411     debug(PRINTF)
412     {
413         API_VERSION* dbghelpVersion = dbghelp.ImagehlpApiVersion();
414         printf("DbgHelp Version %d.%d.%d\n", dbghelpVersion.MajorVersion, dbghelpVersion.MinorVersion, dbghelpVersion.Revision);
415     }
416 
417     HANDLE hProcess = GetCurrentProcess();
418 
419     DWORD symOptions = dbghelp.SymGetOptions();
420     symOptions |= SYMOPT_LOAD_LINES;
421     symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
422     symOptions |= SYMOPT_DEFERRED_LOAD;
423     symOptions  = dbghelp.SymSetOptions( symOptions );
424 
425     debug(PRINTF) printf("Search paths: %s\n", generateSearchPath().ptr);
426 
427     if (!dbghelp.SymInitialize(hProcess, generateSearchPath().ptr, TRUE))
428         return;
429 
430     dbghelp.SymRegisterCallback64(hProcess, &FixupDebugHeader, 0);
431 
432     InitializeCriticalSection(&mutex);
433     initialized = true;
434 }