/*
- * Copyright 2012 Facebook, Inc.
+ * Copyright 2017-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.
* limitations under the License.
*/
-
-#include "folly/experimental/symbolizer/Dwarf.h"
+#include <folly/experimental/symbolizer/Dwarf.h>
#include <type_traits>
+#if FOLLY_HAVE_LIBDWARF_DWARF_H
+#include <libdwarf/dwarf.h>
+#else
#include <dwarf.h>
+#endif
namespace folly {
namespace symbolizer {
namespace {
// All following read* functions read from a StringPiece, advancing the
-// StringPiece, and throwing an exception if there's not enough room
+// StringPiece, and aborting if there's not enough room.
// Read (bitwise) one object of type T
template <class T>
typename std::enable_if<std::is_pod<T>::value, T>::type
read(folly::StringPiece& sp) {
- enforce(sp.size() >= sizeof(T), "underflow");
+ FOLLY_SAFE_CHECK(sp.size() >= sizeof(T), "underflow");
T x;
memcpy(&x, sp.data(), sizeof(T));
sp.advance(sizeof(T));
// Read "len" bytes
folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) {
- enforce(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;
folly::StringPiece readNullTerminated(folly::StringPiece& sp) {
const char* p = static_cast<const char*>(
memchr(sp.data(), 0, sp.size()));
- enforce(p, "invalid null-terminated string");
+ FOLLY_SAFE_CHECK(p, "invalid null-terminated string");
folly::StringPiece ret(sp.data(), p);
sp.assign(p + 1, sp.end());
return ret;
void skipPadding(folly::StringPiece& sp, const char* start, size_t alignment) {
size_t remainder = (sp.data() - start) % alignment;
if (remainder) {
- enforce(alignment - remainder <= sp.size(), "invalid padding");
+ FOLLY_SAFE_CHECK(alignment - remainder <= sp.size(), "invalid padding");
sp.advance(alignment - remainder);
}
}
-void stripSlashes(folly::StringPiece& sp, bool keepInitialSlash) {
- if (sp.empty()) {
- return;
- }
+// Simplify a path -- as much as we can while not moving data around...
+void simplifyPath(folly::StringPiece& sp) {
+ // Strip leading slashes and useless patterns (./), leaving one initial
+ // slash.
+ for (;;) {
+ if (sp.empty()) {
+ return;
+ }
- const char* p = sp.begin();
- for (; p != sp.end() && *p == '/'; ++p);
+ // Strip leading slashes, leaving one.
+ while (sp.startsWith("//")) {
+ sp.advance(1);
+ }
- const char* q = sp.end();
- for (; q != p && q[-1] == '/'; --q);
+ if (sp.startsWith("/./")) {
+ // Note 2, not 3, to keep it absolute
+ sp.advance(2);
+ continue;
+ }
- if (keepInitialSlash && p != sp.begin()) {
- --p;
+ if (sp.removePrefix("./")) {
+ // Also remove any subsequent slashes to avoid making this path absolute.
+ while (sp.startsWith('/')) {
+ sp.advance(1);
+ }
+ continue;
+ }
+
+ break;
}
- sp.assign(p, q);
+ // Strip trailing slashes and useless patterns (/.).
+ for (;;) {
+ if (sp.empty()) {
+ return;
+ }
+
+ // Strip trailing slashes, except when this is the root path.
+ while (sp.size() > 1 && sp.removeSuffix('/')) { }
+
+ if (sp.removeSuffix("/.")) {
+ continue;
+ }
+
+ break;
+ }
}
} // namespace
baseDir_.clear(); // subDir_ is absolute
}
- // Make sure that baseDir_ isn't empty; subDir_ may be
+ simplifyPath(baseDir_);
+ simplifyPath(subDir_);
+ simplifyPath(file_);
+
+ // Make sure it's never the case that baseDir_ is empty, but subDir_ isn't.
if (baseDir_.empty()) {
swap(baseDir_, subDir_);
}
-
- stripSlashes(baseDir_, true); // keep leading slash if it exists
- stripSlashes(subDir_, false);
- stripSlashes(file_, false);
}
size_t Dwarf::Path::size() const {
- return
- baseDir_.size() + !subDir_.empty() + subDir_.size() + !file_.empty() +
- file_.size();
+ size_t size = 0;
+ bool needsSlash = false;
+
+ if (!baseDir_.empty()) {
+ size += baseDir_.size();
+ needsSlash = !baseDir_.endsWith('/');
+ }
+
+ if (!subDir_.empty()) {
+ size += needsSlash;
+ size += subDir_.size();
+ needsSlash = !subDir_.endsWith('/');
+ }
+
+ if (!file_.empty()) {
+ size += needsSlash;
+ size += file_.size();
+ }
+
+ return size;
}
size_t Dwarf::Path::toBuffer(char* buf, size_t bufSize) const {
size_t totalSize = 0;
+ bool needsSlash = false;
auto append = [&] (folly::StringPiece sp) {
if (bufSize >= 2) {
if (!baseDir_.empty()) {
append(baseDir_);
+ needsSlash = !baseDir_.endsWith('/');
}
if (!subDir_.empty()) {
- assert(!baseDir_.empty());
- append("/");
+ if (needsSlash) {
+ append("/");
+ }
append(subDir_);
+ needsSlash = !subDir_.endsWith('/');
}
if (!file_.empty()) {
- append("/");
+ if (needsSlash) {
+ append("/");
+ }
append(file_);
}
if (bufSize) {
dest.append(baseDir_.begin(), baseDir_.end());
}
if (!subDir_.empty()) {
- assert(!baseDir_.empty());
- dest.push_back('/');
+ if (!dest.empty() && dest.back() != '/') {
+ dest.push_back('/');
+ }
dest.append(subDir_.begin(), subDir_.end());
}
if (!file_.empty()) {
- dest.push_back('/');
+ if (!dest.empty() && dest.back() != '/') {
+ dest.push_back('/');
+ }
dest.append(file_.begin(), file_.end());
}
assert(dest.size() == initialSize + size());
auto initialLength = read<uint32_t>(chunk);
is64Bit_ = (initialLength == (uint32_t)-1);
auto length = is64Bit_ ? read<uint64_t>(chunk) : initialLength;
- enforce(length <= chunk.size(), "invalid DWARF section");
+ FOLLY_SAFE_CHECK(length <= chunk.size(), "invalid DWARF section");
chunk.reset(chunk.data(), length);
data_.assign(chunk.end(), data_.end());
return true;
if (!elfSection) {
return false;
}
-
+#ifdef SHF_COMPRESSED
+ if (elfSection->sh_flags & SHF_COMPRESSED) {
+ return false;
+ }
+#endif
*section = elf_->getSectionBody(*elfSection);
return true;
}
// Make sure that all .debug_* sections exist
if (!getSection(".debug_info", &info_) ||
!getSection(".debug_abbrev", &abbrev_) ||
- !getSection(".debug_aranges", &aranges_) ||
!getSection(".debug_line", &line_) ||
!getSection(".debug_str", &strings_)) {
elf_ = nullptr;
return;
}
- getSection(".debug_str", &strings_);
+
+ // Optional: fast address range lookup. If missing .debug_info can
+ // be used - but it's much slower (linear scan).
+ getSection(".debug_aranges", &aranges_);
}
bool Dwarf::readAbbreviation(folly::StringPiece& section,
// attributes
const char* attributeBegin = section.data();
for (;;) {
- enforce(!section.empty(), "invalid attribute section");
+ FOLLY_SAFE_CHECK(!section.empty(), "invalid attribute section");
auto attr = readAttribute(section);
if (attr.name == 0 && attr.form == 0) {
break;
}
}
- throw std::runtime_error("could not find abbreviation code");
+ FOLLY_SAFE_CHECK(false, "could not find abbreviation code");
}
Dwarf::AttributeValue Dwarf::readAttributeValue(
case DW_FORM_indirect: // form is explicitly specified
return readAttributeValue(sp, readULEB(sp), is64Bit);
default:
- throw std::runtime_error("invalid attribute form");
+ FOLLY_SAFE_CHECK(false, "invalid attribute form");
}
}
folly::StringPiece Dwarf::getStringFromStringSection(uint64_t offset) const {
- enforce(offset < strings_.size(), "invalid strp offset");
+ FOLLY_SAFE_CHECK(offset < strings_.size(), "invalid strp offset");
folly::StringPiece sp(strings_);
sp.advance(offset);
return readNullTerminated(sp);
}
-bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
- locationInfo = LocationInfo();
-
- if (!elf_) { // no file
- return false;
- }
-
- // Find address range in .debug_aranges, map to compilation unit
- Section arangesSection(aranges_);
+/**
+ * Find @address in .debug_aranges and return the offset in
+ * .debug_info for compilation unit to which this address belongs.
+ */
+bool Dwarf::findDebugInfoOffset(uintptr_t address,
+ StringPiece aranges,
+ uint64_t& offset) {
+ Section arangesSection(aranges);
folly::StringPiece chunk;
- uint64_t debugInfoOffset;
- bool found = false;
- while (!found && arangesSection.next(chunk)) {
+ while (arangesSection.next(chunk)) {
auto version = read<uint16_t>(chunk);
- enforce(version == 2, "invalid aranges version");
+ FOLLY_SAFE_CHECK(version == 2, "invalid aranges version");
- debugInfoOffset = readOffset(chunk, arangesSection.is64Bit());
+ offset = readOffset(chunk, arangesSection.is64Bit());
auto addressSize = read<uint8_t>(chunk);
- enforce(addressSize == sizeof(uintptr_t), "invalid address size");
+ FOLLY_SAFE_CHECK(addressSize == sizeof(uintptr_t), "invalid address size");
auto segmentSize = read<uint8_t>(chunk);
- enforce(segmentSize == 0, "segmented architecture not supported");
+ FOLLY_SAFE_CHECK(segmentSize == 0, "segmented architecture not supported");
// Padded to a multiple of 2 addresses.
// Strangely enough, this is the only place in the DWARF spec that requires
// padding.
- skipPadding(chunk, aranges_.data(), 2 * sizeof(uintptr_t));
+ skipPadding(chunk, aranges.data(), 2 * sizeof(uintptr_t));
for (;;) {
auto start = read<uintptr_t>(chunk);
auto length = read<uintptr_t>(chunk);
- if (start == 0) {
+ if (start == 0 && length == 0) {
break;
}
// Is our address in this range?
if (address >= start && address < start + length) {
- found = true;
- break;
+ return true;
}
}
}
+ return false;
+}
- if (!found) {
- return false;
- }
-
- // Read compilation unit header from .debug_info
- folly::StringPiece sp(info_);
- sp.advance(debugInfoOffset);
- Section debugInfoSection(sp);
- enforce(debugInfoSection.next(chunk), "invalid debug info");
+/**
+ * Find the @locationInfo for @address in the compilation unit represented
+ * by the @sp .debug_info entry.
+ * Returns whether the address was found.
+ * Advances @sp to the next entry in .debug_info.
+ */
+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);
- enforce(version >= 2 && version <= 4, "invalid info version");
+ FOLLY_SAFE_CHECK(version >= 2 && version <= 4, "invalid info version");
uint64_t abbrevOffset = readOffset(chunk, debugInfoSection.is64Bit());
auto addressSize = read<uint8_t>(chunk);
- enforce(addressSize == sizeof(uintptr_t), "invalid address size");
+ 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
+ // 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);
- enforce(code != 0, "invalid code");
+ FOLLY_SAFE_CHECK(code != 0, "invalid code");
auto abbr = getAbbreviation(code, abbrevOffset);
- enforce(abbr.tag == DW_TAG_compile_unit, "expecting compile unit entry");
+ 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());
// Read attributes, extracting the few we care about
bool foundLineOffset = false;
locationInfo.mainFile = Path(compilationDirectory, "", mainFileName);
}
- if (foundLineOffset) {
- folly::StringPiece lineSection(line_);
- lineSection.advance(lineOffset);
- LineNumberVM lineVM(lineSection, compilationDirectory);
+ if (!foundLineOffset) {
+ return false;
+ }
+
+ folly::StringPiece lineSection(line_);
+ lineSection.advance(lineOffset);
+ LineNumberVM lineVM(lineSection, compilationDirectory);
- // Execute line number VM program to find file and line
- locationInfo.hasFileAndLine =
+ // Execute line number VM program to find file and line
+ locationInfo.hasFileAndLine =
lineVM.findAddress(address, locationInfo.file, locationInfo.line);
+ return locationInfo.hasFileAndLine;
+}
+
+bool Dwarf::findAddress(uintptr_t address,
+ LocationInfo& locationInfo,
+ LocationInfoMode mode) const {
+ locationInfo = LocationInfo();
+
+ if (mode == LocationInfoMode::DISABLED) {
+ return false;
}
- return true;
+ if (!elf_) { // No file.
+ return false;
+ }
+
+ if (!aranges_.empty()) {
+ // Fast path: find the right .debug_info entry by looking up the
+ // address in .debug_aranges.
+ uint64_t offset = 0;
+ if (findDebugInfoOffset(address, aranges_, offset)) {
+ // Read compilation unit header from .debug_info
+ folly::StringPiece infoEntry(info_);
+ infoEntry.advance(offset);
+ findLocation(address, infoEntry, locationInfo);
+ return locationInfo.hasFileAndLine;
+ } else if (mode == LocationInfoMode::FAST) {
+ // NOTE: Clang (when using -gdwarf-aranges) doesn't generate entries
+ // in .debug_aranges for some functions, but always generates
+ // .debug_info entries. Scanning .debug_info is slow, so fall back to
+ // it only if such behavior is requested via LocationInfoMode.
+ return false;
+ } else {
+ DCHECK(mode == LocationInfoMode::FULL);
+ // 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);
+ }
+ return locationInfo.hasFileAndLine;
}
Dwarf::LineNumberVM::LineNumberVM(folly::StringPiece data,
folly::StringPiece compilationDirectory)
: compilationDirectory_(compilationDirectory) {
Section section(data);
- enforce(section.next(data_), "invalid line number VM");
+ FOLLY_SAFE_CHECK(section.next(data_), "invalid line number VM");
is64Bit_ = section.is64Bit();
init();
reset();
void Dwarf::LineNumberVM::init() {
version_ = read<uint16_t>(data_);
- enforce(version_ >= 2 && version_ <= 4, "invalid version in line number VM");
+ FOLLY_SAFE_CHECK(version_ >= 2 && version_ <= 4,
+ "invalid version in line number VM");
uint64_t headerLength = readOffset(data_, is64Bit_);
- enforce(headerLength <= data_.size(),
- "invalid line number VM header length");
+ FOLLY_SAFE_CHECK(headerLength <= data_.size(),
+ "invalid line number VM header length");
folly::StringPiece header(data_.data(), headerLength);
data_.assign(header.end(), data_.end());
minLength_ = read<uint8_t>(header);
if (version_ == 4) { // Version 2 and 3 records don't have this
uint8_t maxOpsPerInstruction = read<uint8_t>(header);
- enforce(maxOpsPerInstruction == 1, "VLIW not supported");
+ FOLLY_SAFE_CHECK(maxOpsPerInstruction == 1, "VLIW not supported");
}
defaultIsStmt_ = read<uint8_t>(header);
lineBase_ = read<int8_t>(header); // yes, signed
lineRange_ = read<uint8_t>(header);
opcodeBase_ = read<uint8_t>(header);
- enforce(opcodeBase_ != 0, "invalid opcode base");
+ FOLLY_SAFE_CHECK(opcodeBase_ != 0, "invalid opcode base");
standardOpcodeLengths_ = reinterpret_cast<const uint8_t*>(header.data());
header.advance(opcodeBase_ - 1);
Dwarf::LineNumberVM::FileName Dwarf::LineNumberVM::getFileName(uint64_t index)
const {
- enforce(index != 0, "invalid file index 0");
+ FOLLY_SAFE_CHECK(index != 0, "invalid file index 0");
FileName fn;
if (index <= fileNameCount_) {
folly::StringPiece program = data_;
for (; index; --index) {
- enforce(nextDefineFile(program, fn), "invalid file index");
+ FOLLY_SAFE_CHECK(nextDefineFile(program, fn), "invalid file index");
}
return fn;
return folly::StringPiece();
}
- enforce(index <= includeDirectoryCount_, "invalid include directory");
+ FOLLY_SAFE_CHECK(index <= includeDirectoryCount_,
+ "invalid include directory");
folly::StringPiece includeDirectories = includeDirectories_;
folly::StringPiece dir;
// Extended opcode
auto length = readULEB(program);
// the opcode itself should be included in the length, so length >= 1
- enforce(length != 0, "invalid extended opcode length");
- auto extendedOpcode = read<uint8_t>(program);
+ FOLLY_SAFE_CHECK(length != 0, "invalid extended opcode length");
+ read<uint8_t>(program); // extended opcode
--length;
if (opcode == DW_LNE_define_file) {
- enforce(readFileName(program, fn),
- "invalid empty file in DW_LNE_define_file");
+ FOLLY_SAFE_CHECK(readFileName(program, fn),
+ "invalid empty file in DW_LNE_define_file");
return true;
}
// Extended opcode
auto length = readULEB(program);
// the opcode itself should be included in the length, so length >= 1
- enforce(length != 0, "invalid extende opcode length");
+ FOLLY_SAFE_CHECK(length != 0, "invalid extended opcode length");
auto extendedOpcode = read<uint8_t>(program);
--length;
} // namespace symbolizer
} // namespace folly
-