diff --git a/runtime/druntime/src/core/internal/backtrace/dwarf.d b/runtime/druntime/src/core/internal/backtrace/dwarf.d index b3e93ec7be1..05dd414b73c 100644 --- a/runtime/druntime/src/core/internal/backtrace/dwarf.d +++ b/runtime/druntime/src/core/internal/backtrace/dwarf.d @@ -49,15 +49,9 @@ 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; +version (Posix): -static if (hasExecinfo || hasLibunwind): +import core.internal.string; version (OSX) version = Darwin; diff --git a/runtime/druntime/src/core/internal/backtrace/handler.d b/runtime/druntime/src/core/internal/backtrace/handler.d index a505ea8535c..8f28b428c60 100644 --- a/runtime/druntime/src/core/internal/backtrace/handler.d +++ b/runtime/druntime/src/core/internal/backtrace/handler.d @@ -25,8 +25,6 @@ version (DRuntime_Use_Libunwind): import core.internal.backtrace.dwarf; import core.internal.backtrace.libunwind; -import core.stdc.string; -import core.sys.posix.dlfcn; /// Ditto class LibunwindHandler : Throwable.TraceInfo @@ -50,11 +48,6 @@ class LibunwindHandler : Throwable.TraceInfo */ public this (size_t frames_to_skip = 1) nothrow @nogc { - 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 +56,17 @@ class LibunwindHandler : Throwable.TraceInfo while (frames_to_skip > 0 && unw_step(&cursor) > 0) --frames_to_skip; - unw_proc_info_t pip = void; + // 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_proc_info(&cursor, &pip) == 0) - frame.address += pip.start_ip; + if (unw_get_reg(&cursor, UNW_REG_IP, &ip) == 0) + // 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; this.numframes++; if (unw_step(&cursor) <= 0) @@ -91,8 +90,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..8b69280e805 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: diff --git a/runtime/druntime/src/core/runtime.d b/runtime/druntime/src/core/runtime.d index ce88142174c..4ac8e5cd951 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 version (Posix) private class DefaultTraceInfo : Throwable.TraceInfo { import core.demangle; import core.stdc.stdlib : free; @@ -901,30 +901,100 @@ 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. + import core.internal.container.array; + Array!(const(char)[]) frameNames; + frameNames.length = numframes; + size_t startIdx; + foreach (idx; 0 .. numframes) + { + frameNames[idx] = getFrameName(callstack[idx]); + + // 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 (!startIdx && frameNames[idx] == BaseExceptionFunctionName) + startIdx = idx + 1; + } + + int ret = 0; + foreach (idx; startIdx .. numframes) + { + ret = dg( idx, frameNames[idx] ); + if ( ret ) + break; + } + return ret; } - return ret; } } @@ -942,40 +1012,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) + const(char)[] fixline( const(char)[] buf, return ref char[4096] fixbuf ) const { - immutable len = min(buf.length, fixbuf.length); - fixbuf[0 .. len] = buf[0 .. len]; - return fixbuf[0 .. len]; - } - else - { - 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]; + } } } }