1 module bc.core.system.backtrace; 2 3 version (D_BetterC) {} 4 else version (linux): 5 6 /** 7 * This struct us used to mimic private class in https://github.com/dlang/druntime/blob/master/src/core/runtime.d#L734 8 * so we can access callstack. 9 */ 10 struct TraceInfo 11 { 12 // class fields 13 void* _vtbl; 14 void* _monitor; 15 void* _interface; // introduced in DMD 2.071 16 17 // make sure the ABI matches 18 static assert ( 19 {static interface I {} static class C: I {} return __traits(classInstanceSize, C);}() == (void*[3]).sizeof 20 ); 21 22 // actual fields we care about 23 static enum MAXFRAMES = 128; 24 int numframes; 25 void*[MAXFRAMES] callstack; 26 27 @property void*[] frames() return nothrow @trusted @nogc { 28 return callstack.ptr[0 .. numframes]; 29 } 30 31 version (Posix) 32 { 33 /// Gather trace info from Throwable 34 this(Throwable ex) nothrow @trusted @nogc pure 35 { 36 this(ex.info); 37 } 38 39 this(Throwable.TraceInfo ti) nothrow @trusted @nogc pure 40 { 41 if (ti !is null) 42 { 43 auto obj = cast(Object)ti; 44 45 // this can change in druntime 46 assert(typeid(obj).name == "core.runtime.DefaultTraceInfo", "Unexpected trace info type"); 47 48 auto trace = cast(TraceInfo*)(cast(void*)obj); 49 if (trace.numframes) 50 { 51 this.numframes = trace.numframes; 52 this.callstack[0..numframes] = trace.callstack[0..numframes]; 53 } 54 } 55 } 56 } 57 58 /// Gets current trace info 59 static TraceInfo current()() nothrow @trusted @nogc 60 { 61 version (Posix) 62 { 63 import bc.core.system.linux.execinfo : backtrace, thread_stackBottom; 64 65 // just a copy from: https://github.com/dlang/druntime/blob/master/src/core/runtime.d#L742 66 // again, cant't use directly as it's not @nogc 67 68 // it may not be 1 but it is good enough to get 69 // in CALL instruction address range for backtrace 70 enum CALL_INSTRUCTION_SIZE = 1; 71 72 TraceInfo ret; 73 74 static if (__traits(compiles, backtrace((void**).init, int.init))) 75 ret.numframes = backtrace(ret.callstack.ptr, MAXFRAMES); 76 // Backtrace succeeded, adjust the frame to point to the caller 77 if (ret.numframes >= 2) 78 foreach (ref elem; ret.callstack) 79 elem -= CALL_INSTRUCTION_SIZE; 80 else // backtrace() failed, do it ourselves 81 { 82 static void** getBasePtr() nothrow @nogc 83 { 84 version (D_InlineAsm_X86) 85 asm nothrow @nogc { naked; mov EAX, EBP; ret; } 86 else 87 version (D_InlineAsm_X86_64) 88 asm nothrow @nogc { naked; mov RAX, RBP; ret; } 89 else 90 return null; 91 } 92 93 auto stackTop = getBasePtr(); 94 auto stackBottom = cast(void**)thread_stackBottom(); 95 void* dummy; 96 97 if (stackTop && &dummy < stackTop && stackTop < stackBottom) 98 { 99 auto stackPtr = stackTop; 100 101 for (ret.numframes = 0; stackTop <= stackPtr && stackPtr < stackBottom && ret.numframes < MAXFRAMES; ) 102 { 103 ret.callstack[ret.numframes++] = *(stackPtr + 1) - CALL_INSTRUCTION_SIZE; 104 stackPtr = cast(void**) *stackPtr; 105 } 106 } 107 } 108 } 109 else static assert(0, "Unsupported platform"); 110 111 if (ret.numframes > 1) 112 { 113 // drop first frame as it points to this method 114 import std.algorithm : copy; 115 ret.numframes--; 116 ret.callstack[1..ret.numframes+1].copy(ret.callstack[0..ret.numframes]); 117 } 118 119 return ret; 120 } 121 122 /// Dumps trace info to the provided sink. 123 /// Returns: size of written data 124 size_t dumpTo(S)(ref S sink) nothrow @nogc @trusted // TODO: well.. 125 { 126 if (numframes) 127 { 128 version (Posix) 129 { 130 import bc.core.system.linux.dwarf : dumpCallstack, getFirstFrame; 131 import bc.core.system.linux.elf : Image; 132 import bc.core.system.linux.execinfo : backtrace_symbols; 133 import core.sys.posix.stdlib : free; 134 135 const char** frameList = () @trusted { return backtrace_symbols(&callstack[0], cast(int) numframes); }(); 136 scope(exit) () @trusted { free(cast(void*)frameList); }(); 137 138 auto first = getFirstFrame(callstack[0..numframes], frameList); 139 version (LDC) {} 140 else { 141 static if (__VERSION__ < 2092) enum FIRSTFRAME = 4; 142 else static if (__VERSION__ < 2096) enum FIRSTFRAME = 5; 143 else enum FIRSTFRAME = 0; 144 145 // getFirstFrame searches for throw in stack, that is not always the case (ie when printing out just the current stack) 146 if (!first) first = FIRSTFRAME; 147 } 148 149 auto image = Image.openSelf(); 150 if (image.isValid) 151 return image.processDebugLineSectionData(sink, callstack[first..numframes], &frameList[first], &dumpCallstack!S); 152 153 return dumpCallstack(sink, image, callstack[first..numframes], &frameList[first], null); 154 } 155 else static assert(0, "Unsupported platform"); 156 } 157 return 0; 158 } 159 } 160 161 version (Posix) 162 { 163 // get current callstack 164 unittest 165 { 166 import bc.string.string : String; 167 168 auto ti = TraceInfo.current(); 169 String buf, buf2; 170 immutable ret = ti.dumpTo(buf); 171 assert(ret == buf.length); 172 173 // import std.stdio : writeln; 174 // writeln(cast(const(char)[])buf[]); 175 } 176 177 // get callstack from defaultTraceHandler 178 unittest 179 { 180 import bc.string.string : String; 181 import core.runtime : defaultTraceHandler; 182 183 auto dti = defaultTraceHandler(); 184 assert(dti !is null); 185 186 String buf, buf2; 187 auto ti = TraceInfo(dti); 188 immutable ret = ti.dumpTo(buf); 189 assert(ret == buf.length); 190 191 foreach (ln; dti) { buf2 ~= ln; buf2 ~= '\n'; } 192 193 // import std.stdio : writeln; 194 // writeln("-----------------"); 195 // writeln("our: ", cast(const(char)[])buf[]); 196 // writeln("-----------------"); 197 // writeln("orig: ", cast(const(char)[])buf2[]); 198 // writeln("-----------------"); 199 static if (__VERSION__ >= 2095) 200 { 201 // we try to reflect last compiler behavior, previous might differ 202 assert(buf[] == buf2[0..$-1]); 203 } 204 else 205 { 206 import std.algorithm : countUntil; 207 immutable ln = buf[].countUntil('\n'); 208 assert(ln>0); 209 assert(buf[0..ln] == buf2[0..ln]); 210 } 211 } 212 }