folly: symbolizer: slow address->{file+line-number} lookup if `.debug_aranges` is...
authorLucian Grijincu <lucian@fb.com>
Fri, 19 Feb 2016 02:46:44 +0000 (18:46 -0800)
committerfacebook-github-bot-0 <folly-bot@fb.com>
Fri, 19 Feb 2016 03:20:27 +0000 (19:20 -0800)
Summary:Binaries linked with `gold` and `--strip-debug-non-line` don't have an `.debug_aranges` section

We still want to map `address->{file+line-number}` to get nice stack
traces even though this might be slower (linear search all compilation unit entries in `.debug_info`).

Before:
```
$ # link with gold + --strip-debug-non-line
$ folly/experimental/exception_tracer/exception_tracer_test
E0217 15:02:13.694947 1321814 ExceptionTracer.cpp:179] Exception type: std::runtime_error (9 frames)
    @ 000000000040ad2d __cxa_throw
    @ 0000000000409df3 bar()
    @ 0000000000409eab baz()
    @ 0000000000407c77 main
    @ 00007f00dd9860f5 __libc_start_main
    @ 000000000040991b (unknown)
```

After (similar to the output without `--strip-debug-non-line`):
```
E0217 18:37:37.579596 1583124 ExceptionTracer.cpp:179] Exception type: std::runtime_error (9 frames)
    @ 000000000040ad6d __cxa_throw
                       ./folly/experimental/exception_tracer/ExceptionTracerLib.cpp:57
    @ 0000000000409e33 bar()
                       ./folly/experimental/exception_tracer/ExceptionTracerTest.cpp:24
    @ 0000000000409eeb baz()
                       ./folly/experimental/exception_tracer/ExceptionTracerTest.cpp:51
    @ 0000000000407c87 main
                       ./folly/experimental/exception_tracer/ExceptionTracerTest.cpp:96
    @ 00007f1d16ff80f5 __libc_start_main
    @ 000000000040995b (unknown)
```

Differential Revision: D2947965

fb-gh-sync-id: e517bab324b1dcb70cadc9a5211ce794e35c83a5
shipit-source-id: e517bab324b1dcb70cadc9a5211ce794e35c83a5

folly/experimental/symbolizer/Dwarf.cpp
folly/experimental/symbolizer/Dwarf.h
folly/experimental/symbolizer/test/DwarfBenchmark.cpp [new file with mode: 0644]

