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 }