#include <folly/Range.h>
#include <folly/debugging/symbolizer/DwarfImpl.h>
#include <array>
#include <type_traits>
#include <folly/Optional.h>
#include <folly/debugging/symbolizer/DwarfUtil.h>
#include <folly/lang/SafeAssert.h>
#include <folly/portability/Config.h>
#if FOLLY_HAVE_DWARF && FOLLY_HAVE_ELF
#include <dwarf.h>
#ifndef DW_TAG_skeleton_unit
#define DW_TAG_skeleton_unit …
#endif
namespace folly {
namespace symbolizer {
struct CallLocation {
Path file = {};
uint64_t line = 0;
folly::StringPiece name;
};
DwarfImpl::DwarfImpl(
ElfCacheBase* elfCache, CompilationUnits& cu, LocationInfoMode mode)
: elfCache_(elfCache), cu_(cu), mode_(mode) {}
bool DwarfImpl::findLocation(
uintptr_t address,
SymbolizedFrame& frame,
folly::Range<SymbolizedFrame*> inlineFrames,
folly::FunctionRef<void(folly::StringPiece)> eachParameterName,
bool checkAddress) const {
auto mainCu = cu_.mainCompilationUnit;
Die die = getDieAtOffset(mainCu, mainCu.firstDie);
if (die.abbr.tag != DW_TAG_compile_unit &&
die.abbr.tag != DW_TAG_skeleton_unit) {
FOLLY_SAFE_DFATAL("Unsupported die.abbr.tag: ", die.abbr.tag);
return false;
}
folly::Optional<uint64_t> lineOffset;
folly::StringPiece compilationDirectory;
folly::Optional<folly::StringPiece> mainFileName;
folly::Optional<uint64_t> baseAddrCU;
folly::Optional<uint64_t> rangesOffset;
bool seenLowPC = false;
bool seenHighPC = false;
enum : unsigned {
kStmtList = 1U << 0,
kCompDir = 1U << 1,
kName = 1U << 2,
kLowPC = 1U << 3,
kHighPCOrRanges = 1U << 4,
};
unsigned expectedAttributes = kStmtList | kCompDir | kName | kLowPC;
bool foundAddress = !checkAddress;
if (!foundAddress) {
expectedAttributes |= kHighPCOrRanges;
}
forEachAttribute(mainCu, die, [&](const Attribute& attr) {
switch (attr.spec.name) {
case DW_AT_stmt_list:
expectedAttributes &= ~kStmtList;
lineOffset = std::get<uint64_t>(attr.attrValue);
break;
case DW_AT_comp_dir:
expectedAttributes &= ~kCompDir;
compilationDirectory = std::get<folly::StringPiece>(attr.attrValue);
break;
case DW_AT_name:
expectedAttributes &= ~kName;
mainFileName = std::get<folly::StringPiece>(attr.attrValue);
break;
case DW_AT_low_pc:
expectedAttributes &= ~kLowPC;
baseAddrCU = std::get<uint64_t>(attr.attrValue);
if (!foundAddress) {
if (address < *baseAddrCU) {
return false;
}
seenLowPC = true;
if (seenHighPC) {
foundAddress = true;
} else if (rangesOffset) {
if (!isAddrInRangeList(
mainCu,
address,
baseAddrCU,
*rangesOffset,
mainCu.addrSize)) {
return false;
}
foundAddress = true;
}
}
break;
case DW_AT_high_pc:
expectedAttributes &= ~kHighPCOrRanges;
if (!foundAddress) {
if (address >= std::get<uint64_t>(attr.attrValue)) {
return false;
}
seenHighPC = true;
foundAddress = seenLowPC;
}
break;
case DW_AT_ranges:
expectedAttributes &= ~kHighPCOrRanges;
if (!foundAddress) {
rangesOffset = std::get<uint64_t>(attr.attrValue);
if (seenLowPC) {
if (!isAddrInRangeList(
mainCu,
address,
baseAddrCU,
*rangesOffset,
mainCu.addrSize)) {
return false;
}
foundAddress = true;
}
}
break;
}
return (expectedAttributes != 0);
});
if (!foundAddress || !lineOffset) {
return false;
}
if (mainFileName) {
frame.location.hasMainFile = true;
frame.location.mainFile = Path(compilationDirectory, "", *mainFileName);
}
folly::StringPiece lineSection(mainCu.debugSections.debugLine);
lineSection.advance(*lineOffset);
DwarfLineNumberVM lineVM(
lineSection, compilationDirectory, mainCu.debugSections);
frame.location.hasFileAndLine =
lineVM.findAddress(address, frame.location.file, frame.location.line);
if (!frame.location.hasFileAndLine) {
return false;
}
bool checkInline =
(mode_ == LocationInfoMode::FULL_WITH_INLINE && !inlineFrames.empty());
if (!checkInline && !eachParameterName) {
return true;
}
auto& cu = cu_.defaultCompilationUnit();
if (cu_.splitCU.hasValue()) {
die = getDieAtOffset(*cu_.splitCU, cu_.splitCU->firstDie);
}
std::array<DIEAbbreviation, kMaxAbbreviationEntries> abbrs;
cu.abbrCache = folly::range(abbrs);
folly::StringPiece abbrev = cu.debugSections.debugAbbrev;
abbrev.advance(cu.abbrevOffset.value_or(0));
DIEAbbreviation abbr;
while (readAbbreviation(abbrev, abbr)) {
if (abbr.code != 0 && abbr.code <= kMaxAbbreviationEntries) {
cu.abbrCache.data()[abbr.code - 1] = abbr;
}
}
Die subprogram;
if (!findSubProgramDieForAddress(cu, die, address, baseAddrCU, subprogram)) {
return true;
}
if (auto name = getFunctionNameFromDie(cu, subprogram); !name.empty()) {
if (!folly::StringPiece(frame.name).startsWith(name)) {
frame.name = name.data();
}
}
if (eachParameterName) {
forEachChild(cu, subprogram, [&](const Die& child) {
if (child.abbr.tag == DW_TAG_formal_parameter) {
if (auto name =
getAttribute<folly::StringPiece>(cu, child, DW_AT_name)) {
eachParameterName(*name);
}
}
return true;
});
}
if (!checkInline || !subprogram.abbr.hasChildren) {
return true;
}
size_t size =
std::min<size_t>(kMaxInlineLocationInfoPerFrame, inlineFrames.size()) + 1;
CallLocation callLocations[kMaxInlineLocationInfoPerFrame + 1];
size_t numFound = 0;
findInlinedSubroutineDieForAddress(
cu,
subprogram,
lineVM,
address,
baseAddrCU,
folly::Range<CallLocation*>(callLocations, size),
numFound);
folly::Range<CallLocation*> inlineLocations(callLocations, numFound);
fillInlineFrames(address, frame, inlineLocations, inlineFrames);
return true;
}
void DwarfImpl::fillInlineFrames(
uintptr_t address,
SymbolizedFrame& frame,
folly::Range<CallLocation*> inlineLocations,
folly::Range<SymbolizedFrame*> inlineFrames) const {
if (inlineLocations.empty()) {
return;
}
size_t numFound = inlineLocations.size();
const auto innerMostFile = frame.location.file;
const auto innerMostLine = frame.location.line;
frame.location.hasMainFile = false;
frame.location.mainFile = Path{};
frame.location.hasFileAndLine = true;
frame.location.file = inlineLocations[0].file;
frame.location.line = inlineLocations[0].line;
for (size_t i = 0; i < numFound - 1; i++) {
inlineLocations[i].file = inlineLocations[i + 1].file;
inlineLocations[i].line = inlineLocations[i + 1].line;
}
inlineLocations[numFound - 1].file = innerMostFile;
inlineLocations[numFound - 1].line = innerMostLine;
inlineLocations =
inlineLocations.subpiece(0, std::min(numFound, inlineFrames.size()));
std::reverse(inlineLocations.begin(), inlineLocations.end());
for (size_t i = 0; i < inlineLocations.size(); i++) {
inlineFrames[i].found = true;
inlineFrames[i].addr = address;
inlineFrames[i].name = inlineLocations[i].name.data();
inlineFrames[i].location.hasFileAndLine = true;
inlineFrames[i].location.file = inlineLocations[i].file;
inlineFrames[i].location.line = inlineLocations[i].line;
}
}
CompilationUnit DwarfImpl::findCompilationUnit(
const CompilationUnit& cu, uint64_t targetOffset) const {
FOLLY_SAFE_DCHECK(
targetOffset < cu.debugSections.debugInfo.size(),
"unexpected target address");
uint64_t offset = 0;
while (offset < cu.debugSections.debugInfo.size()) {
folly::StringPiece chunk(cu.debugSections.debugInfo);
chunk.advance(offset);
auto initialLength = read<uint32_t>(chunk);
auto is64Bit = (initialLength == (uint32_t)-1);
auto size = is64Bit ? read<uint64_t>(chunk) : initialLength;
if (size > chunk.size()) {
FOLLY_SAFE_DFATAL(
"invalid chunk size: ", size, " chunk.size(): ", chunk.size());
break;
}
size += is64Bit ? 12 : 4;
if (offset + size > targetOffset) {
break;
}
offset += size;
}
return getCompilationUnits(
elfCache_, cu.debugSections, offset, false)
.mainCompilationUnit;
}
size_t DwarfImpl::forEachChild(
const CompilationUnit& cu,
const Die& die,
folly::FunctionRef<bool(const Die& die)> f) const {
size_t nextDieOffset =
forEachAttribute(cu, die, [&](const Attribute&) { return true; });
if (!die.abbr.hasChildren) {
return nextDieOffset;
}
auto childDie = getDieAtOffset(cu, nextDieOffset);
while (childDie.code != 0) {
if (!f(childDie)) {
return childDie.offset;
}
size_t siblingOffset =
forEachChild(cu, childDie, [](const Die&) { return true; });
childDie = getDieAtOffset(cu, siblingOffset);
}
return childDie.offset + 1;
}
bool DwarfImpl::isAddrInRangeList(
const CompilationUnit& cu,
uint64_t address,
folly::Optional<uint64_t> baseAddr,
size_t offset,
uint8_t addrSize) const {
if (addrSize != 4 && addrSize != 8) {
FOLLY_SAFE_DFATAL("wrong address size: ", int(addrSize));
return false;
}
if (cu.version <= 4 && !cu.debugSections.debugRanges.empty()) {
const bool is64BitAddr = addrSize == 8;
folly::StringPiece sp = cu.debugSections.debugRanges;
if (offset > sp.size()) {
return false;
}
sp.advance(offset);
const uint64_t maxAddr = is64BitAddr ? std::numeric_limits<uint64_t>::max()
: std::numeric_limits<uint32_t>::max();
while (sp.size() >= 2 * addrSize) {
uint64_t begin = readOffset(sp, is64BitAddr);
uint64_t end = readOffset(sp, is64BitAddr);
if (begin == maxAddr) {
baseAddr = end;
continue;
}
if (begin == 0 && end == 0) {
break;
}
if (baseAddr && address >= begin + *baseAddr &&
address < end + *baseAddr) {
return true;
}
}
}
if (cu.version == 5 && !cu.debugSections.debugRnglists.empty() &&
cu.addrBase.has_value()) {
auto debugRnglists = cu.debugSections.debugRnglists;
debugRnglists.advance(offset);
while (!debugRnglists.empty()) {
auto kind = read<uint8_t>(debugRnglists);
switch (kind) {
case DW_RLE_end_of_list:
return false;
case DW_RLE_base_addressx: {
auto index = readULEB(debugRnglists);
auto sp = cu.debugSections.debugAddr.subpiece(
*cu.addrBase + index * sizeof(uint64_t));
baseAddr = read<uint64_t>(sp);
} break;
case DW_RLE_startx_endx: {
auto indexStart = readULEB(debugRnglists);
auto indexEnd = readULEB(debugRnglists);
auto spStart = cu.debugSections.debugAddr.subpiece(
*cu.addrBase + indexStart * sizeof(uint64_t));
auto start = read<uint64_t>(spStart);
auto spEnd = cu.debugSections.debugAddr.subpiece(
*cu.addrBase + indexEnd * sizeof(uint64_t));
auto end = read<uint64_t>(spEnd);
if (address >= start && address < end) {
return true;
}
} break;
case DW_RLE_startx_length: {
auto indexStart = readULEB(debugRnglists);
auto length = readULEB(debugRnglists);
auto spStart = cu.debugSections.debugAddr.subpiece(
*cu.addrBase + indexStart * sizeof(uint64_t));
auto start = read<uint64_t>(spStart);
auto end = start + length;
if (start != end && address >= start && address < end) {
return true;
}
} break;
case DW_RLE_offset_pair: {
auto offsetStart = readULEB(debugRnglists);
auto offsetEnd = readULEB(debugRnglists);
if (baseAddr && address >= (*baseAddr + offsetStart) &&
address < (*baseAddr + offsetEnd)) {
return true;
}
} break;
case DW_RLE_base_address:
baseAddr = read<uint64_t>(debugRnglists);
break;
case DW_RLE_start_end: {
uint64_t start = read<uint64_t>(debugRnglists);
uint64_t end = read<uint64_t>(debugRnglists);
if (address >= start && address < end) {
return true;
}
} break;
case DW_RLE_start_length: {
uint64_t start = read<uint64_t>(debugRnglists);
uint64_t end = start + readULEB(debugRnglists);
if (address >= start && address < end) {
return true;
}
} break;
default:
FOLLY_SAFE_DFATAL(
"Unexpected debug_rnglists entry kind: ", static_cast<int>(kind));
return false;
}
}
}
return false;
}
bool DwarfImpl::findSubProgramDieForAddress(
const CompilationUnit& cu,
const Die& die,
uint64_t address,
folly::Optional<uint64_t> baseAddrCU,
Die& subprogram) const {
forEachChild(cu, die, [&](const Die& childDie) {
if (childDie.abbr.tag == DW_TAG_subprogram) {
folly::Optional<uint64_t> lowPc;
folly::Optional<uint64_t> highPc;
folly::Optional<bool> isHighPcAddr;
folly::Optional<uint64_t> rangeOffset;
forEachAttribute(cu, childDie, [&](const Attribute& attr) {
switch (attr.spec.name) {
case DW_AT_ranges:
rangeOffset =
std::get<uint64_t>(attr.attrValue) + cu.rangesBase.value_or(0);
break;
case DW_AT_low_pc:
lowPc = std::get<uint64_t>(attr.attrValue);
break;
case DW_AT_high_pc:
isHighPcAddr = attr.spec.form == DW_FORM_addr ||
attr.spec.form == DW_FORM_addrx ||
attr.spec.form == DW_FORM_addrx1 ||
attr.spec.form == DW_FORM_addrx2 ||
attr.spec.form == DW_FORM_addrx3 ||
attr.spec.form == DW_FORM_addrx4;
highPc = std::get<uint64_t>(attr.attrValue);
break;
}
return true;
});
bool pcMatch = lowPc && highPc && isHighPcAddr && address >= *lowPc &&
(address < (*isHighPcAddr ? *highPc : *lowPc + *highPc));
if (pcMatch) {
subprogram = childDie;
return false;
}
bool rangeMatch = rangeOffset &&
isAddrInRangeList(cu, address, baseAddrCU, *rangeOffset, cu.addrSize);
if (rangeMatch) {
subprogram = childDie;
return false;
}
}
return !findSubProgramDieForAddress(
cu, childDie, address, baseAddrCU, subprogram);
});
return subprogram.abbr.tag == DW_TAG_subprogram;
}
void DwarfImpl::findInlinedSubroutineDieForAddress(
const CompilationUnit& cu,
const Die& die,
const DwarfLineNumberVM& lineVM,
uint64_t address,
folly::Optional<uint64_t> baseAddrCU,
folly::Range<CallLocation*> locations,
size_t& numFound) const {
if (numFound >= locations.size()) {
return;
}
forEachChild(cu, die, [&](const Die& childDie) {
if (childDie.abbr.tag == DW_TAG_try_block ||
childDie.abbr.tag == DW_TAG_catch_block ||
childDie.abbr.tag == DW_TAG_entry_point ||
childDie.abbr.tag == DW_TAG_common_block ||
childDie.abbr.tag == DW_TAG_lexical_block) {
findInlinedSubroutineDieForAddress(
cu, childDie, lineVM, address, baseAddrCU, locations, numFound);
return true;
}
if (childDie.abbr.tag != DW_TAG_subprogram &&
childDie.abbr.tag != DW_TAG_inlined_subroutine) {
return true;
}
folly::Optional<uint64_t> lowPc;
folly::Optional<uint64_t> highPc;
folly::Optional<bool> isHighPcAddr;
folly::Optional<uint64_t> abstractOrigin;
folly::Optional<uint64_t> abstractOriginRefType;
folly::Optional<uint64_t> callFile;
folly::Optional<uint64_t> callLine;
folly::Optional<uint64_t> rangeOffset;
forEachAttribute(cu, childDie, [&](const Attribute& attr) {
switch (attr.spec.name) {
case DW_AT_ranges:
rangeOffset =
std::get<uint64_t>(attr.attrValue) + cu.rangesBase.value_or(0);
break;
case DW_AT_low_pc:
lowPc = std::get<uint64_t>(attr.attrValue);
break;
case DW_AT_high_pc:
isHighPcAddr = attr.spec.form == DW_FORM_addr ||
attr.spec.form == DW_FORM_addrx ||
attr.spec.form == DW_FORM_addrx1 ||
attr.spec.form == DW_FORM_addrx2 ||
attr.spec.form == DW_FORM_addrx3 ||
attr.spec.form == DW_FORM_addrx4;
highPc = std::get<uint64_t>(attr.attrValue);
break;
case DW_AT_abstract_origin:
abstractOriginRefType = attr.spec.form;
abstractOrigin = std::get<uint64_t>(attr.attrValue);
break;
case DW_AT_call_line:
callLine = std::get<uint64_t>(attr.attrValue);
break;
case DW_AT_call_file:
callFile = std::get<uint64_t>(attr.attrValue);
break;
}
return true;
});
bool pcMatch = lowPc && highPc && isHighPcAddr && address >= *lowPc &&
(address < (*isHighPcAddr ? *highPc : (*lowPc + *highPc)));
bool rangeMatch = rangeOffset &&
isAddrInRangeList(cu, address, baseAddrCU, *rangeOffset, cu.addrSize);
if (!pcMatch && !rangeMatch) {
return true;
}
if (!abstractOrigin || !abstractOriginRefType || !callLine || !callFile) {
return false;
}
locations[numFound].file = lineVM.getFullFileName(*callFile);
locations[numFound].line = *callLine;
locations[numFound].name = (*abstractOriginRefType != DW_FORM_ref_addr)
? getFunctionName(cu, cu.offset + *abstractOrigin)
: getFunctionName(
findCompilationUnit(cu, *abstractOrigin), *abstractOrigin);
findInlinedSubroutineDieForAddress(
cu, childDie, lineVM, address, baseAddrCU, locations, ++numFound);
return false;
});
}
}
}
#endif