1 /**
2  * Libunwind-based implementation of `TraceInfo`
3  *
4  * This module exposes an handler that uses libunwind to print stack traces.
5  * It is used when druntime is packaged with `DRuntime_Use_Libunwind` or when
6  * the user uses the following in `main`:
7  * ---
8  * import core.runtime;
9  * import core.internal.backtrace.handler;
10  * Runtime.traceHandler = &libunwindDefaultTraceHandler;
11  * ---
12  *
13  * Note that this module uses `dladdr` to retrieve the function's name.
14  * To ensure that local (non-library) functions have their name printed,
15  * the flag `-L--export-dynamic` must be used while compiling,
16  * otherwise only the executable name will be available.
17  *
18  * Authors: Mathias 'Geod24' Lang
19  * Copyright: D Language Foundation - 2020
20  * See_Also: https://www.nongnu.org/libunwind/man/libunwind(3).html
21  */
22 module core.internal.backtrace.handler;
23 
24 version (DRuntime_Use_Libunwind):
25 
26 import core.internal.backtrace.dwarf;
27 import core.internal.backtrace.libunwind;
28 import core.stdc.string;
29 import core.sys.posix.dlfcn;
30 
31 /// Ditto
32 class LibunwindHandler : Throwable.TraceInfo
33 {
34     private static struct FrameInfo
35     {
36         const(void)* address;
37     }
38 
39     size_t numframes;
40     enum MAXFRAMES = 128;
41     FrameInfo[MAXFRAMES] callstack = void;
42 
43     /**
44      * Create a new instance of this trace handler saving the current context
45      *
46      * Params:
47      *   frames_to_skip = The number of frames leading to this one.
48      *                    Defaults to 1. Note that the opApply will not
49      *                    show any frames that appear before _d_throwdwarf.
50      */
51     public this (size_t frames_to_skip = 1) nothrow @nogc
52     {
53         import core.stdc.string : strlen;
54 
55         static assert(typeof(FrameInfo.address).sizeof == unw_word_t.sizeof,
56                       "Mismatch in type size for call to unw_get_proc_name");
57 
58         unw_context_t context;
59         unw_cursor_t cursor;
60         unw_getcontext(&context);
61         unw_init_local(&cursor, &context);
62 
63         while (frames_to_skip > 0 && unw_step(&cursor) > 0)
64             --frames_to_skip;
65 
66         unw_proc_info_t pip = void;
67         foreach (idx, ref frame; this.callstack)
68         {
69             if (unw_get_proc_info(&cursor, &pip) == 0)
70                 frame.address += pip.start_ip;
71 
72             this.numframes++;
73             if (unw_step(&cursor) <= 0)
74                 break;
75         }
76     }
77 
78     ///
79     override int opApply (scope int delegate(ref const(char[])) dg) const
80     {
81         return this.opApply((ref size_t, ref const(char[]) buf) => dg(buf));
82     }
83 
84     ///
85     override int opApply (scope int delegate(ref size_t, ref const(char[])) dg) const
86     {
87         // https://code.woboq.org/userspace/glibc/debug/backtracesyms.c.html
88         // The logic that glibc's backtrace use is to check for for `dli_fname`,
89         // the file name, and error if not present, then check for `dli_sname`.
90         // In case `dli_fname` is present but not `dli_sname`, the address is
91         // printed related to the file. We just print the file.
92         static const(char)[] getFrameName (const(void)* ptr)
93         {
94             Dl_info info = void;
95             // Note: See the module documentation about `-L--export-dynamic`
96             if (dladdr(ptr, &info))
97             {
98                 // Return symbol name if possible
99                 if (info.dli_sname !is null && info.dli_sname[0] != '\0')
100                     return info.dli_sname[0 .. strlen(info.dli_sname)];
101 
102                 // Fall back to file name
103                 if (info.dli_fname !is null && info.dli_fname[0] != '\0')
104                     return info.dli_fname[0 .. strlen(info.dli_fname)];
105             }
106 
107             // `dladdr` failed
108             return "<ERROR: Unable to retrieve function name>";
109         }
110 
111         return traceHandlerOpApplyImpl(numframes,
112             i => callstack[i].address,
113             i => getFrameName(callstack[i].address),
114             dg);
115     }
116 
117     ///
118     override string toString () const
119     {
120         string buf;
121         foreach ( i, line; this )
122             buf ~= i ? "\n" ~ line : line;
123         return buf;
124     }
125 }
126 
127 /**
128  * Convenience function for power users wishing to test this module
129  * See `core.runtime.defaultTraceHandler` for full documentation.
130  */
131 Throwable.TraceInfo defaultTraceHandler (void* ptr = null)
132 {
133     // avoid recursive GC calls in finalizer, trace handlers should be made @nogc instead
134     import core.memory : GC;
135     if (GC.inFinalizer)
136         return null;
137 
138     return new LibunwindHandler();
139 }