Skip to content

Commit

Permalink
Merge pull request #161 from Gerold103/issue-160-stripped-debug
Browse files Browse the repository at this point in the history
Symbols can't be resolved, if debug symbols are stripped into .debug file
  • Loading branch information
bombela authored Apr 8, 2021
2 parents 6313577 + f093895 commit e0f09b0
Showing 1 changed file with 64 additions and 22 deletions.
86 changes: 64 additions & 22 deletions backward.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,12 @@ class TraceResolverLinuxBase : public TraceResolverImplBase {
// variable; In that case, we actually open /proc/self/exe, which
// is always the actual executable (even if it was deleted/replaced!)
// but display the path that /proc/self/exe links to.
// However, this right away reduces probability of successful symbol
// resolution, because libbfd may try to find *.debug files in the
// same dir, in case symbols are stripped. As a result, it may try
// to find a file /proc/self/<exe_name>.debug, which obviously does
// not exist. /proc/self/exe is a last resort. First load attempt
// should go for the original executable file path.
symbol_info.dli_fname = "/proc/self/exe";
return exec_path_;
} else {
Expand Down Expand Up @@ -1373,9 +1379,45 @@ class TraceResolverLinuxImpl<trace_resolver_tag::libbfd>
}

trace.object_filename = resolve_exec_path(symbol_info);
bfd_fileobject &fobj = load_object_with_bfd(symbol_info.dli_fname);
if (!fobj.handle) {
return trace; // sad, we couldn't load the object :(
bfd_fileobject *fobj;
// Before rushing to resolution need to ensure the executable
// file still can be used. For that compare inode numbers of
// what is stored by the executable's file path, and in the
// dli_fname, which not necessarily equals to the executable.
// It can be a shared library, or /proc/self/exe, and in the
// latter case has drawbacks. See the exec path resolution for
// details. In short - the dli object should be used only as
// the last resort.
// If inode numbers are equal, it is known dli_fname and the
// executable file are the same. This is guaranteed by Linux,
// because if the executable file is changed/deleted, it will
// be done in a new inode. The old file will be preserved in
// /proc/self/exe, and may even have inode 0. The latter can
// happen if the inode was actually reused, and the file was
// kept only in the main memory.
//
struct stat obj_stat;
struct stat dli_stat;
if (stat(trace.object_filename.c_str(), &obj_stat) == 0 &&
stat(symbol_info.dli_fname, &dli_stat) == 0 &&
obj_stat.st_ino == dli_stat.st_ino) {
// The executable file, and the shared object containing the
// address are the same file. Safe to use the original path.
// this is preferable. Libbfd will search for stripped debug
// symbols in the same directory.
fobj = load_object_with_bfd(trace.object_filename);
} else{
// The original object file was *deleted*! The only hope is
// that the debug symbols are either inside the shared
// object file, or are in the same directory, and this is
// not /proc/self/exe.
fobj = nullptr;
}
if (fobj == nullptr || !fobj->handle) {
fobj = load_object_with_bfd(symbol_info.dli_fname);
if (!fobj->handle) {
return trace;
}
}

find_sym_result *details_selected; // to be filled.
Expand Down Expand Up @@ -1510,7 +1552,7 @@ class TraceResolverLinuxImpl<trace_resolver_tag::libbfd>
typedef details::hashtable<std::string, bfd_fileobject>::type fobj_bfd_map_t;
fobj_bfd_map_t _fobj_bfd_map;

bfd_fileobject &load_object_with_bfd(const std::string &filename_object) {
bfd_fileobject *load_object_with_bfd(const std::string &filename_object) {
using namespace details;

if (!_bfd_loaded) {
Expand All @@ -1521,11 +1563,11 @@ class TraceResolverLinuxImpl<trace_resolver_tag::libbfd>

fobj_bfd_map_t::iterator it = _fobj_bfd_map.find(filename_object);
if (it != _fobj_bfd_map.end()) {
return it->second;
return &it->second;
}

// this new object is empty for now.
bfd_fileobject &r = _fobj_bfd_map[filename_object];
bfd_fileobject *r = &_fobj_bfd_map[filename_object];

// we do the work temporary in this one;
bfd_handle_t bfd_handle;
Expand Down Expand Up @@ -1574,9 +1616,9 @@ class TraceResolverLinuxImpl<trace_resolver_tag::libbfd>
return r; // damned, that's a stripped file that you got there!
}

r.handle = move(bfd_handle);
r.symtab = move(symtab);
r.dynamic_symtab = move(dynamic_symtab);
r->handle = move(bfd_handle);
r->symtab = move(symtab);
r->dynamic_symtab = move(dynamic_symtab);
return r;
}

Expand All @@ -1595,15 +1637,15 @@ class TraceResolverLinuxImpl<trace_resolver_tag::libbfd>
find_sym_result result;
};

find_sym_result find_symbol_details(bfd_fileobject &fobj, void *addr,
find_sym_result find_symbol_details(bfd_fileobject *fobj, void *addr,
void *base_addr) {
find_sym_context context;
context.self = this;
context.fobj = &fobj;
context.fobj = fobj;
context.addr = addr;
context.base_addr = base_addr;
context.result.found = false;
bfd_map_over_sections(fobj.handle.get(), &find_in_section_trampoline,
bfd_map_over_sections(fobj->handle.get(), &find_in_section_trampoline,
static_cast<void *>(&context));
return context.result;
}
Expand All @@ -1612,24 +1654,24 @@ class TraceResolverLinuxImpl<trace_resolver_tag::libbfd>
find_sym_context *context = static_cast<find_sym_context *>(data);
context->self->find_in_section(
reinterpret_cast<bfd_vma>(context->addr),
reinterpret_cast<bfd_vma>(context->base_addr), *context->fobj, section,
reinterpret_cast<bfd_vma>(context->base_addr), context->fobj, section,
context->result);
}

void find_in_section(bfd_vma addr, bfd_vma base_addr, bfd_fileobject &fobj,
void find_in_section(bfd_vma addr, bfd_vma base_addr, bfd_fileobject *fobj,
asection *section, find_sym_result &result) {
if (result.found)
return;

#ifdef bfd_get_section_flags
if ((bfd_get_section_flags(fobj.handle.get(), section) & SEC_ALLOC) == 0)
if ((bfd_get_section_flags(fobj->handle.get(), section) & SEC_ALLOC) == 0)
#else
if ((bfd_section_flags(section) & SEC_ALLOC) == 0)
#endif
return; // a debug section is never loaded automatically.

#ifdef bfd_get_section_vma
bfd_vma sec_addr = bfd_get_section_vma(fobj.handle.get(), section);
bfd_vma sec_addr = bfd_get_section_vma(fobj->handle.get(), section);
#else
bfd_vma sec_addr = bfd_section_vma(section);
#endif
Expand All @@ -1651,15 +1693,15 @@ class TraceResolverLinuxImpl<trace_resolver_tag::libbfd>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
#endif
if (!result.found && fobj.symtab) {
if (!result.found && fobj->symtab) {
result.found = bfd_find_nearest_line(
fobj.handle.get(), section, fobj.symtab.get(), addr - sec_addr,
fobj->handle.get(), section, fobj->symtab.get(), addr - sec_addr,
&result.filename, &result.funcname, &result.line);
}

if (!result.found && fobj.dynamic_symtab) {
if (!result.found && fobj->dynamic_symtab) {
result.found = bfd_find_nearest_line(
fobj.handle.get(), section, fobj.dynamic_symtab.get(),
fobj->handle.get(), section, fobj->dynamic_symtab.get(),
addr - sec_addr, &result.filename, &result.funcname, &result.line);
}
#if defined(__clang__)
Expand All @@ -1668,13 +1710,13 @@ class TraceResolverLinuxImpl<trace_resolver_tag::libbfd>
}

ResolvedTrace::source_locs_t
backtrace_inliners(bfd_fileobject &fobj, find_sym_result previous_result) {
backtrace_inliners(bfd_fileobject *fobj, find_sym_result previous_result) {
// This function can be called ONLY after a SUCCESSFUL call to
// find_symbol_details. The state is global to the bfd_handle.
ResolvedTrace::source_locs_t results;
while (previous_result.found) {
find_sym_result result;
result.found = bfd_find_inliner_info(fobj.handle.get(), &result.filename,
result.found = bfd_find_inliner_info(fobj->handle.get(), &result.filename,
&result.funcname, &result.line);

if (result
Expand Down

0 comments on commit e0f09b0

Please sign in to comment.