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
12module core.sys.windows.stacktrace;
13version (Windows):
14@system:
15
16import core.demangle;
17import core.stdc.stdlib;
18import core.stdc.string;
19import core.sys.windows.dbghelp;
20import core.sys.windows.imagehlp /+: ADDRESS_MODE+/;
21import core.sys.windows.winbase;
22import core.sys.windows.windef;
23
24//debug=PRINTF;
25debug(PRINTF) import core.stdc.stdio;
26
27
28extern(Windows) void RtlCaptureContext(CONTEXT* ContextRecord);
29extern(Windows) DWORD GetEnvironmentVariableA(LPCSTR lpName, LPSTR pBuffer, DWORD nSize);
30
31extern(Windows) alias USHORT function(ULONG FramesToSkip, ULONG FramesToCapture, PVOID *BackTrace, PULONG BackTraceHash) RtlCaptureStackBackTraceFunc;
32
33private __gshared RtlCaptureStackBackTraceFunc RtlCaptureStackBackTrace;
34private __gshared immutable bool initialized;
35
36
37class StackTrace : Throwable.TraceInfo
38{
39public:
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)
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
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
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
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    {
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     */
126    @trusted static char[][] resolve(const(ulong)[] addresses)
127    {
128        synchronized( typeid(StackTrace) )
129        {
130            return resolveNoSync(addresses);
131        }
132    }
133
134private:
135    ulong[] m_trace;
136
137
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
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
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
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
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)
321extern(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
348private 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
371shared 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