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 @@ ...@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include <folly/experimental/symbolizer/Dwarf.h> #include <folly/experimental/symbolizer/Dwarf.h>
#include <dwarf.h>
#include <chrono>
#include <type_traits> #include <type_traits>
#include <dwarf.h> #include <folly/container/Array.h>
namespace folly { namespace folly {
namespace symbolizer { namespace symbolizer {
...@@ -83,7 +84,7 @@ uint64_t readOffset(folly::StringPiece& sp, bool is64Bit) { ...@@ -83,7 +84,7 @@ uint64_t readOffset(folly::StringPiece& sp, bool is64Bit) {
// Read "len" bytes // Read "len" bytes
folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) { 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); folly::StringPiece ret(sp.data(), len);
sp.advance(len); sp.advance(len);
return ret; return ret;
...@@ -249,7 +250,7 @@ size_t Dwarf::Path::toBuffer(char* buf, size_t bufSize) const { ...@@ -249,7 +250,7 @@ size_t Dwarf::Path::toBuffer(char* buf, size_t bufSize) const {
if (bufSize) { if (bufSize) {
*buf = '\0'; *buf = '\0';
} }
assert(totalSize == size()); FOLLY_SAFE_CHECK(totalSize == size(), "Invalid total size value.");
return totalSize; return totalSize;
} }
...@@ -271,7 +272,7 @@ void Dwarf::Path::toString(std::string& dest) const { ...@@ -271,7 +272,7 @@ void Dwarf::Path::toString(std::string& dest) const {
} }
dest.append(file_.begin(), file_.end()); 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 // Next chunk in section
...@@ -325,13 +326,13 @@ void Dwarf::init() { ...@@ -325,13 +326,13 @@ void Dwarf::init() {
bool Dwarf::readAbbreviation( bool Dwarf::readAbbreviation(
folly::StringPiece& section, folly::StringPiece& section,
DIEAbbreviation& abbr) { DIEAbbreviation& abbr) {
// abbreviation code // Abbreviation code
abbr.code = readULEB(section); abbr.code = readULEB(section);
if (abbr.code == 0) { if (abbr.code == 0) {
return false; return false;
} }
// abbreviation tag // Abbreviation tag
abbr.tag = readULEB(section); abbr.tag = readULEB(section);
// does this entry have children? // does this entry have children?
...@@ -341,20 +342,15 @@ bool Dwarf::readAbbreviation( ...@@ -341,20 +342,15 @@ bool Dwarf::readAbbreviation(
const char* attributeBegin = section.data(); const char* attributeBegin = section.data();
for (;;) { for (;;) {
FOLLY_SAFE_CHECK(!section.empty(), "invalid attribute section"); FOLLY_SAFE_CHECK(!section.empty(), "invalid attribute section");
auto attr = readAttribute(section); auto spec = readAttributeSpec(section);
if (attr.name == 0 && attr.form == 0) { if (!spec) {
break; break;
} }
} }
abbr.attributes.assign(attributeBegin, section.data()); abbr.attributes.assign(attributeBegin, section.data());
return true; 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) Dwarf::DIEAbbreviation Dwarf::getAbbreviation(uint64_t code, uint64_t offset)
const { const {
// Linear search in the .debug_abbrev section, starting at offset // Linear search in the .debug_abbrev section, starting at offset
...@@ -371,55 +367,64 @@ Dwarf::DIEAbbreviation Dwarf::getAbbreviation(uint64_t code, uint64_t offset) ...@@ -371,55 +367,64 @@ Dwarf::DIEAbbreviation Dwarf::getAbbreviation(uint64_t code, uint64_t offset)
FOLLY_SAFE_CHECK(false, "could not find abbreviation code"); FOLLY_SAFE_CHECK(false, "could not find abbreviation code");
} }
Dwarf::AttributeValue Dwarf::readAttributeValue( Dwarf::AttributeSpec Dwarf::readAttributeSpec(folly::StringPiece& sp) {
folly::StringPiece& sp, return {readULEB(sp), readULEB(sp)};
uint64_t form, }
bool is64Bit) const {
switch (form) { Dwarf::Attribute Dwarf::readAttribute(
Dwarf::Die& die,
AttributeSpec& spec,
folly::StringPiece& sp) const {
switch (spec.form) {
case DW_FORM_addr: case DW_FORM_addr:
return read<uintptr_t>(sp); return {spec, &die, read<uintptr_t>(sp)};
case DW_FORM_block1: case DW_FORM_block1:
return readBytes(sp, read<uint8_t>(sp)); return {spec, &die, readBytes(sp, read<uint8_t>(sp))};
case DW_FORM_block2: case DW_FORM_block2:
return readBytes(sp, read<uint16_t>(sp)); return {spec, &die, readBytes(sp, read<uint16_t>(sp))};
case DW_FORM_block4: 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_block: // fallthrough
case DW_FORM_exprloc: case DW_FORM_exprloc:
return readBytes(sp, readULEB(sp)); return {spec, &die, readBytes(sp, readULEB(sp))};
case DW_FORM_data1: // fallthrough case DW_FORM_data1: // fallthrough
case DW_FORM_ref1: case DW_FORM_ref1:
return read<uint8_t>(sp); return {spec, &die, read<uint8_t>(sp)};
case DW_FORM_data2: // fallthrough case DW_FORM_data2: // fallthrough
case DW_FORM_ref2: case DW_FORM_ref2:
return read<uint16_t>(sp); return {spec, &die, read<uint16_t>(sp)};
case DW_FORM_data4: // fallthrough case DW_FORM_data4: // fallthrough
case DW_FORM_ref4: case DW_FORM_ref4:
return read<uint32_t>(sp); return {spec, &die, read<uint32_t>(sp)};
case DW_FORM_data8: // fallthrough case DW_FORM_data8: // fallthrough
case DW_FORM_ref8: 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: case DW_FORM_sdata:
return readSLEB(sp); return {spec, &die, readSLEB(sp)};
case DW_FORM_udata: // fallthrough case DW_FORM_udata: // fallthrough
case DW_FORM_ref_udata: case DW_FORM_ref_udata:
return readULEB(sp); return {spec, &die, readULEB(sp)};
case DW_FORM_flag: case DW_FORM_flag:
return read<uint8_t>(sp); return {spec, &die, read<uint8_t>(sp)};
case DW_FORM_flag_present: case DW_FORM_flag_present:
return 1; return {spec, &die, 1};
case DW_FORM_sec_offset: // fallthrough case DW_FORM_sec_offset: // fallthrough
case DW_FORM_ref_addr: case DW_FORM_ref_addr:
return readOffset(sp, is64Bit); return {spec, &die, readOffset(sp, die.is64Bit)};
case DW_FORM_string: case DW_FORM_string:
return readNullTerminated(sp); return {spec, &die, readNullTerminated(sp)};
case DW_FORM_strp: 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 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: default:
FOLLY_SAFE_CHECK(false, "invalid attribute form"); FOLLY_SAFE_CHECK(false, "invalid attribute form");
} }
return {spec, &die, 0};
} }
folly::StringPiece Dwarf::getStringFromStringSection(uint64_t offset) const { folly::StringPiece Dwarf::getStringFromStringSection(uint64_t offset) const {
...@@ -478,74 +483,41 @@ bool Dwarf::findDebugInfoOffset( ...@@ -478,74 +483,41 @@ bool Dwarf::findDebugInfoOffset(
*/ */
bool Dwarf::findLocation( bool Dwarf::findLocation(
uintptr_t address, uintptr_t address,
StringPiece& infoEntry, const LocationInfoMode mode,
LocationInfo& locationInfo) const { const Dwarf::CompilationUnit& unit,
// For each compilation unit compiled with a DWARF producer, a LocationInfo& locationInfo,
// contribution is made to the .debug_info section of the object LocationInfo inlineLocationInfo[],
// file. Each such contribution consists of a compilation unit uint32_t maxNumInline) const {
// header (see Section 7.5.1.1) followed by a single Die die = getDieAtOffset(unit, unit.firstDie);
// 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);
FOLLY_SAFE_CHECK( FOLLY_SAFE_CHECK(
abbr.tag == DW_TAG_compile_unit, "expecting compile unit entry"); die.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 // Offset in .debug_line for the line number VM program for this
// compilation unit
bool foundLineOffset = false; bool foundLineOffset = false;
uint64_t lineOffset = 0; uint64_t lineOffset = 0;
folly::StringPiece compilationDirectory; folly::StringPiece compilationDirectory;
folly::StringPiece mainFileName; folly::StringPiece mainFileName;
DIEAbbreviation::Attribute attr; forEachAttribute(die, [&](const Dwarf::Attribute& attr) {
folly::StringPiece attributes = abbr.attributes; switch (attr.spec.name) {
for (;;) {
attr = readAttribute(attributes);
if (attr.name == 0 && attr.form == 0) {
break;
}
auto val = readAttributeValue(chunk, attr.form, debugInfoSection.is64Bit());
switch (attr.name) {
case DW_AT_stmt_list: case DW_AT_stmt_list:
// Offset in .debug_line for the line number VM program for this // Offset in .debug_line for the line number VM program for this
// compilation unit // compilation unit
lineOffset = boost::get<uint64_t>(val); lineOffset = boost::get<uint64_t>(attr.attrValue);
foundLineOffset = true; foundLineOffset = true;
break; break;
case DW_AT_comp_dir: case DW_AT_comp_dir:
// Compilation directory // Compilation directory
compilationDirectory = boost::get<folly::StringPiece>(val); compilationDirectory = boost::get<folly::StringPiece>(attr.attrValue);
break; break;
case DW_AT_name: case DW_AT_name:
// File name of main file being compiled // File name of main file being compiled
mainFileName = boost::get<folly::StringPiece>(val); mainFileName = boost::get<folly::StringPiece>(attr.attrValue);
break; break;
} }
} return true;
});
if (!mainFileName.empty()) { if (!mainFileName.empty()) {
locationInfo.hasMainFile = true; locationInfo.hasMainFile = true;
...@@ -563,15 +535,75 @@ bool Dwarf::findLocation( ...@@ -563,15 +535,75 @@ bool Dwarf::findLocation(
// Execute line number VM program to find file and line // Execute line number VM program to find file and line
locationInfo.hasFileAndLine = locationInfo.hasFileAndLine =
lineVM.findAddress(address, locationInfo.file, locationInfo.line); 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; return locationInfo.hasFileAndLine;
} }
bool Dwarf::findAddress( bool Dwarf::findAddress(
uintptr_t address, uintptr_t address,
LocationInfoMode mode,
LocationInfo& locationInfo, LocationInfo& locationInfo,
LocationInfoMode mode) const { LocationInfo inlineLocationInfo[],
locationInfo = LocationInfo(); uint32_t maxNumInline) const {
if (mode == LocationInfoMode::DISABLED) { if (mode == LocationInfoMode::DISABLED) {
return false; return false;
} }
...@@ -586,9 +618,10 @@ bool Dwarf::findAddress( ...@@ -586,9 +618,10 @@ bool Dwarf::findAddress(
uint64_t offset = 0; uint64_t offset = 0;
if (findDebugInfoOffset(address, aranges_, offset)) { if (findDebugInfoOffset(address, aranges_, offset)) {
// Read compilation unit header from .debug_info // Read compilation unit header from .debug_info
folly::StringPiece infoEntry(info_); Dwarf::CompilationUnit unit;
infoEntry.advance(offset); getCompilationUnit(unit, offset, nullptr);
findLocation(address, infoEntry, locationInfo); findLocation(
address, mode, unit, locationInfo, inlineLocationInfo, maxNumInline);
return locationInfo.hasFileAndLine; return locationInfo.hasFileAndLine;
} else if (mode == LocationInfoMode::FAST) { } else if (mode == LocationInfoMode::FAST) {
// NOTE: Clang (when using -gdwarf-aranges) doesn't generate entries // NOTE: Clang (when using -gdwarf-aranges) doesn't generate entries
...@@ -597,20 +630,288 @@ bool Dwarf::findAddress( ...@@ -597,20 +630,288 @@ bool Dwarf::findAddress(
// it only if such behavior is requested via LocationInfoMode. // it only if such behavior is requested via LocationInfoMode.
return false; return false;
} else { } 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. // Fall back to the linear scan.
} }
} }
// Slow path (linear scan): Iterate over all .debug_info entries // Slow path (linear scan): Iterate over all .debug_info entries
// and look for the address in each compilation unit. // and look for the address in each compilation unit.
folly::StringPiece infoEntry(info_); uint64_t offset = 0;
while (!infoEntry.empty() && !locationInfo.hasFileAndLine) { while (offset < info_.size() && !locationInfo.hasFileAndLine) {
findLocation(address, infoEntry, locationInfo); Dwarf::CompilationUnit unit;
getCompilationUnit(unit, offset, nullptr);
offset += unit.size;
findLocation(
address, mode, unit, locationInfo, inlineLocationInfo, maxNumInline);
} }
return locationInfo.hasFileAndLine; 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( Dwarf::LineNumberVM::LineNumberVM(
folly::StringPiece data, folly::StringPiece data,
folly::StringPiece compilationDirectory) folly::StringPiece compilationDirectory)
...@@ -807,8 +1108,8 @@ Dwarf::LineNumberVM::StepResult Dwarf::LineNumberVM::step( ...@@ -807,8 +1108,8 @@ Dwarf::LineNumberVM::StepResult Dwarf::LineNumberVM::step(
} }
if (opcode != 0) { // standard opcode if (opcode != 0) { // standard opcode
// Only interpret opcodes that are recognized by the version we're parsing; // Only interpret opcodes that are recognized by the version we're
// the others are vendor extensions and we should ignore them. // parsing; the others are vendor extensions and we should ignore them.
switch (opcode) { switch (opcode) {
case DW_LNS_copy: case DW_LNS_copy:
basicBlock_ = false; basicBlock_ = false;
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <boost/variant.hpp> #include <boost/variant.hpp>
#include <folly/Optional.h>
#include <folly/Range.h> #include <folly/Range.h>
#include <folly/experimental/symbolizer/Elf.h> #include <folly/experimental/symbolizer/Elf.h>
...@@ -115,9 +116,21 @@ class Dwarf { ...@@ -115,9 +116,21 @@ class Dwarf {
FAST, FAST,
// Scan all CU in .debug_info (slow!) on .debug_aranges lookup failure. // Scan all CU in .debug_info (slow!) on .debug_aranges lookup failure.
FULL, 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 { struct LocationInfo {
// Whether the location info is filled.
bool empty = true;
// Function name in call stack.
folly::StringPiece name;
bool hasMainFile = false; bool hasMainFile = false;
Path mainFile; Path mainFile;
...@@ -126,21 +139,19 @@ class Dwarf { ...@@ -126,21 +139,19 @@ class Dwarf {
uint64_t line = 0; uint64_t line = 0;
}; };
/** // Find the file and line number information corresponding to address.
* Find the file and line number information corresponding to address. bool findAddress(
*/ uintptr_t address,
bool findAddress(uintptr_t address, LocationInfo& info, LocationInfoMode mode) LocationInfoMode mode,
const; LocationInfo& info,
LocationInfo inlineInfo[],
uint32_t maxNumInline = kMaxLocationInfoPerFrame) const;
private: private:
static bool static bool
findDebugInfoOffset(uintptr_t address, StringPiece aranges, uint64_t& offset); findDebugInfoOffset(uintptr_t address, StringPiece aranges, uint64_t& offset);
void init(); void init();
bool findLocation(
uintptr_t address,
StringPiece& infoEntry,
LocationInfo& info) const;
const ElfFile* elf_; const ElfFile* elf_;
...@@ -169,20 +180,80 @@ class Dwarf { ...@@ -169,20 +180,80 @@ class Dwarf {
folly::StringPiece data_; 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. // Abbreviation for a Debugging Information Entry.
struct DIEAbbreviation { struct DIEAbbreviation {
uint64_t code; uint64_t code;
uint64_t tag; uint64_t tag;
bool hasChildren; bool hasChildren = false;
struct Attribute {
uint64_t name;
uint64_t form;
};
folly::StringPiece attributes; 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 {
AttributeSpec spec;
Die* die;
boost::variant<uint64_t, folly::StringPiece> attrValue;
};
struct CodeLocation {
CompilationUnit cu;
Die die;
uint64_t line;
};
// Interpreter for the line number bytecode VM // Interpreter for the line number bytecode VM
class LineNumberVM { class LineNumberVM {
public: public:
...@@ -192,6 +263,12 @@ class Dwarf { ...@@ -192,6 +263,12 @@ class Dwarf {
bool findAddress(uintptr_t address, Path& file, uint64_t& line); 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: private:
void init(); void init();
void reset(); void reset();
...@@ -261,27 +338,82 @@ class Dwarf { ...@@ -261,27 +338,82 @@ class Dwarf {
uint64_t discriminator_; 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); 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 // offset in the .debug_abbrev section
DIEAbbreviation getAbbreviation(uint64_t code, uint64_t offset) const; DIEAbbreviation getAbbreviation(uint64_t code, uint64_t offset) const;
// Read one attribute <name, form> pair, advance sp; returns <0, 0> at end. // Reads attribute name and form from sp.
static DIEAbbreviation::Attribute readAttribute(folly::StringPiece& 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 // Finds a subprogram debugging info entry that contains a given address among
typedef boost::variant<uint64_t, folly::StringPiece> AttributeValue; // children of given die. Depth first search.
AttributeValue void findSubProgramDieForAddress(
readAttributeValue(folly::StringPiece& sp, uint64_t form, bool is64Bit) const; 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 // Get an ELF section by name, return true if found
bool getSection(const char* name, folly::StringPiece* section) const; 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; 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 info_; // .debug_info
folly::StringPiece abbrev_; // .debug_abbrev folly::StringPiece abbrev_; // .debug_abbrev
folly::StringPiece aranges_; // .debug_aranges folly::StringPiece aranges_; // .debug_aranges
......
...@@ -76,8 +76,15 @@ void SymbolizedFrame::set( ...@@ -76,8 +76,15 @@ void SymbolizedFrame::set(
file_ = file; file_ = file;
name = file->getSymbolName(sym); name = file->getSymbolName(sym);
location.name = name;
Dwarf(file.get()).findAddress(address, location, mode);
Dwarf(file.get())
.findAddress(
address,
mode,
location,
inlineLocations,
Dwarf::kMaxLocationInfoPerFrame);
} }
Symbolizer::Symbolizer( Symbolizer::Symbolizer(
...@@ -215,52 +222,19 @@ void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) { ...@@ -215,52 +222,19 @@ void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
doPrint(formatter.format(address)); doPrint(formatter.format(address));
} }
const char padBuf[] = " ";
folly::StringPiece pad(
padBuf, sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
color(kFunctionColor); color(kFunctionColor);
if (!frame.found) { if (!frame.found) {
doPrint(" (not found)"); doPrint(" (not found)");
return; return;
} }
if (!frame.name || frame.name[0] == '\0') { printLocationInfo(frame.location, false);
doPrint(" (unknown)"); for (const Dwarf::LocationInfo& location : frame.inlineLocations) {
} else { if (location.empty) {
char demangledBuf[2048]; break;
demangle(frame.name, demangledBuf, sizeof(demangledBuf));
doPrint(" ");
doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf);
}
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);
}
} }
doPrint("\n");
printLocationInfo(location, true);
} }
} }
...@@ -312,6 +286,60 @@ void SymbolizePrinter::println( ...@@ -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 { namespace {
int getFD(const std::ios& stream) { int getFD(const std::ios& stream) {
......
...@@ -56,6 +56,12 @@ struct SymbolizedFrame { ...@@ -56,6 +56,12 @@ struct SymbolizedFrame {
bool found = false; bool found = false;
const char* name = nullptr; const char* name = nullptr;
Dwarf::LocationInfo location; 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. * Demangle the name and return it. Not async-signal-safe; allocates memory.
...@@ -253,6 +259,9 @@ class SymbolizePrinter { ...@@ -253,6 +259,9 @@ class SymbolizePrinter {
private: private:
void printTerse(uintptr_t address, const SymbolizedFrame& frame); void printTerse(uintptr_t address, const SymbolizedFrame& frame);
void printLocationInfo(
const Dwarf::LocationInfo& locationInfo,
bool isInline);
virtual void doPrint(StringPiece sp) = 0; virtual void doPrint(StringPiece sp) = 0;
static constexpr std::array<const char*, Color::NUM> kColorMap = {{ static constexpr std::array<const char*, Color::NUM> kColorMap = {{
......
...@@ -33,7 +33,9 @@ void run(Dwarf::LocationInfoMode mode, size_t n) { ...@@ -33,7 +33,9 @@ void run(Dwarf::LocationInfoMode mode, size_t n) {
suspender.dismiss(); suspender.dismiss();
for (size_t i = 0; i < n; i++) { for (size_t i = 0; i < n; i++) {
Dwarf::LocationInfo info; 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) { ...@@ -47,6 +49,10 @@ BENCHMARK(DwarfFindAddressFull, n) {
run(folly::symbolizer::Dwarf::LocationInfoMode::FULL, 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[]) { int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true); gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]); google::InitGoogleLogging(argv[0]);
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include <folly/experimental/symbolizer/test/SymbolizerTest.h>
#include <folly/experimental/symbolizer/Symbolizer.h> #include <folly/experimental/symbolizer/Symbolizer.h>
#include <cstdlib> #include <cstdlib>
...@@ -46,16 +47,6 @@ TEST(Symbolizer, Single) { ...@@ -46,16 +47,6 @@ TEST(Symbolizer, Single) {
EXPECT_EQ("SymbolizerTest.cpp", basename.str()); 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... // Test stack frames...
FOLLY_NOINLINE void bar(); FOLLY_NOINLINE void bar();
...@@ -127,6 +118,78 @@ TEST(SymbolizerTest, SymbolCache) { ...@@ -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 test
} // namespace symbolizer } // namespace symbolizer
} // namespace folly } // 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