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 }