From 737707bf18527e788d5ded40e267b8ab085a94c0 Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Fri, 3 May 2024 17:21:07 +0200 Subject: [PATCH 01/11] Rework DefaultTraceInfo such that it can be used without execinfo (`backtrace()`). When execinfo is not supported, it falls back to manual unwinding, which is the same fallback as when `backtrace()` is not successful. --- .../src/core/internal/backtrace/dwarf.d | 8 - runtime/druntime/src/core/runtime.d | 164 +++++++++++++----- 2 files changed, 118 insertions(+), 54 deletions(-) diff --git a/runtime/druntime/src/core/internal/backtrace/dwarf.d b/runtime/druntime/src/core/internal/backtrace/dwarf.d index b3e93ec7be1..241b2c00e24 100644 --- a/runtime/druntime/src/core/internal/backtrace/dwarf.d +++ b/runtime/druntime/src/core/internal/backtrace/dwarf.d @@ -49,16 +49,8 @@ module core.internal.backtrace.dwarf; -import core.internal.execinfo; import core.internal.string; -version (DRuntime_Use_Libunwind) - private enum hasLibunwind = true; -else - private enum hasLibunwind = false; - -static if (hasExecinfo || hasLibunwind): - version (OSX) version = Darwin; else version (iOS) diff --git a/runtime/druntime/src/core/runtime.d b/runtime/druntime/src/core/runtime.d index ce88142174c..e3079b1e2ba 100644 --- a/runtime/druntime/src/core/runtime.d +++ b/runtime/druntime/src/core/runtime.d @@ -826,7 +826,7 @@ version (DRuntime_Use_Libunwind) alias DefaultTraceInfo = LibunwindHandler; } /// Default implementation for most POSIX systems -else static if (hasExecinfo) private class DefaultTraceInfo : Throwable.TraceInfo +else private class DefaultTraceInfo : Throwable.TraceInfo { import core.demangle; import core.stdc.stdlib : free; @@ -901,30 +901,99 @@ else static if (hasExecinfo) private class DefaultTraceInfo : Throwable.TraceInf else version (Darwin) enum enableDwarf = true; else enum enableDwarf = false; - const framelist = backtrace_symbols( callstack.ptr, numframes ); - scope(exit) free(cast(void*) framelist); - - static if (enableDwarf) + static if (hasExecinfo) { - import core.internal.backtrace.dwarf; - return traceHandlerOpApplyImpl(numframes, - i => callstack[i], - (i) { auto str = framelist[i][0 .. strlen(framelist[i])]; return getMangledSymbolName(str); }, - dg); + const framelist = backtrace_symbols( callstack.ptr, numframes ); + scope(exit) free(cast(void*) framelist); + + static if (enableDwarf) + { + import core.internal.backtrace.dwarf; + return traceHandlerOpApplyImpl(numframes, + i => callstack[i], + (i) { auto str = framelist[i][0 .. strlen(framelist[i])]; return getMangledSymbolName(str); }, + dg); + } + else + { + int ret = 0; + for (size_t pos = 0; pos < numframes; ++pos) + { + char[4096] fixbuf = void; + auto buf = framelist[pos][0 .. strlen(framelist[pos])]; + buf = fixline( buf, fixbuf ); + ret = dg( pos, buf ); + if ( ret ) + break; + } + return ret; + } } else { - int ret = 0; - for (size_t pos = 0; pos < numframes; ++pos) + // https://code.woboq.org/userspace/glibc/debug/backtracesyms.c.html + // The logic that glibc's backtrace use is to check for for `dli_fname`, + // the file name, and error if not present, then check for `dli_sname`. + // In case `dli_fname` is present but not `dli_sname`, the address is + // printed related to the file. We just print the file. + static const(char)[] getFrameName (const(void)* ptr) + { + import core.sys.posix.dlfcn; + Dl_info info = void; + // Note: See the module documentation about `-L--export-dynamic` + if (dladdr(ptr, &info)) + { + // Return symbol name if possible + if (info.dli_sname !is null && info.dli_sname[0] != '\0') + return info.dli_sname[0 .. strlen(info.dli_sname)]; + + // Fall back to file name + if (info.dli_fname !is null && info.dli_fname[0] != '\0') + return info.dli_fname[0 .. strlen(info.dli_fname)]; + } + + // `dladdr` failed + return ""; + } + + static if (enableDwarf) + { + import core.internal.backtrace.dwarf; + return traceHandlerOpApplyImpl(numframes, + i => callstack[i], + i => getFrameName(callstack[i]), + dg); + } + else { - char[4096] fixbuf = void; - auto buf = framelist[pos][0 .. strlen(framelist[pos])]; - buf = fixline( buf, fixbuf ); - ret = dg( pos, buf ); - if ( ret ) - break; + // Poor man solution. Does not show line numbers, but does (potentially) show a backtrace of function names. + + int ret = 0; + bool throw_call_found = false; + for (size_t i = 0; i < numframes; ++i) + { + char[4096] fixbuf = void; + auto buf = getFrameName(callstack[i]); + + // NOTE: The first few frames with the current implementation are + // inside core.runtime and the object code, so eliminate + // these for readability. + // They also might depend on build parameters, which would make + // using a fixed number of frames otherwise brittle. + version (LDC) enum BaseExceptionFunctionName = "_d_throw_exception"; + else enum BaseExceptionFunctionName = "_d_throwdwarf"; + if (!throw_call_found) + { + throw_call_found = buf == BaseExceptionFunctionName; + continue; + } + + ret = dg( i, buf ); + if ( ret ) + break; + } + return ret; } - return ret; } } @@ -942,40 +1011,43 @@ private: void*[MAXFRAMES] callstack = void; private: - const(char)[] fixline( const(char)[] buf, return ref char[4096] fixbuf ) const + static if (hasExecinfo) { - size_t symBeg, symEnd; - - getMangledSymbolName(buf, symBeg, symEnd); - - enum min = (size_t a, size_t b) => a <= b ? a : b; - if (symBeg == symEnd || symBeg >= fixbuf.length) - { - immutable len = min(buf.length, fixbuf.length); - fixbuf[0 .. len] = buf[0 .. len]; - return fixbuf[0 .. len]; - } - else + const(char)[] fixline( const(char)[] buf, return ref char[4096] fixbuf ) const { - fixbuf[0 .. symBeg] = buf[0 .. symBeg]; + size_t symBeg, symEnd; - auto sym = demangle(buf[symBeg .. symEnd], fixbuf[symBeg .. $], getCXXDemangler()); + getMangledSymbolName(buf, symBeg, symEnd); - if (sym.ptr !is fixbuf.ptr + symBeg) + enum min = (size_t a, size_t b) => a <= b ? a : b; + if (symBeg == symEnd || symBeg >= fixbuf.length) { - // demangle reallocated the buffer, copy the symbol to fixbuf - immutable len = min(fixbuf.length - symBeg, sym.length); - memmove(fixbuf.ptr + symBeg, sym.ptr, len); - if (symBeg + len == fixbuf.length) - return fixbuf[]; + immutable len = min(buf.length, fixbuf.length); + fixbuf[0 .. len] = buf[0 .. len]; + return fixbuf[0 .. len]; } + else + { + fixbuf[0 .. symBeg] = buf[0 .. symBeg]; + + auto sym = demangle(buf[symBeg .. symEnd], fixbuf[symBeg .. $], getCXXDemangler()); - immutable pos = symBeg + sym.length; - assert(pos < fixbuf.length); - immutable tail = buf.length - symEnd; - immutable len = min(fixbuf.length - pos, tail); - fixbuf[pos .. pos + len] = buf[symEnd .. symEnd + len]; - return fixbuf[0 .. pos + len]; + if (sym.ptr !is fixbuf.ptr + symBeg) + { + // demangle reallocated the buffer, copy the symbol to fixbuf + immutable len = min(fixbuf.length - symBeg, sym.length); + memmove(fixbuf.ptr + symBeg, sym.ptr, len); + if (symBeg + len == fixbuf.length) + return fixbuf[]; + } + + immutable pos = symBeg + sym.length; + assert(pos < fixbuf.length); + immutable tail = buf.length - symEnd; + immutable len = min(fixbuf.length - pos, tail); + fixbuf[pos .. pos + len] = buf[symEnd .. symEnd + len]; + return fixbuf[0 .. pos + len]; + } } } } From 6084cbc2eaabea58918a9340bc9782451cc5807a Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Fri, 3 May 2024 17:22:50 +0200 Subject: [PATCH 02/11] The Libunwind implementation in druntime is broken and gives wrong (and thus confusing!) line numbers in the backtrace. For now, do not use libunwind for Musl. This does mean that musl will use the fallback path in DefaultTraceInfo which uses manual unwinding, i.e. requiring `-frame-pointer=all`. --- driver/main.cpp | 2 -- tests/dmd/runnable/test17559.d | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/driver/main.cpp b/driver/main.cpp index 9dcceeadf6c..3aa484668eb 100644 --- a/driver/main.cpp +++ b/driver/main.cpp @@ -856,8 +856,6 @@ void registerPredefinedTargetVersions() { } else if (triple.isMusl()) { VersionCondition::addPredefinedGlobalIdent("CRuntime_Musl"); VersionCondition::addPredefinedGlobalIdent("CppRuntime_Gcc"); - // use libunwind for backtraces - VersionCondition::addPredefinedGlobalIdent("DRuntime_Use_Libunwind"); } else if (global.params.isUClibcEnvironment) { VersionCondition::addPredefinedGlobalIdent("CRuntime_UClibc"); } else { diff --git a/tests/dmd/runnable/test17559.d b/tests/dmd/runnable/test17559.d index 822ef940248..f29cc4cc7a9 100644 --- a/tests/dmd/runnable/test17559.d +++ b/tests/dmd/runnable/test17559.d @@ -1,7 +1,7 @@ // REQUIRED_ARGS: -g // REQUIRED_ARGS(linux freebsd dragonflybsd): -L-export-dynamic // LDC (required for Win32 and -O): REQUIRED_ARGS(windows32mscoff): -link-defaultlib-debug -// LDC (FreeBSD's libexecinfo apparently doesn't like elided frame pointers): REQUIRED_ARGS(freebsd): -link-defaultlib-debug -frame-pointer=all +// LDC (Alpine Linux and FreeBSD's libexecinfo apparently doesn't like elided frame pointers): REQUIRED_ARGS(freebsd linux): -link-defaultlib-debug -frame-pointer=all // PERMUTE_ARGS: // DISABLED: osx From af59dc495373950c941c59e9d73b429f559b8bd6 Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Fri, 3 May 2024 18:07:39 +0200 Subject: [PATCH 03/11] fix windows build --- runtime/druntime/src/core/internal/backtrace/dwarf.d | 2 ++ runtime/druntime/src/core/runtime.d | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/runtime/druntime/src/core/internal/backtrace/dwarf.d b/runtime/druntime/src/core/internal/backtrace/dwarf.d index 241b2c00e24..05dd414b73c 100644 --- a/runtime/druntime/src/core/internal/backtrace/dwarf.d +++ b/runtime/druntime/src/core/internal/backtrace/dwarf.d @@ -49,6 +49,8 @@ module core.internal.backtrace.dwarf; +version (Posix): + import core.internal.string; version (OSX) diff --git a/runtime/druntime/src/core/runtime.d b/runtime/druntime/src/core/runtime.d index e3079b1e2ba..54b1d9516c3 100644 --- a/runtime/druntime/src/core/runtime.d +++ b/runtime/druntime/src/core/runtime.d @@ -826,7 +826,7 @@ version (DRuntime_Use_Libunwind) alias DefaultTraceInfo = LibunwindHandler; } /// Default implementation for most POSIX systems -else private class DefaultTraceInfo : Throwable.TraceInfo +else version (Posix) private class DefaultTraceInfo : Throwable.TraceInfo { import core.demangle; import core.stdc.stdlib : free; From 33d106a8e63bc963b24e88ea96dc25534bd51fd5 Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Fri, 3 May 2024 23:08:16 +0200 Subject: [PATCH 04/11] Fix libunwind druntime code and re-enable for Musl --- driver/main.cpp | 2 ++ .../src/core/internal/backtrace/handler.d | 26 +++++++++++++------ .../src/core/internal/backtrace/libunwind.d | 9 +++++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/driver/main.cpp b/driver/main.cpp index 3aa484668eb..9dcceeadf6c 100644 --- a/driver/main.cpp +++ b/driver/main.cpp @@ -856,6 +856,8 @@ void registerPredefinedTargetVersions() { } else if (triple.isMusl()) { VersionCondition::addPredefinedGlobalIdent("CRuntime_Musl"); VersionCondition::addPredefinedGlobalIdent("CppRuntime_Gcc"); + // use libunwind for backtraces + VersionCondition::addPredefinedGlobalIdent("DRuntime_Use_Libunwind"); } else if (global.params.isUClibcEnvironment) { VersionCondition::addPredefinedGlobalIdent("CRuntime_UClibc"); } else { diff --git a/runtime/druntime/src/core/internal/backtrace/handler.d b/runtime/druntime/src/core/internal/backtrace/handler.d index a505ea8535c..3aec95ddb8e 100644 --- a/runtime/druntime/src/core/internal/backtrace/handler.d +++ b/runtime/druntime/src/core/internal/backtrace/handler.d @@ -25,8 +25,17 @@ version (DRuntime_Use_Libunwind): import core.internal.backtrace.dwarf; import core.internal.backtrace.libunwind; -import core.stdc.string; -import core.sys.posix.dlfcn; +/+ +import core.internal.backtrace.libunwind_importc; + +// Declare these functions again, to make them @nogc nothrow. +extern(C) @nogc nothrow { + int unw_getcontext(unw_context_t*); + int unw_init_local(unw_cursor_t*, unw_context_t*); + int unw_step(unw_cursor_t*); + int unw_get_reg(unw_cursor_t *cp, unw_regnum_t reg, unw_word_t *valp); +} ++/ /// Ditto class LibunwindHandler : Throwable.TraceInfo @@ -52,9 +61,6 @@ class LibunwindHandler : Throwable.TraceInfo { import core.stdc.string : strlen; - static assert(typeof(FrameInfo.address).sizeof == unw_word_t.sizeof, - "Mismatch in type size for call to unw_get_proc_name"); - unw_context_t context; unw_cursor_t cursor; unw_getcontext(&context); @@ -63,11 +69,11 @@ class LibunwindHandler : Throwable.TraceInfo while (frames_to_skip > 0 && unw_step(&cursor) > 0) --frames_to_skip; - unw_proc_info_t pip = void; + unw_word_t ip; foreach (idx, ref frame; this.callstack) { - if (unw_get_proc_info(&cursor, &pip) == 0) - frame.address += pip.start_ip; + if (unw_get_reg(&cursor, UNW_REG_IP, &ip) == 0) + frame.address = cast(void*) ip; this.numframes++; if (unw_step(&cursor) <= 0) @@ -91,8 +97,12 @@ class LibunwindHandler : Throwable.TraceInfo // printed related to the file. We just print the file. static const(char)[] getFrameName (const(void)* ptr) { + import core.sys.posix.dlfcn; + import core.stdc.string; + Dl_info info = void; // Note: See the module documentation about `-L--export-dynamic` + // TODO: Rewrite using libunwind's unw_get_proc_name if (dladdr(ptr, &info)) { // Return symbol name if possible diff --git a/runtime/druntime/src/core/internal/backtrace/libunwind.d b/runtime/druntime/src/core/internal/backtrace/libunwind.d index 3378763eee6..7db66083d02 100644 --- a/runtime/druntime/src/core/internal/backtrace/libunwind.d +++ b/runtime/druntime/src/core/internal/backtrace/libunwind.d @@ -43,6 +43,7 @@ nothrow: * Bindings for libunwind.h */ alias unw_word_t = uintptr_t; +alias unw_regnum_t = int; /// struct unw_context_t @@ -86,6 +87,14 @@ int unw_step(unw_cursor_t*); int unw_get_proc_info(unw_cursor_t*, unw_proc_info_t*); /// Get the name of the current procedure (function) int unw_get_proc_name(unw_cursor_t*, char*, size_t, unw_word_t*); +/// Reads the value of register `reg` in the stack frame identified by cursor `cp` and stores the value in the word pointed to by `valp`. +int unw_get_reg(unw_cursor_t *cp, unw_regnum_t reg, unw_word_t *valp); +/// Architecture independent register numbers +enum +{ + UNW_REG_IP = -1, // instruction pointer + UNW_REG_SP = -2, // stack pointer +} private: From dbe29eee94c55e80473537c87d31a23b89041e71 Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Sun, 5 May 2024 00:04:45 +0200 Subject: [PATCH 05/11] fixup musl backtrace --- .../druntime/src/core/internal/backtrace/handler.d | 11 ----------- tests/dmd/runnable/test17559.d | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/runtime/druntime/src/core/internal/backtrace/handler.d b/runtime/druntime/src/core/internal/backtrace/handler.d index 3aec95ddb8e..4cc7b8aae06 100644 --- a/runtime/druntime/src/core/internal/backtrace/handler.d +++ b/runtime/druntime/src/core/internal/backtrace/handler.d @@ -25,17 +25,6 @@ version (DRuntime_Use_Libunwind): import core.internal.backtrace.dwarf; import core.internal.backtrace.libunwind; -/+ -import core.internal.backtrace.libunwind_importc; - -// Declare these functions again, to make them @nogc nothrow. -extern(C) @nogc nothrow { - int unw_getcontext(unw_context_t*); - int unw_init_local(unw_cursor_t*, unw_context_t*); - int unw_step(unw_cursor_t*); - int unw_get_reg(unw_cursor_t *cp, unw_regnum_t reg, unw_word_t *valp); -} -+/ /// Ditto class LibunwindHandler : Throwable.TraceInfo diff --git a/tests/dmd/runnable/test17559.d b/tests/dmd/runnable/test17559.d index f29cc4cc7a9..822ef940248 100644 --- a/tests/dmd/runnable/test17559.d +++ b/tests/dmd/runnable/test17559.d @@ -1,7 +1,7 @@ // REQUIRED_ARGS: -g // REQUIRED_ARGS(linux freebsd dragonflybsd): -L-export-dynamic // LDC (required for Win32 and -O): REQUIRED_ARGS(windows32mscoff): -link-defaultlib-debug -// LDC (Alpine Linux and FreeBSD's libexecinfo apparently doesn't like elided frame pointers): REQUIRED_ARGS(freebsd linux): -link-defaultlib-debug -frame-pointer=all +// LDC (FreeBSD's libexecinfo apparently doesn't like elided frame pointers): REQUIRED_ARGS(freebsd): -link-defaultlib-debug -frame-pointer=all // PERMUTE_ARGS: // DISABLED: osx From 639da44ea942c168aa9eb833f87aaf12d63ff290 Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Sun, 5 May 2024 22:52:24 +0200 Subject: [PATCH 06/11] fixup --- runtime/druntime/src/core/internal/backtrace/libunwind.d | 2 +- runtime/druntime/src/core/runtime.d | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime/druntime/src/core/internal/backtrace/libunwind.d b/runtime/druntime/src/core/internal/backtrace/libunwind.d index 7db66083d02..8b69280e805 100644 --- a/runtime/druntime/src/core/internal/backtrace/libunwind.d +++ b/runtime/druntime/src/core/internal/backtrace/libunwind.d @@ -88,7 +88,7 @@ int unw_get_proc_info(unw_cursor_t*, unw_proc_info_t*); /// Get the name of the current procedure (function) int unw_get_proc_name(unw_cursor_t*, char*, size_t, unw_word_t*); /// Reads the value of register `reg` in the stack frame identified by cursor `cp` and stores the value in the word pointed to by `valp`. -int unw_get_reg(unw_cursor_t *cp, unw_regnum_t reg, unw_word_t *valp); +int unw_get_reg(unw_cursor_t* cp, unw_regnum_t reg, unw_word_t* valp); /// Architecture independent register numbers enum { diff --git a/runtime/druntime/src/core/runtime.d b/runtime/druntime/src/core/runtime.d index 54b1d9516c3..b1df5468b2b 100644 --- a/runtime/druntime/src/core/runtime.d +++ b/runtime/druntime/src/core/runtime.d @@ -972,7 +972,6 @@ else version (Posix) private class DefaultTraceInfo : Throwable.TraceInfo bool throw_call_found = false; for (size_t i = 0; i < numframes; ++i) { - char[4096] fixbuf = void; auto buf = getFrameName(callstack[i]); // NOTE: The first few frames with the current implementation are From ffb0365e9758764ec6ee316a549e01ca462161b7 Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Tue, 7 May 2024 00:33:08 +0200 Subject: [PATCH 07/11] use libunwind to retrieve procedure names in stack trace (instead of dladdr) --- .../src/core/internal/backtrace/handler.d | 65 ++++++++++--------- .../src/core/internal/backtrace/libunwind.d | 15 +++++ 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/runtime/druntime/src/core/internal/backtrace/handler.d b/runtime/druntime/src/core/internal/backtrace/handler.d index 4cc7b8aae06..8c28111df77 100644 --- a/runtime/druntime/src/core/internal/backtrace/handler.d +++ b/runtime/druntime/src/core/internal/backtrace/handler.d @@ -38,6 +38,9 @@ class LibunwindHandler : Throwable.TraceInfo enum MAXFRAMES = 128; FrameInfo[MAXFRAMES] callstack = void; + enum MAXPROCNAMELENGTH = 500; + char*[MAXFRAMES] namestack = void; // will be allocated using malloc, must be freed in destructor + /** * Create a new instance of this trace handler saving the current context * @@ -48,7 +51,8 @@ class LibunwindHandler : Throwable.TraceInfo */ public this (size_t frames_to_skip = 1) nothrow @nogc { - import core.stdc.string : strlen; + import core.stdc.stdlib : malloc; + import core.stdc.string : memcpy; unw_context_t context; unw_cursor_t cursor; @@ -64,12 +68,41 @@ class LibunwindHandler : Throwable.TraceInfo if (unw_get_reg(&cursor, UNW_REG_IP, &ip) == 0) frame.address = cast(void*) ip; + unw_word_t offset; + char* buffer = cast(char*)malloc(MAXPROCNAMELENGTH); + switch (unw_get_proc_name(&cursor, buffer, MAXPROCNAMELENGTH, &offset)) + { + case UNW_ESUCCESS: + namestack[idx] = buffer; + break; + case UNW_ENOMEM: + // Name is longer than MAXPROCNAMELENGTH, truncated name was put in buffer. + // TODO: realloc larger buffer and try again. + namestack[idx] = buffer; + break; + default: + immutable error_string = ""; + memcpy(buffer, error_string.ptr, error_string.length+1); // include 0-terminator + namestack[idx] = buffer; + break; + } + this.numframes++; if (unw_step(&cursor) <= 0) break; } } + ~this() + { + import core.stdc.stdlib : free; + // Need to deallocate the procedure name strings allocated in constructor. + foreach (ref ptr; this.namestack[0..numframes]) + { + free(ptr); + } + } + /// override int opApply (scope int delegate(ref const(char[])) dg) const { @@ -79,37 +112,11 @@ class LibunwindHandler : Throwable.TraceInfo /// override int opApply (scope int delegate(ref size_t, ref const(char[])) dg) const { - // https://code.woboq.org/userspace/glibc/debug/backtracesyms.c.html - // The logic that glibc's backtrace use is to check for for `dli_fname`, - // the file name, and error if not present, then check for `dli_sname`. - // In case `dli_fname` is present but not `dli_sname`, the address is - // printed related to the file. We just print the file. - static const(char)[] getFrameName (const(void)* ptr) - { - import core.sys.posix.dlfcn; - import core.stdc.string; - - Dl_info info = void; - // Note: See the module documentation about `-L--export-dynamic` - // TODO: Rewrite using libunwind's unw_get_proc_name - if (dladdr(ptr, &info)) - { - // Return symbol name if possible - if (info.dli_sname !is null && info.dli_sname[0] != '\0') - return info.dli_sname[0 .. strlen(info.dli_sname)]; - - // Fall back to file name - if (info.dli_fname !is null && info.dli_fname[0] != '\0') - return info.dli_fname[0 .. strlen(info.dli_fname)]; - } - - // `dladdr` failed - return ""; - } + import core.stdc.string : strlen; return traceHandlerOpApplyImpl(numframes, i => callstack[i].address, - i => getFrameName(callstack[i].address), + i => namestack[i][0..strlen(namestack[i])], dg); } diff --git a/runtime/druntime/src/core/internal/backtrace/libunwind.d b/runtime/druntime/src/core/internal/backtrace/libunwind.d index 8b69280e805..bdcaa2e48c6 100644 --- a/runtime/druntime/src/core/internal/backtrace/libunwind.d +++ b/runtime/druntime/src/core/internal/backtrace/libunwind.d @@ -96,6 +96,21 @@ enum UNW_REG_SP = -2, // stack pointer } +enum +{ + UNW_ESUCCESS = 0, /* no error */ + UNW_EUNSPEC = -6540, /* unspecified (general) error */ + UNW_ENOMEM = -6541, /* out of memory */ + UNW_EBADREG = -6542, /* bad register number */ + UNW_EREADONLYREG = -6543, /* attempt to write read-only register */ + UNW_ESTOPUNWIND = -6544, /* stop unwinding */ + UNW_EINVALIDIP = -6545, /* invalid IP */ + UNW_EBADFRAME = -6546, /* bad frame */ + UNW_EINVAL = -6547, /* unsupported operation or bad value */ + UNW_EBADVERSION = -6548, /* unwind info has unsupported version */ + UNW_ENOINFO = -6549 /* no unwind info found */ +} + private: // The API between libunwind and llvm-libunwind is almost the same, From caac41ac4e41ab7e2b78d02d497cff749b98f1a7 Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Tue, 7 May 2024 01:04:55 +0200 Subject: [PATCH 08/11] deal with out-of-memory in libunwind tracing --- .../src/core/internal/backtrace/handler.d | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/runtime/druntime/src/core/internal/backtrace/handler.d b/runtime/druntime/src/core/internal/backtrace/handler.d index 8c28111df77..afcfdf748c2 100644 --- a/runtime/druntime/src/core/internal/backtrace/handler.d +++ b/runtime/druntime/src/core/internal/backtrace/handler.d @@ -70,21 +70,29 @@ class LibunwindHandler : Throwable.TraceInfo unw_word_t offset; char* buffer = cast(char*)malloc(MAXPROCNAMELENGTH); - switch (unw_get_proc_name(&cursor, buffer, MAXPROCNAMELENGTH, &offset)) + if (buffer) { - case UNW_ESUCCESS: - namestack[idx] = buffer; - break; - case UNW_ENOMEM: - // Name is longer than MAXPROCNAMELENGTH, truncated name was put in buffer. - // TODO: realloc larger buffer and try again. - namestack[idx] = buffer; - break; - default: - immutable error_string = ""; - memcpy(buffer, error_string.ptr, error_string.length+1); // include 0-terminator - namestack[idx] = buffer; - break; + switch (unw_get_proc_name(&cursor, buffer, MAXPROCNAMELENGTH, &offset)) + { + case UNW_ESUCCESS: + namestack[idx] = buffer; + break; + case UNW_ENOMEM: + // Name is longer than MAXPROCNAMELENGTH, truncated name was put in buffer. + // TODO: realloc larger buffer and try again. + namestack[idx] = buffer; + break; + default: + immutable error_string = ""; + memcpy(buffer, error_string.ptr, error_string.length+1); // include 0-terminator + namestack[idx] = buffer; + break; + } + } + else + { + // Out of memory + namestack[idx] = null; } this.numframes++; @@ -99,7 +107,9 @@ class LibunwindHandler : Throwable.TraceInfo // Need to deallocate the procedure name strings allocated in constructor. foreach (ref ptr; this.namestack[0..numframes]) { - free(ptr); + if (ptr) + free(ptr); + ptr = null; } } @@ -116,7 +126,7 @@ class LibunwindHandler : Throwable.TraceInfo return traceHandlerOpApplyImpl(numframes, i => callstack[i].address, - i => namestack[i][0..strlen(namestack[i])], + i => namestack[i] ? namestack[i][0..strlen(namestack[i])] : "", dg); } From 87d535137fed00ed03e9e956879b9172add5da34 Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Tue, 7 May 2024 01:05:39 +0200 Subject: [PATCH 09/11] Adjust libunwind tracing address to point to call instruction instead of right after the caller. --- runtime/druntime/src/core/internal/backtrace/handler.d | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/runtime/druntime/src/core/internal/backtrace/handler.d b/runtime/druntime/src/core/internal/backtrace/handler.d index afcfdf748c2..672fd9a709b 100644 --- a/runtime/druntime/src/core/internal/backtrace/handler.d +++ b/runtime/druntime/src/core/internal/backtrace/handler.d @@ -62,11 +62,17 @@ class LibunwindHandler : Throwable.TraceInfo while (frames_to_skip > 0 && unw_step(&cursor) > 0) --frames_to_skip; + // it may not be 1 but it is good enough to get + // in CALL instruction address range for backtrace + enum CALL_INSTRUCTION_SIZE = 1; + unw_word_t ip; foreach (idx, ref frame; this.callstack) { if (unw_get_reg(&cursor, UNW_REG_IP, &ip) == 0) - frame.address = cast(void*) ip; + // IP is pointing to the instruction _after_ the call instruction, + // adjust the frame address to point to the caller. + frame.address = cast(void*) ip - CALL_INSTRUCTION_SIZE; unw_word_t offset; char* buffer = cast(char*)malloc(MAXPROCNAMELENGTH); From 0bf24439160f95a2584bbf302800f21ac8af1777 Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Tue, 7 May 2024 21:50:29 +0200 Subject: [PATCH 10/11] Revert "use libunwind to retrieve procedure names in stack trace (instead of dladdr)" This reverts commit 10a2b02780d4c0f3f2ced4076e8829c87fe74e63. --- .../src/core/internal/backtrace/handler.d | 75 +++++++------------ .../src/core/internal/backtrace/libunwind.d | 15 ---- 2 files changed, 28 insertions(+), 62 deletions(-) diff --git a/runtime/druntime/src/core/internal/backtrace/handler.d b/runtime/druntime/src/core/internal/backtrace/handler.d index 672fd9a709b..8f28b428c60 100644 --- a/runtime/druntime/src/core/internal/backtrace/handler.d +++ b/runtime/druntime/src/core/internal/backtrace/handler.d @@ -38,9 +38,6 @@ class LibunwindHandler : Throwable.TraceInfo enum MAXFRAMES = 128; FrameInfo[MAXFRAMES] callstack = void; - enum MAXPROCNAMELENGTH = 500; - char*[MAXFRAMES] namestack = void; // will be allocated using malloc, must be freed in destructor - /** * Create a new instance of this trace handler saving the current context * @@ -51,9 +48,6 @@ class LibunwindHandler : Throwable.TraceInfo */ public this (size_t frames_to_skip = 1) nothrow @nogc { - import core.stdc.stdlib : malloc; - import core.stdc.string : memcpy; - unw_context_t context; unw_cursor_t cursor; unw_getcontext(&context); @@ -74,51 +68,12 @@ class LibunwindHandler : Throwable.TraceInfo // adjust the frame address to point to the caller. frame.address = cast(void*) ip - CALL_INSTRUCTION_SIZE; - unw_word_t offset; - char* buffer = cast(char*)malloc(MAXPROCNAMELENGTH); - if (buffer) - { - switch (unw_get_proc_name(&cursor, buffer, MAXPROCNAMELENGTH, &offset)) - { - case UNW_ESUCCESS: - namestack[idx] = buffer; - break; - case UNW_ENOMEM: - // Name is longer than MAXPROCNAMELENGTH, truncated name was put in buffer. - // TODO: realloc larger buffer and try again. - namestack[idx] = buffer; - break; - default: - immutable error_string = ""; - memcpy(buffer, error_string.ptr, error_string.length+1); // include 0-terminator - namestack[idx] = buffer; - break; - } - } - else - { - // Out of memory - namestack[idx] = null; - } - this.numframes++; if (unw_step(&cursor) <= 0) break; } } - ~this() - { - import core.stdc.stdlib : free; - // Need to deallocate the procedure name strings allocated in constructor. - foreach (ref ptr; this.namestack[0..numframes]) - { - if (ptr) - free(ptr); - ptr = null; - } - } - /// override int opApply (scope int delegate(ref const(char[])) dg) const { @@ -128,11 +83,37 @@ class LibunwindHandler : Throwable.TraceInfo /// override int opApply (scope int delegate(ref size_t, ref const(char[])) dg) const { - import core.stdc.string : strlen; + // https://code.woboq.org/userspace/glibc/debug/backtracesyms.c.html + // The logic that glibc's backtrace use is to check for for `dli_fname`, + // the file name, and error if not present, then check for `dli_sname`. + // In case `dli_fname` is present but not `dli_sname`, the address is + // printed related to the file. We just print the file. + static const(char)[] getFrameName (const(void)* ptr) + { + import core.sys.posix.dlfcn; + import core.stdc.string; + + Dl_info info = void; + // Note: See the module documentation about `-L--export-dynamic` + // TODO: Rewrite using libunwind's unw_get_proc_name + if (dladdr(ptr, &info)) + { + // Return symbol name if possible + if (info.dli_sname !is null && info.dli_sname[0] != '\0') + return info.dli_sname[0 .. strlen(info.dli_sname)]; + + // Fall back to file name + if (info.dli_fname !is null && info.dli_fname[0] != '\0') + return info.dli_fname[0 .. strlen(info.dli_fname)]; + } + + // `dladdr` failed + return ""; + } return traceHandlerOpApplyImpl(numframes, i => callstack[i].address, - i => namestack[i] ? namestack[i][0..strlen(namestack[i])] : "", + i => getFrameName(callstack[i].address), dg); } diff --git a/runtime/druntime/src/core/internal/backtrace/libunwind.d b/runtime/druntime/src/core/internal/backtrace/libunwind.d index bdcaa2e48c6..8b69280e805 100644 --- a/runtime/druntime/src/core/internal/backtrace/libunwind.d +++ b/runtime/druntime/src/core/internal/backtrace/libunwind.d @@ -96,21 +96,6 @@ enum UNW_REG_SP = -2, // stack pointer } -enum -{ - UNW_ESUCCESS = 0, /* no error */ - UNW_EUNSPEC = -6540, /* unspecified (general) error */ - UNW_ENOMEM = -6541, /* out of memory */ - UNW_EBADREG = -6542, /* bad register number */ - UNW_EREADONLYREG = -6543, /* attempt to write read-only register */ - UNW_ESTOPUNWIND = -6544, /* stop unwinding */ - UNW_EINVALIDIP = -6545, /* invalid IP */ - UNW_EBADFRAME = -6546, /* bad frame */ - UNW_EINVAL = -6547, /* unsupported operation or bad value */ - UNW_EBADVERSION = -6548, /* unwind info has unsupported version */ - UNW_ENOINFO = -6549 /* no unwind info found */ -} - private: // The API between libunwind and llvm-libunwind is almost the same, From d57d10d115a8786eda97c4722484cccda1c33efb Mon Sep 17 00:00:00 2001 From: Johan Engelen Date: Fri, 17 May 2024 16:48:11 +0200 Subject: [PATCH 11/11] Address comment by kinke --- runtime/druntime/src/core/runtime.d | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/runtime/druntime/src/core/runtime.d b/runtime/druntime/src/core/runtime.d index b1df5468b2b..4ac8e5cd951 100644 --- a/runtime/druntime/src/core/runtime.d +++ b/runtime/druntime/src/core/runtime.d @@ -967,12 +967,13 @@ else version (Posix) private class DefaultTraceInfo : Throwable.TraceInfo else { // Poor man solution. Does not show line numbers, but does (potentially) show a backtrace of function names. - - int ret = 0; - bool throw_call_found = false; - for (size_t i = 0; i < numframes; ++i) + import core.internal.container.array; + Array!(const(char)[]) frameNames; + frameNames.length = numframes; + size_t startIdx; + foreach (idx; 0 .. numframes) { - auto buf = getFrameName(callstack[i]); + frameNames[idx] = getFrameName(callstack[idx]); // NOTE: The first few frames with the current implementation are // inside core.runtime and the object code, so eliminate @@ -981,13 +982,14 @@ else version (Posix) private class DefaultTraceInfo : Throwable.TraceInfo // using a fixed number of frames otherwise brittle. version (LDC) enum BaseExceptionFunctionName = "_d_throw_exception"; else enum BaseExceptionFunctionName = "_d_throwdwarf"; - if (!throw_call_found) - { - throw_call_found = buf == BaseExceptionFunctionName; - continue; - } + if (!startIdx && frameNames[idx] == BaseExceptionFunctionName) + startIdx = idx + 1; + } - ret = dg( i, buf ); + int ret = 0; + foreach (idx; startIdx .. numframes) + { + ret = dg( idx, frameNames[idx] ); if ( ret ) break; }