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 }