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