1 // GNU D Compiler routines for stack backtrace support. 2 // Copyright (C) 2013-2021 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 simpleCallback(void * data,uintptr_t pc)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 */ simpleErrorCallback(void * data,const (char)* msg,int errnum)49 extern(C) void simpleErrorCallback(void* data, const(char)* msg, int errnum) 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 */ pcinfoCallback(void * data,uintptr_t pc,const (char)* filename,int lineno,const (char)* func)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 */ pcinfoErrorCallback(void * data,const (char)* msg,int errnum)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 */ syminfoCallback(void * data,uintptr_t pc,const (char)* symname,uintptr_t symval)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 */ syminfoCallback2(void * data,uintptr_t pc,const (char)* symname,uintptr_t symval)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 resetSymbolCallbackInfo156 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() 191 { 192 if (!initialized) 193 { 194 state = backtrace_create_state(null, false, &simpleErrorCallback, null); 195 initialized = true; 196 } 197 } 198 199 this(int firstFrame) 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) 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() 440 { 441 UnwindBacktraceData stackframe; 442 _Unwind_Backtrace(&unwindCB, &stackframe); 443 return stackframe; 444 } 445 446 BTSymbolData getBacktraceSymbols(UnwindBacktraceData data) 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 } 596