Commit 380c720b authored by Matt Ma's avatar Matt Ma Committed by Facebook Github Bot

folly/experimental/symbolizer: symbolize inlined functions

Summary:
The previous approach (D16760775) reserved space for inlined function info in each `SymbolizedFrame`.

```
struct SymbolizedFrame {
  Dwarf::LocationInfo location;
  Dwarf::LocationInfo inlineLocations[Dwarf::kMaxLocationInfoPerFrame];
  ...
```

That increased the size of `SymbolizedFrame` and `FrameArray` and lead to stack overflow in some already deep stacks.
```
template <size_t N>
struct FrameArray {
  FrameArray() {}

  size_t frameCount = 0;
  uintptr_t addresses[N];
  SymbolizedFrame frames[N];
};
```

To avoid allocate more space on stack, changed to use extra frames to store inline calls:
- Usually the callers allocate `FrameArray<100>` frames, but the stack trace is usually smaller than 100
- Use the unused slots to fill in inlined function info:
 -- each function gets at most `kMaxLocationInfoPerFrame` (currently 3) inlined entries
 -- when the available buffer fills up no more inlined functions are filled in.

To find the inline calling stack, we need first need to find the subprogram Debug Info Entry (with tag DW_TAG_subprogram) with the given address, then recursively find all the inline subroutines (with tag DW_TAG_inlined_subroutine) in the call stack. Sadly debug info has no index we can use for jump, and a linear search over debug info entries (and their attributes) is needed during the process, which would cause performance regression.

```
buck run mode/opt folly/experimental/symbolizer/test:dwarf_benchmark -- --benchmark

============================================================================
folly/experimental/symbolizer/test/DwarfBenchmark.cpprelative  time/iter  iters/s
============================================================================
DwarfFindAddressFast                                         4.03us  248.36K
DwarfFindAddressFull                                         4.03us  248.18K
DwarfFindAddressFullWithInline                             293.23us    3.41K
============================================================================
```

Reviewed By: luciang

Differential Revision: D17586385

