├── LICENSE.txt ├── README.md ├── dub.json ├── sample └── sample.d └── source └── backtrace ├── backtrace.d └── package.d /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | backtrace-d 2 | =========== 3 | 4 | backtrace-d is a library that provides a pretty backtrace for D applications 5 | running under Linux. Its usage is very simple, you'll only need to import the 6 | module `backtrace.backtrace` and call `printPrettyTrace`. Also, make sure that 7 | you compile with debug symbols on. On DMD, for example, use the -g flag. 8 | 9 | backtrace-d was tested using DMD 2.063.2 and LDC2 0.12.0. 10 | 11 | You can also try the experimental `install` call to make backtraces showing on 12 | exceptions beautiful. This works using DMD compiled applications only for now. 13 | 14 | Example on using `printPrettyTrace` 15 | ----------------------------------- 16 | 17 | import backtrace; 18 | import std.stdio; 19 | 20 | void main() { 21 | goToF1(); 22 | } 23 | 24 | void goToF1() { 25 | goToF2(); 26 | } 27 | 28 | void goToF2(uint i = 0) { 29 | if (i == 2) { 30 | printPrettyTrace(stderr); return; 31 | } 32 | goToF2(++i); 33 | } 34 | 35 | This will print the following to the standard error: 36 | 37 | Stack trace: 38 | #1: /path/to/source/app.d line (14) in void app.goToF2(uint) 39 | 40 | (11) 41 | (12) void goToF2(uint i = 0) { 42 | (13) if (i == 2) { 43 | >(14) printPrettyTrace(stderr); return; 44 | (15) } 45 | (16) goToF2(++i); 46 | (17) } 47 | 48 | #2: /path/to/source/app.d line (17) in void app.goToF2(uint) 49 | 50 | (14) printPrettyTrace(stderr); return; 51 | (15) } 52 | (16) goToF2(++i); 53 | >(17) } 54 | 55 | #3: /path/to/source/app.d line (17) in void app.goToF2(uint) 56 | #4: /path/to/source/app.d line (10) in void app.goToF1() 57 | #5: /path/to/source/app.d line (5) in _Dmain 58 | 59 | 60 | Example on using `install` (DMD only) 61 | ------------------------------------- 62 | 63 | static import backtrace; 64 | import std.stdio; 65 | 66 | void main() { 67 | backtrace.install(stderr); 68 | 69 | goToF1(); 70 | } 71 | 72 | void goToF1() { 73 | goToF2(); 74 | } 75 | 76 | void goToF2(uint i = 0) { 77 | if (i == 2) throw new Exception("Exception thrown"); 78 | goToF2(++i); 79 | } 80 | 81 | This will print the following to the standard error: 82 | 83 | object.Exception@source/app.d(15): Exception thrown 84 | ---------------- 85 | Stack trace: 86 | #1: /path/to/source/app.d line (16) in void app.goToF2(uint) 87 | 88 | (13) 89 | (14) void goToF2(uint i = 0) { 90 | (15) if (i == 2) throw new Exception("Exception thrown"); 91 | >(16) goToF2(++i); 92 | (17) } 93 | 94 | #2: /path/to/source/app.d line (17) in void app.goToF2(uint) 95 | 96 | (14) void goToF2(uint i = 0) { 97 | (15) if (i == 2) throw new Exception("Exception thrown"); 98 | (16) goToF2(++i); 99 | >(17) } 100 | 101 | #3: /path/to/source/app.d line (17) in void app.goToF2(uint) 102 | #4: /path/to/source/app.d line (12) in void app.goToF1() 103 | #5: /path/to/source/app.d line (7) in _Dmain 104 | ---------------- 105 | 106 | 107 | You can customize the way the backtrace is printed using the following options: 108 | 109 | PrintOptions options; 110 | options.detailedForN = 2; //number of frames to show code for 111 | options.numberOfLinesBefore = 3; //number of lines of code to show before the specific line 112 | options.numberOfLinesAfter = 3; //number of lines of code to show after the specific line 113 | options.colored = true; //enable colored output for the backtrace 114 | options.stopAtDMain = false; //show stack traces after the entry point of the D code 115 | printPrettyTrace(stdout, options); 116 | 117 | 118 | Documentation 119 | ------------- 120 | 121 | //prints the backtrace to `output` using the printing `options` provided. Frames to skip is used to 122 | //skip frames that belong to the internal code of the library. You might need to change this depending 123 | //on the optimization level of your compiler 124 | void printPrettyTrace(File output, PrintOptions options = PrintOptions.init, uint framesToSkip = 1) 125 | 126 | //install the runtime trace handler to print the backtraces 127 | void install(File file, PrintOptions options = PrintOptions.init, uint framesToSkip = 6) 128 | 129 | //returns an array of backtrace addresses 130 | void*[] getBacktrace() 131 | 132 | //returns an array of symbols provided an array of addresses 133 | Symbol[] getBacktraceSymbols(const(void*[]) backtrace) 134 | 135 | //returns an array of lines and files corresponding to the addresses provided 136 | //this uses `addr2line` tool internally 137 | Trace[] getLineTrace(const(void*[]) backtrace) 138 | 139 | Work to do and bugs 140 | ------------------- 141 | 142 | - Inaccurate address to line resolving. 143 | - Problem could reside in the debug symbols emitted by the compilers or the code that produces backtrace addresses. 144 | 145 | - Integrate the library with `Runtime.traceHandler` (work in progress) 146 | - Only works under DMD for now. 147 | 148 | Feedback and pull requests 149 | -------------------------- 150 | 151 | Please use Github's issue tracker. I'm also open to pull and feature requests. 152 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backtrace-d", 3 | "description": "Provides a pretty backtrace for D applications running under Linux", 4 | "homepage": "https://github.com/yazd/backtrace-d", 5 | "copyright": "Copyright © 2013, Yazan Dabain", 6 | "authors": ["Yazan Dabain"], 7 | "targetType": "sourceLibrary", 8 | "license": "BSL-1.0", 9 | 10 | "configurations": 11 | [ 12 | { 13 | "name": "sample", 14 | "targetType": "executable", 15 | "mainSourceFile": "sample/sample.d" 16 | } 17 | ] 18 | } 19 | 20 | -------------------------------------------------------------------------------- /sample/sample.d: -------------------------------------------------------------------------------- 1 | //Written in the D programming language 2 | 3 | /* 4 | * Sample program to demonstrate Backtrace. 5 | * 6 | * Written by: Jason den Dulk. 7 | */ 8 | 9 | module sample; 10 | 11 | import std.conv; 12 | import std.string; 13 | import std.array; 14 | import std.algorithm; 15 | import std.stdio; 16 | 17 | import backtrace; 18 | 19 | void toStdout() { 20 | writeln("#"); 21 | writeln("# Pretty trace direct to stdout."); 22 | writeln("#"); 23 | printPrettyTrace(); 24 | } 25 | 26 | void toStderr() { 27 | writeln("#"); 28 | writeln("# Pretty trace direct to stderr."); 29 | writeln("#"); 30 | printPrettyTrace(stderr); 31 | } 32 | 33 | void asString() { 34 | writeln("#"); 35 | writeln("# Pretty trace as a string"); 36 | writeln("#"); 37 | write(prettyTrace()); 38 | } 39 | 40 | void onException() { 41 | writeln("#"); 42 | writeln("# Pretty trace after an exception"); 43 | writeln("#"); 44 | throw new Exception("FAIL!"); 45 | } 46 | 47 | void onError() { 48 | writeln("#"); 49 | writeln("# Pretty trace after an error"); 50 | writeln("#"); 51 | int[] x = [1,2,3]; 52 | int y = x[5]; // Range violation; 53 | } 54 | 55 | void main() { 56 | backtrace.install(); 57 | writeln("### Backtrace Sample Demo. ###"); 58 | toStdout(); 59 | toStderr(); 60 | asString(); 61 | try { 62 | onException(); 63 | } catch (Exception e) { 64 | writeln("# Line by line"); 65 | foreach (i,l;e.info) 66 | writeln(i,": ",l); 67 | writeln("# via Throwable.toString()"); 68 | write(e.toString()); 69 | } 70 | onError(); 71 | } 72 | -------------------------------------------------------------------------------- /source/backtrace/backtrace.d: -------------------------------------------------------------------------------- 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 | } 310 | -------------------------------------------------------------------------------- /source/backtrace/package.d: -------------------------------------------------------------------------------- 1 | module backtrace; 2 | 3 | public import backtrace.backtrace; 4 | --------------------------------------------------------------------------------