#include <folly/debugging/symbolizer/DwarfUtil.h>
#include <array>
#include <type_traits>
#include <folly/Optional.h>
#include <folly/Range.h>
#include <folly/experimental/symbolizer/Elf.h>
#include <folly/portability/Config.h>
#include <folly/portability/Unistd.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 {
folly::StringPiece getElfSection(const ElfFile* elf, const char* name) {
const ElfShdr* elfSection = elf->getSectionByName(name);
if (!elfSection) {
return {};
}
#ifdef SHF_COMPRESSED
if (elfSection->sh_flags & SHF_COMPRESSED) {
return {};
}
#endif
return elf->getSectionBody(*elfSection);
}
template <size_t N>
uint64_t readU64(folly::StringPiece& sp) {
FOLLY_SAFE_CHECK(sp.size() >= N, "underflow");
uint64_t x = 0;
memcpy(&x, sp.data(), N);
sp.advance(N);
return x;
}
uint64_t readULEB(folly::StringPiece& sp, uint8_t& shift, uint8_t& val) {
uint64_t r = 0;
shift = 0;
do {
val = read<uint8_t>(sp);
r |= ((uint64_t)(val & 0x7f) << shift);
shift += 7;
} while (val & 0x80);
return r;
}
uint64_t readULEB(folly::StringPiece& sp) {
uint8_t shift;
uint8_t val;
return readULEB(sp, shift, val);
}
int64_t readSLEB(folly::StringPiece& sp) {
uint8_t shift;
uint8_t val;
uint64_t r = readULEB(sp, shift, val);
if (shift < 64 && (val & 0x40)) {
r |= -(1ULL << shift);
}
return r;
}
uint64_t readOffset(folly::StringPiece& sp, bool is64Bit) {
return is64Bit ? read<uint64_t>(sp) : read<uint32_t>(sp);
}
folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) {
FOLLY_SAFE_CHECK(len <= sp.size(), "invalid string length");
folly::StringPiece ret(sp.data(), len);
sp.advance(len);
return ret;
}
folly::StringPiece readNullTerminated(folly::StringPiece& sp) {
const char* p = static_cast<const char*>(memchr(sp.data(), 0, sp.size()));
FOLLY_SAFE_CHECK(p, "invalid null-terminated string");
folly::StringPiece ret(sp.data(), p);
sp.assign(p + 1, sp.end());
return ret;
}
folly::StringPiece getStringFromStringSection(
folly::StringPiece str, uint64_t offset) {
FOLLY_SAFE_CHECK(offset < str.size(), "invalid string offset");
str.advance(offset);
return readNullTerminated(str);
}
AttributeSpec readAttributeSpec(folly::StringPiece& sp) {
AttributeSpec spec;
spec.name = readULEB(sp);
spec.form = readULEB(sp);
if (spec.form == DW_FORM_implicit_const) {
spec.implicitConst = readSLEB(sp);
}
return spec;
}
bool readAbbreviation(folly::StringPiece& section, DIEAbbreviation& abbr) {
abbr.code = readULEB(section);
if (abbr.code == 0) {
return false;
}
abbr.tag = readULEB(section);
abbr.hasChildren = (read<uint8_t>(section) != DW_CHILDREN_no);
const char* attributeBegin = section.data();
for (;;) {
if (section.empty()) {
FOLLY_SAFE_DFATAL("invalid attribute section");
return false;
}
auto spec = readAttributeSpec(section);
if (!spec) {
break;
}
}
abbr.attributes.assign(attributeBegin, section.data());
return true;
}
namespace {
DIEAbbreviation getAbbreviation(
folly::StringPiece debugAbbrev, uint64_t code, uint64_t offset) {
debugAbbrev.advance(offset);
DIEAbbreviation abbr;
while (readAbbreviation(debugAbbrev, abbr)) {
if (abbr.code == code) {
return abbr;
}
}
FOLLY_SAFE_DFATAL("could not find abbreviation code");
return {};
}
bool findCompiliationOffset(
folly::StringPiece debugCuIndex, uint64_t dwoId, CompilationUnit& cu) {
if (debugCuIndex.empty()) {
return false;
}
auto version = read<uint32_t>(debugCuIndex);
if (version != 2 && version != (5 << (kIsLittleEndian ? 0 : 16))) {
return false;
}
auto numColumns = read<uint32_t>(debugCuIndex);
read<uint32_t>(debugCuIndex);
auto numBuckets = read<uint32_t>(debugCuIndex);
folly::StringPiece signatureSection = debugCuIndex;
folly::StringPiece indexesSection = debugCuIndex;
indexesSection.advance(numBuckets * sizeof(uint64_t));
ssize_t idx = -1;
for (unsigned i = 0; i < numBuckets; i++) {
uint64_t hash = read<uint64_t>(signatureSection);
uint32_t index = read<uint32_t>(indexesSection);
if (hash == dwoId) {
idx = index;
break;
}
}
if (idx <= 0) {
return false;
}
debugCuIndex.advance(numBuckets * sizeof(uint64_t));
debugCuIndex.advance(numBuckets * sizeof(uint32_t));
ssize_t infoSectionIndex = -1;
ssize_t abbrevSectionIndex = -1;
ssize_t strOffsetsSectionIndex = -1;
ssize_t rnglistsSectionIndex = -1;
for (unsigned i = 0; i != numColumns; ++i) {
auto index = read<uint32_t>(debugCuIndex);
if (index == DW_SECT_INFO) {
infoSectionIndex = i;
} else if (index == DW_SECT_ABBREV) {
abbrevSectionIndex = i;
} else if (index == DW_SECT_STR_OFFSETS) {
strOffsetsSectionIndex = i;
} else if (index == DW_SECT_RNGLISTS) {
rnglistsSectionIndex = i;
}
}
if (infoSectionIndex == -1 || abbrevSectionIndex == -1 ||
strOffsetsSectionIndex == -1) {
return false;
}
debugCuIndex.advance((idx - 1) * numColumns * sizeof(uint32_t));
for (unsigned i = 0; i != numColumns; ++i) {
auto offset = read<uint32_t>(debugCuIndex);
if (i == infoSectionIndex) {
cu.offset = offset;
}
if (i == abbrevSectionIndex) {
cu.abbrevOffset = offset;
}
if (i == strOffsetsSectionIndex) {
cu.strOffsetsBase = offset;
}
if (i == rnglistsSectionIndex) {
cu.rnglistsBase = offset;
}
}
return true;
}
bool parseCompilationUnitMetadata(CompilationUnit& cu, size_t offset) {
auto debugInfo = cu.debugSections.debugInfo;
folly::StringPiece chunk(debugInfo);
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;
if (cu.size > chunk.size()) {
FOLLY_SAFE_DFATAL(
"invalid size: ", cu.size, " chunk.size(): ", chunk.size());
return false;
}
cu.size += cu.is64Bit ? 12 : 4;
cu.version = read<uint16_t>(chunk);
if (cu.version < 2 || cu.version > 5) {
FOLLY_SAFE_DFATAL("invalid info version: ", cu.version);
return false;
}
if (cu.version == 5) {
cu.unitType = read<uint8_t>(chunk);
if (cu.unitType != DW_UT_compile && cu.unitType != DW_UT_skeleton &&
cu.unitType != DW_UT_split_compile) {
return false;
}
cu.addrSize = read<uint8_t>(chunk);
if (cu.addrSize != sizeof(uintptr_t)) {
FOLLY_SAFE_DFATAL("invalid address size: ", cu.addrSize);
return false;
}
uint64_t abbrevOffset = readOffset(chunk, cu.is64Bit);
if (!cu.abbrevOffset.hasValue()) {
cu.abbrevOffset = abbrevOffset;
}
if (cu.unitType == DW_UT_skeleton || cu.unitType == DW_UT_split_compile) {
cu.dwoId = read<uint64_t>(chunk);
}
} else {
cu.unitType = DW_UT_compile;
uint64_t abbrevOffset = readOffset(chunk, cu.is64Bit);
if (!cu.abbrevOffset.hasValue()) {
cu.abbrevOffset = abbrevOffset;
}
cu.addrSize = read<uint8_t>(chunk);
if (cu.addrSize != sizeof(uintptr_t)) {
FOLLY_SAFE_DFATAL("invalid address size: ", cu.addrSize);
return false;
}
}
cu.firstDie = chunk.data() - debugInfo.data();
Die die = getDieAtOffset(cu, cu.firstDie);
if (die.abbr.tag != DW_TAG_compile_unit &&
die.abbr.tag != DW_TAG_skeleton_unit) {
return false;
}
forEachAttribute(cu, die, [&](const Attribute& attr) {
switch (attr.spec.name) {
case DW_AT_comp_dir:
cu.compDir = std::get<folly::StringPiece>(attr.attrValue);
break;
case DW_AT_addr_base:
case DW_AT_GNU_addr_base:
cu.addrBase = std::get<uint64_t>(attr.attrValue);
break;
case DW_AT_GNU_ranges_base:
cu.rangesBase = std::get<uint64_t>(attr.attrValue);
break;
case DW_AT_loclists_base:
cu.loclistsBase = std::get<uint64_t>(attr.attrValue);
break;
case DW_AT_rnglists_base:
cu.rnglistsBase = std::get<uint64_t>(attr.attrValue);
break;
case DW_AT_str_offsets_base:
cu.strOffsetsBase = std::get<uint64_t>(attr.attrValue);
break;
case DW_AT_GNU_dwo_name:
case DW_AT_dwo_name:
cu.dwoName = std::get<folly::StringPiece>(attr.attrValue);
break;
case DW_AT_GNU_dwo_id:
cu.dwoId = std::get<uint64_t>(attr.attrValue);
break;
}
return true;
});
return true;
}
}
CompilationUnits getCompilationUnits(
ElfCacheBase* elfCache,
const DebugSections& debugSections,
uint64_t offset,
bool requireSplitDwarf) {
const folly::StringPiece debugInfo = debugSections.debugInfo;
FOLLY_SAFE_DCHECK(offset < debugInfo.size(), "unexpected offset");
CompilationUnits cu;
cu.mainCompilationUnit.debugSections = debugSections;
if (!parseCompilationUnitMetadata(cu.mainCompilationUnit, offset)) {
return cu;
}
if (!requireSplitDwarf || !cu.mainCompilationUnit.dwoId.hasValue() ||
!cu.mainCompilationUnit.dwoName.hasValue()) {
cu.mainCompilationUnit.rangesBase.reset();
return cu;
}
CompilationUnit splitCU;
splitCU.addrBase = cu.mainCompilationUnit.addrBase;
splitCU.rangesBase = cu.mainCompilationUnit.rangesBase;
if (cu.mainCompilationUnit.version <= 4) {
cu.mainCompilationUnit.rangesBase.reset();
}
static const size_t kPathLimit = 4 << 10;
ElfFile* elf = nullptr;
{
char path[kPathLimit];
if (cu.mainCompilationUnit.compDir.size() + strlen("/") +
cu.mainCompilationUnit.dwoName->size() + 1 >
kPathLimit) {
return cu;
}
path[0] = '\0';
strncat(
path,
cu.mainCompilationUnit.compDir.data(),
cu.mainCompilationUnit.compDir.size());
strcat(path, "/");
strncat(
path,
cu.mainCompilationUnit.dwoName->data(),
cu.mainCompilationUnit.dwoName->size());
elf = elfCache->getFile(path).get();
}
bool useDWP = false;
if (elf == nullptr) {
if (strlen(cu.mainCompilationUnit.debugSections.elf->filepath()) + 5 >
kPathLimit) {
return cu;
}
char dwpPath[kPathLimit];
strcpy(dwpPath, cu.mainCompilationUnit.debugSections.elf->filepath());
strcat(dwpPath, ".dwp");
elf = elfCache->getFile(dwpPath).get();
if (elf == nullptr) {
return cu;
}
useDWP = true;
}
splitCU.debugSections = {
.elf = elf,
.debugCuIndex = getElfSection(elf, ".debug_cu_index"),
.debugAbbrev = getElfSection(elf, ".debug_abbrev.dwo"),
.debugAddr = cu.mainCompilationUnit.debugSections.debugAddr,
.debugAranges = cu.mainCompilationUnit.debugSections.debugAranges,
.debugInfo = getElfSection(elf, ".debug_info.dwo"),
.debugLine = getElfSection(elf, ".debug_line.dwo"),
.debugLineStr = cu.mainCompilationUnit.debugSections.debugLineStr,
.debugLoclists = getElfSection(elf, ".debug_loclists.dwo"),
.debugRanges = cu.mainCompilationUnit.debugSections.debugRanges,
.debugRnglists = getElfSection(elf, ".debug_rnglists.dwo"),
.debugStr = getElfSection(elf, ".debug_str.dwo"),
.debugStrOffsets = getElfSection(elf, ".debug_str_offsets.dwo")};
if (splitCU.debugSections.debugInfo.empty() ||
splitCU.debugSections.debugAbbrev.empty() ||
splitCU.debugSections.debugLine.empty() ||
splitCU.debugSections.debugStr.empty()) {
return cu;
}
if (useDWP) {
if (!findCompiliationOffset(
splitCU.debugSections.debugCuIndex,
*cu.mainCompilationUnit.dwoId,
splitCU) ||
!parseCompilationUnitMetadata(splitCU, splitCU.offset)) {
return cu;
}
if (cu.mainCompilationUnit.version == 5) {
splitCU.strOffsetsBase =
splitCU.strOffsetsBase.value_or(0) + (splitCU.is64Bit ? 16 : 8);
splitCU.rnglistsBase =
splitCU.rnglistsBase.value_or(0) + (splitCU.is64Bit ? 20 : 12);
}
} else {
for (size_t dwoOffset = 0;
!parseCompilationUnitMetadata(splitCU, dwoOffset) &&
dwoOffset < splitCU.debugSections.debugInfo.size();) {
dwoOffset = dwoOffset + splitCU.size;
}
if (!splitCU.dwoId || *splitCU.dwoId != *cu.mainCompilationUnit.dwoId) {
return cu;
}
if (cu.mainCompilationUnit.version == 5) {
splitCU.strOffsetsBase = splitCU.is64Bit ? 16 : 8;
splitCU.rnglistsBase = splitCU.is64Bit ? 20 : 12;
}
}
cu.splitCU.emplace(std::move(splitCU));
return cu;
}
Die getDieAtOffset(const CompilationUnit& cu, uint64_t offset) {
const folly::StringPiece debugInfo = cu.debugSections.debugInfo;
FOLLY_SAFE_DCHECK(offset < debugInfo.size(), "unexpected offset");
Die die;
folly::StringPiece sp = folly::StringPiece{
debugInfo.data() + offset, debugInfo.data() + cu.offset + cu.size};
die.offset = offset;
die.is64Bit = cu.is64Bit;
auto code = readULEB(sp);
die.code = code;
if (code == 0) {
return die;
}
die.attrOffset = sp.data() - debugInfo.data() - offset;
die.abbr = !cu.abbrCache.empty() && die.code < kMaxAbbreviationEntries
? cu.abbrCache[die.code - 1]
: getAbbreviation(
cu.debugSections.debugAbbrev,
die.code,
cu.abbrevOffset.value_or(0));
return die;
}
Attribute readAttribute(
const CompilationUnit& cu,
const Die& die,
AttributeSpec spec,
folly::StringPiece& info) {
auto getStringUsingOffsetTable = [&](uint64_t index) {
if (!cu.strOffsetsBase.has_value() &&
!(cu.version < 5 && cu.dwoId.has_value())) {
return folly::StringPiece();
}
auto strOffsetsOffset = cu.strOffsetsBase.value_or(0) +
index * (cu.is64Bit ? sizeof(uint64_t) : sizeof(uint32_t));
auto sp = cu.debugSections.debugStrOffsets.subpiece(strOffsetsOffset);
uint64_t strOffset = readOffset(sp, cu.is64Bit);
return getStringFromStringSection(cu.debugSections.debugStr, strOffset);
};
auto readDebugAddr = [&](uint64_t index) {
if (!cu.addrBase.has_value()) {
return uint64_t(0);
}
auto sp = cu.debugSections.debugAddr.subpiece(
*cu.addrBase + index * sizeof(uint64_t));
return read<uint64_t>(sp);
};
switch (spec.form) {
case DW_FORM_addr:
return {spec, die, read<uintptr_t>(info)};
case DW_FORM_block1:
return {spec, die, readBytes(info, read<uint8_t>(info))};
case DW_FORM_block2:
return {spec, die, readBytes(info, read<uint16_t>(info))};
case DW_FORM_block4:
return {spec, die, readBytes(info, read<uint32_t>(info))};
case DW_FORM_block:
[[fallthrough]];
case DW_FORM_exprloc:
return {spec, die, readBytes(info, readULEB(info))};
case DW_FORM_data1:
[[fallthrough]];
case DW_FORM_ref1:
return {spec, die, read<uint8_t>(info)};
case DW_FORM_data2:
[[fallthrough]];
case DW_FORM_ref2:
return {spec, die, read<uint16_t>(info)};
case DW_FORM_data4:
[[fallthrough]];
case DW_FORM_ref4:
return {spec, die, read<uint32_t>(info)};
case DW_FORM_data8:
[[fallthrough]];
case DW_FORM_ref8:
[[fallthrough]];
case DW_FORM_ref_sig8:
return {spec, die, read<uint64_t>(info)};
case DW_FORM_sdata:
return {spec, die, to_unsigned(readSLEB(info))};
case DW_FORM_udata:
[[fallthrough]];
case DW_FORM_ref_udata:
return {spec, die, readULEB(info)};
case DW_FORM_flag:
return {spec, die, read<uint8_t>(info)};
case DW_FORM_flag_present:
return {spec, die, 1u};
case DW_FORM_sec_offset:
[[fallthrough]];
case DW_FORM_ref_addr:
return {spec, die, readOffset(info, die.is64Bit)};
case DW_FORM_string:
return {spec, die, readNullTerminated(info)};
case DW_FORM_strp:
return {
spec,
die,
getStringFromStringSection(
cu.debugSections.debugStr, readOffset(info, die.is64Bit))};
case DW_FORM_indirect:
spec.form = readULEB(info);
return readAttribute(cu, die, spec, info);
case DW_FORM_implicit_const:
return {spec, die, to_unsigned(spec.implicitConst)};
case DW_FORM_addrx:
case DW_FORM_GNU_addr_index:
return {spec, die, readDebugAddr(readULEB(info))};
case DW_FORM_addrx1:
return {spec, die, readDebugAddr(readU64<1>(info))};
case DW_FORM_addrx2:
return {spec, die, readDebugAddr(readU64<2>(info))};
case DW_FORM_addrx3:
return {spec, die, readDebugAddr(readU64<3>(info))};
case DW_FORM_addrx4:
return {spec, die, readDebugAddr(readU64<4>(info))};
case DW_FORM_line_strp:
return {
spec,
die,
getStringFromStringSection(
cu.debugSections.debugLineStr, readOffset(info, die.is64Bit))};
case DW_FORM_strx:
return {spec, die, getStringUsingOffsetTable(readULEB(info))};
case DW_FORM_strx1:
return {spec, die, getStringUsingOffsetTable(readU64<1>(info))};
case DW_FORM_strx2:
return {spec, die, getStringUsingOffsetTable(readU64<2>(info))};
case DW_FORM_strx3:
return {spec, die, getStringUsingOffsetTable(readU64<3>(info))};
case DW_FORM_strx4:
return {spec, die, getStringUsingOffsetTable(readU64<4>(info))};
case DW_FORM_GNU_str_index:
return {spec, die, getStringUsingOffsetTable(readULEB(info))};
case DW_FORM_rnglistx: {
auto index = readULEB(info);
if (!cu.rnglistsBase.has_value()) {
return {spec, die, 0u};
}
const uint64_t offsetSize =
cu.is64Bit ? sizeof(uint64_t) : sizeof(uint32_t);
auto sp = cu.debugSections.debugRnglists.subpiece(
*cu.rnglistsBase + index * offsetSize);
auto offset = readOffset(sp, cu.is64Bit);
return {spec, die, *cu.rnglistsBase + offset};
} break;
case DW_FORM_loclistx: {
auto index = readULEB(info);
if (!cu.loclistsBase.has_value()) {
return {spec, die, 0u};
}
const uint64_t offsetSize =
cu.is64Bit ? sizeof(uint64_t) : sizeof(uint32_t);
auto sp = cu.debugSections.debugLoclists.subpiece(
*cu.loclistsBase + index * offsetSize);
auto offset = readOffset(sp, cu.is64Bit);
return {spec, die, *cu.loclistsBase + offset};
} break;
case DW_FORM_data16:
return {spec, die, readBytes(info, 16)};
case DW_FORM_ref_sup4:
case DW_FORM_ref_sup8:
case DW_FORM_strp_sup:
FOLLY_SAFE_DFATAL(
"Unexpected DWARF5 supplimentary object files: ", spec.form);
return {spec, die, 0u};
default:
FOLLY_SAFE_DFATAL("invalid attribute form: ", spec.form);
return {spec, die, 0u};
}
return {spec, die, 0u};
}
size_t forEachAttribute(
const CompilationUnit& cu,
const Die& die,
folly::FunctionRef<bool(const Attribute&)> f) {
auto attrs = die.abbr.attributes;
auto values = folly::StringPiece{
cu.debugSections.debugInfo.data() + die.offset + die.attrOffset,
cu.debugSections.debugInfo.data() + cu.offset + cu.size};
while (auto spec = readAttributeSpec(attrs)) {
auto attr = readAttribute(cu, die, spec, values);
if (!f(attr)) {
return static_cast<size_t>(-1);
}
}
return values.data() - cu.debugSections.debugInfo.data();
}
folly::StringPiece getFunctionNameFromDie(
const CompilationUnit& srcu, const Die& die) {
folly::StringPiece name;
forEachAttribute(srcu, die, [&](const Attribute& attr) {
switch (attr.spec.name) {
case DW_AT_linkage_name:
name = std::get<folly::StringPiece>(attr.attrValue);
break;
case DW_AT_name:
if (name.empty()) {
name = std::get<folly::StringPiece>(attr.attrValue);
}
break;
}
return true;
});
return name;
}
folly::StringPiece getFunctionName(
const CompilationUnit& srcu, uint64_t dieOffset) {
auto declDie = getDieAtOffset(srcu, dieOffset);
auto name = getFunctionNameFromDie(srcu, declDie);
return name.empty()
? getFunctionNameFromDie(srcu, findDefinitionDie(srcu, declDie))
: name;
}
Die findDefinitionDie(const CompilationUnit& cu, const Die& die) {
auto offset = getAttribute<uint64_t>(cu, die, DW_AT_specification);
if (!offset) {
return die;
}
return getDieAtOffset(cu, cu.offset + offset.value());
}
}
}
#endif