fbshipit-source-id: 1b84b3f3a576573ce24092b433a501a3bdf76be0
parent e4d37c6c
......@@ -83,7 +83,7 @@ void printExceptionInfo(
symbolizer.symbolize(addresses, frames.data(), frameCount);
OStreamSymbolizePrinter osp(out, options);
osp.println(addresses, frames.data(), frameCount);
osp.println(frames.data(), frameCount);
}
} catch (const std::exception& e) {
out << "\n !! caught " << folly::exceptionStr(e) << "\n";
......
......@@ -16,21 +16,79 @@
#include <folly/experimental/symbolizer/Dwarf.h>
#include <type_traits>
#include <array>
#include <dwarf.h>
#include <type_traits>
namespace folly {
namespace symbolizer {
Dwarf::Dwarf(const ElfFile* elf) : elf_(elf) {
init();
}
Dwarf::Section::Section(folly::StringPiece d) : is64Bit_(false), data_(d) {}
namespace detail {
// Abbreviation for a Debugging Information Entry.
struct DIEAbbreviation {
uint64_t code;
uint64_t tag;
bool hasChildren = false;
folly::StringPiece attributes;
};
struct CompilationUnit {
bool is64Bit;
uint8_t version;
uint8_t addrSize;
// Offset in .debug_info of this compilation unit.
uint32_t offset;
uint32_t size;
// Offset in .debug_info for the first DIE in this compilation unit.
uint32_t firstDie;
uint64_t abbrevOffset;
// Only the CompilationUnit that contains the caller functions needs this
// cache.
// Indexed by (abbr.code - 1) if (abbr.code - 1) < abbrCache.size();
folly::Range<DIEAbbreviation*> abbrCache;
};
struct Die {
bool is64Bit;
// Offset from start to first attribute
uint8_t attrOffset;
// Offset within debug info.
uint32_t offset;
uint64_t code;
DIEAbbreviation abbr;
};
struct AttributeSpec {
uint64_t name = 0;
uint64_t form = 0;
explicit operator bool() const {
return name != 0 || form != 0;
}
};
struct Attribute {
AttributeSpec spec;
const Die& die;
boost::variant<uint64_t, folly::StringPiece> attrValue;
};
struct CodeLocation {
Die die;
uint64_t line;
};
} // namespace detail
namespace {
// Maximum number of DIEAbbreviation to cache in a compilation unit. Used to
// speed up inline function lookup.
static const uint32_t kMaxAbbreviationEntries = 1000;
// All following read* functions read from a StringPiece, advancing the
// StringPiece, and aborting if there's not enough room.
......@@ -83,7 +141,7 @@ uint64_t readOffset(folly::StringPiece& sp, bool is64Bit) {
// Read "len" bytes
folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) {
FOLLY_SAFE_CHECK(len >= sp.size(), "invalid string length");
FOLLY_SAFE_CHECK(len <= sp.size(), "invalid string length");
folly::StringPiece ret(sp.data(), len);
sp.advance(len);
return ret;
......@@ -107,6 +165,176 @@ void skipPadding(folly::StringPiece& sp, const char* start, size_t alignment) {
}
}
detail::AttributeSpec readAttributeSpec(folly::StringPiece& sp) {
return {readULEB(sp), readULEB(sp)};
}
// Reads an abbreviation from a StringPiece, return true if at end; advance sp
bool readAbbreviation(
folly::StringPiece& section,
detail::DIEAbbreviation& abbr) {
// Abbreviation code
abbr.code = readULEB(section);
if (abbr.code == 0) {
return false;
}
// Abbreviation tag
abbr.tag = readULEB(section);
// does this entry have children?
abbr.hasChildren = (read<uint8_t>(section) != DW_CHILDREN_no);
// attributes
const char* attributeBegin = section.data();
for (;;) {
FOLLY_SAFE_CHECK(!section.empty(), "invalid attribute section");
auto spec = readAttributeSpec(section);
if (!spec) {
break;
}
}
abbr.attributes.assign(attributeBegin, section.data());
return true;
}
folly::StringPiece getStringFromStringSection(
folly::StringPiece str,
uint64_t offset) {
FOLLY_SAFE_CHECK(offset < str.size(), "invalid string offset");
str.advance(offset);
return readNullTerminated(str);
}
detail::Attribute readAttribute(
const detail::Die& die,
detail::AttributeSpec spec,
folly::StringPiece& info,
folly::StringPiece str) {
switch (spec.form) {
case DW_FORM_addr:
return {spec, die, read<uintptr_t>(info)};
case DW_FORM_block1:
return {spec, die, readBytes(info, read<uint8_t>(info))};
case DW_FORM_block2:
return {spec, die, readBytes(info, read<uint16_t>(info))};
case DW_FORM_block4:
return {spec, die, readBytes(info, read<uint32_t>(info))};
case DW_FORM_block:
FOLLY_FALLTHROUGH;
case DW_FORM_exprloc:
return {spec, die, readBytes(info, readULEB(info))};
case DW_FORM_data1:
FOLLY_FALLTHROUGH;
case DW_FORM_ref1:
return {spec, die, read<uint8_t>(info)};
case DW_FORM_data2:
FOLLY_FALLTHROUGH;
case DW_FORM_ref2:
return {spec, die, read<uint16_t>(info)};
case DW_FORM_data4:
FOLLY_FALLTHROUGH;
case DW_FORM_ref4:
return {spec, die, read<uint32_t>(info)};
case DW_FORM_data8:
FOLLY_FALLTHROUGH;
case DW_FORM_ref8:
FOLLY_FALLTHROUGH;
case DW_FORM_ref_sig8:
return {spec, die, read<uint64_t>(info)};
case DW_FORM_sdata:
return {spec, die, readSLEB(info)};
case DW_FORM_udata:
FOLLY_FALLTHROUGH;
case DW_FORM_ref_udata:
return {spec, die, readULEB(info)};
case DW_FORM_flag:
return {spec, die, read<uint8_t>(info)};
case DW_FORM_flag_present:
return {spec, die, 1};
case DW_FORM_sec_offset:
FOLLY_FALLTHROUGH;
case DW_FORM_ref_addr:
return {spec, die, readOffset(info, die.is64Bit)};
case DW_FORM_string:
return {spec, die, readNullTerminated(info)};
case DW_FORM_strp:
return {spec,
die,
getStringFromStringSection(str, readOffset(info, die.is64Bit))};
case DW_FORM_indirect: // form is explicitly specified
// Update spec with the actual FORM.
spec.form = readULEB(info);
return readAttribute(die, spec, info, str);
default:
FOLLY_SAFE_CHECK(false, "invalid attribute form");
}
return {spec, die, 0};
}
detail::CompilationUnit getCompilationUnit(
folly::StringPiece info,
uint64_t offset) {
FOLLY_SAFE_DCHECK(offset < info.size(), "unexpected offset");
detail::CompilationUnit cu;
folly::StringPiece chunk(info);
cu.offset = offset;
chunk.advance(offset);
auto initialLength = read<uint32_t>(chunk);
cu.is64Bit = (initialLength == (uint32_t)-1);
cu.size = cu.is64Bit ? read<uint64_t>(chunk) : initialLength;
FOLLY_SAFE_CHECK(cu.size <= chunk.size(), "invalid chunk size");
cu.size += cu.is64Bit ? 12 : 4;
cu.version = read<uint16_t>(chunk);
FOLLY_SAFE_CHECK(cu.version >= 2 && cu.version <= 4, "invalid info version");
cu.abbrevOffset = readOffset(chunk, cu.is64Bit);
cu.addrSize = read<uint8_t>(chunk);
FOLLY_SAFE_CHECK(cu.addrSize == sizeof(uintptr_t), "invalid address size");
cu.firstDie = chunk.data() - info.data();
return cu;
}
// Finds the Compilation Unit starting at offset.
detail::CompilationUnit findCompilationUnit(
folly::StringPiece info,
uint64_t targetOffset) {
FOLLY_SAFE_DCHECK(targetOffset < info.size(), "unexpected target address");
uint64_t offset = 0;
while (offset < info.size()) {
folly::StringPiece chunk(info);
chunk.advance(offset);
auto initialLength = read<uint32_t>(chunk);
auto is64Bit = (initialLength == (uint32_t)-1);
auto size = is64Bit ? read<uint64_t>(chunk) : initialLength;
FOLLY_SAFE_CHECK(size <= chunk.size(), "invalid chunk size");
size += is64Bit ? 12 : 4;
if (offset + size > targetOffset) {
break;
}
offset += size;
}
return getCompilationUnit(info, offset);
}
void readCompilationUnitAbbrs(
folly::StringPiece abbrev,
detail::CompilationUnit& cu) {
abbrev.advance(cu.abbrevOffset);
detail::DIEAbbreviation abbr;
while (readAbbreviation(abbrev, abbr)) {
// Abbreviation code 0 is reserved for null debugging information entries.
if (abbr.code != 0 && abbr.code <= kMaxAbbreviationEntries) {
cu.abbrCache.data()[abbr.code - 1] = abbr;
}
}
}
// Simplify a path -- as much as we can while not moving data around...
void simplifyPath(folly::StringPiece& sp) {
// Strip leading slashes and useless patterns (./), leaving one initial
......@@ -158,6 +386,23 @@ void simplifyPath(folly::StringPiece& sp) {
} // namespace
Dwarf::Dwarf(const ElfFile* elf)
: elf_(elf),
debugInfo_(getSection(".debug_info")),
debugAbbrev_(getSection(".debug_abbrev")),
debugLine_(getSection(".debug_line")),
debugStr_(getSection(".debug_str")),
debugAranges_(getSection(".debug_aranges")) {
// NOTE: debugAranges_ is for fast address range lookup.
// If missing .debug_info can be used - but it's much slower (linear scan).
if (debugInfo_.empty() || debugAbbrev_.empty() || debugLine_.empty() ||
debugStr_.empty()) {
elf_ = nullptr;
}
}
Dwarf::Section::Section(folly::StringPiece d) : is64Bit_(false), data_(d) {}
Dwarf::Path::Path(
folly::StringPiece baseDir,
folly::StringPiece subDir,
......@@ -293,75 +538,26 @@ bool Dwarf::Section::next(folly::StringPiece& chunk) {
return true;
}
bool Dwarf::getSection(const char* name, folly::StringPiece* section) const {
folly::StringPiece Dwarf::getSection(const char* name) const {
const ElfW(Shdr)* elfSection = elf_->getSectionByName(name);
if (!elfSection) {
return false;
return {};
}
#ifdef SHF_COMPRESSED
if (elfSection->sh_flags & SHF_COMPRESSED) {
return false;
return {};
}
#endif
*section = elf_->getSectionBody(*elfSection);
return true;
return elf_->getSectionBody(*elfSection);
}
void Dwarf::init() {
// Make sure that all .debug_* sections exist
if (!getSection(".debug_info", &info_) ||
!getSection(".debug_abbrev", &abbrev_) ||
!getSection(".debug_line", &line_) ||
!getSection(".debug_str", &strings_)) {
elf_ = nullptr;
return;
}
// 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,
DIEAbbreviation& abbr) {
// abbreviation code
abbr.code = readULEB(section);
if (abbr.code == 0) {
return false;
}
// abbreviation tag
abbr.tag = readULEB(section);
// does this entry have children?
abbr.hasChildren = (read<uint8_t>(section) != DW_CHILDREN_no);
// attributes
const char* attributeBegin = section.data();
for (;;) {
FOLLY_SAFE_CHECK(!section.empty(), "invalid attribute section");
auto attr = readAttribute(section);
if (attr.name == 0 && attr.form == 0) {
break;
}
}
abbr.attributes.assign(attributeBegin, section.data());
return true;
}
Dwarf::DIEAbbreviation::Attribute Dwarf::readAttribute(folly::StringPiece& sp) {
return {readULEB(sp), readULEB(sp)};
}
Dwarf::DIEAbbreviation Dwarf::getAbbreviation(uint64_t code, uint64_t offset)
detail::DIEAbbreviation Dwarf::getAbbreviation(uint64_t code, uint64_t offset)
const {
// Linear search in the .debug_abbrev section, starting at offset
folly::StringPiece section = abbrev_;
folly::StringPiece section = debugAbbrev_;
section.advance(offset);
Dwarf::DIEAbbreviation abbr;
detail::DIEAbbreviation abbr;
while (readAbbreviation(section, abbr)) {
if (abbr.code == code) {
return abbr;
......@@ -371,64 +567,6 @@ Dwarf::DIEAbbreviation Dwarf::getAbbreviation(uint64_t code, uint64_t offset)
FOLLY_SAFE_CHECK(false, "could not find abbreviation code");
}
Dwarf::AttributeValue Dwarf::readAttributeValue(
folly::StringPiece& sp,
uint64_t form,
bool is64Bit) const {
switch (form) {
case DW_FORM_addr:
return read<uintptr_t>(sp);
case DW_FORM_block1:
return readBytes(sp, read<uint8_t>(sp));
case DW_FORM_block2:
return readBytes(sp, read<uint16_t>(sp));
case DW_FORM_block4:
return readBytes(sp, read<uint32_t>(sp));
case DW_FORM_block: // fallthrough
case DW_FORM_exprloc:
return readBytes(sp, readULEB(sp));
case DW_FORM_data1: // fallthrough
case DW_FORM_ref1:
return read<uint8_t>(sp);
case DW_FORM_data2: // fallthrough
case DW_FORM_ref2:
return read<uint16_t>(sp);
case DW_FORM_data4: // fallthrough
case DW_FORM_ref4:
return read<uint32_t>(sp);
case DW_FORM_data8: // fallthrough
case DW_FORM_ref8:
return read<uint64_t>(sp);
case DW_FORM_sdata:
return readSLEB(sp);
case DW_FORM_udata: // fallthrough
case DW_FORM_ref_udata:
return readULEB(sp);
case DW_FORM_flag:
return read<uint8_t>(sp);
case DW_FORM_flag_present:
return 1;
case DW_FORM_sec_offset: // fallthrough
case DW_FORM_ref_addr:
return readOffset(sp, is64Bit);
case DW_FORM_string:
return readNullTerminated(sp);
case DW_FORM_strp:
return getStringFromStringSection(readOffset(sp, is64Bit));
case DW_FORM_indirect: // form is explicitly specified
return readAttributeValue(sp, readULEB(sp), is64Bit);
default:
FOLLY_SAFE_CHECK(false, "invalid attribute form");
}
}
folly::StringPiece Dwarf::getStringFromStringSection(uint64_t offset) const {
FOLLY_SAFE_CHECK(offset < strings_.size(), "invalid strp offset");
folly::StringPiece sp(strings_);
sp.advance(offset);
return readNullTerminated(sp);
}
/**
* Find @address in .debug_aranges and return the offset in
* .debug_info for compilation unit to which this address belongs.
......@@ -478,74 +616,41 @@ bool Dwarf::findDebugInfoOffset(
*/
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);
FOLLY_SAFE_CHECK(version >= 2 && version <= 4, "invalid info version");
uint64_t abbrevOffset = readOffset(chunk, debugInfoSection.is64Bit());
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
// 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);
const LocationInfoMode mode,
detail::CompilationUnit& cu,
LocationInfo& locationInfo,
folly::Range<Dwarf::LocationInfo*> inlineLocationInfo) const {
detail::Die die = getDieAtOffset(cu, cu.firstDie);
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());
die.abbr.tag == DW_TAG_compile_unit, "expecting compile unit entry");
// Read attributes, extracting the few we care about
// Offset in .debug_line for the line number VM program for this
// compilation unit
bool foundLineOffset = false;
uint64_t lineOffset = 0;
folly::StringPiece compilationDirectory;
folly::StringPiece mainFileName;
DIEAbbreviation::Attribute attr;
folly::StringPiece attributes = abbr.attributes;
for (;;) {
attr = readAttribute(attributes);
if (attr.name == 0 && attr.form == 0) {
break;
}
auto val = readAttributeValue(chunk, attr.form, debugInfoSection.is64Bit());
switch (attr.name) {
forEachAttribute(cu, die, [&](const detail::Attribute& attr) {
switch (attr.spec.name) {
case DW_AT_stmt_list:
// Offset in .debug_line for the line number VM program for this
// compilation unit
lineOffset = boost::get<uint64_t>(val);
lineOffset = boost::get<uint64_t>(attr.attrValue);
foundLineOffset = true;
break;
case DW_AT_comp_dir:
// Compilation directory
compilationDirectory = boost::get<folly::StringPiece>(val);
compilationDirectory = boost::get<folly::StringPiece>(attr.attrValue);
break;
case DW_AT_name:
// File name of main file being compiled
mainFileName = boost::get<folly::StringPiece>(val);
mainFileName = boost::get<folly::StringPiece>(attr.attrValue);
break;
}
}
// Iterate through all attributes until find all above.
return true;
});
if (!mainFileName.empty()) {
locationInfo.hasMainFile = true;
......@@ -556,22 +661,78 @@ bool Dwarf::findLocation(
return false;
}
folly::StringPiece lineSection(line_);
folly::StringPiece lineSection(debugLine_);
lineSection.advance(lineOffset);
LineNumberVM lineVM(lineSection, compilationDirectory);
// Execute line number VM program to find file and line
locationInfo.hasFileAndLine =
lineVM.findAddress(address, locationInfo.file, locationInfo.line);
// Look up whether inline function.
if (mode == Dwarf::LocationInfoMode::FULL_WITH_INLINE &&
!inlineLocationInfo.empty() && locationInfo.hasFileAndLine) {
// Re-get the compilation unit with abbreviation cached.
std::array<detail::DIEAbbreviation, kMaxAbbreviationEntries> abbrs;
cu.abbrCache = folly::range(abbrs);
readCompilationUnitAbbrs(debugAbbrev_, cu);
detail::Die subprogram;
findSubProgramDieForAddress(cu, die, address, subprogram);
// Subprogram is the DIE of caller function.
if (subprogram.abbr.hasChildren) {
size_t size = std::min<size_t>(
Dwarf::kMaxInlineLocationInfoPerFrame, inlineLocationInfo.size());
detail::CodeLocation isrLocs[size];
auto isrLocsRange = folly::Range<detail::CodeLocation*>(isrLocs, size);
findInlinedSubroutineDieForAddress(cu, subprogram, address, isrLocsRange);
uint32_t numFound = isrLocsRange.data() - isrLocs;
auto* prevLocation = &locationInfo;
auto* nextLocation = inlineLocationInfo.data();
auto* isrLoc = isrLocs;
// The line in locationInfo is the line in the deepest inline functions,
// and the lines in inlineLocationInfo are the line of callers in call
// sequence. A shuffle is needed to match lines with their function name.
uint64_t callerLine = locationInfo.line;
while (numFound > 0) {
prevLocation->line = isrLoc->line;
folly::Optional<folly::StringPiece> name;
folly::Optional<uint64_t> file;
forEachAttribute(cu, isrLoc->die, [&](const detail::Attribute& attr) {
switch (attr.spec.name) {
case DW_AT_name:
name = boost::get<StringPiece>(attr.attrValue);
break;
case DW_AT_decl_file:
file = boost::get<uint64_t>(attr.attrValue);
break;
}
return !file.has_value() || !name.has_value();
});
if (!name.has_value() || !file.has_value()) {
break;
}
nextLocation->hasFileAndLine = true;
nextLocation->name = name.value();
nextLocation->file = lineVM.getFullFileName(file.value());
prevLocation = nextLocation;
nextLocation++;
isrLoc++;
numFound--;
}
prevLocation->line = callerLine;
}
}
return locationInfo.hasFileAndLine;
}
bool Dwarf::findAddress(
uintptr_t address,
LocationInfoMode mode,
LocationInfo& locationInfo,
LocationInfoMode mode) const {
locationInfo = LocationInfo();
folly::Range<Dwarf::LocationInfo*> inlineLocationInfo) const {
if (mode == LocationInfoMode::DISABLED) {
return false;
}
......@@ -580,15 +741,14 @@ bool Dwarf::findAddress(
return false;
}
if (!aranges_.empty()) {
if (!debugAranges_.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)) {
if (findDebugInfoOffset(address, debugAranges_, offset)) {
// Read compilation unit header from .debug_info
folly::StringPiece infoEntry(info_);
infoEntry.advance(offset);
findLocation(address, infoEntry, locationInfo);
auto unit = getCompilationUnit(debugInfo_, offset);
findLocation(address, mode, unit, locationInfo, inlineLocationInfo);
return locationInfo.hasFileAndLine;
} else if (mode == LocationInfoMode::FAST) {
// NOTE: Clang (when using -gdwarf-aranges) doesn't generate entries
......@@ -597,20 +757,203 @@ bool Dwarf::findAddress(
// it only if such behavior is requested via LocationInfoMode.
return false;
} else {
FOLLY_SAFE_DCHECK(mode == LocationInfoMode::FULL, "unexpected mode");
FOLLY_SAFE_DCHECK(
mode == LocationInfoMode::FULL ||
mode == LocationInfoMode::FULL_WITH_INLINE,
"unexpected mode");
// Fall back to the linear scan.
}
}
// Slow path (linear scan): Iterate over all .debug_info entries
// and look for the address in each compilation unit.
folly::StringPiece infoEntry(info_);
while (!infoEntry.empty() && !locationInfo.hasFileAndLine) {
findLocation(address, infoEntry, locationInfo);
uint64_t offset = 0;
while (offset < debugInfo_.size() && !locationInfo.hasFileAndLine) {
auto unit = getCompilationUnit(debugInfo_, offset);
offset += unit.size;
findLocation(address, mode, unit, locationInfo, inlineLocationInfo);
}
return locationInfo.hasFileAndLine;
}
detail::Die Dwarf::getDieAtOffset(
const detail::CompilationUnit& cu,
uint64_t offset) const {
FOLLY_SAFE_DCHECK(offset < debugInfo_.size(), "unexpected offset");
detail::Die die;
folly::StringPiece sp = folly::StringPiece{
debugInfo_.data() + offset, debugInfo_.data() + cu.offset + cu.size};
die.offset = offset;
die.is64Bit = cu.is64Bit;
auto code = readULEB(sp);
die.code = code;
if (code == 0) {
return die;
}
die.attrOffset = sp.data() - debugInfo_.data() - offset;
die.abbr = !cu.abbrCache.empty() && die.code < kMaxAbbreviationEntries
? cu.abbrCache[die.code - 1]
: getAbbreviation(die.code, cu.abbrevOffset);
return die;
}
size_t Dwarf::forEachChild(
const detail::CompilationUnit& cu,
const detail::Die& die,
folly::FunctionRef<bool(const detail::Die& die)> f) const {
size_t nextDieOffset =
forEachAttribute(cu, die, [&](const detail::Attribute&) { return true; });
if (!die.abbr.hasChildren) {
return nextDieOffset;
}
auto childDie = getDieAtOffset(cu, nextDieOffset);
while (childDie.code != 0) {
if (!f(childDie)) {
return childDie.offset;
}
// NOTE: Don't run `f` over grandchildren, just skip over them.
size_t siblingOffset =
forEachChild(cu, childDie, [](const detail::Die&) { return true; });
childDie = getDieAtOffset(cu, siblingOffset);
}
// childDie is now a dummy die whose offset is to the code 0 marking the
// end of the children. Need to add one to get the offset of the next die.
return childDie.offset + 1;
}
/*
* Iterate over all attributes of the given DIE, calling the given callable
* for each. Iteration is stopped early if any of the calls return false.
*/
size_t Dwarf::forEachAttribute(
const detail::CompilationUnit& cu,
const detail::Die& die,
folly::FunctionRef<bool(const detail::Attribute& die)> f) const {
auto attrs = die.abbr.attributes;
auto values =
folly::StringPiece{debugInfo_.data() + die.offset + die.attrOffset,
debugInfo_.data() + cu.offset + cu.size};
while (auto spec = readAttributeSpec(attrs)) {
auto attr = readAttribute(die, spec, values, debugStr_);
if (!f(attr)) {
return static_cast<size_t>(-1);
}
}
return values.data() - debugInfo_.data();
}
void Dwarf::findSubProgramDieForAddress(
const detail::CompilationUnit& cu,
const detail::Die& die,
uint64_t address,
detail::Die& subprogram) const {
forEachChild(cu, die, [&](const detail::Die& childDie) {
if (childDie.abbr.tag == DW_TAG_subprogram) {
uint64_t lowPc = 0;
uint64_t highPc = 0;
bool isHighPcAddr = false;
StringPiece name;
forEachAttribute(cu, childDie, [&](const detail::Attribute& attr) {
switch (attr.spec.name) {
// Here DW_AT_ranges is not supported since it requires looking up
// in a different section (.debug_ranges).
case DW_AT_low_pc:
lowPc = boost::get<uint64_t>(attr.attrValue);
break;
case DW_AT_high_pc:
// Value of DW_AT_high_pc attribute can be an address
// (DW_FORM_addr) or an offset (DW_FORM_data).
isHighPcAddr = (attr.spec.form == DW_FORM_addr);
highPc = boost::get<uint64_t>(attr.attrValue);
break;
case DW_AT_name:
name = boost::get<StringPiece>(attr.attrValue);
break;
}
// Iterate through all attributes until find all above.
return true;
});
if (address > lowPc &&
address < (isHighPcAddr ? highPc : lowPc + highPc)) {
subprogram = childDie;
return false;
}
} else if (
childDie.abbr.tag == DW_TAG_namespace ||
childDie.abbr.tag == DW_TAG_class_type) {
findSubProgramDieForAddress(cu, childDie, address, subprogram);
}
// Iterates through children until find the inline subprogram.
return true;
});
}
void Dwarf::findInlinedSubroutineDieForAddress(
const detail::CompilationUnit& cu,
const detail::Die& die,
uint64_t address,
folly::Range<detail::CodeLocation*>& isrLoc) const {
if (isrLoc.empty()) {
return;
}
forEachChild(cu, die, [&](const detail::Die& childDie) {
if (childDie.abbr.tag != DW_TAG_inlined_subroutine) {
return true; // Skip DIE that is not inlined_subroutine.
}
uint64_t lowPc = 0;
uint64_t highPc = 0;
bool isHighPcAddr = false;
uint64_t origin = 0;
uint64_t originRefType = 0;
uint64_t callLine = 0;
forEachAttribute(cu, childDie, [&](const detail::Attribute& attr) {
switch (attr.spec.name) {
// Here DW_AT_ranges is not supported since it requires looking up
// in a different section (.debug_ranges).
case DW_AT_low_pc:
lowPc = boost::get<uint64_t>(attr.attrValue);
break;
case DW_AT_high_pc:
// Value of DW_AT_high_pc attribute can be an address
// (DW_FORM_addr) or an offset (DW_FORM_data).
isHighPcAddr = (attr.spec.form == DW_FORM_addr);
highPc = boost::get<uint64_t>(attr.attrValue);
break;
case DW_AT_abstract_origin:
originRefType = attr.spec.form;
origin = boost::get<uint64_t>(attr.attrValue);
break;
case DW_AT_call_line:
callLine = boost::get<uint64_t>(attr.attrValue);
break;
}
// Iterate through all until find all above attributes.
return true;
});
if (address < lowPc || address > (isHighPcAddr ? highPc : lowPc + highPc)) {
// Address doesn't match. Keep searching other children.
return true;
}
isrLoc[0].line = callLine;
if (originRefType == DW_FORM_ref1 || originRefType == DW_FORM_ref2 ||
originRefType == DW_FORM_ref4 || originRefType == DW_FORM_ref8 ||
originRefType == DW_FORM_ref_udata) {
isrLoc[0].die = getDieAtOffset(cu, cu.offset + origin);
isrLoc.advance(1);
findInlinedSubroutineDieForAddress(cu, childDie, address, isrLoc);
} else if (originRefType == DW_FORM_ref_addr) {
auto srcu = findCompilationUnit(debugInfo_, origin);
isrLoc[0].die = getDieAtOffset(cu, origin);
isrLoc.advance(1);
findInlinedSubroutineDieForAddress(srcu, childDie, address, isrLoc);
}
return false;
});
}
Dwarf::LineNumberVM::LineNumberVM(
folly::StringPiece data,
folly::StringPiece compilationDirectory)
......
......@@ -20,12 +20,33 @@
#include <boost/variant.hpp>
#include <folly/Function.h>
#include <folly/Range.h>
#include <folly/experimental/symbolizer/Elf.h>
namespace folly {
namespace symbolizer {
namespace detail {
// A top level chunk in the .debug_info that contains a compilation unit.
struct CompilationUnit;
// Debugging information entry to define a low-level representation of a
// source program. Each debugging information entry consists of an identifying
// tag and a series of attributes. An entry, or group of entries together,
// provide a description of a corresponding entity in the source program.
struct Die;
// Abbreviation for a Debugging Information Entry.
struct DIEAbbreviation;
struct AttributeSpec;
struct Attribute;
struct CodeLocation;
} // namespace detail
/**
* DWARF record parser.
*
......@@ -50,9 +71,11 @@ namespace symbolizer {
* DW_LNE_define_file instructions, etc.
*/
class Dwarf {
// Note that Dwarf uses (and returns) StringPiece a lot.
// The StringPieces point within sections in the ELF file, and so will
// be live for as long as the passed-in ElfFile is live.
/**
* Note that Dwarf uses (and returns) StringPiece a lot.
* The StringPieces point within sections in the ELF file, and so will
* be live for as long as the passed-in ElfFile is live.
*/
public:
/** Create a DWARF parser around an ELF file. */
explicit Dwarf(const ElfFile* elf);
......@@ -61,7 +84,150 @@ class Dwarf {
* Represent a file path a s collection of three parts (base directory,
* subdirectory, and file).
*/
class Path {
class Path;
enum class LocationInfoMode {
// Don't resolve location info.
DISABLED,
// Perform CU lookup using .debug_aranges (might be incomplete).
FAST,
// Scan all CU in .debug_info (slow!) on .debug_aranges lookup failure.
FULL,
// Scan .debug_info (super slower, use with caution) for inline functions in
// addition to FULL.
FULL_WITH_INLINE,
};
/**
* More than one location info may exist if current frame is an inline
* function call.
*/
static const uint32_t kMaxInlineLocationInfoPerFrame = 3;
/** Contains location info like file name, line number, etc. */
struct LocationInfo;
/** Find the file and line number information corresponding to address. */
bool findAddress(
uintptr_t address,
LocationInfoMode mode,
LocationInfo& info,
folly::Range<Dwarf::LocationInfo*> inlineInfo = {}) const;
private:
using AttributeValue = boost::variant<uint64_t, folly::StringPiece>;
/**
* DWARF section made up of chunks, each prefixed with a length header. The
* length indicates whether the chunk is DWARF-32 or DWARF-64, which guides
* interpretation of "section offset" records. (yes, DWARF-32 and DWARF-64
* sections may coexist in the same file).
*/
class Section;
/** Interpreter for the line number bytecode VM */
class LineNumberVM;
/**
* Finds location info (file and line) for a given address in the given
* compilation unit.
*/
bool findLocation(
uintptr_t address,
const LocationInfoMode mode,
detail::CompilationUnit& unit,
LocationInfo& info,
folly::Range<Dwarf::LocationInfo*> inlineInfo = {}) const;
/**
* Finds a subprogram debugging info entry that contains a given address among
* children of given die. Depth first search.
*/
void findSubProgramDieForAddress(
const detail::CompilationUnit& cu,
const detail::Die& die,
uint64_t address,
detail::Die& subprogram) const;
/**
* Finds inlined subroutine DIEs and their caller lines that contains a given
* address among children of given die. Depth first search.
*/
void findInlinedSubroutineDieForAddress(
const detail::CompilationUnit& cu,
const detail::Die& die,
uint64_t address,
folly::Range<detail::CodeLocation*>& isrLoc) const;
static bool
findDebugInfoOffset(uintptr_t address, StringPiece aranges, uint64_t& offset);
/** Get an ELF section by name. */
folly::StringPiece getSection(const char* name) const;
/** cu must exist during the life cycle of created detail::Die. */
detail::Die getDieAtOffset(const detail::CompilationUnit& cu, uint64_t offset)
const;
/**
* Iterates over all children of a debugging info entry, calling the given
* callable for each. Iteration is stopped early if any of the calls return
* false. Returns the offset of next DIE after iterations.
*/
size_t forEachChild(
const detail::CompilationUnit& cu,
const detail::Die& die,
folly::FunctionRef<bool(const detail::Die& die)> f) const;
/**
* Gets abbreviation corresponding to a code, in the chunk starting at
* offset in the .debug_abbrev section
*/
detail::DIEAbbreviation getAbbreviation(uint64_t code, uint64_t offset) const;
/**
* Iterates over all attributes of a debugging info entry, calling the given
* callable for each. If all attributes are visited, then return the offset of
* next DIE, or else iteration is stopped early and return size_t(-1) if any
* of the calls return false.
*/
size_t forEachAttribute(
const detail::CompilationUnit& cu,
const detail::Die& die,
folly::FunctionRef<bool(const detail::Attribute& die)> f) const;
const ElfFile* elf_;
const folly::StringPiece debugInfo_; // .debug_info
const folly::StringPiece debugAbbrev_; // .debug_abbrev
const folly::StringPiece debugLine_; // .debug_line
const folly::StringPiece debugStr_; // .debug_str
const folly::StringPiece debugAranges_; // .debug_aranges
};
class Dwarf::Section {
public:
Section() : is64Bit_(false) {}
explicit Section(folly::StringPiece d);
/**
* Return next chunk, if any; the 4- or 12-byte length was already
* parsed and isn't part of the chunk.
*/
bool next(folly::StringPiece& chunk);
/** Is the current chunk 64 bit? */
bool is64Bit() const {
return is64Bit_;
}
private:
// Yes, 32- and 64- bit sections may coexist. Yikes!
bool is64Bit_;
folly::StringPiece data_;
};
class Dwarf::Path {
public:
Path() {}
......@@ -100,91 +266,23 @@ class Dwarf {
return s;
}
// TODO(tudorb): Implement operator==, operator!=; not as easy as it
// seems as the same path can be represented in multiple ways
private:
folly::StringPiece baseDir_;
folly::StringPiece subDir_;
folly::StringPiece file_;
};
enum class LocationInfoMode {
// Don't resolve location info.
DISABLED,
// Perform CU lookup using .debug_aranges (might be incomplete).
FAST,
// Scan all CU in .debug_info (slow!) on .debug_aranges lookup failure.
FULL,
};
};
struct LocationInfo {
struct Dwarf::LocationInfo {
bool hasFileAndLine = false;
bool hasMainFile = false;
// Function name in call stack.
folly::StringPiece name;
Path mainFile;
bool hasFileAndLine = false;
Path file;
uint64_t line = 0;
};
/**
* Find the file and line number information corresponding to address.
*/
bool findAddress(uintptr_t address, LocationInfo& info, LocationInfoMode mode)
const;
private:
static bool
findDebugInfoOffset(uintptr_t address, StringPiece aranges, uint64_t& offset);
void init();
bool findLocation(
uintptr_t address,
StringPiece& infoEntry,
LocationInfo& info) const;
const ElfFile* elf_;
// DWARF section made up of chunks, each prefixed with a length header.
// The length indicates whether the chunk is DWARF-32 or DWARF-64, which
// guides interpretation of "section offset" records.
// (yes, DWARF-32 and DWARF-64 sections may coexist in the same file)
class Section {
public:
Section() : is64Bit_(false) {}
explicit Section(folly::StringPiece d);
// Return next chunk, if any; the 4- or 12-byte length was already
// parsed and isn't part of the chunk.
bool next(folly::StringPiece& chunk);
// Is the current chunk 64 bit?
bool is64Bit() const {
return is64Bit_;
}
private:
// Yes, 32- and 64- bit sections may coexist. Yikes!
bool is64Bit_;
folly::StringPiece data_;
};
// Abbreviation for a Debugging Information Entry.
struct DIEAbbreviation {
uint64_t code;
uint64_t tag;
bool hasChildren;
struct Attribute {
uint64_t name;
uint64_t form;
};
folly::StringPiece attributes;
};
};
// Interpreter for the line number bytecode VM
class LineNumberVM {
class Dwarf::LineNumberVM {
public:
LineNumberVM(
folly::StringPiece data,
......@@ -192,18 +290,24 @@ class Dwarf {
bool findAddress(uintptr_t address, Path& file, uint64_t& line);
/** Gets full file name at given index including directory. */
Path getFullFileName(uint64_t index) const {
auto fn = getFileName(index);
return Path({}, getIncludeDirectory(fn.directoryIndex), fn.relativeName);
}
private:
void init();
void reset();
// Execute until we commit one new row to the line number matrix
/** Execute until we commit one new row to the line number matrix */
bool next(folly::StringPiece& program);
enum StepResult {
CONTINUE, // Continue feeding opcodes
COMMIT, // Commit new <address, file, line> tuple
END, // End of sequence
};
// Execute one opcode
/** Execute one opcode */
StepResult step(folly::StringPiece& program);
struct FileName {
......@@ -212,19 +316,24 @@ class Dwarf {
// otherwise, 1-based index in the list of include directories
uint64_t directoryIndex;
};
// Read one FileName object, advance sp
/** Read one FileName object, advance sp */
static bool readFileName(folly::StringPiece& sp, FileName& fn);
// Get file name at given index; may be in the initial table
// (fileNames_) or defined using DW_LNE_define_file (and we reexecute
// enough of the program to find it, if so)
/**
* Get file name at given index; may be in the initial table
* (fileNames_) or defined using DW_LNE_define_file (and we reexecute
* enough of the program to find it, if so)
*/
FileName getFileName(uint64_t index) const;
// Get include directory at given index
/** Get include directory at given index */
folly::StringPiece getIncludeDirectory(uint64_t index) const;
// Execute opcodes until finding a DW_LNE_define_file and return true;
// return file at the end.
/**
* Execute opcodes until finding a DW_LNE_define_file and return true;
* return file at the end.
*/
bool nextDefineFile(folly::StringPiece& program, FileName& fn) const;
// Initialization
......@@ -259,34 +368,6 @@ class Dwarf {
bool epilogueBegin_;
uint64_t isa_;
uint64_t discriminator_;
};
// Read an abbreviation from a StringPiece, return true if at end; advance sp
static bool readAbbreviation(folly::StringPiece& sp, DIEAbbreviation& abbr);
// Get abbreviation corresponding to a code, in the chunk starting at
// offset in the .debug_abbrev section
DIEAbbreviation getAbbreviation(uint64_t code, uint64_t offset) const;
// Read one attribute <name, form> pair, advance sp; returns <0, 0> at end.
static DIEAbbreviation::Attribute readAttribute(folly::StringPiece& sp);
// Read one attribute value, advance sp
typedef boost::variant<uint64_t, folly::StringPiece> AttributeValue;
AttributeValue
readAttributeValue(folly::StringPiece& sp, uint64_t form, bool is64Bit) const;
// Get an ELF section by name, return true if found
bool getSection(const char* name, folly::StringPiece* section) const;
// Get a string from the .debug_str section
folly::StringPiece getStringFromStringSection(uint64_t offset) const;
folly::StringPiece info_; // .debug_info
folly::StringPiece abbrev_; // .debug_abbrev
folly::StringPiece aranges_; // .debug_aranges
folly::StringPiece line_; // .debug_line
folly::StringPiece strings_; // .debug_str
};
inline std::ostream& operator<<(std::ostream& out, const Dwarf::Path& path) {
......
......@@ -65,7 +65,8 @@ ElfCache* defaultElfCache() {
void SymbolizedFrame::set(
const std::shared_ptr<ElfFile>& file,
uintptr_t address,
Dwarf::LocationInfoMode mode) {
Dwarf::LocationInfoMode mode,
folly::Range<Dwarf::LocationInfo*> inlineLocations) {
clear();
found = true;
......@@ -77,7 +78,7 @@ void SymbolizedFrame::set(
file_ = file;
name = file->getSymbolName(sym);
Dwarf(file.get()).findAddress(address, location, mode);
Dwarf(file.get()).findAddress(address, mode, location, inlineLocations);
}
Symbolizer::Symbolizer(
......@@ -91,9 +92,10 @@ Symbolizer::Symbolizer(
}
void Symbolizer::symbolize(
const uintptr_t* addresses,
SymbolizedFrame* frames,
size_t addrCount) {
folly::Range<const uintptr_t*> addrs,
folly::Range<SymbolizedFrame*> frames) {
size_t addrCount = addrs.size();
size_t frameCount = frames.size();
size_t remaining = 0;
for (size_t i = 0; i < addrCount; ++i) {
auto& frame = frames[i];
......@@ -119,6 +121,10 @@ void Symbolizer::symbolize(
}
selfPath[selfSize] = '\0';
for (size_t i = 0; i < addrCount; i++) {
frames[i].addr = addrs[i];
}
for (auto lmap = _r_debug.r_map; lmap != nullptr && remaining != 0;
lmap = lmap->l_next) {
// The empty string is used in place of the filename for the link_map
......@@ -139,8 +145,10 @@ void Symbolizer::symbolize(
continue;
}
auto const addr = addresses[i];
if (symbolCache_) {
auto const addr = frame.addr;
// Mode FULL_WITH_INLINE (may have multiple frames for an address) is not
// supported in symbolCache_.
if (symbolCache_ && mode_ != Dwarf::LocationInfoMode::FULL_WITH_INLINE) {
// Need a write lock, because EvictingCacheMap brings found item to
// front of eviction list.
auto lockedSymbolCache = symbolCache_->wlock();
......@@ -157,9 +165,49 @@ void Symbolizer::symbolize(
auto const adjusted = addr - lmap->l_addr;
if (elfFile->getSectionContainingAddress(adjusted)) {
if (mode_ == Dwarf::LocationInfoMode::FULL_WITH_INLINE &&
frameCount > addrCount) {
size_t maxInline = std::min<size_t>(
Dwarf::kMaxInlineLocationInfoPerFrame, frameCount - addrCount);
Dwarf::LocationInfo inlineLocations[maxInline];
folly::Range<Dwarf::LocationInfo*> inlineLocRange(
inlineLocations, maxInline);
frame.set(elfFile, adjusted, mode_, inlineLocRange);
// Find out how many LocationInfo were filled in.
size_t numInlined = std::distance(
inlineLocRange.begin(),
std::find_if(
inlineLocRange.begin(), inlineLocRange.end(), [&](auto loc) {
return !loc.hasFileAndLine;
}));
// Move the rest of the frames to make space for inlined location info
std::move_backward(
frames.begin() + i,
frames.begin() + addrCount,
frames.begin() + addrCount + numInlined);
// Insert inlined location info in the space we opened up above. The
// inlineLocations contains inline functions in call sequence, and
// here we store them reversely in frames (caller in later frame).
for (size_t k = 0; k < numInlined; k++) {
frames[i + k].found = true;
frames[i + k].addr = addr;
frames[i + k].location = inlineLocations[numInlined - k - 1];
frames[i + k].name =
inlineLocations[numInlined - k - 1].name.data();
}
// Skip over the newly added inlined items.
i += numInlined;
addrCount += numInlined;
} else {
frame.set(elfFile, adjusted, mode_);
}
--remaining;
if (symbolCache_) {
if (symbolCache_ &&
mode_ != Dwarf::LocationInfoMode::FULL_WITH_INLINE) {
// frame may already have been set here. That's ok, we'll just
// overwrite, which doesn't cause a correctness problem.
symbolCache_->wlock()->set(addr, frame);
......@@ -198,9 +246,9 @@ folly::StringPiece AddressFormatter::format(uintptr_t address) {
return folly::StringPiece(buf_, end);
}
void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
void SymbolizePrinter::print(const SymbolizedFrame& frame) {
if (options_ & TERSE) {
printTerse(address, frame);
printTerse(frame);
return;
}
......@@ -212,7 +260,7 @@ void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
color(kAddressColor);
AddressFormatter formatter;
doPrint(formatter.format(address));
doPrint(formatter.format(frame.addr));
}
const char padBuf[] = " ";
......@@ -274,16 +322,12 @@ void SymbolizePrinter::color(SymbolizePrinter::Color color) {
doPrint(kColorMap[color]);
}
void SymbolizePrinter::println(
uintptr_t address,
const SymbolizedFrame& frame) {
print(address, frame);
void SymbolizePrinter::println(const SymbolizedFrame& frame) {
print(frame);
doPrint("\n");
}
void SymbolizePrinter::printTerse(
uintptr_t address,
const SymbolizedFrame& frame) {
void SymbolizePrinter::printTerse(const SymbolizedFrame& frame) {
if (frame.found && frame.name && frame.name[0] != '\0') {
char demangledBuf[2048] = {0};
demangle(frame.name, demangledBuf, sizeof(demangledBuf));
......@@ -295,6 +339,7 @@ void SymbolizePrinter::printTerse(
char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t));
char* p = end;
*p-- = '\0';
auto address = frame.addr;
while (address != 0) {
*p-- = kHexChars[address & 0xf];
address >>= 4;
......@@ -304,11 +349,10 @@ void SymbolizePrinter::printTerse(
}
void SymbolizePrinter::println(
const uintptr_t* addresses,
const SymbolizedFrame* frames,
size_t frameCount) {
for (size_t i = 0; i < frameCount; ++i) {
println(addresses[i], frames[i]);
println(frames[i]);
}
}
......
......@@ -47,13 +47,15 @@ struct SymbolizedFrame {
void set(
const std::shared_ptr<ElfFile>& file,
uintptr_t address,
Dwarf::LocationInfoMode mode);
Dwarf::LocationInfoMode mode,
folly::Range<Dwarf::LocationInfo*> inlineLocations = {});
void clear() {
*this = SymbolizedFrame();
}
bool found = false;
uintptr_t addr = 0;
const char* name = nullptr;
Dwarf::LocationInfo location;
......@@ -127,24 +129,45 @@ class Symbolizer {
ElfCacheBase* cache,
Dwarf::LocationInfoMode mode = kDefaultLocationInfoMode,
size_t symbolCacheSize = 0);
/**
* Symbolize given addresses.
*
* - all entries in @addrs will be symbolized (if possible, e.g. if they're
* valid code addresses)
*
* - if `mode_ == FULL_WITH_INLINE` and `frames.size() > addrs.size()` then at
* most `frames.size() - addrs.size()` additional inlined functions will
* also be symbolized (at most `kMaxInlineLocationInfoPerFrame` per @addr
* entry).
*/
void symbolize(
folly::Range<const uintptr_t*> addrs,
folly::Range<SymbolizedFrame*> frames);
void symbolize(
const uintptr_t* addresses,
SymbolizedFrame* frames,
size_t frameCount);
size_t frameCount) {
symbolize(
folly::Range<const uintptr_t*>(addresses, frameCount),
folly::Range<SymbolizedFrame*>(frames, frameCount));
}
template <size_t N>
void symbolize(FrameArray<N>& fa) {
symbolize(fa.addresses, fa.frames, fa.frameCount);
symbolize(
folly::Range<const uintptr_t*>(fa.addresses, fa.frameCount),
folly::Range<SymbolizedFrame*>(fa.frames, N));
}
/**
* Shortcut to symbolize one address.
*/
bool symbolize(uintptr_t address, SymbolizedFrame& frame) {
symbolize(&address, &frame, 1);
symbolize(
folly::Range<const uintptr_t*>(&address, 1),
folly::Range<SymbolizedFrame*>(&frame, 1));
return frame.found;
}
......@@ -180,22 +203,19 @@ class AddressFormatter {
class SymbolizePrinter {
public:
/**
* Print one address, no ending newline.
* Print one frame, no ending newline.
*/
void print(uintptr_t address, const SymbolizedFrame& frame);
void print(const SymbolizedFrame& frame);
/**
* Print one address with ending newline.
* Print one frame with ending newline.
*/
void println(uintptr_t address, const SymbolizedFrame& frame);
void println(const SymbolizedFrame& frame);
/**
* Print multiple addresses on separate lines.
* Print multiple frames on separate lines.
*/
void println(
const uintptr_t* addresses,
const SymbolizedFrame* frames,
size_t frameCount);
void println(const SymbolizedFrame* frames, size_t frameCount);
/**
* Print a string, no endling newline.
......@@ -205,13 +225,13 @@ class SymbolizePrinter {
}
/**
* Print multiple addresses on separate lines, skipping the first
* Print multiple frames on separate lines, skipping the first
* skip addresses.
*/
template <size_t N>
void println(const FrameArray<N>& fa, size_t skip = 0) {
if (skip < fa.frameCount) {
println(fa.addresses + skip, fa.frames + skip, fa.frameCount - skip);
println(fa.frames + skip, fa.frameCount - skip);
}
}
......@@ -252,7 +272,7 @@ class SymbolizePrinter {
const bool isTty_;
private:
void printTerse(uintptr_t address, const SymbolizedFrame& frame);
void printTerse(const SymbolizedFrame& frame);
virtual void doPrint(StringPiece sp) = 0;
static constexpr std::array<const char*, Color::NUM> kColorMap = {{
......
......@@ -15,6 +15,7 @@
*/
#include <folly/Benchmark.h>
#include <folly/Range.h>
#include <folly/experimental/symbolizer/Dwarf.h>
#include <folly/portability/GFlags.h>
......@@ -33,7 +34,9 @@ void run(Dwarf::LocationInfoMode mode, size_t n) {
suspender.dismiss();
for (size_t i = 0; i < n; i++) {
Dwarf::LocationInfo info;
dwarf.findAddress(uintptr_t(&dummy), info, mode);
auto inlineInfo = std::
array<Dwarf::LocationInfo, Dwarf::kMaxInlineLocationInfoPerFrame>();
dwarf.findAddress(uintptr_t(&dummy), mode, info, folly::range(inlineInfo));
}
}
......@@ -47,6 +50,10 @@ BENCHMARK(DwarfFindAddressFull, n) {
run(folly::symbolizer::Dwarf::LocationInfoMode::FULL, n);
}
BENCHMARK(DwarfFindAddressFullWithInline, n) {
run(folly::symbolizer::Dwarf::LocationInfoMode::FULL_WITH_INLINE, n);
}
int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
......
......@@ -16,6 +16,7 @@
#include <folly/experimental/symbolizer/Symbolizer.h>
#include <array>
#include <cstdlib>
#include <folly/Range.h>
......@@ -46,10 +47,11 @@ TEST(Symbolizer, Single) {
EXPECT_EQ("SymbolizerTest.cpp", basename.str());
}
FrameArray<100>* framesToFill{nullptr};
void* framesToFill{nullptr};
template <size_t kNumFrames = 100>
int comparator(const void* ap, const void* bp) {
getStackTrace(*framesToFill);
getStackTrace(*static_cast<FrameArray<kNumFrames>*>(framesToFill));
int a = *static_cast<const int*>(ap);
int b = *static_cast<const int*>(bp);
......@@ -61,9 +63,9 @@ FOLLY_NOINLINE void bar();
void bar(FrameArray<100>& frames) {
framesToFill = &frames;
int a[2] = {1, 2};
std::array<int, 2> a = {1, 2};
// Use qsort, which is in a different library
qsort(a, 2, sizeof(int), comparator);
qsort(a.data(), 2, sizeof(int), comparator<100>);
framesToFill = nullptr;
}
......@@ -127,6 +129,69 @@ TEST(SymbolizerTest, SymbolCache) {
}
}
namespace {
template <size_t kNumFrames = 100>
FOLLY_ALWAYS_INLINE void inlineFoo(FrameArray<kNumFrames>& frames) {
framesToFill = &frames;
std::array<int, 2> a = {1, 2};
// Use qsort, which is in a different library
qsort(a.data(), 2, sizeof(int), comparator<kNumFrames>);
framesToFill = nullptr;
}
template <size_t kNumFrames = 100>
FOLLY_ALWAYS_INLINE void inlineBar(FrameArray<kNumFrames>& frames) {
inlineFoo(frames);
}
} // namespace
TEST(SymbolizerTest, InlineFunctionBasic) {
Symbolizer symbolizer(
nullptr, Dwarf::LocationInfoMode::FULL_WITH_INLINE, 100);
FrameArray<100> frames;
inlineBar<100>(frames);
symbolizer.symbolize(frames);
EXPECT_EQ("inlineFoo<100>", std::string(frames.frames[5].name));
EXPECT_EQ(
"folly/experimental/symbolizer/test/SymbolizerTest.cpp",
std::string(frames.frames[5].location.file.toString()));
EXPECT_EQ(139, frames.frames[5].location.line);
EXPECT_EQ("inlineBar<100>", std::string(frames.frames[6].name));
EXPECT_EQ(
"folly/experimental/symbolizer/test/SymbolizerTest.cpp",
std::string(frames.frames[6].location.file.toString()));
EXPECT_EQ(145, frames.frames[6].location.line);
FrameArray<100> frames2;
inlineBar<100>(frames2);
symbolizer.symbolize(frames2);
for (size_t i = 0; i < frames.frameCount; i++) {
EXPECT_STREQ(frames.frames[i].name, frames2.frames[i].name);
}
}
// No inline frames should be filled because of no extra frames.
TEST(SymbolizerTest, InlineFunctionBasicNoExtraFrames) {
Symbolizer symbolizer(
nullptr, Dwarf::LocationInfoMode::FULL_WITH_INLINE, 100);
FrameArray<8> frames;
inlineBar<8>(frames);
symbolizer.symbolize(frames);
Symbolizer symbolizer2(nullptr, Dwarf::LocationInfoMode::FULL, 100);
FrameArray<8> frames2;
inlineBar<8>(frames2);
symbolizer2.symbolize(frames2);
for (size_t i = 0; i < frames.frameCount; i++) {
EXPECT_STREQ(frames.frames[i].name, frames2.frames[i].name);
}
}
} // namespace test
} // namespace symbolizer
} // namespace folly
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment