Commit 1c76bae6 authored by Matt Ma's avatar Matt Ma Committed by Facebook Github Bot

Support inline functions in folly stack trace symbolizer

Summary:
Basic idea is to find the subprogram for a given stack trace address, scan the inlined subroutines of that subprogram recursively for all inline function calls, then find the name and location of these inline function calls.

Now only pure inline functions are supported, and class inline functions are not. Class inline functions needs a lookup path "caller->obj->type", then lookup types in .debug_types, which is more complex.

The debug info parser idea was stolen from hphp/tools/debug-parser/dwarfstate.h

Not sure if we want to add a flag to enable this feature. We can discuss over review.

Reviewed By: luciang

Differential Revision: D16760775

fbshipit-source-id: 1a7c23486605e7d8837070a697a875789ae41416
parent cffb3628
......@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/symbolizer/Dwarf.h>
#include <dwarf.h>
#include <chrono>
#include <type_traits>
#include <dwarf.h>
#include <folly/container/Array.h>
namespace folly {
namespace symbolizer {
......@@ -83,7 +84,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;
......@@ -249,7 +250,7 @@ size_t Dwarf::Path::toBuffer(char* buf, size_t bufSize) const {
if (bufSize) {
*buf = '\0';
}
assert(totalSize == size());
FOLLY_SAFE_CHECK(totalSize == size(), "Invalid total size value.");
return totalSize;
}
......@@ -271,7 +272,7 @@ void Dwarf::Path::toString(std::string& dest) const {
}
dest.append(file_.begin(), file_.end());
}
assert(dest.size() == initialSize + size());
FOLLY_SAFE_CHECK(dest.size() == initialSize + size(), "Invalid path size.");
}
// Next chunk in section
......@@ -325,13 +326,13 @@ void Dwarf::init() {
bool Dwarf::readAbbreviation(
folly::StringPiece& section,
DIEAbbreviation& abbr) {
// abbreviation code
// Abbreviation code
abbr.code = readULEB(section);
if (abbr.code == 0) {
return false;
}
// abbreviation tag
// Abbreviation tag
abbr.tag = readULEB(section);
// does this entry have children?
......@@ -341,20 +342,15 @@ bool Dwarf::readAbbreviation(
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) {
auto spec = readAttributeSpec(section);
if (!spec) {
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)
const {
// Linear search in the .debug_abbrev section, starting at offset
......@@ -371,55 +367,64 @@ 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) {
Dwarf::AttributeSpec Dwarf::readAttributeSpec(folly::StringPiece& sp) {
return {readULEB(sp), readULEB(sp)};
}
Dwarf::Attribute Dwarf::readAttribute(
Dwarf::Die& die,
AttributeSpec& spec,
folly::StringPiece& sp) const {
switch (spec.form) {
case DW_FORM_addr:
return read<uintptr_t>(sp);
return {spec, &die, read<uintptr_t>(sp)};
case DW_FORM_block1:
return readBytes(sp, read<uint8_t>(sp));
return {spec, &die, readBytes(sp, read<uint8_t>(sp))};
case DW_FORM_block2:
return readBytes(sp, read<uint16_t>(sp));
return {spec, &die, readBytes(sp, read<uint16_t>(sp))};
case DW_FORM_block4:
return readBytes(sp, read<uint32_t>(sp));
return {spec, &die, readBytes(sp, read<uint32_t>(sp))};
case DW_FORM_block: // fallthrough
case DW_FORM_exprloc:
return readBytes(sp, readULEB(sp));
return {spec, &die, readBytes(sp, readULEB(sp))};
case DW_FORM_data1: // fallthrough
case DW_FORM_ref1:
return read<uint8_t>(sp);
return {spec, &die, read<uint8_t>(sp)};
case DW_FORM_data2: // fallthrough
case DW_FORM_ref2:
return read<uint16_t>(sp);
return {spec, &die, read<uint16_t>(sp)};
case DW_FORM_data4: // fallthrough
case DW_FORM_ref4:
return read<uint32_t>(sp);
return {spec, &die, read<uint32_t>(sp)};
case DW_FORM_data8: // fallthrough
case DW_FORM_ref8:
return read<uint64_t>(sp);
case DW_FORM_ref_sig8:
return {spec, &die, read<uint64_t>(sp)};
case DW_FORM_sdata:
return readSLEB(sp);
return {spec, &die, readSLEB(sp)};
case DW_FORM_udata: // fallthrough
case DW_FORM_ref_udata:
return readULEB(sp);
return {spec, &die, readULEB(sp)};
case DW_FORM_flag:
return read<uint8_t>(sp);
return {spec, &die, read<uint8_t>(sp)};
case DW_FORM_flag_present:
return 1;
return {spec, &die, 1};
case DW_FORM_sec_offset: // fallthrough
case DW_FORM_ref_addr:
return readOffset(sp, is64Bit);
return {spec, &die, readOffset(sp, die.is64Bit)};
case DW_FORM_string:
return readNullTerminated(sp);
return {spec, &die, readNullTerminated(sp)};
case DW_FORM_strp:
return getStringFromStringSection(readOffset(sp, is64Bit));
return {
spec, &die, getStringFromStringSection(readOffset(sp, die.is64Bit))};
case DW_FORM_indirect: // form is explicitly specified
return readAttributeValue(sp, readULEB(sp), is64Bit);
// Update spec with the actual FORM.
spec.form = readULEB(sp);
return readAttribute(die, spec, sp);
default:
FOLLY_SAFE_CHECK(false, "invalid attribute form");
}
return {spec, &die, 0};
}
folly::StringPiece Dwarf::getStringFromStringSection(uint64_t offset) const {
......@@ -478,74 +483,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,
const Dwarf::CompilationUnit& unit,
LocationInfo& locationInfo,
LocationInfo inlineLocationInfo[],
uint32_t maxNumInline) const {
Die die = getDieAtOffset(unit, unit.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(die, [&](const Dwarf::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;
}
}
return true;
});
if (!mainFileName.empty()) {
locationInfo.hasMainFile = true;
......@@ -563,15 +535,75 @@ bool Dwarf::findLocation(
// 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 &&
locationInfo.hasFileAndLine) {
// Re-get the compilation unit with abbreviation cached.
CompilationUnit cu;
std::array<Dwarf::DIEAbbreviation, kMaxAbbreviationEntries> abbrs;
cu.abbrCache = folly::Range<Dwarf::DIEAbbreviation*>(
abbrs.data(), kMaxAbbreviationEntries);
getCompilationUnit(cu, unit.offset, cu.abbrCache.data());
die.cu = &cu;
Die subprogram;
findSubProgramDieForAddress(die, address, subprogram);
// Subprogram is the DIE of caller function.
if (subprogram.abbr.hasChildren) {
auto isrLocs =
std::array<Dwarf::CodeLocation, kMaxLocationInfoPerFrame>();
uint32_t numFound = 0;
findInlinedSubroutineDieForAddress(
cu, subprogram, address, isrLocs.data(), numFound);
numFound = std::min(numFound, maxNumInline);
LocationInfo* prevLocation = &locationInfo;
LocationInfo* nextLocation = inlineLocationInfo;
Dwarf::CodeLocation* isrLoc = isrLocs.data();
// The line of code in inline function.
uint64_t callerLine = locationInfo.line;
while (numFound > 0) {
// linePtr points to the caller line.
prevLocation->line = isrLoc->line;
folly::Optional<Dwarf::AttributeValue> name;
folly::Optional<Dwarf::AttributeValue> file;
forEachAttribute(isrLoc->die, [&](const Dwarf::Attribute& attr) {
switch (attr.spec.name) {
case DW_AT_name:
name = attr.attrValue;
break;
case DW_AT_decl_file:
file = attr.attrValue;
break;
}
return !file.has_value() || !name.has_value();
});
if (!name.has_value() || !file.has_value()) {
break;
}
nextLocation->empty = false;
nextLocation->hasFileAndLine = true;
nextLocation->name = boost::get<StringPiece>(name.value());
nextLocation->file =
lineVM.getFullFileName(boost::get<uint64_t>(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();
LocationInfo inlineLocationInfo[],
uint32_t maxNumInline) const {
if (mode == LocationInfoMode::DISABLED) {
return false;
}
......@@ -586,9 +618,10 @@ bool Dwarf::findAddress(
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);
Dwarf::CompilationUnit unit;
getCompilationUnit(unit, offset, nullptr);
findLocation(
address, mode, unit, locationInfo, inlineLocationInfo, maxNumInline);
return locationInfo.hasFileAndLine;
} else if (mode == LocationInfoMode::FAST) {
// NOTE: Clang (when using -gdwarf-aranges) doesn't generate entries
......@@ -597,20 +630,288 @@ 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 < info_.size() && !locationInfo.hasFileAndLine) {
Dwarf::CompilationUnit unit;
getCompilationUnit(unit, offset, nullptr);
offset += unit.size;
findLocation(
address, mode, unit, locationInfo, inlineLocationInfo, maxNumInline);
}
return locationInfo.hasFileAndLine;
}
void Dwarf::getCompilationUnit(
Dwarf::CompilationUnit& cu,
uint64_t offset,
Dwarf::DIEAbbreviation* abbrs) const {
FOLLY_SAFE_DCHECK(offset < info_.size(), "unexpected offset");
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");
if (abbrs != nullptr) {
folly::StringPiece abbrev = abbrev_;
abbrev.advance(cu.abbrevOffset);
Dwarf::DIEAbbreviation abbr;
while (readAbbreviation(abbrev, abbr) &&
abbr.code <= kMaxAbbreviationEntries) {
// Abbreviation code 0 is reserved for null debugging information entries.
abbrs[abbr.code - 1] = abbr;
}
}
cu.firstDie = chunk.data() - info_.data();
}
Dwarf::CompilationUnit Dwarf::findCompilationUnit(uint64_t offset) const {
FOLLY_SAFE_DCHECK(offset < info_.size(), "unexpected offset");
Dwarf::CompilationUnit unit;
uint64_t cur = 0;
while (cur < info_.size()) {
if (cur > offset) {
return unit;
}
getCompilationUnit(unit, cur, nullptr);
cur += unit.size;
}
return unit;
}
Dwarf::Die Dwarf::getDieAtOffset(
const Dwarf::CompilationUnit& cu,
uint64_t offset) const {
FOLLY_SAFE_DCHECK(offset < info_.size(), "unexpected offset");
Dwarf::Die die;
die.cu = &cu;
folly::StringPiece sp = folly::StringPiece{
info_.data() + offset, info_.data() + cu.offset + cu.size};
die.offset = offset;
die.is64Bit = cu.is64Bit;
die.siblingDelta = 0;
die.nextDieDelta = 0;
auto code = readULEB(sp);
die.code = code;
if (code == 0) {
return die;
}
die.attrOffset = sp.data() - info_.data() - offset;
die.abbr = !cu.abbrCache.empty() && die.code < kMaxAbbreviationEntries
? cu.abbrCache[die.code - 1]
: getAbbreviation(die.code, cu.abbrevOffset);
return die;
}
Dwarf::Die Dwarf::getNextSibling(Dwarf::Die& die) const {
folly::StringPiece sp = {info_.data() + die.offset,
info_.data() + die.cu->offset + die.cu->size};
if (die.siblingDelta != 0) {
sp.advance(die.siblingDelta);
} else {
if (die.nextDieDelta == 0) {
moveToNextDie(die);
FOLLY_SAFE_CHECK(die.nextDieDelta != 0, "Invalid delta value.");
}
if (die.siblingDelta != 0) {
sp.advance(die.siblingDelta);
} else if (die.abbr.hasChildren) {
forEachChild(die, [](const Dwarf::Die&) { return true; });
sp.advance(die.siblingDelta);
} else {
FOLLY_SAFE_CHECK(die.nextDieDelta != 0, "Invalid delta value.");
sp.advance(die.nextDieDelta);
}
}
return getDieAtOffset(*(die.cu), sp.data() - info_.data());
}
template <typename F>
void Dwarf::forEachChild(Dwarf::Die& die, F&& f) const {
if (!die.abbr.hasChildren) {
return;
}
if (die.nextDieDelta == 0) {
moveToNextDie(die);
FOLLY_SAFE_CHECK(die.nextDieDelta, "Invalid delta value.");
}
auto sibling = getDieAtOffset(*(die.cu), die.offset + die.nextDieDelta);
while (sibling.code) {
if (!f(sibling)) {
return;
}
sibling = getNextSibling(sibling);
}
// Sibling is 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.
die.siblingDelta = sibling.offset + 1 - die.offset;
}
/*
* 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.
*/
template <typename F>
void Dwarf::forEachAttribute(Dwarf::Die& die, F&& f) const {
auto attrs = die.abbr.attributes;
auto values =
folly::StringPiece{info_.data() + die.offset + die.attrOffset,
info_.data() + die.cu->offset + die.cu->size};
while (auto spec = readAttributeSpec(attrs)) {
auto attr = readAttribute(die, spec, values);
if (!f(attr)) {
return;
}
}
}
void Dwarf::moveToNextDie(Dwarf::Die& die) const {
auto attrs = die.abbr.attributes;
auto values =
folly::StringPiece{info_.data() + die.offset + die.attrOffset,
info_.data() + die.cu->offset + die.cu->size};
while (auto spec = readAttributeSpec(attrs)) {
readAttribute(die, spec, values);
}
die.nextDieDelta = values.data() - info_.data() - die.offset;
if (!die.abbr.hasChildren) {
die.siblingDelta = die.nextDieDelta;
}
}
void Dwarf::findSubProgramDieForAddress(
Dwarf::Die& die,
uint64_t address,
Dwarf::Die& subprogram) const {
forEachChild(die, [&](Dwarf::Die& childDie) {
if (childDie.abbr.tag == DW_TAG_subprogram) {
uint64_t lowPc = 0;
uint64_t highPc = 0;
bool isHighPcAddr = false;
StringPiece name;
forEachAttribute(childDie, [&](const Dwarf::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;
}
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(childDie, address, subprogram);
}
return true;
});
}
void Dwarf::findInlinedSubroutineDieForAddress(
const CompilationUnit& cu,
Dwarf::Die& die,
uint64_t address,
Dwarf::CodeLocation* isrLoc,
uint32_t& numFound) const {
if (numFound >= kMaxLocationInfoPerFrame) {
return;
}
forEachChild(die, [&](Dwarf::Die& childDie) {
if (childDie.abbr.tag == DW_TAG_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(childDie, [&](const Dwarf::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;
}
return true;
});
if (address > lowPc &&
address < (isHighPcAddr ? highPc : lowPc + highPc)) {
if (originRefType == DW_FORM_ref1 || originRefType == DW_FORM_ref2 ||
originRefType == DW_FORM_ref4 || originRefType == DW_FORM_ref8 ||
originRefType == DW_FORM_ref_udata) {
isrLoc->cu = cu;
isrLoc->die = getDieAtOffset(cu, cu.offset + origin);
} else if (originRefType == DW_FORM_ref_addr) {
isrLoc->cu = findCompilationUnit(origin);
isrLoc->die = getDieAtOffset(cu, origin);
}
isrLoc->line = callLine;
numFound++;
const Dwarf::CompilationUnit& srcu = isrLoc->cu;
findInlinedSubroutineDieForAddress(
srcu, childDie, address, ++isrLoc, numFound);
return false;
}
}
return true;
});
}
Dwarf::LineNumberVM::LineNumberVM(
folly::StringPiece data,
folly::StringPiece compilationDirectory)
......@@ -807,8 +1108,8 @@ Dwarf::LineNumberVM::StepResult Dwarf::LineNumberVM::step(
}
if (opcode != 0) { // standard opcode
// Only interpret opcodes that are recognized by the version we're parsing;
// the others are vendor extensions and we should ignore them.
// Only interpret opcodes that are recognized by the version we're
// parsing; the others are vendor extensions and we should ignore them.
switch (opcode) {
case DW_LNS_copy:
basicBlock_ = false;
......
......@@ -20,6 +20,7 @@
#include <boost/variant.hpp>
#include <folly/Optional.h>
#include <folly/Range.h>
#include <folly/experimental/symbolizer/Elf.h>
......@@ -115,9 +116,21 @@ class Dwarf {
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 kMaxLocationInfoPerFrame = 5;
struct LocationInfo {
// Whether the location info is filled.
bool empty = true;
// Function name in call stack.
folly::StringPiece name;
bool hasMainFile = false;
Path mainFile;
......@@ -126,21 +139,19 @@ class Dwarf {
uint64_t line = 0;
};
/**
* Find the file and line number information corresponding to address.
*/
bool findAddress(uintptr_t address, LocationInfo& info, LocationInfoMode mode)
const;
// Find the file and line number information corresponding to address.
bool findAddress(
uintptr_t address,
LocationInfoMode mode,
LocationInfo& info,
LocationInfo inlineInfo[],
uint32_t maxNumInline = kMaxLocationInfoPerFrame) 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_;
......@@ -169,18 +180,78 @@ class Dwarf {
folly::StringPiece data_;
};
struct AttributeSpec {
uint64_t name = 0;
uint64_t form = 0;
explicit operator bool() const {
return name != 0 || form != 0;
}
};
// Abbreviation for a Debugging Information Entry.
struct DIEAbbreviation {
uint64_t code;
uint64_t tag;
bool hasChildren;
bool hasChildren = false;
folly::StringPiece attributes;
};
// Maximum number of DIEAbbreviation to cache in a compilation unit. Used to
// speed up inline function lookup.
static const uint32_t kMaxAbbreviationEntries = 1000;
// A top level chunk in the .debug_info that contains a compilation unit.
struct CompilationUnit {
// Offset in .debug_info of this compilation unit.
uint64_t offset;
uint64_t size;
bool is64Bit;
uint8_t version;
uint8_t addrSize;
uint64_t abbrevOffset;
// Offset in .debug_info for the first DIE in this compilation unit.
uint64_t firstDie;
// 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;
};
// 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 {
const CompilationUnit* cu;
// Offset within debug info.
uint64_t offset;
bool is64Bit;
uint64_t code;
// Offset from start to first attribute
uint8_t attrOffset;
// If we know where the next sibling is (eg via DW_AT_sibling), and
// it fits in uint32_t, the delta we need to add to offset to get
// there; otherwise zero.
uint32_t siblingDelta;
// If we know where the next die is, and it fits in uint32_t, this
// is the delta we need to add to offset to get there; otherwise zero.
// if there are no children, this will be the same as sibling.
uint32_t nextDieDelta;
DIEAbbreviation abbr;
};
struct Attribute {
uint64_t name;
uint64_t form;
AttributeSpec spec;
Die* die;
boost::variant<uint64_t, folly::StringPiece> attrValue;
};
folly::StringPiece attributes;
struct CodeLocation {
CompilationUnit cu;
Die die;
uint64_t line;
};
// Interpreter for the line number bytecode VM
......@@ -192,6 +263,12 @@ 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();
......@@ -261,27 +338,82 @@ class Dwarf {
uint64_t discriminator_;
};
// Read an abbreviation from a StringPiece, return true if at end; advance sp
using AttributeValue = boost::variant<uint64_t, folly::StringPiece>;
// Finds the Compilation Unit starting at offset.
void getCompilationUnit(
Dwarf::CompilationUnit& cu,
uint64_t offset,
Dwarf::DIEAbbreviation* abbrs) const;
// Finds the Compilation Unit that contains offset.
CompilationUnit findCompilationUnit(uint64_t offset) const;
// cu must exist during the life cycle of created Die.
Die getDieAtOffset(const CompilationUnit& cu, uint64_t offset) const;
Die getNextSibling(Die& die) const;
// Reads 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
// Gets 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);
// Reads attribute name and form from sp.
static AttributeSpec readAttributeSpec(folly::StringPiece& sp);
// Reads attribute value of given DIE
Attribute readAttribute(Die& die, AttributeSpec& spec, folly::StringPiece& sp)
const;
// 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;
// Finds a subprogram debugging info entry that contains a given address among
// children of given die. Depth first search.
void findSubProgramDieForAddress(
Dwarf::Die& die,
uint64_t address,
Dwarf::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 CompilationUnit& cu,
Dwarf::Die& die,
uint64_t address,
CodeLocation* isrLoc,
uint32_t& numFound) 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.
template <typename F>
void forEachChild(Die& die, F&& f) const;
// Iterates over all attributes of a debugging info entry, calling the given
// callable for each. Iteration is stopped early if any of the calls return
// false.
template <typename F>
void forEachAttribute(Die& die, F&& f) const;
// Iterate over all attributes of the given DIE, and move to its first child
// if any or its next sibling.
void moveToNextDie(Dwarf::Die& die) 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
// Gets a string from the .debug_str section
folly::StringPiece getStringFromStringSection(uint64_t offset) const;
// Finds location info (file and line) for a given address in the given
// compilation unit.
bool findLocation(
uintptr_t address,
const LocationInfoMode mode,
const CompilationUnit& unit,
LocationInfo& info,
LocationInfo inlineInfo[],
uint32_t maxNumInline = kMaxLocationInfoPerFrame) const;
folly::StringPiece info_; // .debug_info
folly::StringPiece abbrev_; // .debug_abbrev
folly::StringPiece aranges_; // .debug_aranges
......
......@@ -76,8 +76,15 @@ void SymbolizedFrame::set(
file_ = file;
name = file->getSymbolName(sym);
Dwarf(file.get()).findAddress(address, location, mode);
location.name = name;
Dwarf(file.get())
.findAddress(
address,
mode,
location,
inlineLocations,
Dwarf::kMaxLocationInfoPerFrame);
}
Symbolizer::Symbolizer(
......@@ -215,52 +222,19 @@ void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
doPrint(formatter.format(address));
}
const char padBuf[] = " ";
folly::StringPiece pad(
padBuf, sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
color(kFunctionColor);
if (!frame.found) {
doPrint(" (not found)");
return;
}
if (!frame.name || frame.name[0] == '\0') {
doPrint(" (unknown)");
} else {
char demangledBuf[2048];
demangle(frame.name, demangledBuf, sizeof(demangledBuf));
doPrint(" ");
doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
printLocationInfo(frame.location, false);
for (const Dwarf::LocationInfo& location : frame.inlineLocations) {
if (location.empty) {
break;
}
if (!(options_ & NO_FILE_AND_LINE)) {
color(kFileColor);
char fileBuf[PATH_MAX];
fileBuf[0] = '\0';
if (frame.location.hasFileAndLine) {
frame.location.file.toBuffer(fileBuf, sizeof(fileBuf));
doPrint("\n");
doPrint(pad);
doPrint(fileBuf);
char buf[22];
uint32_t n = uint64ToBufferUnsafe(frame.location.line, buf);
doPrint(":");
doPrint(StringPiece(buf, n));
}
if (frame.location.hasMainFile) {
char mainFileBuf[PATH_MAX];
mainFileBuf[0] = '\0';
frame.location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf));
if (!frame.location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) {
doPrint("\n");
doPrint(pad);
doPrint("-> ");
doPrint(mainFileBuf);
}
}
printLocationInfo(location, true);
}
}
......@@ -312,6 +286,60 @@ void SymbolizePrinter::println(
}
}
void SymbolizePrinter::printLocationInfo(
const Dwarf::LocationInfo& location,
bool isInline) {
const char padBuf[] = " ";
folly::StringPiece pad(
padBuf, sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
// Inline function has no address in stack trace.
if (isInline) {
doPrint(pad);
}
color(kFunctionColor);
if (location.name.empty()) {
doPrint(" (unknown)");
} else if (isInline) {
doPrint(location.name);
} else {
char demangledBuf[2048];
demangle(location.name.data(), demangledBuf, sizeof(demangledBuf));
doPrint(" ");
doPrint(demangledBuf[0] == '\0' ? location.name : demangledBuf);
}
if (!(options_ & NO_FILE_AND_LINE)) {
color(kFileColor);
char fileBuf[PATH_MAX];
fileBuf[0] = '\0';
if (location.hasFileAndLine) {
location.file.toBuffer(fileBuf, sizeof(fileBuf));
doPrint("\n");
doPrint(pad);
doPrint(fileBuf);
char buf[22];
uint32_t n = uint64ToBufferUnsafe(location.line, buf);
doPrint(":");
doPrint(StringPiece(buf, n));
}
if (location.hasMainFile) {
char mainFileBuf[PATH_MAX];
mainFileBuf[0] = '\0';
location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf));
if (!location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) {
doPrint("\n");
doPrint(pad);
doPrint("-> ");
doPrint(mainFileBuf);
}
}
}
}
namespace {
int getFD(const std::ios& stream) {
......
......@@ -56,6 +56,12 @@ struct SymbolizedFrame {
bool found = false;
const char* name = nullptr;
Dwarf::LocationInfo location;
// If stack trace happens on an inline function call, more than one locations
// need to be printed. First element is always the location of caller
// function, and the subsequent locations are inline function locations if
// applicable. std::vector is not used here in favor of signal-safe and memory
// constrained situations like std::bad_alloc.
Dwarf::LocationInfo inlineLocations[Dwarf::kMaxLocationInfoPerFrame];
/**
* Demangle the name and return it. Not async-signal-safe; allocates memory.
......@@ -253,6 +259,9 @@ class SymbolizePrinter {
private:
void printTerse(uintptr_t address, const SymbolizedFrame& frame);
void printLocationInfo(
const Dwarf::LocationInfo& locationInfo,
bool isInline);
virtual void doPrint(StringPiece sp) = 0;
static constexpr std::array<const char*, Color::NUM> kColorMap = {{
......
......@@ -33,7 +33,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::kMaxLocationInfoPerFrame>();
dwarf.findAddress(uintptr_t(&dummy), mode, info, inlineInfo.data());
}
}
......@@ -47,6 +49,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]);
......
......@@ -14,6 +14,7 @@
* limitations under the License.
*/
#include <folly/experimental/symbolizer/test/SymbolizerTest.h>
#include <folly/experimental/symbolizer/Symbolizer.h>
#include <cstdlib>
......@@ -46,16 +47,6 @@ TEST(Symbolizer, Single) {
EXPECT_EQ("SymbolizerTest.cpp", basename.str());
}
FrameArray<100>* framesToFill{nullptr};
int comparator(const void* ap, const void* bp) {
getStackTrace(*framesToFill);
int a = *static_cast<const int*>(ap);
int b = *static_cast<const int*>(bp);
return a < b ? -1 : a > b ? 1 : 0;
}
// Test stack frames...
FOLLY_NOINLINE void bar();
......@@ -127,6 +118,78 @@ TEST(SymbolizerTest, SymbolCache) {
}
}
namespace {
FOLLY_ALWAYS_INLINE void inlineBar(FrameArray<100>& frames) {
inlineFoo(frames);
}
} // namespace
TEST(SymbolizerTest, InlineFunctionBasic) {
Symbolizer symbolizer(
nullptr, Dwarf::LocationInfoMode::FULL_WITH_INLINE, 100);
FrameArray<100> frames;
inlineBar(frames);
symbolizer.symbolize(frames);
StringSymbolizePrinter printer;
// Stack trace for SymbolizerTest_InlineFunctionBasic_Test function call.
printer.print(4, frames.frames[4]);
EXPECT_STREQ(
printer.str().c_str(),
R"( @ 0000000000000004 folly::symbolizer::test::SymbolizerTest_InlineFunctionBasic_Test::TestBody()
./folly/experimental/symbolizer/test/SymbolizerTest.h:134
-> ./folly/experimental/symbolizer/test/SymbolizerTest.cpp
inlineBar
folly/experimental/symbolizer/test/SymbolizerTest.cpp:124
inlineFoo
folly/experimental/symbolizer/test/SymbolizerTest.h:39)");
}
TEST(SymbolizerTest, InlineFunctionInSeparateFile) {
Symbolizer symbolizer(
nullptr, Dwarf::LocationInfoMode::FULL_WITH_INLINE, 100);
FrameArray<100> frames;
inlineFunctionInSeparateFile(frames);
symbolizer.symbolize(frames);
StringSymbolizePrinter printer;
// Stack trace for SymbolizerTest_InlineFunctionInSeparateFile_Test function
// call.
printer.print(4, frames.frames[4]);
EXPECT_STREQ(
printer.str().c_str(),
R"( @ 0000000000000004 folly::symbolizer::test::SymbolizerTest_InlineFunctionInSeparateFile_Test::TestBody()
./folly/experimental/symbolizer/test/SymbolizerTest.h:155
-> ./folly/experimental/symbolizer/test/SymbolizerTest.cpp
inlineFunctionInSeparateFile
folly/experimental/symbolizer/test/SymbolizerTest.h:44
inlineFoo
folly/experimental/symbolizer/test/SymbolizerTest.h:39)");
}
// Not supported yet.
TEST(SymbolizerTest, InlineFunctionInClass) {
Symbolizer symbolizer(
nullptr, Dwarf::LocationInfoMode::FULL_WITH_INLINE, 100);
FrameArray<100> frames;
ClassWithInlineFunction cif;
cif.inlineFunctionInClass(frames);
symbolizer.symbolize(frames);
StringSymbolizePrinter printer;
// Stack trace for SymbolizerTest_InlineFunctionInClass_Test function call.
printer.print(4, frames.frames[4]);
EXPECT_STREQ(
printer.str().c_str(),
R"( @ 0000000000000004 folly::symbolizer::test::SymbolizerTest_InlineFunctionInClass_Test::TestBody()
./folly/experimental/symbolizer/test/SymbolizerTest.h:39
-> ./folly/experimental/symbolizer/test/SymbolizerTest.cpp)");
}
} // namespace test
} // namespace symbolizer
} // namespace folly
......
/*
* Copyright 2019-present 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.
*/
#pragma once
#include <folly/container/Array.h>
#include <folly/experimental/symbolizer/Symbolizer.h>
namespace folly {
namespace symbolizer {
namespace test {
FrameArray<100>* framesToFill{nullptr};
int comparator(const void* ap, const void* bp) {
getStackTrace(*framesToFill);
int a = *static_cast<const int*>(ap);
int b = *static_cast<const int*>(bp);
return a < b ? -1 : a > b ? 1 : 0;
}
FOLLY_ALWAYS_INLINE void inlineFoo(FrameArray<100>& frames) {
framesToFill = &frames;
int a[2] = {1, 2};
// Use qsort, which is in a different library
qsort(a, 2, sizeof(int), comparator);
framesToFill = nullptr;
}
FOLLY_ALWAYS_INLINE void inlineFunctionInSeparateFile(FrameArray<100>& frames) {
inlineFoo(frames);
}
class ClassWithInlineFunction {
public:
FOLLY_ALWAYS_INLINE void inlineFunctionInClass(FrameArray<100>& frames) {
inlineFoo(frames);
}
};
} // 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