From: Lucian Grijincu Date: Fri, 19 Feb 2016 02:46:44 +0000 (-0800) Subject: folly: symbolizer: slow address->{file+line-number} lookup if `.debug_aranges` is... X-Git-Tag: deprecate-dynamic-initializer~59 X-Git-Url: http://plrg.eecs.uci.edu/git/?a=commitdiff_plain;h=4d4ca9ecafc9d4ce62b9ce84e6a6bb20607ded32;p=folly.git folly: symbolizer: slow address->{file+line-number} lookup if `.debug_aranges` is missing (e.g. --strip-debug-non-line) 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 --- diff --git a/folly/experimental/symbolizer/Dwarf.cpp b/folly/experimental/symbolizer/Dwarf.cpp index 1dc3e079..c13420ca 100644 --- a/folly/experimental/symbolizer/Dwarf.cpp +++ b/folly/experimental/symbolizer/Dwarf.cpp @@ -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(chunk); FOLLY_SAFE_CHECK(version == 2, "invalid aranges version"); - debugInfoOffset = readOffset(chunk, arangesSection.is64Bit()); + offset = readOffset(chunk, arangesSection.is64Bit()); auto addressSize = read(chunk); FOLLY_SAFE_CHECK(addressSize == sizeof(uintptr_t), "invalid address size"); auto segmentSize = read(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(chunk); auto length = read(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(chunk); @@ -481,14 +498,17 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const { auto addressSize = read(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, diff --git a/folly/experimental/symbolizer/Dwarf.h b/folly/experimental/symbolizer/Dwarf.h index 87f9b175..ba48486f 100644 --- a/folly/experimental/symbolizer/Dwarf.h +++ b/folly/experimental/symbolizer/Dwarf.h @@ -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 index 00000000..96c352f9 --- /dev/null +++ b/folly/experimental/symbolizer/test/DwarfBenchmark.cpp @@ -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 +#include +#include + +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; +}