Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions runtime/druntime/src/core/internal/backtrace/dwarf.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
23 changes: 13 additions & 10 deletions runtime/druntime/src/core/internal/backtrace/handler.d
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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)
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions runtime/druntime/src/core/internal/backtrace/libunwind.d
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ nothrow:
* Bindings for libunwind.h
*/
alias unw_word_t = uintptr_t;
alias unw_regnum_t = int;

///
struct unw_context_t
Expand Down Expand Up @@ -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:

Expand Down
165 changes: 119 additions & 46 deletions runtime/druntime/src/core/runtime.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 "<ERROR: Unable to retrieve function name>";
}

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

Expand All @@ -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];
}
}
}
}