1 //Written in the D programming language 2 3 module backtrace.backtrace; 4 5 version(linux) { 6 // allow only linux platform 7 } else { 8 pragma(msg, "backtrace only works in a Linux environment"); 9 } 10 11 version(linux): 12 13 import std.stdio; 14 import core.sys.linux.execinfo; 15 16 private enum maxBacktraceSize = 32; 17 private alias TraceHandler = Throwable.TraceInfo function(void* ptr); 18 19 extern (C) void* thread_stackBottom(); 20 21 struct Trace { 22 string file; 23 uint line; 24 } 25 26 struct Symbol { 27 string line; 28 29 string demangled() const { 30 import std.demangle; 31 import std.algorithm, std.range; 32 import std.conv : to; 33 dchar[] symbolWith0x = line.retro().find(")").dropOne().until("(").array().retro().array(); 34 if (symbolWith0x.length == 0) return ""; 35 else return demangle(symbolWith0x.until("+").to!string()); 36 } 37 } 38 39 struct PrintOptions { 40 uint detailedForN = 2; 41 bool colored = false; 42 uint numberOfLinesBefore = 3; 43 uint numberOfLinesAfter = 3; 44 bool stopAtDMain = true; 45 } 46 47 version(DigitalMars) { 48 49 void*[] getBacktrace() { 50 enum CALL_INST_LENGTH = 1; // I don't know the size of the call instruction 51 // and whether it is always 5. I picked 1 instead 52 // because it is enough to get the backtrace 53 // to point at the call instruction 54 void*[maxBacktraceSize] buffer; 55 56 static void** getBasePtr() { 57 version(D_InlineAsm_X86) { 58 asm { naked; mov EAX, EBP; ret; } 59 } else version(D_InlineAsm_X86_64) { 60 asm { naked; mov RAX, RBP; ret; } 61 } else return null; 62 } 63 64 auto stackTop = getBasePtr(); 65 auto stackBottom = cast(void**) thread_stackBottom(); 66 void* dummy; 67 uint traceSize = 0; 68 69 if (stackTop && &dummy < stackTop && stackTop < stackBottom) { 70 auto stackPtr = stackTop; 71 72 for (traceSize = 0; stackTop <= stackPtr && stackPtr < stackBottom && traceSize < buffer.length; ) { 73 buffer[traceSize++] = (*(stackPtr + 1)) - CALL_INST_LENGTH; 74 stackPtr = cast(void**) *stackPtr; 75 } 76 } 77 78 return buffer[0 .. traceSize].dup; 79 } 80 81 } else { 82 83 void*[] getBacktrace() { 84 void*[maxBacktraceSize] buffer; 85 auto size = backtrace(buffer.ptr, buffer.length); 86 return buffer[0 .. size].dup; 87 } 88 89 } 90 91 Symbol[] getBacktraceSymbols(const(void*[]) backtrace) { 92 import core.stdc.stdlib : free; 93 import std.conv : to; 94 95 Symbol[] symbols = new Symbol[backtrace.length]; 96 char** c_symbols = backtrace_symbols(backtrace.ptr, cast(int) backtrace.length); 97 foreach (i; 0 .. backtrace.length) { 98 symbols[i] = Symbol(c_symbols[i].to!string()); 99 } 100 free(c_symbols); 101 102 return symbols; 103 } 104 105 Trace[] getLineTrace(const(void*[]) backtrace) { 106 import std.conv : to; 107 import std.string : chomp; 108 import std.algorithm, std.range; 109 import std.process; 110 111 auto addr2line = pipeProcess(["addr2line", "-e" ~ exePath()], Redirect.stdin | Redirect.stdout); 112 scope(exit) addr2line.pid.wait(); 113 114 Trace[] trace = new Trace[backtrace.length]; 115 116 foreach (i, bt; backtrace) { 117 addr2line.stdin.writefln("0x%X", bt); 118 addr2line.stdin.flush(); 119 dstring reply = addr2line.stdout.readln!dstring().chomp(); 120 with (trace[i]) { 121 auto split = reply.retro().findSplit(":"); 122 if (split[0].equal("?")) line = 0; 123 else line = split[0].retro().to!uint; 124 file = split[2].retro().to!string; 125 } 126 } 127 128 executeShell("kill -INT " ~ addr2line.pid.processID.to!string); 129 return trace; 130 } 131 132 private string exePath() { 133 import std.file : readLink; 134 import std.path : absolutePath; 135 string link = readLink("/proc/self/exe"); 136 string path = absolutePath(link, "/proc/self/"); 137 return path; 138 } 139 140 void printPrettyTrace(PrintOptions options = PrintOptions.init, uint framesToSkip = 2) { 141 printPrettyTrace(stdout, options, framesToSkip); 142 } 143 144 void printPrettyTrace(File output, PrintOptions options = PrintOptions.init, uint framesToSkip = 1) { 145 void*[] bt = getBacktrace(); 146 output.write(getPrettyTrace(bt, options, framesToSkip)); 147 } 148 149 string prettyTrace(PrintOptions options = PrintOptions.init, uint framesToSkip = 1) { 150 void*[] bt = getBacktrace(); 151 return getPrettyTrace(bt, options, framesToSkip); 152 } 153 154 private string getPrettyTrace(const(void*[]) bt, PrintOptions options = PrintOptions.init, uint framesToSkip = 1) { 155 import std.algorithm : max; 156 import std.range; 157 import std.format; 158 159 Symbol[] symbols = getBacktraceSymbols(bt); 160 Trace[] trace = getLineTrace(bt); 161 162 enum Color : char { 163 black = '0', 164 red, 165 green, 166 yellow, 167 blue, 168 magenta, 169 cyan, 170 white 171 } 172 173 string forecolor(Color color) { 174 if (!options.colored) return ""; 175 else return "\u001B[3" ~ color ~ "m"; 176 } 177 178 string backcolor(Color color) { 179 if (!options.colored) return ""; 180 else return "\u001B[4" ~ color ~ "m"; 181 } 182 183 string reset() { 184 if (!options.colored) return ""; 185 else return "\u001B[0m"; 186 } 187 188 auto output = appender!string(); 189 190 output.put("Stack trace:\n"); 191 192 foreach(i, t; trace.drop(framesToSkip)) { 193 auto symbol = symbols[framesToSkip + i].demangled; 194 195 formattedWrite( 196 output, 197 "#%d: %s%s%s line %s(%s)%s%s%s%s%s @ %s0x%s%s\n", 198 i + 1, 199 forecolor(Color.red), 200 t.file, 201 reset(), 202 forecolor(Color.yellow), 203 t.line, 204 reset(), 205 symbol.length == 0 ? "" : " in ", 206 forecolor(Color.green), 207 symbol, 208 reset(), 209 forecolor(Color.green), 210 bt[i + 1], 211 reset() 212 ); 213 214 if (i < options.detailedForN) { 215 uint startingLine = max(t.line - options.numberOfLinesBefore - 1, 0); 216 uint endingLine = t.line + options.numberOfLinesAfter; 217 218 if (t.file == "??") continue; 219 220 File code; 221 try { 222 code = File(t.file, "r"); 223 } catch (Exception ex) { 224 continue; 225 } 226 227 auto lines = code.byLine(); 228 229 lines.drop(startingLine); 230 auto lineNumber = startingLine + 1; 231 output.put("\n"); 232 foreach (line; lines.take(endingLine - startingLine)) { 233 formattedWrite( 234 output, 235 "%s%s(%d)%s%s%s\n", 236 forecolor(t.line == lineNumber ? Color.yellow : Color.cyan), 237 t.line == lineNumber ? ">" : " ", 238 lineNumber, 239 forecolor(t.line == lineNumber ? Color.yellow : Color.blue), 240 line, 241 reset(), 242 ); 243 lineNumber++; 244 } 245 output.put("\n"); 246 } 247 248 if (options.stopAtDMain && symbol == "_Dmain") break; 249 } 250 return output.data; 251 } 252 253 private class BTTraceHandler : Throwable.TraceInfo { 254 import std.algorithm; 255 256 void*[] backtrace; 257 PrintOptions options; 258 uint framesToSkip; 259 260 this(PrintOptions options, uint framesToSkip) { 261 this.options = options; 262 this.framesToSkip = framesToSkip; 263 backtrace = getBacktrace(); 264 } 265 266 override int opApply(scope int delegate(ref const(char[])) dg) const { 267 return opApply((ref size_t i, ref const(char[]) s) { 268 return dg(s); 269 }); 270 } 271 272 override int opApply(scope int delegate(ref size_t, ref const(char[])) dg) const { 273 int result = 0; 274 auto prettyTrace = getPrettyTrace(backtrace, options, framesToSkip); 275 auto bylines = prettyTrace.splitter("\n"); 276 size_t i = 0; 277 foreach (l; bylines) { 278 result = dg(i, l); 279 if (result) 280 break; 281 ++i; 282 } 283 return result; 284 } 285 286 override string toString() const { 287 return getPrettyTrace(backtrace, options, framesToSkip); 288 } 289 } 290 291 private static PrintOptions runtimePrintOptions; 292 private static uint runtimeFramesToSkip; 293 294 private Throwable.TraceInfo btTraceHandler(void* ptr) { 295 return new BTTraceHandler(runtimePrintOptions, runtimeFramesToSkip); 296 } 297 298 // This is kept for backwards compatibility, however, file was never used 299 // so it is redundant. 300 void install(File file, PrintOptions options = PrintOptions.init, uint framesToSkip = 5) { 301 install(options, framesToSkip); 302 } 303 304 void install(PrintOptions options = PrintOptions.init, uint framesToSkip = 5) { 305 import core.runtime; 306 runtimePrintOptions = options; 307 runtimeFramesToSkip = framesToSkip; 308 Runtime.traceHandler = &btTraceHandler; 309 }