Skip to content

Commit

Permalink
Use /proc/self/exe as the last resort for libbfd
Browse files Browse the repository at this point in the history
Libbfd implementation of Backward had a bug, that it couldn't
resolve anything in case symbols were stripped into a separate
file. The problem was that an executable path was passed as
/proc/self/exe to be able to find symbols even when the original
file is deleted from the file system, or is replaced.

When symbols are stripped from an executable, libbfd tries to find
a <debuglink>.debug file in the same folder, where <debuglink> is
a name built into executable file. With /proc/self/exe it was
trying to find /proc/self/<debuglink>.debug file, which obviously
never exists.

The patch makes Backward first try to open the original
executable, and only in case of a failure fallback to
/proc/self/exe workaround.

It is worth mentioning, that it is not enough just to check that
the original file is there. There can be *something* by the
executable's file path, but not necessarily the original file -
it could be replaced. For that the patch compares inode numbers of
the file and or /proc/self/exe. If they are equal, files are the
same, and it is safe to use the original path.

Fallback to /proc/self/exe happens only if the original file is
replaced or deleted.

The regression was introduced in
1ecbdc6 ("Handle executable being
deleted or replaced by directly opening").

Note, that the bug still exists for libdwarf, and probably for
other libs too, but it is not so trivial to fix the bug for them.
For example, libdwarf does not look for .debug file automatically
at all. This is a subject for a separate patch.

Closes #160
  • Loading branch information
Gerold103 committed Jul 24, 2020
1 parent a9b2ab5 commit f093895
Showing 1 changed file with 45 additions and 3 deletions.
48 changes: 45 additions & 3 deletions backward.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,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 @@ -1140,9 +1146,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

0 comments on commit f093895

Please sign in to comment.