Make ElfCache follow .gnu_debuglink
authorMark Williams <mwilliams@fb.com>
Wed, 14 Sep 2016 22:48:52 +0000 (15:48 -0700)
committerFacebook Github Bot 2 <facebook-github-bot-2-bot@fb.com>
Wed, 14 Sep 2016 22:53:38 +0000 (15:53 -0700)
Summary:
If you split out debug info into a separate file, folly::Symbolizer
didn't find it, and failed to include file and line number info in
backtraces.

This adds a new open mode which follows the .gnu_debuginfo link, and
uses it from ElfCache and SignalSafeElfCache.

Reviewed By: meyering, luciang

Differential Revision: D3852311

fbshipit-source-id: fec9e57378ae59fa1b82d41a163bb9cfcf9ca23c

folly/experimental/symbolizer/Elf.cpp
folly/experimental/symbolizer/Elf.h
folly/experimental/symbolizer/ElfCache.cpp
folly/experimental/symbolizer/test/gnu_debuglink_test.sh [new file with mode: 0755]

index a61829a5ddfcb6232905a97a4dcfb863444ddcc3..b0e3f9a7891802641bb8af7f40df67ab862c6396 100644 (file)
@@ -58,8 +58,9 @@ void ElfFile::open(const char* name, bool readOnly) {
   }
 }
 
-int ElfFile::openNoThrow(const char* name, bool readOnly, const char** msg)
-  noexcept {
+int ElfFile::openNoThrow(const char* name,
+                         bool readOnly,
+                         const char** msg) noexcept {
   FOLLY_SAFE_CHECK(fd_ == -1, "File already open");
   fd_ = ::open(name, readOnly ? O_RDONLY : O_RDWR);
   if (fd_ == -1) {
@@ -95,6 +96,43 @@ int ElfFile::openNoThrow(const char* name, bool readOnly, const char** msg)
   return kSuccess;
 }
 
+int ElfFile::openAndFollow(const char* name,
+                           bool readOnly,
+                           const char** msg) noexcept {
+  auto result = openNoThrow(name, readOnly, msg);
+  if (!readOnly || result != kSuccess) return result;
+
+  /* NOTE .gnu_debuglink specifies only the name of the debugging info file
+   * (with no directory components). GDB checks 3 different directories, but
+   * ElfFile only supports the first version:
+   *     - dirname(name)
+   *     - dirname(name) + /.debug/
+   *     - X/dirname(name)/ - where X is set in gdb's `debug-file-directory`.
+   */
+  auto dirend = strrchr(name, '/');
+  // include ending '/' if any.
+  auto dirlen = dirend != nullptr ? dirend + 1 - name : 0;
+
+  auto debuginfo = getSectionByName(".gnu_debuglink");
+  if (!debuginfo) return result;
+
+  // The section starts with the filename, with any leading directory
+  // components removed, followed by a zero byte.
+  auto debugFileName = getSectionBody(*debuginfo);
+  auto debugFileLen = strlen(debugFileName.begin());
+  if (dirlen + debugFileLen >= PATH_MAX) {
+    return result;
+  }
+
+  char linkname[PATH_MAX];
+  memcpy(linkname, name, dirlen);
+  memcpy(linkname + dirlen, debugFileName.begin(), debugFileLen + 1);
+  reset();
+  result = openNoThrow(linkname, readOnly, msg);
+  if (result == kSuccess) return result;
+  return openNoThrow(name, readOnly, msg);
+}
+
 ElfFile::~ElfFile() {
   reset();
 }
index 176a9e89cecc649acfc02f5061a125b0a223fd55..b329288151fa61e76c7cb696bcd7fab591f6f54c 100644 (file)
@@ -58,9 +58,14 @@ class ElfFile {
     kSystemError = -1,
     kInvalidElfFile = -2,
   };
+  // Open the ELF file. Does not throw on error.
   int openNoThrow(const char* name, bool readOnly=true,
                   const char** msg=nullptr) noexcept;
 
+  // Like openNoThrow, but follow .gnu_debuglink if present
+  int openAndFollow(const char* name, bool readOnly=true,
+                    const char** msg=nullptr) noexcept;
+
   // Open the ELF file. Throws on error.
   void open(const char* name, bool readOnly=true);
 
index 8e9eff2f7210a0eda6a71c222d9695bf08b2ca56..472c0b93a9fb37654943c0c74a181d4ca38160b8 100644 (file)
@@ -48,7 +48,7 @@ std::shared_ptr<ElfFile> SignalSafeElfCache::getFile(StringPiece p) {
   auto& f = slots_[n];
 
   const char* msg = "";
-  int r = f->openNoThrow(path.data(), true, &msg);
+  int r = f->openAndFollow(path.data(), true, &msg);
   if (r != ElfFile::kSuccess) {
     return nullptr;
   }
@@ -77,7 +77,7 @@ std::shared_ptr<ElfFile> ElfCache::getFile(StringPiece p) {
 
   // No negative caching
   const char* msg = "";
-  int r = entry->file.openNoThrow(path.c_str(), true, &msg);
+  int r = entry->file.openAndFollow(path.c_str(), true, &msg);
   if (r != ElfFile::kSuccess) {
     return nullptr;
   }
diff --git a/folly/experimental/symbolizer/test/gnu_debuglink_test.sh b/folly/experimental/symbolizer/test/gnu_debuglink_test.sh
new file mode 100755 (executable)
index 0000000..115ddc2
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+crash="$1"
+rm -f "$crash."{debuginfo,strip}
+objcopy --only-keep-debug "$crash" "$crash.debuginfo"
+objcopy --strip-debug --add-gnu-debuglink="$crash.debuginfo" "$crash" "$crash.strip"
+
+echo '{"op":"start","test":"gnu_debuglink_test"}';
+start=$(date +%s)
+if "$crash.strip" 2>&1 | grep 'Crash.cpp:[0-9]*$' > /dev/null; then
+    result='"status":"passed"';
+else
+    result='"status":"failed"';
+fi
+end=$(date +%s)
+echo '{"op":"test_done","test":"gnu_debuglink_test",'"$result"'}'
+echo '{"op":"all_done","results":[{"name":"gnu_debuglink_test",'"$result"',"start_time":'"$start"',"end_time":'"$end"',"details":"nothing"}]}'