The OpenD Programming Language

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 (LDC)
51                 static enum INTERNALFRAMES = 0;
52             else version (Win64)
53                 static enum INTERNALFRAMES = 3;
54             else version (Win32)
55                 static 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                 static enum INTERNALFRAMES = 1;
64             else version (Win32)
65                 static 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                  static assert(0, "unimplemented");
234 
235         size_t frameNum = 0;
236         size_t nframes = 0;
237 
238         // do ... while so that we don't skip the first stackframe
239         do
240         {
241             if (frameNum >= skip)
242             {
243                 buffer[nframes++] = stackframe.AddrPC.Offset;
244                 if (nframes >= buffer.length)
245                     break;
246             }
247             frameNum++;
248         }
249         while (dbghelp.StackWalk64(imageType, hProcess, hThread, &stackframe,
250                                    &ctxt, null, null, null, null));
251         return buffer[0 .. nframes];
252     }
253 
254     static char[][] resolveNoSync(const(ulong)[] addresses) @system
255     {
256         auto dbghelp  = DbgHelp.get();
257         if (dbghelp is null)
258             return []; // dbghelp.dll not available
259 
260         HANDLE hProcess = GetCurrentProcess();
261 
262         static struct BufSymbol
263         {
264         align(1):
265             IMAGEHLP_SYMBOLA64 _base;
266             TCHAR[1024] _buf = void;
267         }
268         BufSymbol bufSymbol=void;
269         IMAGEHLP_SYMBOLA64* symbol = &bufSymbol._base;
270         symbol.SizeOfStruct = IMAGEHLP_SYMBOLA64.sizeof;
271         symbol.MaxNameLength = bufSymbol._buf.length;
272 
273         char[][] trace;
274         version (LDC) bool haveEncounteredDThrowException = false;
275         foreach (pc; addresses)
276         {
277             char[] res;
278             if (dbghelp.SymGetSymFromAddr64(hProcess, pc, null, symbol) &&
279                 *symbol.Name.ptr)
280             {
281                 version (LDC)
282                 {
283                     // when encountering the first `_d_throw_exception()` frame,
284                     // clear the current trace and continue with the next frame
285                     if (!haveEncounteredDThrowException)
286                     {
287                         auto len = strlen(symbol.Name.ptr);
288                         // ignore missing leading underscore (Win64 release) or additional module prefix (Win64 debug)
289                         //import core.stdc.stdio; printf("RAW: %s\n", symbol.Name.ptr);
290                         if (len >= 17 && symbol.Name.ptr[len-17 .. len] == "d_throw_exception")
291                         {
292                             haveEncounteredDThrowException = true;
293                             trace.length = 0;
294                             continue;
295                         }
296                     }
297                 }
298 
299                 DWORD disp;
300                 IMAGEHLP_LINEA64 line=void;
301                 line.SizeOfStruct = IMAGEHLP_LINEA64.sizeof;
302 
303                 if (dbghelp.SymGetLineFromAddr64(hProcess, pc, &disp, &line))
304                     res = formatStackFrame(cast(void*)pc, symbol.Name.ptr,
305                                            line.FileName, line.LineNumber);
306                 else
307                     res = formatStackFrame(cast(void*)pc, symbol.Name.ptr);
308             }
309             else
310                 res = formatStackFrame(cast(void*)pc);
311             trace ~= res;
312         }
313         return trace;
314     }
315 
316     static char[] formatStackFrame(void* pc)
317     {
318         import core.stdc.stdio : snprintf;
319         char[2+2*size_t.sizeof+1] buf=void;
320 
321         immutable len = snprintf(buf.ptr, buf.length, "0x%p", pc);
322         cast(uint)len < buf.length || assert(0);
323         return buf[0 .. len].dup;
324     }
325 
326     static char[] formatStackFrame(void* pc, char* symName)
327     {
328         char[2048] demangleBuf=void;
329 
330         auto res = formatStackFrame(pc);
331         res ~= " in ";
332         const(char)[] tempSymName = symName[0 .. strlen(symName)];
333         // Deal with dmd mangling of long names for OMF 32 bits builds
334         // Note that `target.d` only defines `CRuntime_DigitalMars` for OMF builds
335         version (CRuntime_DigitalMars)
336         {
337             size_t decodeIndex = 0;
338             tempSymName = decodeDmdString(tempSymName, decodeIndex);
339         }
340         res ~= demangle(tempSymName, demangleBuf);
341         return res;
342     }
343 
344     static char[] formatStackFrame(void* pc, char* symName,
345                                    const scope char* fileName, uint lineNum)
346     {
347         import core.stdc.stdio : snprintf;
348         char[11] buf=void;
349 
350         auto res = formatStackFrame(pc, symName);
351         res ~= " at ";
352         res ~= fileName[0 .. strlen(fileName)];
353         res ~= "(";
354         immutable len = snprintf(buf.ptr, buf.length, "%u", lineNum);
355         cast(uint)len < buf.length || assert(0);
356         res ~= buf[0 .. len];
357         res ~= ")";
358         return res;
359     }
360 }
361 
362 
363 // Workaround OPTLINK bug (Bugzilla 8263)
364 extern(Windows) BOOL FixupDebugHeader(HANDLE hProcess, ULONG ActionCode,
365                                       ulong CallbackContext, ulong UserContext) @system
366 {
367     if (ActionCode == CBA_READ_MEMORY)
368     {
369         auto p = cast(IMAGEHLP_CBA_READ_MEMORY*)CallbackContext;
370         if (!(p.addr & 0xFF) && p.bytes == 0x1C &&
371             // IMAGE_DEBUG_DIRECTORY.PointerToRawData
372             (*cast(DWORD*)(p.addr + 24) & 0xFF) == 0x20)
373         {
374             immutable base = DbgHelp.get().SymGetModuleBase64(hProcess, p.addr);
375             // IMAGE_DEBUG_DIRECTORY.AddressOfRawData
376             if (base + *cast(DWORD*)(p.addr + 20) == p.addr + 0x1C &&
377                 *cast(DWORD*)(p.addr + 0x1C) == 0 &&
378                 *cast(DWORD*)(p.addr + 0x20) == ('N'|'B'<<8|'0'<<16|'9'<<24))
379             {
380                 debug(PRINTF) printf("fixup IMAGE_DEBUG_DIRECTORY.AddressOfRawData\n");
381                 memcpy(p.buf, cast(void*)p.addr, 0x1C);
382                 *cast(DWORD*)(p.buf + 20) = cast(DWORD)(p.addr - base) + 0x20;
383                 *p.bytesread = 0x1C;
384                 return TRUE;
385             }
386         }
387     }
388     return FALSE;
389 }
390 
391 private string generateSearchPath() @system
392 {
393     __gshared string[3] defaultPathList = ["_NT_SYMBOL_PATH",
394                                            "_NT_ALTERNATE_SYMBOL_PATH",
395                                            "SYSTEMROOT"];
396 
397     string path;
398     char[2048] temp = void;
399     DWORD len;
400 
401     foreach ( e; defaultPathList )
402     {
403         if ( (len = GetEnvironmentVariableA( e.ptr, temp.ptr, temp.length )) > 0 )
404         {
405             path ~= temp[0 .. len];
406             path ~= ";";
407         }
408     }
409     path ~= "\0";
410     return path;
411 }
412 
413 
414 shared static this()
415 {
416     auto dbghelp = DbgHelp.get();
417 
418     if ( dbghelp is null )
419         return; // dbghelp.dll not available
420 
421     auto kernel32Handle = LoadLibraryA( "kernel32.dll" );
422     if (kernel32Handle !is null)
423     {
424         RtlCaptureStackBackTrace = cast(RtlCaptureStackBackTraceFunc) GetProcAddress(kernel32Handle, "RtlCaptureStackBackTrace");
425         debug(PRINTF)
426         {
427             if (RtlCaptureStackBackTrace !is null)
428                 printf("Found RtlCaptureStackBackTrace\n");
429         }
430     }
431 
432     debug(PRINTF)
433     {
434         API_VERSION* dbghelpVersion = dbghelp.ImagehlpApiVersion();
435         printf("DbgHelp Version %d.%d.%d\n", dbghelpVersion.MajorVersion, dbghelpVersion.MinorVersion, dbghelpVersion.Revision);
436     }
437 
438     HANDLE hProcess = GetCurrentProcess();
439 
440     DWORD symOptions = dbghelp.SymGetOptions();
441     symOptions |= SYMOPT_LOAD_LINES;
442     symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
443     symOptions |= SYMOPT_DEFERRED_LOAD;
444     symOptions  = dbghelp.SymSetOptions( symOptions );
445 
446     debug(PRINTF) printf("Search paths: %s\n", generateSearchPath().ptr);
447 
448     if (!dbghelp.SymInitialize(hProcess, generateSearchPath().ptr, TRUE))
449         return;
450 
451   version (LDC) {} else
452     dbghelp.SymRegisterCallback64(hProcess, &FixupDebugHeader, 0);
453 
454     InitializeCriticalSection(&mutex);
455     initialized = true;
456 }