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 nulib.system.win32.stacktrace;
13 
14 
15 import core.demangle;
16 import core.stdc.stdlib;
17 import core.stdc.string;
18 import nulib.system.win32.dbghelp;
19 import nulib.system.win32.imagehlp /+: ADDRESS_MODE+/;
20 import nulib.system.win32.winbase;
21 import nulib.system.win32.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 (LDC)
51                 enum INTERNALFRAMES = 0;
52             else version (Win64)
53                 enum INTERNALFRAMES = 3;
54             else version (Win32)
55                 enum INTERNALFRAMES = 2;
56 
57             skip += INTERNALFRAMES; //skip the stack frames within the StackTrace class
58         }
59         else
60         {
61             //When a exception context is given the first stack frame is repeated for some reason
62             version (Win64)
63                 enum INTERNALFRAMES = 1;
64             else version (Win32)
65                 enum INTERNALFRAMES = 1;
66 
67             skip += INTERNALFRAMES;
68         }
69         if (initialized)
70             m_trace = trace(tracebuf[], skip, context);
71     }
72 
73     override int opApply( scope int delegate(ref const(char[])) dg ) const
74     {
75         return opApply( (ref size_t, ref const(char[]) buf)
76                         {
77                             return dg( buf );
78                         });
79     }
80 
81 
82     override int opApply( scope int delegate(ref size_t, ref const(char[])) dg ) const
83     {
84         int result;
85         foreach ( i, e; resolve(m_trace) )
86         {
87             if ( (result = dg( i, e )) != 0 )
88                 break;
89         }
90         return result;
91     }
92 
93 
94     @trusted override string toString() const
95     {
96         string result;
97 
98         foreach ( e; this )
99         {
100             result ~= e ~ "\n";
101         }
102         return result;
103     }
104 
105     /**
106      * Receive a stack trace in the form of an address list. One form accepts
107      * an allocated buffer, the other form automatically allocates the buffer.
108      *
109      * Params:
110      *  skip = How many stack frames should be skipped.
111      *  context = The context that should be used. If null the current context is used.
112      *  buffer = The buffer to use for the trace. This should be at least 63 elements.
113      * Returns:
114      *  A list of addresses that can be passed to resolve at a later point in time.
115      */
116     static ulong[] trace(size_t skip = 0, CONTEXT* context = null)
117     {
118         return trace(new ulong[63], skip, context);
119     }
120 
121     /// ditto
122     static ulong[] trace(ulong[] buffer, size_t skip = 0, CONTEXT* context = null) @nogc
123     {
124         EnterCriticalSection(&mutex);
125         scope(exit) LeaveCriticalSection(&mutex);
126 
127         return traceNoSync(buffer, skip, context);
128     }
129 
130     /**
131      * Resolve a stack trace.
132      * Params:
133      *  addresses = A list of addresses to resolve.
134      * Returns:
135      *  An array of strings with the results.
136      */
137     @trusted static char[][] resolve(const(ulong)[] addresses)
138     {
139         // FIXME: make @nogc to avoid having to disable resolution within finalizers
140         import core.memory : GC;
141         if (GC.inFinalizer)
142             return null;
143 
144         EnterCriticalSection(&mutex);
145         scope(exit) LeaveCriticalSection(&mutex);
146 
147         return resolveNoSync(addresses);
148     }
149 
150 private:
151     ulong[128] tracebuf;
152     ulong[] m_trace;
153 
154 
155     static ulong[] traceNoSync(ulong[] buffer, size_t skip, CONTEXT* context) @nogc
156     {
157         auto dbghelp  = DbgHelp.get();
158         if (dbghelp is null)
159             return []; // dbghelp.dll not available
160 
161         if (buffer.length >= 63 && RtlCaptureStackBackTrace !is null &&
162             context is null)
163         {
164             version (Win64)
165             {
166                 auto bufptr = cast(void**)buffer.ptr;
167             }
168             version (Win32)
169             {
170                 size_t[63] bufstorage = void; // On windows xp the sum of "frames to skip" and "frames to capture" can't be greater then 63
171                 auto bufptr = cast(void**)bufstorage.ptr;
172             }
173             auto backtraceLength = RtlCaptureStackBackTrace(cast(ULONG)skip, cast(ULONG)(63 - skip), bufptr, null);
174 
175             // If we get a backtrace and it does not have the maximum length use it.
176             // Otherwise rely on tracing through StackWalk64 which is slower but works when no frame pointers are available.
177             if (backtraceLength > 1 && backtraceLength < 63 - skip)
178             {
179                 debug(PRINTF) printf("Using result from RtlCaptureStackBackTrace\n");
180                 version (Win32)
181                 {
182                     foreach (i, ref e; buffer[0 .. backtraceLength])
183                     {
184                         e = bufstorage[i];
185                     }
186                 }
187                 return buffer[0..backtraceLength];
188             }
189         }
190 
191         HANDLE       hThread  = GetCurrentThread();
192         HANDLE       hProcess = GetCurrentProcess();
193         CONTEXT      ctxt;
194 
195         if (context is null)
196         {
197             ctxt.ContextFlags = CONTEXT_FULL;
198             RtlCaptureContext(&ctxt);
199         }
200         else
201         {
202             ctxt = *context;
203         }
204 
205         //x86
206         STACKFRAME64 stackframe;
207         with (stackframe)
208         {
209             version (X86)
210             {
211                 enum Flat = ADDRESS_MODE.AddrModeFlat;
212                 AddrPC.Offset    = ctxt.Eip;
213                 AddrPC.Mode      = Flat;
214                 AddrFrame.Offset = ctxt.Ebp;
215                 AddrFrame.Mode   = Flat;
216                 AddrStack.Offset = ctxt.Esp;
217                 AddrStack.Mode   = Flat;
218             }
219         else version (X86_64)
220             {
221                 enum Flat = ADDRESS_MODE.AddrModeFlat;
222                 AddrPC.Offset    = ctxt.Rip;
223                 AddrPC.Mode      = Flat;
224                 AddrFrame.Offset = ctxt.Rbp;
225                 AddrFrame.Mode   = Flat;
226                 AddrStack.Offset = ctxt.Rsp;
227                 AddrStack.Mode   = Flat;
228             }
229         }
230 
231         version (X86)         enum imageType = IMAGE_FILE_MACHINE_I386;
232         else version (X86_64) enum imageType = IMAGE_FILE_MACHINE_AMD64;
233         else version (AArch64) enum imageType = IMAGE_FILE_MACHINE_ARM64;
234         else                  static assert(0, "unimplemented");
235 
236         size_t frameNum = 0;
237         size_t nframes = 0;
238 
239         // do ... while so that we don't skip the first stackframe
240         do
241         {
242             if (frameNum >= skip)
243             {
244                 buffer[nframes++] = stackframe.AddrPC.Offset;
245                 if (nframes >= buffer.length)
246                     break;
247             }
248             frameNum++;
249         }
250         while (dbghelp.StackWalk64(imageType, hProcess, hThread, &stackframe,
251                                    &ctxt, null, null, null, null));
252         return buffer[0 .. nframes];
253     }
254 
255     static char[][] resolveNoSync(const(ulong)[] addresses)
256     {
257         auto dbghelp  = DbgHelp.get();
258         if (dbghelp is null)
259             return []; // dbghelp.dll not available
260 
261         HANDLE hProcess = GetCurrentProcess();
262 
263         static struct BufSymbol
264         {
265         align(1):
266             IMAGEHLP_SYMBOLA64 _base;
267             TCHAR[1024] _buf = void;
268         }
269         BufSymbol bufSymbol=void;
270         IMAGEHLP_SYMBOLA64* symbol = &bufSymbol._base;
271         symbol.SizeOfStruct = IMAGEHLP_SYMBOLA64.sizeof;
272         symbol.MaxNameLength = bufSymbol._buf.length;
273 
274         char[][] trace;
275         version (LDC) bool haveEncounteredDThrowException = false;
276         foreach (pc; addresses)
277         {
278             char[] res;
279             if (dbghelp.SymGetSymFromAddr64(hProcess, pc, null, symbol) &&
280                 *symbol.Name.ptr)
281             {
282                 version (LDC)
283                 {
284                     // when encountering the first `_d_throw_exception()` frame,
285                     // clear the current trace and continue with the next frame
286                     if (!haveEncounteredDThrowException)
287                     {
288                         auto len = strlen(symbol.Name.ptr);
289                         // ignore missing leading underscore (Win64 release) or additional module prefix (Win64 debug)
290                         //import core.stdc.stdio; printf("RAW: %s\n", symbol.Name.ptr);
291                         if (len >= 17 && symbol.Name.ptr[len-17 .. len] == "d_throw_exception")
292                         {
293                             haveEncounteredDThrowException = true;
294                             trace.length = 0;
295                             continue;
296                         }
297                     }
298                 }
299 
300                 DWORD disp;
301                 IMAGEHLP_LINEA64 line=void;
302                 line.SizeOfStruct = IMAGEHLP_LINEA64.sizeof;
303 
304                 if (dbghelp.SymGetLineFromAddr64(hProcess, pc, &disp, &line))
305                     res = formatStackFrame(cast(void*)pc, symbol.Name.ptr,
306                                            line.FileName, line.LineNumber);
307                 else
308                     res = formatStackFrame(cast(void*)pc, symbol.Name.ptr);
309             }
310             else
311                 res = formatStackFrame(cast(void*)pc);
312             trace ~= res;
313         }
314         return trace;
315     }
316 
317     static char[] formatStackFrame(void* pc)
318     {
319         import core.stdc.stdio : snprintf;
320         char[2+2*size_t.sizeof+1] buf=void;
321 
322         immutable len = snprintf(buf.ptr, buf.length, "0x%p", pc);
323         cast(uint)len < buf.length || assert(0);
324         return buf[0 .. len].dup;
325     }
326 
327     static char[] formatStackFrame(void* pc, char* symName)
328     {
329         char[2048] demangleBuf=void;
330 
331         auto res = formatStackFrame(pc);
332         res ~= " in ";
333         const(char)[] tempSymName = symName[0 .. strlen(symName)];
334         res ~= demangle(tempSymName, demangleBuf);
335         return res;
336     }
337 
338     static char[] formatStackFrame(void* pc, char* symName,
339                                    const scope char* fileName, uint lineNum)
340     {
341         import core.stdc.stdio : snprintf;
342         char[11] buf=void;
343 
344         auto res = formatStackFrame(pc, symName);
345         res ~= " at ";
346         res ~= fileName[0 .. strlen(fileName)];
347         res ~= "(";
348         immutable len = snprintf(buf.ptr, buf.length, "%u", lineNum);
349         cast(uint)len < buf.length || assert(0);
350         res ~= buf[0 .. len];
351         res ~= ")";
352         return res;
353     }
354 }
355 
356 
357 private string generateSearchPath()
358 {
359     __gshared string[3] defaultPathList = ["_NT_SYMBOL_PATH",
360                                            "_NT_ALTERNATE_SYMBOL_PATH",
361                                            "SYSTEMROOT"];
362 
363     string path;
364     char[2048] temp = void;
365     DWORD len;
366 
367     foreach ( e; defaultPathList )
368     {
369         if ( (len = GetEnvironmentVariableA( e.ptr, temp.ptr, temp.length )) > 0 )
370         {
371             path ~= temp[0 .. len];
372             path ~= ";";
373         }
374     }
375     path ~= "\0";
376     return path;
377 }
378 
379 
380 shared static this()
381 {
382     auto dbghelp = DbgHelp.get();
383 
384     if ( dbghelp is null )
385         return; // dbghelp.dll not available
386 
387     auto kernel32Handle = LoadLibraryA( "kernel32.dll" );
388     if (kernel32Handle !is null)
389     {
390         RtlCaptureStackBackTrace = cast(RtlCaptureStackBackTraceFunc) GetProcAddress(kernel32Handle, "RtlCaptureStackBackTrace");
391         debug(PRINTF)
392         {
393             if (RtlCaptureStackBackTrace !is null)
394                 printf("Found RtlCaptureStackBackTrace\n");
395         }
396     }
397 
398     debug(PRINTF)
399     {
400         API_VERSION* dbghelpVersion = dbghelp.ImagehlpApiVersion();
401         printf("DbgHelp Version %d.%d.%d\n", dbghelpVersion.MajorVersion, dbghelpVersion.MinorVersion, dbghelpVersion.Revision);
402     }
403 
404     HANDLE hProcess = GetCurrentProcess();
405 
406     DWORD symOptions = dbghelp.SymGetOptions();
407     symOptions |= SYMOPT_LOAD_LINES;
408     symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
409     symOptions |= SYMOPT_DEFERRED_LOAD;
410     symOptions  = dbghelp.SymSetOptions( symOptions );
411 
412     debug(PRINTF) printf("Search paths: %s\n", generateSearchPath().ptr);
413 
414     if (!dbghelp.SymInitialize(hProcess, generateSearchPath().ptr, TRUE))
415         return;
416 
417     InitializeCriticalSection(&mutex);
418     initialized = true;
419 }