1 // GNU D Compiler routines for stack backtrace support.
2 // Copyright (C) 2013-2020 Free Software Foundation, Inc.
3 
4 // GCC is free software; you can redistribute it and/or modify it under
5 // the terms of the GNU General Public License as published by the Free
6 // Software Foundation; either version 3, or (at your option) any later
7 // version.
8 
9 // GCC is distributed in the hope that it will be useful, but WITHOUT ANY
10 // WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 // for more details.
13 
14 // Under Section 7 of GPL version 3, you are granted additional
15 // permissions described in the GCC Runtime Library Exception, version
16 // 3.1, as published by the Free Software Foundation.
17 
18 // You should have received a copy of the GNU General Public License and
19 // a copy of the GCC Runtime Library Exception along with this program;
20 // see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
21 // <http://www.gnu.org/licenses/>.
22 
23 module gcc.backtrace;
24 
25 import gcc.libbacktrace;
26 
version(Posix)27 version (Posix)
28 {
29     // NOTE: The first 5 frames with the current implementation are
30     //       inside core.runtime and the object code, so eliminate
31     //       these for readability.  The alternative would be to
32     //       exclude the first N frames that are in a list of
33     //       mangled function names.
34     private enum FIRSTFRAME = 5;
35 }
36 else
37 {
38     // NOTE: On Windows, the number of frames to exclude is based on
39     //       whether the exception is user or system-generated, so
40     //       it may be necessary to exclude a list of function names
41     //       instead.
42     private enum FIRSTFRAME = 0;
43 }
44 
45 // Max size per line of the traceback.
46 private enum MAX_BUFSIZE = 1536;
47 
48 static if (BACKTRACE_SUPPORTED && !BACKTRACE_USES_MALLOC)
49 {
50     import core.stdc.stdint, core.stdc.string, core.stdc.stdio;
51     private enum MAXFRAMES = 128;
52 
simpleCallback(void * data,uintptr_t pc)53     extern(C) int simpleCallback(void* data, uintptr_t pc)
54     {
55         auto context = cast(LibBacktrace)data;
56 
57         if (context.numPCs == MAXFRAMES)
58             return 1;
59 
60         context.pcs[context.numPCs++] = pc;
61         return 0;
62     }
63 
64     /*
65      * Used for backtrace_create_state and backtrace_simple
66      */
simpleErrorCallback(void * data,const (char)* msg,int errnum)67     extern(C) void simpleErrorCallback(void* data, const(char)* msg, int errnum)
68     {
69         if (data) // context is not available in backtrace_create_state
70         {
71             auto context = cast(LibBacktrace)data;
72             strncpy(context.errorBuf.ptr, msg, context.errorBuf.length - 1);
73             context.error = errnum;
74         }
75     }
76 
77     /*
78      * Used for backtrace_pcinfo
79      */
pcinfoCallback(void * data,uintptr_t pc,const (char)* filename,int lineno,const (char)* func)80     extern(C) int pcinfoCallback(void* data, uintptr_t pc, const(char)* filename,
81                                  int lineno, const(char)* func)
82     {
83         auto context = cast(SymbolCallbackInfo*)data;
84 
85         // Try to get the function name via backtrace_syminfo
86         if (func is null)
87         {
88             SymbolCallbackInfo2 info;
89             info.base = context;
90             info.filename = filename;
91             info.lineno = lineno;
92             if (backtrace_syminfo(context.state, pc, &syminfoCallback2, null, &info) != 0)
93             {
94                 return context.retval;
95             }
96         }
97 
98         auto sym = SymbolOrError(0, SymbolInfo(func, filename, lineno, cast(void*)pc));
99         context.retval = context.applyCB(context.num, sym);
100         context.num++;
101 
102         return context.retval;
103     }
104 
105     /*
106      * Used for backtrace_pcinfo and backtrace_syminfo
107      */
pcinfoErrorCallback(void * data,const (char)* msg,int errnum)108     extern(C) void pcinfoErrorCallback(void* data, const(char)* msg, int errnum)
109     {
110         auto context = cast(SymbolCallbackInfo*)data;
111 
112         if (errnum == -1)
113         {
114             context.noInfo = true;
115             return;
116         }
117 
118         SymbolOrError symError;
119         symError.errnum = errnum;
120         symError.msg = msg;
121 
122         size_t i = 0;
123         context.retval = context.applyCB(i, symError);
124     }
125 
126     /*
127      * Used for backtrace_syminfo (in opApply)
128      */
syminfoCallback(void * data,uintptr_t pc,const (char)* symname,uintptr_t symval)129     extern(C) void syminfoCallback(void* data, uintptr_t pc,
130                                    const(char)* symname, uintptr_t symval)
131     {
132         auto context = cast(SymbolCallbackInfo*)data;
133 
134         auto sym = SymbolOrError(0, SymbolInfo(symname, null, 0, cast(void*)pc));
135         context.retval = context.applyCB(context.num, sym);
136 
137         context.num++;
138     }
139 
140     /*
141      * This callback is used if backtrace_syminfo is called from the pcinfoCallback
142      * callback. It merges it's information with the information from pcinfoCallback.
143      */
syminfoCallback2(void * data,uintptr_t pc,const (char)* symname,uintptr_t symval)144     extern(C) void syminfoCallback2(void* data, uintptr_t pc,
145                                     const(char)* symname, uintptr_t symval)
146     {
147         auto context = cast(SymbolCallbackInfo2*)data;
148 
149         auto sym = SymbolOrError(0, SymbolInfo(symname, context.filename, context.lineno,
150             cast(void*)pc));
151         context.base.retval = context.base.applyCB(context.base.num, sym);
152 
153         context.base.num++;
154     }
155 
156     /*
157      * The callback type used with the opApply overload which returns a SymbolOrError
158      */
159     private alias int delegate(ref size_t, ref SymbolOrError) ApplyCallback;
160 
161     /*
162      * Passed to syminfoCallback, pcinfoCallback and pcinfoErrorCallback
163      */
164     struct SymbolCallbackInfo
165     {
166         bool noInfo = false;       // True if debug info / symbol table is not available
167         size_t num = 0;            // Counter for opApply
168         int retval;                // Value returned by applyCB
169         backtrace_state* state;
170 
171         // info.fileName / funcName / errmsg may become invalid after this delegate returned
172         ApplyCallback applyCB;
173 
resetSymbolCallbackInfo174         void reset()
175         {
176             noInfo = false;
177             num = 0;
178         }
179     }
180 
181     /*
182      * Passed to the syminfoCallback2 callback. That function merges it's
183      * funcName with this information and updates base as all other callbacks do.
184      */
185     struct SymbolCallbackInfo2
186     {
187         SymbolCallbackInfo* base;
188         const(char)* filename;
189         int lineno;
190     }
191 
192     /*
193      * Contains a valid symbol or an error message if errnum is != 0.
194      */
195     struct SymbolOrError
196     {
197         int errnum; // == 0: No error
198         union
199         {
200             SymbolInfo symbol;
201             const(char)* msg;
202         }
203     }
204 
205     // FIXME: state is never freed as libbacktrace doesn't provide a free function...
206     public class LibBacktrace : Throwable.TraceInfo
207     {
208         enum MaxAlignment = (void*).alignof;
209 
210         static void initLibBacktrace()
211         {
212             if (!initialized)
213             {
214                 state = backtrace_create_state(null, false, &simpleErrorCallback, null);
215                 initialized = true;
216             }
217         }
218 
219         this(int firstFrame = FIRSTFRAME)
220         {
221             _firstFrame = firstFrame;
222 
223             initLibBacktrace();
224 
225             if (state)
226             {
227                 backtrace_simple(state, _firstFrame, &simpleCallback,
228                                  &simpleErrorCallback, cast(void*)this);
229             }
230         }
231 
232         override int opApply(scope int delegate(ref const(char[])) dg) const
233         {
234             return opApply(
235                 (ref size_t, ref const(char[]) buf)
236                 {
237                     return dg(buf);
238                 }
239             );
240         }
241 
242         override int opApply(scope int delegate(ref size_t, ref const(char[])) dg) const
243         {
244             return opApply(
245                 (ref size_t i, ref SymbolOrError sym)
246                 {
247                     char[MAX_BUFSIZE] buffer = void;
248                     char[] msg;
249                     if (sym.errnum != 0)
250                     {
251                         auto retval = snprintf(buffer.ptr, buffer.length,
252                                                "libbacktrace error: '%s' errno: %d", sym.msg, sym.errnum);
253                         if (retval >= buffer.length)
254                             retval = buffer.length - 1; // Ignore zero terminator
255                         if (retval > 0)
256                             msg = buffer[0 .. retval];
257                         return dg(i, msg);
258                     }
259                     else
260                     {
261                         msg = formatLine(sym.symbol, buffer);
262                         int ret = dg(i, msg);
263 
264                         if (!ret && sym.symbol.funcName && strcmp(sym.symbol.funcName, "_Dmain") == 0)
265                             return 1;
266                         return ret;
267                     }
268                 }
269             );
270         }
271 
272         int opApply(scope ApplyCallback dg) const
273         {
274             initLibBacktrace();
275 
276             // If backtrace_simple produced an error report it and exit
277             if (!state || error != 0)
278             {
279                 size_t pos = 0;
280                 SymbolOrError symError;
281                 if (!state)
282                 {
283                     symError.msg = "libbacktrace failed to initialize\0";
284                     symError.errnum = 1;
285                 }
286                 else
287                 {
288                     symError.errnum = error;
289                     symError.msg = errorBuf.ptr;
290                 }
291 
292                 return dg(pos, symError);
293             }
294 
295             SymbolCallbackInfo cinfo;
296             cinfo.applyCB = dg;
297             cinfo.state = cast(backtrace_state*)state;
298 
299             // Try using debug info first
300             foreach (i, pc; pcs[0 .. numPCs])
301             {
302                 // FIXME: We may violate const guarantees here...
303                 if (backtrace_pcinfo(cast(backtrace_state*)state, pc, &pcinfoCallback,
304                     &pcinfoErrorCallback, &cinfo) != 0)
305                 {
306                     break; // User delegate requested abort or no debug info at all
307                 }
308             }
309 
310             // If no error or other error which has already been reported via callback
311             if (!cinfo.noInfo)
312                 return cinfo.retval;
313 
314             // Try using symbol table
315             cinfo.reset();
316             foreach (pc; pcs[0 .. numPCs])
317             {
318                 if (backtrace_syminfo(cast(backtrace_state*)state, pc, &syminfoCallback,
319                     &pcinfoErrorCallback, &cinfo) == 0)
320                 {
321                     break;
322                 }
323             }
324 
325             if (!cinfo.noInfo)
326                 return cinfo.retval;
327 
328             // No symbol table
329             foreach (i, pc; pcs[0 .. numPCs])
330             {
331                 auto sym = SymbolOrError(0, SymbolInfo(null, null, 0, cast(void*)pc));
332                 if (auto ret = dg(i, sym) != 0)
333                     return ret;
334             }
335 
336             return 0;
337         }
338 
339         override string toString() const
340         {
341             string buf;
342             foreach (i, const(char[]) line; this)
343                 buf ~= i ? "\n" ~ line : line;
344             return buf;
345         }
346 
347     private:
348         static backtrace_state* state = null;
349         static bool initialized       = false;
350         size_t                 numPCs = 0;
351         uintptr_t[MAXFRAMES] pcs;
352 
353         int       error = 0;
354         int _firstFrame = 0;
355         char[128] errorBuf = "\0";
356     }
357 }
358 else
359 {
360     /*
361      * Our fallback backtrace implementation using libgcc's unwind
362      * and backtrace support. In theory libbacktrace should be available
363      * everywhere where this code works. We keep it anyway till libbacktrace
364      * is well-tested.
365      */
366     public class UnwindBacktrace : Throwable.TraceInfo
367     {
368         this(int firstFrame = FIRSTFRAME)
369         {
370             _firstFrame = firstFrame;
371             _callstack = getBacktrace();
372             _framelist = getBacktraceSymbols(_callstack);
373         }
374 
375         override int opApply(scope int delegate(ref const(char[])) dg) const
376         {
377             return opApply(
378                 (ref size_t, ref const(char[]) buf)
379                 {
380                     return dg(buf);
381                 }
382             );
383         }
384 
385         override int opApply(scope int delegate(ref size_t, ref const(char[])) dg) const
386         {
387             char[MAX_BUFSIZE] buffer = void;
388             int ret = 0;
389 
390             for (int i = _firstFrame; i < _framelist.entries; ++i)
391             {
392                 auto pos = cast(size_t)(i - _firstFrame);
393                 auto msg = formatLine(_framelist.symbols[i], buffer);
394                 ret = dg(pos, msg);
395                 if (ret)
396                     break;
397             }
398             return ret;
399         }
400 
401         override string toString() const
402         {
403             string buf;
404             foreach (i, line; this)
405                 buf ~= i ? "\n" ~ line : line;
406             return buf;
407         }
408 
409     private:
410         BTSymbolData     _framelist;
411         UnwindBacktraceData _callstack;
412         int              _firstFrame = 0;
413     }
414 
415     // Implementation details
416 private:
417     import gcc.unwind;
418 
419     version (linux)
420         import core.sys.linux.dlfcn;
421     else version (OSX)
422         import core.sys.darwin.dlfcn;
423     else version (FreeBSD)
424         import core.sys.freebsd.dlfcn;
425     else version (NetBSD)
426         import core.sys.netbsd.dlfcn;
427     else version (Solaris)
428         import core.sys.netbsd.dlfcn;
429     else version (Posix)
430         import core.sys.posix.dlfcn;
431 
432     private enum MAXFRAMES = 128;
433 
434     struct UnwindBacktraceData
435     {
436         void*[MAXFRAMES] callstack;
437         int numframes = 0;
438     }
439 
440     struct BTSymbolData
441     {
442         size_t entries;
443         SymbolInfo[MAXFRAMES] symbols;
444     }
445 
446     static extern (C) _Unwind_Reason_Code unwindCB(_Unwind_Context *ctx, void *d)
447     {
448         UnwindBacktraceData* bt = cast(UnwindBacktraceData*)d;
449         if (bt.numframes >= MAXFRAMES)
450             return _URC_NO_REASON;
451 
452         bt.callstack[bt.numframes] = cast(void*)_Unwind_GetIP(ctx);
453         bt.numframes++;
454         return _URC_NO_REASON;
455     }
456 
457     UnwindBacktraceData getBacktrace()
458     {
459         UnwindBacktraceData stackframe;
460         _Unwind_Backtrace(&unwindCB, &stackframe);
461         return stackframe;
462     }
463 
464     BTSymbolData getBacktraceSymbols(UnwindBacktraceData data)
465     {
466         BTSymbolData symData;
467 
468         for (auto i = 0; i < data.numframes; i++)
469         {
470             static if ( __traits(compiles, Dl_info))
471             {
472                 Dl_info funcInfo;
473 
474                 if (data.callstack[i] !is null && dladdr(data.callstack[i], &funcInfo) != 0)
475                 {
476                     symData.symbols[symData.entries].funcName = funcInfo.dli_sname;
477 
478                     symData.symbols[symData.entries].address = data.callstack[i];
479                     symData.entries++;
480                 }
481                 else
482                 {
483                     symData.symbols[symData.entries].address = data.callstack[i];
484                     symData.entries++;
485                 }
486             }
487             else
488             {
489                 symData.symbols[symData.entries].address = data.callstack[i];
490                 symData.entries++;
491             }
492         }
493 
494         return symData;
495     }
496 }
497 
498 /*
499  * Struct representing a symbol (function) in the backtrace
500  */
501 struct SymbolInfo
502 {
503     const(char)* funcName, fileName;
504     size_t line;
505     const(void)* address;
506 }
507 
508 /*
509  * Format one output line for symbol sym.
510  * Returns a slice of buffer.
511  */
512 char[] formatLine(const SymbolInfo sym, return ref char[MAX_BUFSIZE] buffer)
513 {
514     import core.demangle, core.stdc.config;
515     import core.stdc.stdio : snprintf, printf;
516     import core.stdc.string : strlen;
517 
518     size_t bufferLength = 0;
519 
520     void appendToBuffer(Args...)(const(char)* format, Args args)
521     {
522         const count = snprintf(buffer.ptr + bufferLength,
523                                buffer.length - bufferLength,
524                                format, args);
525         assert(count >= 0);
526         bufferLength += count;
527         if (bufferLength >= buffer.length)
528             bufferLength = buffer.length - 1;
529     }
530 
531 
532     if (sym.fileName is null)
533         appendToBuffer("??:? ");
534     else
535         appendToBuffer("%s:%d ", sym.fileName, sym.line);
536 
537     if (sym.funcName is null)
538         appendToBuffer("???");
539     else
540     {
541         char[1024] symbol = void;
542         auto demangled = demangle(sym.funcName[0 .. strlen(sym.funcName)], symbol);
543 
544         if (demangled.length > 0)
545             appendToBuffer("%.*s ", cast(int) demangled.length, demangled.ptr);
546     }
547 
548     version (D_LP64)
549         appendToBuffer("[0x%llx]", cast(size_t)sym.address);
550     else
551         appendToBuffer("[0x%x]", cast(size_t)sym.address);
552 
553     return buffer[0 .. bufferLength];
554 }
555 
556 
557 unittest
558 {
559     char[MAX_BUFSIZE] sbuf = '\0';
560     char[] result;
561     string longString;
562     for (size_t i = 0; i < 60; i++)
563         longString ~= "abcdefghij";
564     longString ~= '\0';
565 
566     auto symbol = SymbolInfo(null, null, 0, null);
567     result = formatLine(symbol, sbuf);
568     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
569 
570     symbol = SymbolInfo(longString.ptr, null, 0, null);
571     result = formatLine(symbol, sbuf);
572     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
573 
574     symbol = SymbolInfo("func", "test.d", 0, null);
575     result = formatLine(symbol, sbuf);
576     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
577 
578     symbol = SymbolInfo("func", longString.ptr, 0, null);
579     result = formatLine(symbol, sbuf);
580     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
581 
582     symbol = SymbolInfo(longString.ptr, "test.d", 0, null);
583     result = formatLine(symbol, sbuf);
584     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
585 
586     symbol = SymbolInfo(longString.ptr, longString.ptr, 0, null);
587     result = formatLine(symbol, sbuf);
588     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
589 
590     symbol = SymbolInfo("func", "test.d", 1000, null);
591     result = formatLine(symbol, sbuf);
592     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
593 
594     symbol = SymbolInfo(null, (longString[0..500] ~ '\0').ptr, 100000000, null);
595     result = formatLine(symbol, sbuf);
596     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
597 
598     symbol = SymbolInfo("func", "test.d", 0, cast(void*)0x100000);
599     result = formatLine(symbol, sbuf);
600     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
601 
602     symbol = SymbolInfo("func", null, 0, cast(void*)0x100000);
603     result = formatLine(symbol, sbuf);
604     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
605 
606     symbol = SymbolInfo(null, "test.d", 0, cast(void*)0x100000);
607     result = formatLine(symbol, sbuf);
608     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
609 
610     symbol = SymbolInfo(longString.ptr, "test.d", 0, cast(void*)0x100000);
611     result = formatLine(symbol, sbuf);
612     assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0');
613 }
614