]>
Commit | Line | Data |
---|---|---|
b4c522fa | 1 | // GNU D Compiler routines for stack backtrace support. |
99dee823 | 2 | // Copyright (C) 2013-2021 Free Software Foundation, Inc. |
b4c522fa IB |
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 | 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 | ||
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 | */ | |
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 | */ | |
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 | */ | |
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 | */ | |
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 | */ | |
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 | ||
174 | 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 | } |