| // GNU D Compiler routines for stack backtrace support. | |
| // Copyright (C) 2013-2025 Free Software Foundation, Inc. | |
| // GCC is free software; you can redistribute it and/or modify it under | |
| // the terms of the GNU General Public License as published by the Free | |
| // Software Foundation; either version 3, or (at your option) any later | |
| // version. | |
| // GCC is distributed in the hope that it will be useful, but WITHOUT ANY | |
| // WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| // for more details. | |
| // Under Section 7 of GPL version 3, you are granted additional | |
| // permissions described in the GCC Runtime Library Exception, version | |
| // 3.1, as published by the Free Software Foundation. | |
| // You should have received a copy of the GNU General Public License and | |
| // a copy of the GCC Runtime Library Exception along with this program; | |
| // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see | |
| // <http://www.gnu.org/licenses/>. | |
| module gcc.backtrace; | |
| import gcc.libbacktrace; | |
| // Max size per line of the traceback. | |
| private enum MAX_BUFSIZE = 1536; | |
| static if (BACKTRACE_SUPPORTED && !BACKTRACE_USES_MALLOC) | |
| { | |
| import core.stdc.stdint, core.stdc.string, core.stdc.stdio; | |
| private enum MAXFRAMES = 128; | |
| extern(C) int simpleCallback(void* data, uintptr_t pc) | |
| { | |
| auto context = cast(LibBacktrace)data; | |
| if (context.numPCs == MAXFRAMES) | |
| return 1; | |
| context.pcs[context.numPCs++] = pc; | |
| return 0; | |
| } | |
| /* | |
| * Used for backtrace_create_state and backtrace_simple | |
| */ | |
| extern(C) void simpleErrorCallback(void* data, const(char)* msg, int errnum) @nogc | |
| { | |
| if (data) // context is not available in backtrace_create_state | |
| { | |
| auto context = cast(LibBacktrace)data; | |
| strncpy(context.errorBuf.ptr, msg, context.errorBuf.length - 1); | |
| context.error = errnum; | |
| } | |
| } | |
| /* | |
| * Used for backtrace_pcinfo | |
| */ | |
| extern(C) int pcinfoCallback(void* data, uintptr_t pc, const(char)* filename, | |
| int lineno, const(char)* func) | |
| { | |
| auto context = cast(SymbolCallbackInfo*)data; | |
| // Try to get the function name via backtrace_syminfo | |
| if (func is null) | |
| { | |
| SymbolCallbackInfo2 info; | |
| info.base = context; | |
| info.filename = filename; | |
| info.lineno = lineno; | |
| if (backtrace_syminfo(context.state, pc, &syminfoCallback2, null, &info) != 0) | |
| { | |
| return context.retval; | |
| } | |
| } | |
| auto sym = SymbolOrError(0, SymbolInfo(func, filename, lineno, cast(void*)pc)); | |
| context.retval = context.applyCB(context.num, sym); | |
| context.num++; | |
| return context.retval; | |
| } | |
| /* | |
| * Used for backtrace_pcinfo and backtrace_syminfo | |
| */ | |
| extern(C) void pcinfoErrorCallback(void* data, const(char)* msg, int errnum) | |
| { | |
| auto context = cast(SymbolCallbackInfo*)data; | |
| if (errnum == -1) | |
| { | |
| context.noInfo = true; | |
| return; | |
| } | |
| SymbolOrError symError; | |
| symError.errnum = errnum; | |
| symError.msg = msg; | |
| size_t i = 0; | |
| context.retval = context.applyCB(i, symError); | |
| } | |
| /* | |
| * Used for backtrace_syminfo (in opApply) | |
| */ | |
| extern(C) void syminfoCallback(void* data, uintptr_t pc, | |
| const(char)* symname, uintptr_t symval) | |
| { | |
| auto context = cast(SymbolCallbackInfo*)data; | |
| auto sym = SymbolOrError(0, SymbolInfo(symname, null, 0, cast(void*)pc)); | |
| context.retval = context.applyCB(context.num, sym); | |
| context.num++; | |
| } | |
| /* | |
| * This callback is used if backtrace_syminfo is called from the pcinfoCallback | |
| * callback. It merges it's information with the information from pcinfoCallback. | |
| */ | |
| extern(C) void syminfoCallback2(void* data, uintptr_t pc, | |
| const(char)* symname, uintptr_t symval) | |
| { | |
| auto context = cast(SymbolCallbackInfo2*)data; | |
| auto sym = SymbolOrError(0, SymbolInfo(symname, context.filename, context.lineno, | |
| cast(void*)pc)); | |
| context.base.retval = context.base.applyCB(context.base.num, sym); | |
| context.base.num++; | |
| } | |
| /* | |
| * The callback type used with the opApply overload which returns a SymbolOrError | |
| */ | |
| private alias int delegate(ref size_t, ref SymbolOrError) ApplyCallback; | |
| /* | |
| * Passed to syminfoCallback, pcinfoCallback and pcinfoErrorCallback | |
| */ | |
| struct SymbolCallbackInfo | |
| { | |
| bool noInfo = false; // True if debug info / symbol table is not available | |
| size_t num = 0; // Counter for opApply | |
| int retval; // Value returned by applyCB | |
| backtrace_state* state; | |
| // info.fileName / funcName / errmsg may become invalid after this delegate returned | |
| ApplyCallback applyCB; | |
| void reset() | |
| { | |
| noInfo = false; | |
| num = 0; | |
| } | |
| } | |
| /* | |
| * Passed to the syminfoCallback2 callback. That function merges it's | |
| * funcName with this information and updates base as all other callbacks do. | |
| */ | |
| struct SymbolCallbackInfo2 | |
| { | |
| SymbolCallbackInfo* base; | |
| const(char)* filename; | |
| int lineno; | |
| } | |
| /* | |
| * Contains a valid symbol or an error message if errnum is != 0. | |
| */ | |
| struct SymbolOrError | |
| { | |
| int errnum; // == 0: No error | |
| union | |
| { | |
| SymbolInfo symbol; | |
| const(char)* msg; | |
| } | |
| } | |
| // FIXME: state is never freed as libbacktrace doesn't provide a free function... | |
| public class LibBacktrace : Throwable.TraceInfo | |
| { | |
| static void initLibBacktrace() @nogc | |
| { | |
| if (!initialized) | |
| { | |
| state = backtrace_create_state(null, false, &simpleErrorCallback, null); | |
| initialized = true; | |
| } | |
| } | |
| this(int firstFrame) @nogc | |
| { | |
| _firstFrame = firstFrame; | |
| initLibBacktrace(); | |
| if (state) | |
| { | |
| backtrace_simple(state, _firstFrame, &simpleCallback, | |
| &simpleErrorCallback, cast(void*)this); | |
| } | |
| } | |
| override int opApply(scope int delegate(ref const(char[])) dg) const | |
| { | |
| return opApply( | |
| (ref size_t, ref const(char[]) buf) | |
| { | |
| return dg(buf); | |
| } | |
| ); | |
| } | |
| override int opApply(scope int delegate(ref size_t, ref const(char[])) dg) const | |
| { | |
| return opApply( | |
| (ref size_t i, ref SymbolOrError sym) | |
| { | |
| char[MAX_BUFSIZE] buffer = void; | |
| char[] msg; | |
| if (sym.errnum != 0) | |
| { | |
| auto retval = snprintf(buffer.ptr, buffer.length, | |
| "libbacktrace error: '%s' errno: %d", sym.msg, sym.errnum); | |
| if (retval >= buffer.length) | |
| retval = buffer.length - 1; // Ignore zero terminator | |
| if (retval > 0) | |
| msg = buffer[0 .. retval]; | |
| return dg(i, msg); | |
| } | |
| else | |
| { | |
| msg = formatLine(sym.symbol, buffer); | |
| int ret = dg(i, msg); | |
| if (!ret && sym.symbol.funcName && strcmp(sym.symbol.funcName, "_Dmain") == 0) | |
| return 1; | |
| return ret; | |
| } | |
| } | |
| ); | |
| } | |
| int opApply(scope ApplyCallback dg) const | |
| { | |
| initLibBacktrace(); | |
| // If backtrace_simple produced an error report it and exit | |
| if (!state || error != 0) | |
| { | |
| size_t pos = 0; | |
| SymbolOrError symError; | |
| if (!state) | |
| { | |
| symError.msg = "libbacktrace failed to initialize\0"; | |
| symError.errnum = 1; | |
| } | |
| else | |
| { | |
| symError.errnum = error; | |
| symError.msg = errorBuf.ptr; | |
| } | |
| return dg(pos, symError); | |
| } | |
| SymbolCallbackInfo cinfo; | |
| cinfo.applyCB = dg; | |
| cinfo.state = cast(backtrace_state*)state; | |
| // Try using debug info first | |
| foreach (i, pc; pcs[0 .. numPCs]) | |
| { | |
| // FIXME: We may violate const guarantees here... | |
| if (backtrace_pcinfo(cast(backtrace_state*)state, pc, &pcinfoCallback, | |
| &pcinfoErrorCallback, &cinfo) != 0) | |
| { | |
| break; // User delegate requested abort or no debug info at all | |
| } | |
| } | |
| // If no error or other error which has already been reported via callback | |
| if (!cinfo.noInfo) | |
| return cinfo.retval; | |
| // Try using symbol table | |
| cinfo.reset(); | |
| foreach (pc; pcs[0 .. numPCs]) | |
| { | |
| if (backtrace_syminfo(cast(backtrace_state*)state, pc, &syminfoCallback, | |
| &pcinfoErrorCallback, &cinfo) == 0) | |
| { | |
| break; | |
| } | |
| } | |
| if (!cinfo.noInfo) | |
| return cinfo.retval; | |
| // No symbol table | |
| foreach (i, pc; pcs[0 .. numPCs]) | |
| { | |
| auto sym = SymbolOrError(0, SymbolInfo(null, null, 0, cast(void*)pc)); | |
| if (auto ret = dg(i, sym) != 0) | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| override string toString() const | |
| { | |
| string buf; | |
| foreach (i, const(char[]) line; this) | |
| buf ~= i ? "\n" ~ line : line; | |
| return buf; | |
| } | |
| private: | |
| static backtrace_state* state = null; | |
| static bool initialized = false; | |
| size_t numPCs = 0; | |
| uintptr_t[MAXFRAMES] pcs; | |
| int error = 0; | |
| int _firstFrame = 0; | |
| char[128] errorBuf = "\0"; | |
| } | |
| } | |
| else | |
| { | |
| /* | |
| * Our fallback backtrace implementation using libgcc's unwind | |
| * and backtrace support. In theory libbacktrace should be available | |
| * everywhere where this code works. We keep it anyway till libbacktrace | |
| * is well-tested. | |
| */ | |
| public class UnwindBacktrace : Throwable.TraceInfo | |
| { | |
| this(int firstFrame) @nogc | |
| { | |
| _firstFrame = firstFrame; | |
| _callstack = getBacktrace(); | |
| _framelist = getBacktraceSymbols(_callstack); | |
| } | |
| override int opApply(scope int delegate(ref const(char[])) dg) const | |
| { | |
| return opApply( | |
| (ref size_t, ref const(char[]) buf) | |
| { | |
| return dg(buf); | |
| } | |
| ); | |
| } | |
| override int opApply(scope int delegate(ref size_t, ref const(char[])) dg) const | |
| { | |
| char[MAX_BUFSIZE] buffer = void; | |
| int ret = 0; | |
| for (int i = _firstFrame; i < _framelist.entries; ++i) | |
| { | |
| auto pos = cast(size_t)(i - _firstFrame); | |
| auto msg = formatLine(_framelist.symbols[i], buffer); | |
| ret = dg(pos, msg); | |
| if (ret) | |
| break; | |
| } | |
| return ret; | |
| } | |
| override string toString() const | |
| { | |
| string buf; | |
| foreach (i, line; this) | |
| buf ~= i ? "\n" ~ line : line; | |
| return buf; | |
| } | |
| private: | |
| BTSymbolData _framelist; | |
| UnwindBacktraceData _callstack; | |
| int _firstFrame = 0; | |
| } | |
| // Implementation details | |
| private: | |
| import gcc.unwind; | |
| version (linux) | |
| import core.sys.linux.dlfcn; | |
| else version (OSX) | |
| import core.sys.darwin.dlfcn; | |
| else version (FreeBSD) | |
| import core.sys.freebsd.dlfcn; | |
| else version (NetBSD) | |
| import core.sys.netbsd.dlfcn; | |
| else version (OpenBSD) | |
| import core.sys.openbsd.dlfcn; | |
| else version (Solaris) | |
| import core.sys.solaris.dlfcn; | |
| else version (Posix) | |
| import core.sys.posix.dlfcn; | |
| private enum MAXFRAMES = 128; | |
| struct UnwindBacktraceData | |
| { | |
| void*[MAXFRAMES] callstack; | |
| int numframes = 0; | |
| } | |
| struct BTSymbolData | |
| { | |
| size_t entries; | |
| SymbolInfo[MAXFRAMES] symbols; | |
| } | |
| static extern (C) _Unwind_Reason_Code unwindCB(_Unwind_Context *ctx, void *d) | |
| { | |
| UnwindBacktraceData* bt = cast(UnwindBacktraceData*)d; | |
| if (bt.numframes >= MAXFRAMES) | |
| return _URC_NO_REASON; | |
| bt.callstack[bt.numframes] = cast(void*)_Unwind_GetIP(ctx); | |
| bt.numframes++; | |
| return _URC_NO_REASON; | |
| } | |
| UnwindBacktraceData getBacktrace() @nogc | |
| { | |
| UnwindBacktraceData stackframe; | |
| _Unwind_Backtrace(&unwindCB, &stackframe); | |
| return stackframe; | |
| } | |
| BTSymbolData getBacktraceSymbols(UnwindBacktraceData data) @nogc | |
| { | |
| BTSymbolData symData; | |
| for (auto i = 0; i < data.numframes; i++) | |
| { | |
| static if ( __traits(compiles, Dl_info)) | |
| { | |
| Dl_info funcInfo; | |
| if (data.callstack[i] !is null && dladdr(data.callstack[i], &funcInfo) != 0) | |
| { | |
| symData.symbols[symData.entries].funcName = funcInfo.dli_sname; | |
| symData.symbols[symData.entries].address = data.callstack[i]; | |
| symData.entries++; | |
| } | |
| else | |
| { | |
| symData.symbols[symData.entries].address = data.callstack[i]; | |
| symData.entries++; | |
| } | |
| } | |
| else | |
| { | |
| symData.symbols[symData.entries].address = data.callstack[i]; | |
| symData.entries++; | |
| } | |
| } | |
| return symData; | |
| } | |
| } | |
| /* | |
| * Struct representing a symbol (function) in the backtrace | |
| */ | |
| struct SymbolInfo | |
| { | |
| const(char)* funcName, fileName; | |
| size_t line; | |
| const(void)* address; | |
| } | |
| /* | |
| * Format one output line for symbol sym. | |
| * Returns a slice of buffer. | |
| */ | |
| char[] formatLine(const SymbolInfo sym, return ref char[MAX_BUFSIZE] buffer) | |
| { | |
| import core.demangle, core.stdc.config; | |
| import core.stdc.stdio : snprintf, printf; | |
| import core.stdc.string : strlen; | |
| size_t bufferLength = 0; | |
| void appendToBuffer(Args...)(const(char)* format, Args args) | |
| { | |
| const count = snprintf(buffer.ptr + bufferLength, | |
| buffer.length - bufferLength, | |
| format, args); | |
| assert(count >= 0); | |
| bufferLength += count; | |
| if (bufferLength >= buffer.length) | |
| bufferLength = buffer.length - 1; | |
| } | |
| if (sym.fileName is null) | |
| appendToBuffer("??:? "); | |
| else | |
| appendToBuffer("%s:%d ", sym.fileName, sym.line); | |
| if (sym.funcName is null) | |
| appendToBuffer("???"); | |
| else | |
| { | |
| char[1024] symbol = void; | |
| auto demangled = demangle(sym.funcName[0 .. strlen(sym.funcName)], symbol); | |
| if (demangled.length > 0) | |
| appendToBuffer("%.*s ", cast(int) demangled.length, demangled.ptr); | |
| } | |
| version (D_LP64) | |
| appendToBuffer("[0x%llx]", cast(size_t)sym.address); | |
| else | |
| appendToBuffer("[0x%x]", cast(size_t)sym.address); | |
| return buffer[0 .. bufferLength]; | |
| } | |
| unittest | |
| { | |
| char[MAX_BUFSIZE] sbuf = '\0'; | |
| char[] result; | |
| string longString; | |
| for (size_t i = 0; i < 60; i++) | |
| longString ~= "abcdefghij"; | |
| longString ~= '\0'; | |
| auto symbol = SymbolInfo(null, null, 0, null); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| symbol = SymbolInfo(longString.ptr, null, 0, null); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| symbol = SymbolInfo("func", "test.d", 0, null); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| symbol = SymbolInfo("func", longString.ptr, 0, null); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| symbol = SymbolInfo(longString.ptr, "test.d", 0, null); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| symbol = SymbolInfo(longString.ptr, longString.ptr, 0, null); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| symbol = SymbolInfo("func", "test.d", 1000, null); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| symbol = SymbolInfo(null, (longString[0..500] ~ '\0').ptr, 100000000, null); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| symbol = SymbolInfo("func", "test.d", 0, cast(void*)0x100000); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| symbol = SymbolInfo("func", null, 0, cast(void*)0x100000); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| symbol = SymbolInfo(null, "test.d", 0, cast(void*)0x100000); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| symbol = SymbolInfo(longString.ptr, "test.d", 0, cast(void*)0x100000); | |
| result = formatLine(symbol, sbuf); | |
| assert(result.length < MAX_BUFSIZE && result.ptr[result.length] == '\0' && sbuf[$-1] == '\0'); | |
| } |