index 1dc3e079c7c3e6eb9858c80dd273226385c1256d..c13420ca422fd6a2b84e2a3af41afe58470e5210 100644 (file)
@@ -309,13 +309,16 @@ void Dwarf::init() {
   // Make sure that all .debug_* sections exist
   if (!getSection(".debug_info", &info_) ||
       !getSection(".debug_abbrev", &abbrev_) ||
-      !getSection(".debug_aranges", &aranges_) ||
       !getSection(".debug_line", &line_) ||
       !getSection(".debug_str", &strings_)) {
     elf_ = nullptr;
     return;
   }
   getSection(".debug_str", &strings_);
+
+  // Optional: fast address range lookup. If missing .debug_info can
+  // be used - but it's much slower (linear scan).
+  getSection(".debug_aranges", &aranges_);
 }
 
 bool Dwarf::readAbbreviation(folly::StringPiece& section,
@@ -423,23 +426,20 @@ folly::StringPiece Dwarf::getStringFromStringSection(uint64_t offset) const {
   return readNullTerminated(sp);
 }
 
-bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
-  locationInfo = LocationInfo();
-
-  if (!elf_) {  // no file
-    return false;
-  }
-
-  // Find address range in .debug_aranges, map to compilation unit
-  Section arangesSection(aranges_);
+/**
+ * Find @address in .debug_aranges and return the offset in
+ * .debug_info for compilation unit to which this address belongs.
+ */
+bool Dwarf::findDebugInfoOffset(uintptr_t address,
+                                StringPiece aranges,
+                                uint64_t& offset) {
+  Section arangesSection(aranges);
   folly::StringPiece chunk;
-  uint64_t debugInfoOffset;
-  bool found = false;
-  while (!found && arangesSection.next(chunk)) {
+  while (arangesSection.next(chunk)) {
     auto version = read<uint16_t>(chunk);
     FOLLY_SAFE_CHECK(version == 2, "invalid aranges version");
 
-    debugInfoOffset = readOffset(chunk, arangesSection.is64Bit());
+    offset = readOffset(chunk, arangesSection.is64Bit());
     auto addressSize = read<uint8_t>(chunk);
     FOLLY_SAFE_CHECK(addressSize == sizeof(uintptr_t), "invalid address size");
     auto segmentSize = read<uint8_t>(chunk);
@@ -448,7 +448,7 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
     // Padded to a multiple of 2 addresses.
     // Strangely enough, this is the only place in the DWARF spec that requires
     // padding.
-    skipPadding(chunk, aranges_.data(), 2 * sizeof(uintptr_t));
+    skipPadding(chunk, aranges.data(), 2 * sizeof(uintptr_t));
     for (;;) {
       auto start = read<uintptr_t>(chunk);
       auto length = read<uintptr_t>(chunk);
@@ -459,20 +459,37 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
 
       // Is our address in this range?
       if (address >= start && address < start + length) {
-        found = true;
-        break;
+        return true;
       }
     }
   }
+  return false;
+}
 
-  if (!found) {
-    return false;
-  }
-
-  // Read compilation unit header from .debug_info
-  folly::StringPiece sp(info_);
-  sp.advance(debugInfoOffset);
-  Section debugInfoSection(sp);
+/**
+ * Find the @locationInfo for @address in the compilation unit represented
+ * by the @sp .debug_info entry.
+ * Returns whether the address was found.
+ * Advances @sp to the next entry in .debug_info.
+ */
+bool Dwarf::findLocation(uintptr_t address,
+                         StringPiece& infoEntry,
+                         LocationInfo& locationInfo) const {
+  // For each compilation unit compiled with a DWARF producer, a
+  // contribution is made to the .debug_info section of the object
+  // file. Each such contribution consists of a compilation unit
+  // header (see Section 7.5.1.1) followed by a single
+  // DW_TAG_compile_unit or DW_TAG_partial_unit debugging information
+  // entry, together with its children.
+
+  // 7.5.1.1 Compilation Unit Header
+  //  1. unit_length (4B or 12B): read by Section::next
+  //  2. version (2B)
+  //  3. debug_abbrev_offset (4B or 8B): offset into the .debug_abbrev section
+  //  4. address_size (1B)
+
+  Section debugInfoSection(infoEntry);
+  folly::StringPiece chunk;
   FOLLY_SAFE_CHECK(debugInfoSection.next(chunk), "invalid debug info");
 
   auto version = read<uint16_t>(chunk);
@@ -481,14 +498,17 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
   auto addressSize = read<uint8_t>(chunk);
   FOLLY_SAFE_CHECK(addressSize == sizeof(uintptr_t), "invalid address size");
 
-  // We survived so far.  The first (and only) DIE should be
-  // DW_TAG_compile_unit
+  // We survived so far. The first (and only) DIE should be DW_TAG_compile_unit
+  // NOTE: - binutils <= 2.25 does not issue DW_TAG_partial_unit.
+  //       - dwarf compression tools like `dwz` may generate it.
   // TODO(tudorb): Handle DW_TAG_partial_unit?
   auto code = readULEB(chunk);
   FOLLY_SAFE_CHECK(code != 0, "invalid code");
   auto abbr = getAbbreviation(code, abbrevOffset);
   FOLLY_SAFE_CHECK(abbr.tag == DW_TAG_compile_unit,
                    "expecting compile unit entry");
+  // Skip children entries, advance to the next compilation unit entry.
+  infoEntry.advance(chunk.end() - infoEntry.begin());
 
   // Read attributes, extracting the few we care about
   bool foundLineOffset = false;
@@ -528,17 +548,49 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
     locationInfo.mainFile = Path(compilationDirectory, "", mainFileName);
   }
 
-  if (foundLineOffset) {
-    folly::StringPiece lineSection(line_);
-    lineSection.advance(lineOffset);
-    LineNumberVM lineVM(lineSection, compilationDirectory);
+  if (!foundLineOffset) {
+    return false;
+  }
+
+  folly::StringPiece lineSection(line_);
+  lineSection.advance(lineOffset);
+  LineNumberVM lineVM(lineSection, compilationDirectory);
 
-    // Execute line number VM program to find file and line
-    locationInfo.hasFileAndLine =
+  // Execute line number VM program to find file and line
+  locationInfo.hasFileAndLine =
       lineVM.findAddress(address, locationInfo.file, locationInfo.line);
+  return locationInfo.hasFileAndLine;
+}
+
+bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
+  locationInfo = LocationInfo();
+
+  if (!elf_) { // no file
+    return false;
   }
 
-  return true;
+  if (!aranges_.empty()) {
+    // Fast path: find the right .debug_info entry by looking up the
+    // address in .debug_aranges.
+    uint64_t offset = 0;
+    if (findDebugInfoOffset(address, aranges_, offset)) {
+      // Read compilation unit header from .debug_info
+      folly::StringPiece infoEntry(info_);
+      infoEntry.advance(offset);
+      findLocation(address, infoEntry, locationInfo);
+      return true;
+    }
+  }
+
+  // Slow path (linear scan): Iterate over all .debug_info entries
+  // and look for the address in each compilation unit.
+  // NOTE: clang doesn't generate entries in .debug_aranges for some
+  // functions, but always generates .debug_info entries.
+  folly::StringPiece infoEntry(info_);
+  while (!infoEntry.empty() && !locationInfo.hasFileAndLine) {
+    findLocation(address, infoEntry, locationInfo);
+  }
+  return locationInfo.hasFileAndLine;
 }
 
 Dwarf::LineNumberVM::LineNumberVM(folly::StringPiece data,
index 87f9b175b20bcb7e06681708d0e31fd20f1da99d..ba48486f9dac2a4e4d2f05ecb45a14f0e8bc0972 100644 (file)
@@ -117,6 +117,12 @@ class Dwarf {
 
  private:
   void init();
+  static bool findDebugInfoOffset(uintptr_t address,
+                                  StringPiece aranges,
+                                  uint64_t& offset);
+  bool findLocation(uintptr_t address,
+                    StringPiece& infoEntry,
+                    LocationInfo& info) const;
 
   const ElfFile* elf_;
 
diff --git a/folly/experimental/symbolizer/test/DwarfBenchmark.cpp b/folly/experimental/symbolizer/test/DwarfBenchmark.cpp
new file mode 100644 (file)
index 0000000..96c352f
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <folly/Benchmark.h>
+#include <folly/experimental/symbolizer/Dwarf.h>
+#include <gflags/gflags.h>
+
+void dummy() {}
+
+BENCHMARK(DwarfFindAddress, n) {
+  folly::BenchmarkSuspender suspender;
+  using namespace folly::symbolizer;
+  // NOTE: Using '/proc/self/exe' only works if the code for @dummy is
+  // statically linked into the binary.
+  ElfFile elf("/proc/self/exe");
+  Dwarf dwarf(&elf);
+  suspender.dismiss();
+  for (size_t i = 0; i < n; i++) {
+    Dwarf::LocationInfo info;
+    dwarf.findAddress(uintptr_t(&dummy), info);
+  }
+}
+
+int main(int argc, char* argv[]) {
+  gflags::ParseCommandLineFlags(&argc, &argv, true);
+  google::InitGoogleLogging(argv[0]);
+  folly::runBenchmarksOnFlag();
+  return 0;
+}