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 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 } 614