Commit fd3c895f authored by Tudor Bosman's avatar Tudor Bosman Committed by Jordan DeLong

Async-signal-safe symbolizer, fatal signal handler

Test Plan: test added

Reviewed By: lucian@fb.com

FB internal diff: D1076170

@override-unit-failures
parent 35dc92a7
...@@ -54,15 +54,19 @@ std::ostream& operator<<(std::ostream& out, const ExceptionInfo& info) { ...@@ -54,15 +54,19 @@ std::ostream& operator<<(std::ostream& out, const ExceptionInfo& info) {
<< (info.frames.size() == 1 ? " frame" : " frames") << (info.frames.size() == 1 ? " frame" : " frames")
<< ")\n"; << ")\n";
try { try {
Symbolizer symbolizer; std::vector<AddressInfo> addresses;
folly::StringPiece symbolName; addresses.reserve(info.frames.size());
Dwarf::LocationInfo location;
for (auto ip : info.frames) { for (auto ip : info.frames) {
// Symbolize the previous address because the IP might be in the // Symbolize the previous address because the IP might be in the
// next function, per glog/src/signalhandler.cc // next function, per glog/src/signalhandler.cc
symbolizer.symbolize(ip-1, symbolName, location); addresses.emplace_back(ip - 1);
Symbolizer::write(out, ip, symbolName, location);
} }
Symbolizer symbolizer;
symbolizer.symbolize(addresses.data(), addresses.size());
OStreamSymbolizePrinter osp(out);
osp.print(addresses.data(), addresses.size());
} catch (const std::exception& e) { } catch (const std::exception& e) {
out << "\n !! caught " << folly::exceptionStr(e) << "\n"; out << "\n !! caught " << folly::exceptionStr(e) << "\n";
} catch (...) { } catch (...) {
......
...@@ -34,13 +34,13 @@ Dwarf::Section::Section(folly::StringPiece d) : is64Bit_(false), data_(d) { ...@@ -34,13 +34,13 @@ Dwarf::Section::Section(folly::StringPiece d) : is64Bit_(false), data_(d) {
namespace { namespace {
// All following read* functions read from a StringPiece, advancing the // 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 // Read (bitwise) one object of type T
template <class T> template <class T>
typename std::enable_if<std::is_pod<T>::value, T>::type typename std::enable_if<std::is_pod<T>::value, T>::type
read(folly::StringPiece& sp) { read(folly::StringPiece& sp) {
enforce(sp.size() >= sizeof(T), "underflow"); FOLLY_SAFE_CHECK(sp.size() >= sizeof(T), "underflow");
T x; T x;
memcpy(&x, sp.data(), sizeof(T)); memcpy(&x, sp.data(), sizeof(T));
sp.advance(sizeof(T)); sp.advance(sizeof(T));
...@@ -85,7 +85,7 @@ uint64_t readOffset(folly::StringPiece& sp, bool is64Bit) { ...@@ -85,7 +85,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) {
enforce(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;
...@@ -95,7 +95,7 @@ folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) { ...@@ -95,7 +95,7 @@ folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) {
folly::StringPiece readNullTerminated(folly::StringPiece& sp) { folly::StringPiece readNullTerminated(folly::StringPiece& sp) {
const char* p = static_cast<const char*>( const char* p = static_cast<const char*>(
memchr(sp.data(), 0, sp.size())); 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); folly::StringPiece ret(sp.data(), p);
sp.assign(p + 1, sp.end()); sp.assign(p + 1, sp.end());
return ret; return ret;
...@@ -105,7 +105,7 @@ folly::StringPiece readNullTerminated(folly::StringPiece& sp) { ...@@ -105,7 +105,7 @@ folly::StringPiece readNullTerminated(folly::StringPiece& sp) {
void skipPadding(folly::StringPiece& sp, const char* start, size_t alignment) { void skipPadding(folly::StringPiece& sp, const char* start, size_t alignment) {
size_t remainder = (sp.data() - start) % alignment; size_t remainder = (sp.data() - start) % alignment;
if (remainder) { if (remainder) {
enforce(alignment - remainder <= sp.size(), "invalid padding"); FOLLY_SAFE_CHECK(alignment - remainder <= sp.size(), "invalid padding");
sp.advance(alignment - remainder); sp.advance(alignment - remainder);
} }
} }
...@@ -233,7 +233,7 @@ bool Dwarf::Section::next(folly::StringPiece& chunk) { ...@@ -233,7 +233,7 @@ bool Dwarf::Section::next(folly::StringPiece& chunk) {
auto initialLength = read<uint32_t>(chunk); auto initialLength = read<uint32_t>(chunk);
is64Bit_ = (initialLength == (uint32_t)-1); is64Bit_ = (initialLength == (uint32_t)-1);
auto length = is64Bit_ ? read<uint64_t>(chunk) : initialLength; 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); chunk.reset(chunk.data(), length);
data_.assign(chunk.end(), data_.end()); data_.assign(chunk.end(), data_.end());
return true; return true;
...@@ -279,7 +279,7 @@ bool Dwarf::readAbbreviation(folly::StringPiece& section, ...@@ -279,7 +279,7 @@ bool Dwarf::readAbbreviation(folly::StringPiece& section,
// attributes // attributes
const char* attributeBegin = section.data(); const char* attributeBegin = section.data();
for (;;) { for (;;) {
enforce(!section.empty(), "invalid attribute section"); FOLLY_SAFE_CHECK(!section.empty(), "invalid attribute section");
auto attr = readAttribute(section); auto attr = readAttribute(section);
if (attr.name == 0 && attr.form == 0) { if (attr.name == 0 && attr.form == 0) {
break; break;
...@@ -308,7 +308,7 @@ Dwarf::DIEAbbreviation Dwarf::getAbbreviation(uint64_t code, uint64_t offset) ...@@ -308,7 +308,7 @@ Dwarf::DIEAbbreviation Dwarf::getAbbreviation(uint64_t code, uint64_t offset)
} }
} }
throw std::runtime_error("could not find abbreviation code"); FOLLY_SAFE_CHECK(false, "could not find abbreviation code");
} }
Dwarf::AttributeValue Dwarf::readAttributeValue( Dwarf::AttributeValue Dwarf::readAttributeValue(
...@@ -356,12 +356,12 @@ Dwarf::AttributeValue Dwarf::readAttributeValue( ...@@ -356,12 +356,12 @@ Dwarf::AttributeValue Dwarf::readAttributeValue(
case DW_FORM_indirect: // form is explicitly specified case DW_FORM_indirect: // form is explicitly specified
return readAttributeValue(sp, readULEB(sp), is64Bit); return readAttributeValue(sp, readULEB(sp), is64Bit);
default: default:
throw std::runtime_error("invalid attribute form"); FOLLY_SAFE_CHECK(false, "invalid attribute form");
} }
} }
folly::StringPiece Dwarf::getStringFromStringSection(uint64_t offset) const { 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_); folly::StringPiece sp(strings_);
sp.advance(offset); sp.advance(offset);
return readNullTerminated(sp); return readNullTerminated(sp);
...@@ -381,13 +381,13 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const { ...@@ -381,13 +381,13 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
bool found = false; bool found = false;
while (!found && arangesSection.next(chunk)) { while (!found && arangesSection.next(chunk)) {
auto version = read<uint16_t>(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()); debugInfoOffset = readOffset(chunk, arangesSection.is64Bit());
auto addressSize = read<uint8_t>(chunk); 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); 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. // Padded to a multiple of 2 addresses.
// Strangely enough, this is the only place in the DWARF spec that requires // Strangely enough, this is the only place in the DWARF spec that requires
...@@ -417,21 +417,22 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const { ...@@ -417,21 +417,22 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const {
folly::StringPiece sp(info_); folly::StringPiece sp(info_);
sp.advance(debugInfoOffset); sp.advance(debugInfoOffset);
Section debugInfoSection(sp); Section debugInfoSection(sp);
enforce(debugInfoSection.next(chunk), "invalid debug info"); FOLLY_SAFE_CHECK(debugInfoSection.next(chunk), "invalid debug info");
auto version = read<uint16_t>(chunk); 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()); uint64_t abbrevOffset = readOffset(chunk, debugInfoSection.is64Bit());
auto addressSize = read<uint8_t>(chunk); 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 // We survived so far. The first (and only) DIE should be
// DW_TAG_compile_unit // DW_TAG_compile_unit
// TODO(tudorb): Handle DW_TAG_partial_unit? // TODO(tudorb): Handle DW_TAG_partial_unit?
auto code = readULEB(chunk); auto code = readULEB(chunk);
enforce(code != 0, "invalid code"); FOLLY_SAFE_CHECK(code != 0, "invalid code");
auto abbr = getAbbreviation(code, abbrevOffset); 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");
// Read attributes, extracting the few we care about // Read attributes, extracting the few we care about
bool foundLineOffset = false; bool foundLineOffset = false;
...@@ -488,7 +489,7 @@ Dwarf::LineNumberVM::LineNumberVM(folly::StringPiece data, ...@@ -488,7 +489,7 @@ Dwarf::LineNumberVM::LineNumberVM(folly::StringPiece data,
folly::StringPiece compilationDirectory) folly::StringPiece compilationDirectory)
: compilationDirectory_(compilationDirectory) { : compilationDirectory_(compilationDirectory) {
Section section(data); Section section(data);
enforce(section.next(data_), "invalid line number VM"); FOLLY_SAFE_CHECK(section.next(data_), "invalid line number VM");
is64Bit_ = section.is64Bit(); is64Bit_ = section.is64Bit();
init(); init();
reset(); reset();
...@@ -510,23 +511,24 @@ void Dwarf::LineNumberVM::reset() { ...@@ -510,23 +511,24 @@ void Dwarf::LineNumberVM::reset() {
void Dwarf::LineNumberVM::init() { void Dwarf::LineNumberVM::init() {
version_ = read<uint16_t>(data_); 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_); uint64_t headerLength = readOffset(data_, is64Bit_);
enforce(headerLength <= data_.size(), FOLLY_SAFE_CHECK(headerLength <= data_.size(),
"invalid line number VM header length"); "invalid line number VM header length");
folly::StringPiece header(data_.data(), headerLength); folly::StringPiece header(data_.data(), headerLength);
data_.assign(header.end(), data_.end()); data_.assign(header.end(), data_.end());
minLength_ = read<uint8_t>(header); minLength_ = read<uint8_t>(header);
if (version_ == 4) { // Version 2 and 3 records don't have this if (version_ == 4) { // Version 2 and 3 records don't have this
uint8_t maxOpsPerInstruction = read<uint8_t>(header); 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); defaultIsStmt_ = read<uint8_t>(header);
lineBase_ = read<int8_t>(header); // yes, signed lineBase_ = read<int8_t>(header); // yes, signed
lineRange_ = read<uint8_t>(header); lineRange_ = read<uint8_t>(header);
opcodeBase_ = 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()); standardOpcodeLengths_ = reinterpret_cast<const uint8_t*>(header.data());
header.advance(opcodeBase_ - 1); header.advance(opcodeBase_ - 1);
...@@ -561,7 +563,7 @@ bool Dwarf::LineNumberVM::next(folly::StringPiece& program) { ...@@ -561,7 +563,7 @@ bool Dwarf::LineNumberVM::next(folly::StringPiece& program) {
Dwarf::LineNumberVM::FileName Dwarf::LineNumberVM::getFileName(uint64_t index) Dwarf::LineNumberVM::FileName Dwarf::LineNumberVM::getFileName(uint64_t index)
const { const {
enforce(index != 0, "invalid file index 0"); FOLLY_SAFE_CHECK(index != 0, "invalid file index 0");
FileName fn; FileName fn;
if (index <= fileNameCount_) { if (index <= fileNameCount_) {
...@@ -578,7 +580,7 @@ Dwarf::LineNumberVM::FileName Dwarf::LineNumberVM::getFileName(uint64_t index) ...@@ -578,7 +580,7 @@ Dwarf::LineNumberVM::FileName Dwarf::LineNumberVM::getFileName(uint64_t index)
folly::StringPiece program = data_; folly::StringPiece program = data_;
for (; index; --index) { for (; index; --index) {
enforce(nextDefineFile(program, fn), "invalid file index"); FOLLY_SAFE_CHECK(nextDefineFile(program, fn), "invalid file index");
} }
return fn; return fn;
...@@ -590,7 +592,8 @@ folly::StringPiece Dwarf::LineNumberVM::getIncludeDirectory(uint64_t index) ...@@ -590,7 +592,8 @@ folly::StringPiece Dwarf::LineNumberVM::getIncludeDirectory(uint64_t index)
return folly::StringPiece(); return folly::StringPiece();
} }
enforce(index <= includeDirectoryCount_, "invalid include directory"); FOLLY_SAFE_CHECK(index <= includeDirectoryCount_,
"invalid include directory");
folly::StringPiece includeDirectories = includeDirectories_; folly::StringPiece includeDirectories = includeDirectories_;
folly::StringPiece dir; folly::StringPiece dir;
...@@ -638,13 +641,13 @@ bool Dwarf::LineNumberVM::nextDefineFile(folly::StringPiece& program, ...@@ -638,13 +641,13 @@ bool Dwarf::LineNumberVM::nextDefineFile(folly::StringPiece& program,
// Extended opcode // Extended opcode
auto length = readULEB(program); auto length = readULEB(program);
// the opcode itself should be included in the length, so length >= 1 // the opcode itself should be included in the length, so length >= 1
enforce(length != 0, "invalid extended opcode length"); FOLLY_SAFE_CHECK(length != 0, "invalid extended opcode length");
read<uint8_t>(program); // extended opcode read<uint8_t>(program); // extended opcode
--length; --length;
if (opcode == DW_LNE_define_file) { if (opcode == DW_LNE_define_file) {
enforce(readFileName(program, fn), FOLLY_SAFE_CHECK(readFileName(program, fn),
"invalid empty file in DW_LNE_define_file"); "invalid empty file in DW_LNE_define_file");
return true; return true;
} }
...@@ -733,7 +736,7 @@ Dwarf::LineNumberVM::StepResult Dwarf::LineNumberVM::step( ...@@ -733,7 +736,7 @@ Dwarf::LineNumberVM::StepResult Dwarf::LineNumberVM::step(
// Extended opcode // Extended opcode
auto length = readULEB(program); auto length = readULEB(program);
// the opcode itself should be included in the length, so length >= 1 // 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); auto extendedOpcode = read<uint8_t>(program);
--length; --length;
......
...@@ -41,9 +41,8 @@ namespace symbolizer { ...@@ -41,9 +41,8 @@ namespace symbolizer {
* actually support many of the version 4 features (such as VLIW, multiple * actually support many of the version 4 features (such as VLIW, multiple
* operations per instruction) * operations per instruction)
* *
* Note that the DWARF record parser does not allocate heap memory at all * Note that the DWARF record parser does not allocate heap memory at all.
* during normal operation (it might in the error case, as throwing exceptions * This is on purpose: you can use the parser from
* uses the heap). This is on purpose: you can use the parser from
* memory-constrained situations (such as an exception handler for * memory-constrained situations (such as an exception handler for
* std::out_of_memory) If it weren't for this requirement, some things would * std::out_of_memory) If it weren't for this requirement, some things would
* be much simpler: the Path class would be unnecessary and would be replaced * be much simpler: the Path class would be unnecessary and would be replaced
......
...@@ -62,8 +62,8 @@ const char* ElfFile::iterateStrings(const ElfW(Shdr)& stringTable, Fn fn) ...@@ -62,8 +62,8 @@ const char* ElfFile::iterateStrings(const ElfW(Shdr)& stringTable, Fn fn)
template <class Fn> template <class Fn>
const ElfW(Sym)* ElfFile::iterateSymbols(const ElfW(Shdr)& section, Fn fn) const ElfW(Sym)* ElfFile::iterateSymbols(const ElfW(Shdr)& section, Fn fn)
const { const {
enforce(section.sh_entsize == sizeof(ElfW(Sym)), FOLLY_SAFE_CHECK(section.sh_entsize == sizeof(ElfW(Sym)),
"invalid entry size in symbol table"); "invalid entry size in symbol table");
const ElfW(Sym)* sym = &at<ElfW(Sym)>(section.sh_offset); const ElfW(Sym)* sym = &at<ElfW(Sym)>(section.sh_offset);
const ElfW(Sym)* end = sym + (section.sh_size / section.sh_entsize); const ElfW(Sym)* end = sym + (section.sh_size / section.sh_entsize);
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
namespace folly { namespace folly {
namespace symbolizer { namespace symbolizer {
ElfFile::ElfFile() ElfFile::ElfFile() noexcept
: fd_(-1), : fd_(-1),
file_(static_cast<char*>(MAP_FAILED)), file_(static_cast<char*>(MAP_FAILED)),
length_(0), length_(0),
...@@ -40,18 +40,33 @@ ElfFile::ElfFile() ...@@ -40,18 +40,33 @@ ElfFile::ElfFile()
} }
ElfFile::ElfFile(const char* name, bool readOnly) ElfFile::ElfFile(const char* name, bool readOnly)
: fd_(open(name, (readOnly) ? O_RDONLY : O_RDWR)), : fd_(-1),
file_(static_cast<char*>(MAP_FAILED)), file_(static_cast<char*>(MAP_FAILED)),
length_(0), length_(0),
baseAddress_(0) { baseAddress_(0) {
open(name, readOnly);
}
void ElfFile::open(const char* name, bool readOnly) {
const char* msg = "";
int r = openNoThrow(name, readOnly, &msg);
folly::checkUnixError(r, msg);
}
int ElfFile::openNoThrow(const char* name, bool readOnly, const char** msg)
noexcept {
FOLLY_SAFE_CHECK(fd_ == -1, "File already open");
fd_ = ::open(name, readOnly ? O_RDONLY : O_RDWR);
if (fd_ == -1) { if (fd_ == -1) {
folly::throwSystemError("open ", name); if (msg) *msg = "open";
return -1;
} }
struct stat st; struct stat st;
int r = fstat(fd_, &st); int r = fstat(fd_, &st);
if (r == -1) { if (r == -1) {
folly::throwSystemError("fstat"); if (msg) *msg = "fstat";
return -1;
} }
length_ = st.st_size; length_ = st.st_size;
...@@ -61,9 +76,11 @@ ElfFile::ElfFile(const char* name, bool readOnly) ...@@ -61,9 +76,11 @@ ElfFile::ElfFile(const char* name, bool readOnly)
} }
file_ = static_cast<char*>(mmap(nullptr, length_, prot, MAP_SHARED, fd_, 0)); file_ = static_cast<char*>(mmap(nullptr, length_, prot, MAP_SHARED, fd_, 0));
if (file_ == MAP_FAILED) { if (file_ == MAP_FAILED) {
folly::throwSystemError("mmap"); if (msg) *msg = "mmap";
return -1;
} }
init(); init();
return 0;
} }
ElfFile::~ElfFile() { ElfFile::~ElfFile() {
...@@ -112,18 +129,18 @@ void ElfFile::init() { ...@@ -112,18 +129,18 @@ void ElfFile::init() {
auto& elfHeader = this->elfHeader(); auto& elfHeader = this->elfHeader();
// Validate ELF magic numbers // Validate ELF magic numbers
enforce(elfHeader.e_ident[EI_MAG0] == ELFMAG0 && FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_MAG0] == ELFMAG0 &&
elfHeader.e_ident[EI_MAG1] == ELFMAG1 && elfHeader.e_ident[EI_MAG1] == ELFMAG1 &&
elfHeader.e_ident[EI_MAG2] == ELFMAG2 && elfHeader.e_ident[EI_MAG2] == ELFMAG2 &&
elfHeader.e_ident[EI_MAG3] == ELFMAG3, elfHeader.e_ident[EI_MAG3] == ELFMAG3,
"invalid ELF magic"); "invalid ELF magic");
// Validate ELF class (32/64 bits) // Validate ELF class (32/64 bits)
#define EXPECTED_CLASS P1(ELFCLASS, __ELF_NATIVE_CLASS) #define EXPECTED_CLASS P1(ELFCLASS, __ELF_NATIVE_CLASS)
#define P1(a, b) P2(a, b) #define P1(a, b) P2(a, b)
#define P2(a, b) a ## b #define P2(a, b) a ## b
enforce(elfHeader.e_ident[EI_CLASS] == EXPECTED_CLASS, FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_CLASS] == EXPECTED_CLASS,
"invalid ELF class"); "invalid ELF class");
#undef P1 #undef P1
#undef P2 #undef P2
#undef EXPECTED_CLASS #undef EXPECTED_CLASS
...@@ -136,24 +153,24 @@ void ElfFile::init() { ...@@ -136,24 +153,24 @@ void ElfFile::init() {
#else #else
# error Unsupported byte order # error Unsupported byte order
#endif #endif
enforce(elfHeader.e_ident[EI_DATA] == EXPECTED_ENCODING, FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_DATA] == EXPECTED_ENCODING,
"invalid ELF encoding"); "invalid ELF encoding");
#undef EXPECTED_ENCODING #undef EXPECTED_ENCODING
// Validate ELF version (1) // Validate ELF version (1)
enforce(elfHeader.e_ident[EI_VERSION] == EV_CURRENT && FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_VERSION] == EV_CURRENT &&
elfHeader.e_version == EV_CURRENT, elfHeader.e_version == EV_CURRENT,
"invalid ELF version"); "invalid ELF version");
// We only support executable and shared object files // We only support executable and shared object files
enforce(elfHeader.e_type == ET_EXEC || elfHeader.e_type == ET_DYN, FOLLY_SAFE_CHECK(elfHeader.e_type == ET_EXEC || elfHeader.e_type == ET_DYN,
"invalid ELF file type"); "invalid ELF file type");
enforce(elfHeader.e_phnum != 0, "no program header!"); FOLLY_SAFE_CHECK(elfHeader.e_phnum != 0, "no program header!");
enforce(elfHeader.e_phentsize == sizeof(ElfW(Phdr)), FOLLY_SAFE_CHECK(elfHeader.e_phentsize == sizeof(ElfW(Phdr)),
"invalid program header entry size"); "invalid program header entry size");
enforce(elfHeader.e_shentsize == sizeof(ElfW(Shdr)), FOLLY_SAFE_CHECK(elfHeader.e_shentsize == sizeof(ElfW(Shdr)),
"invalid section header entry size"); "invalid section header entry size");
const ElfW(Phdr)* programHeader = &at<ElfW(Phdr)>(elfHeader.e_phoff); const ElfW(Phdr)* programHeader = &at<ElfW(Phdr)>(elfHeader.e_phoff);
bool foundBase = false; bool foundBase = false;
...@@ -167,11 +184,11 @@ void ElfFile::init() { ...@@ -167,11 +184,11 @@ void ElfFile::init() {
} }
} }
enforce(foundBase, "could not find base address"); FOLLY_SAFE_CHECK(foundBase, "could not find base address");
} }
const ElfW(Shdr)* ElfFile::getSectionByIndex(size_t idx) const { const ElfW(Shdr)* ElfFile::getSectionByIndex(size_t idx) const {
enforce(idx < elfHeader().e_shnum, "invalid section index"); FOLLY_SAFE_CHECK(idx < elfHeader().e_shnum, "invalid section index");
return &at<ElfW(Shdr)>(elfHeader().e_shoff + idx * sizeof(ElfW(Shdr))); return &at<ElfW(Shdr)>(elfHeader().e_shoff + idx * sizeof(ElfW(Shdr)));
} }
...@@ -180,19 +197,21 @@ folly::StringPiece ElfFile::getSectionBody(const ElfW(Shdr)& section) const { ...@@ -180,19 +197,21 @@ folly::StringPiece ElfFile::getSectionBody(const ElfW(Shdr)& section) const {
} }
void ElfFile::validateStringTable(const ElfW(Shdr)& stringTable) const { void ElfFile::validateStringTable(const ElfW(Shdr)& stringTable) const {
enforce(stringTable.sh_type == SHT_STRTAB, "invalid type for string table"); FOLLY_SAFE_CHECK(stringTable.sh_type == SHT_STRTAB,
"invalid type for string table");
const char* start = file_ + stringTable.sh_offset; const char* start = file_ + stringTable.sh_offset;
// First and last bytes must be 0 // First and last bytes must be 0
enforce(stringTable.sh_size == 0 || FOLLY_SAFE_CHECK(stringTable.sh_size == 0 ||
(start[0] == '\0' && start[stringTable.sh_size - 1] == '\0'), (start[0] == '\0' && start[stringTable.sh_size - 1] == '\0'),
"invalid string table"); "invalid string table");
} }
const char* ElfFile::getString(const ElfW(Shdr)& stringTable, size_t offset) const char* ElfFile::getString(const ElfW(Shdr)& stringTable, size_t offset)
const { const {
validateStringTable(stringTable); validateStringTable(stringTable);
enforce(offset < stringTable.sh_size, "invalid offset in string table"); FOLLY_SAFE_CHECK(offset < stringTable.sh_size,
"invalid offset in string table");
return file_ + stringTable.sh_offset + offset; return file_ + stringTable.sh_offset + offset;
} }
......
...@@ -26,20 +26,14 @@ ...@@ -26,20 +26,14 @@
#include <stdexcept> #include <stdexcept>
#include <system_error> #include <system_error>
#include "folly/Conv.h"
#include "folly/Likely.h" #include "folly/Likely.h"
#include "folly/Range.h" #include "folly/Range.h"
#include "folly/Conv.h" #include "folly/SafeAssert.h"
namespace folly { namespace folly {
namespace symbolizer { namespace symbolizer {
template <class... Args>
inline void enforce(bool v, Args... args) {
if (UNLIKELY(!v)) {
throw std::runtime_error(folly::to<std::string>(args...));
}
}
/** /**
* ELF file parser. * ELF file parser.
* *
...@@ -49,8 +43,20 @@ inline void enforce(bool v, Args... args) { ...@@ -49,8 +43,20 @@ inline void enforce(bool v, Args... args) {
*/ */
class ElfFile { class ElfFile {
public: public:
ElfFile(); ElfFile() noexcept;
// Note: may throw, call openNoThrow() explicitly if you don't want to throw
explicit ElfFile(const char* name, bool readOnly=true); explicit ElfFile(const char* name, bool readOnly=true);
// Open the ELF file.
// Returns 0 on success, -1 (and sets errno) on failure and (if msg is not
// NULL) sets *msg to a static string indicating what failed.
int openNoThrow(const char* name, bool readOnly=true,
const char** msg=nullptr) noexcept;
// Open the ELF file. Throws on error.
void open(const char* name, bool readOnly=true);
~ElfFile(); ~ElfFile();
ElfFile(ElfFile&& other); ElfFile(ElfFile&& other);
...@@ -124,6 +130,8 @@ class ElfFile { ...@@ -124,6 +130,8 @@ class ElfFile {
* Find symbol definition by address. * Find symbol definition by address.
* Note that this is the file virtual address, so you need to undo * Note that this is the file virtual address, so you need to undo
* any relocation that might have happened. * any relocation that might have happened.
*
* Returns {nullptr, nullptr} if not found.
*/ */
typedef std::pair<const ElfW(Shdr)*, const ElfW(Sym)*> Symbol; typedef std::pair<const ElfW(Shdr)*, const ElfW(Sym)*> Symbol;
Symbol getDefinitionByAddress(uintptr_t address) const; Symbol getDefinitionByAddress(uintptr_t address) const;
...@@ -133,6 +141,8 @@ class ElfFile { ...@@ -133,6 +141,8 @@ class ElfFile {
* *
* If a symbol with this name cannot be found, a <nullptr, nullptr> Symbol * If a symbol with this name cannot be found, a <nullptr, nullptr> Symbol
* will be returned. This is O(N) in the number of symbols in the file. * will be returned. This is O(N) in the number of symbols in the file.
*
* Returns {nullptr, nullptr} if not found.
*/ */
Symbol getSymbolByName(const char* name) const; Symbol getSymbolByName(const char* name) const;
...@@ -142,7 +152,7 @@ class ElfFile { ...@@ -142,7 +152,7 @@ class ElfFile {
template <class T> template <class T>
const T& getSymbolValue(const ElfW(Sym)* symbol) const { const T& getSymbolValue(const ElfW(Sym)* symbol) const {
const ElfW(Shdr)* section = getSectionByIndex(symbol->st_shndx); const ElfW(Shdr)* section = getSectionByIndex(symbol->st_shndx);
enforce(section, "Symbol's section index is invalid"); FOLLY_SAFE_CHECK(section, "Symbol's section index is invalid");
return valueAt<T>(*section, symbol->st_value); return valueAt<T>(*section, symbol->st_value);
} }
...@@ -161,7 +171,7 @@ class ElfFile { ...@@ -161,7 +171,7 @@ class ElfFile {
template <class T> template <class T>
const T& getAddressValue(const ElfW(Addr) addr) const { const T& getAddressValue(const ElfW(Addr) addr) const {
const ElfW(Shdr)* section = getSectionContainingAddress(addr); const ElfW(Shdr)* section = getSectionContainingAddress(addr);
enforce(section, "Address does not refer to existing section"); FOLLY_SAFE_CHECK(section, "Address does not refer to existing section");
return valueAt<T>(*section, addr); return valueAt<T>(*section, addr);
} }
...@@ -185,8 +195,8 @@ class ElfFile { ...@@ -185,8 +195,8 @@ class ElfFile {
template <class T> template <class T>
const typename std::enable_if<std::is_pod<T>::value, T>::type& const typename std::enable_if<std::is_pod<T>::value, T>::type&
at(ElfW(Off) offset) const { at(ElfW(Off) offset) const {
enforce(offset + sizeof(T) <= length_, FOLLY_SAFE_CHECK(offset + sizeof(T) <= length_,
"Offset is not contained within our mmapped file"); "Offset is not contained within our mmapped file");
return *reinterpret_cast<T*>(file_ + offset); return *reinterpret_cast<T*>(file_ + offset);
} }
...@@ -202,11 +212,13 @@ class ElfFile { ...@@ -202,11 +212,13 @@ class ElfFile {
// TODO: For other file types, st_value holds a file offset directly. Since // TODO: For other file types, st_value holds a file offset directly. Since
// I don't have a use-case for that right now, just assert that // I don't have a use-case for that right now, just assert that
// nobody wants this. We can always add it later. // nobody wants this. We can always add it later.
enforce(elfHeader().e_type == ET_EXEC || elfHeader().e_type == ET_DYN, FOLLY_SAFE_CHECK(
"Only exectuables and shared objects are supported"); elfHeader().e_type == ET_EXEC || elfHeader().e_type == ET_DYN,
enforce(addr >= section.sh_addr && "Only exectuables and shared objects are supported");
(addr + sizeof(T)) <= (section.sh_addr + section.sh_size), FOLLY_SAFE_CHECK(
"Address is not contained within the provided segment"); addr >= section.sh_addr &&
(addr + sizeof(T)) <= (section.sh_addr + section.sh_size),
"Address is not contained within the provided segment");
return at<T>(section.sh_offset + (addr - section.sh_addr)); return at<T>(section.sh_offset + (addr - section.sh_addr));
} }
......
/*
* Copyright 2013 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.
*/
#include "folly/experimental/symbolizer/LineReader.h"
#include <cstring>
#include "folly/FileUtil.h"
namespace folly { namespace symbolizer {
LineReader::LineReader(int fd, char* buf, size_t bufSize)
: fd_(fd),
buf_(buf),
bufEnd_(buf_ + bufSize),
bol_(buf),
eol_(buf),
end_(buf),
state_(kReading) {
}
LineReader::State LineReader::readLine(StringPiece& line) {
bol_ = eol_; // Start past what we already returned
for (;;) {
// Search for newline
char* newline = static_cast<char*>(memchr(eol_, '\n', end_ - eol_));
if (newline) {
eol_ = newline + 1;
break;
} else if (state_ != kReading || (bol_ == buf_ && end_ == bufEnd_)) {
// If the buffer is full with one line (line too long), or we're
// at the end of the file, return what we have.
eol_ = end_;
break;
}
// We don't have a full line in the buffer, but we have room to read.
// Move to the beginning of the buffer.
memmove(buf_, eol_, end_ - eol_);
end_ -= (eol_ - buf_);
bol_ = buf_;
eol_ = end_;
// Refill
ssize_t available = bufEnd_ - end_;
ssize_t n = readFull(fd_, end_, available);
if (n < 0) {
state_ = kError;
n = 0;
} else if (n < available) {
state_ = kEof;
}
end_ += n;
}
line.assign(bol_, eol_);
return eol_ != bol_ ? kReading : state_;
}
}} // namespaces
/*
* Copyright 2013 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.
*/
#ifndef FOLLY_SYMBOLIZER_LINEREADER_H_
#define FOLLY_SYMBOLIZER_LINEREADER_H_
#include <cstddef>
#include <boost/noncopyable.hpp>
#include "folly/Range.h"
namespace folly { namespace symbolizer {
/**
* Async-signal-safe line reader.
*/
class LineReader : private boost::noncopyable {
public:
/**
* Create a line reader that reads into a user-provided buffer (of size
* bufSize).
*/
LineReader(int fd, char* buf, size_t bufSize);
enum State {
kReading,
kEof,
kError
};
/**
* Read the next line from the file.
*
* If the line is at most bufSize characters long, including the trailing
* newline, it will be returned (including the trailing newline).
*
* If the line is longer than bufSize, we return the first bufSize bytes
* (which won't include a trailing newline) and then continue from that
* point onwards.
*
* The lines returned are not null-terminated.
*
* Returns kReading with a valid line, kEof if at end of file, or kError
* if a read error was encountered.
*
* Example:
* bufSize = 10
* input has "hello world\n"
* The first call returns "hello worl"
* The second call returns "d\n"
*/
State readLine(StringPiece& line);
private:
int const fd_;
char* const buf_;
char* const bufEnd_;
// buf_ <= bol_ <= eol_ <= end_ <= bufEnd_
//
// [buf_, end_): current buffer contents (read from file)
//
// [buf_, bol_): free (already processed, can be discarded)
// [bol_, eol_): current line, including \n if it exists, eol_ points
// 1 character past the \n
// [eol_, end_): read, unprocessed
// [end_, bufEnd_): free
char* bol_;
char* eol_;
char* end_;
State state_;
};
}} // namespaces
#endif /* FOLLY_SYMBOLIZER_LINEREADER_H_ */
/*
* Copyright 2013 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.
*/
// This is heavily inspired by the signal handler from google-glog
#include "folly/experimental/symbolizer/SignalHandler.h"
#include <sys/types.h>
#include <atomic>
#include <ctime>
#include <mutex>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <vector>
#include <glog/logging.h>
#include "folly/Conv.h"
#include "folly/FileUtil.h"
#include "folly/Portability.h"
#include "folly/ScopeGuard.h"
#include "folly/experimental/symbolizer/Symbolizer.h"
namespace folly { namespace symbolizer {
namespace {
/**
* Fatal signal handler registry.
*/
class FatalSignalCallbackRegistry {
public:
typedef std::function<void()> Func;
FatalSignalCallbackRegistry();
void add(Func func);
void markInstalled();
void run();
private:
std::atomic<bool> installed_;
std::mutex mutex_;
std::vector<Func> handlers_;
};
FatalSignalCallbackRegistry::FatalSignalCallbackRegistry()
: installed_(false) {
}
void FatalSignalCallbackRegistry::add(Func func) {
std::lock_guard<std::mutex> lock(mutex_);
CHECK(!installed_)
<< "FatalSignalCallbackRegistry::add may not be used "
"after installing the signal handlers.";
handlers_.push_back(std::move(func));
}
void FatalSignalCallbackRegistry::markInstalled() {
std::lock_guard<std::mutex> lock(mutex_);
CHECK(!installed_.exchange(true))
<< "FatalSignalCallbackRegistry::markInstalled must be called "
<< "at most once";
}
void FatalSignalCallbackRegistry::run() {
if (!installed_) {
return; // Shouldn't happen
}
for (auto& fn : handlers_) {
fn();
}
}
// Leak it so we don't have to worry about destruction order
FatalSignalCallbackRegistry* gFatalSignalCallbackRegistry =
new FatalSignalCallbackRegistry;
struct {
int number;
const char* name;
struct sigaction oldAction;
} kFatalSignals[] = {
{ SIGSEGV, "SIGSEGV" },
{ SIGILL, "SIGILL" },
{ SIGFPE, "SIGFPE" },
{ SIGABRT, "SIGABRT" },
{ SIGBUS, "SIGBUS" },
{ SIGTERM, "SIGTERM" },
{ 0, nullptr }
};
void callPreviousSignalHandler(int signum) {
// Restore disposition to old disposition, then kill ourselves with the same
// signal. The signal will be blocked until we return from our handler,
// then it will invoke the default handler and abort.
for (auto p = kFatalSignals; p->name; ++p) {
if (p->number == signum) {
sigaction(signum, &p->oldAction, nullptr);
return;
}
}
// Not one of the signals we know about. Oh well. Reset to default.
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_DFL;
sigaction(signum, &sa, nullptr);
raise(signum);
}
void printDec(uint64_t val) {
char buf[20];
uint32_t n = uint64ToBufferUnsafe(val, buf);
writeFull(STDERR_FILENO, buf, n);
}
const char kHexChars[] = "0123456789abcdef";
void printHex(uint64_t val) {
// TODO(tudorb): Add this to folly/Conv.h
char buf[2 + 2 * sizeof(uint64_t)]; // "0x" prefix, 2 digits for each byte
char* end = buf + sizeof(buf);
char* p = end;
do {
*--p = kHexChars[val & 0x0f];
val >>= 4;
} while (val != 0);
*--p = 'x';
*--p = '0';
writeFull(STDERR_FILENO, p, end - p);
}
void print(StringPiece sp) {
writeFull(STDERR_FILENO, sp.data(), sp.size());
}
void dumpTimeInfo() {
SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
time_t now = time(nullptr);
print("*** Aborted at ");
printDec(now);
print(" (Unix time, try 'date -d @");
printDec(now);
print("') ***\n");
}
void dumpSignalInfo(int signum, siginfo_t* siginfo) {
SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
// Get the signal name, if possible.
const char* name = nullptr;
for (auto p = kFatalSignals; p->name; ++p) {
if (p->number == signum) {
name = p->name;
break;
}
}
print("*** Signal ");
printDec(signum);
if (name) {
print(" (");
print(name);
print(")");
}
print(" (");
printHex(reinterpret_cast<uint64_t>(siginfo->si_addr));
print(") received by PID ");
printDec(getpid());
print(" (TID ");
printHex((uint64_t)pthread_self());
print("), stack trace: ***\n");
}
void dumpStackTrace() {
SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
// Get and symbolize stack trace
constexpr size_t kMaxStackTraceDepth = 100;
AddressInfo addresses[kMaxStackTraceDepth];
// Skip the getStackTrace frame
ssize_t stackTraceDepth = getStackTrace(addresses, kMaxStackTraceDepth, 1);
if (stackTraceDepth < 0) {
print("(error retrieving stack trace)\n");
} else {
Symbolizer symbolizer;
symbolizer.symbolize(addresses, stackTraceDepth);
FDSymbolizePrinter printer(STDERR_FILENO);
printer.print(addresses, stackTraceDepth);
}
}
std::atomic<pthread_t*> gSignalThread;
// Here be dragons.
void innerSignalHandler(int signum, siginfo_t* info, void* uctx) {
// First, let's only let one thread in here at a time.
pthread_t myId = pthread_self();
pthread_t* prevSignalThread = nullptr;
while (!gSignalThread.compare_exchange_strong(prevSignalThread, &myId)) {
if (pthread_equal(*prevSignalThread, myId)) {
print("Entered fatal signal handler recursively. We're in trouble.\n");
return;
}
// Wait a while, try again.
timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 100L * 1000 * 1000; // 100ms
nanosleep(&ts, nullptr);
prevSignalThread = nullptr;
}
dumpTimeInfo();
dumpSignalInfo(signum, info);
dumpStackTrace();
// Run user callbacks
gFatalSignalCallbackRegistry->run();
}
void signalHandler(int signum, siginfo_t* info, void* uctx) {
SCOPE_EXIT { fsyncNoInt(STDERR_FILENO); };
try {
innerSignalHandler(signum, info, uctx);
} catch (...) {
// Ignore any exceptions. What? Exceptions?
print("Exception in innerSignalHandler!\n");
}
gSignalThread = nullptr;
// Kill ourselves with the previous handler.
callPreviousSignalHandler(signum);
}
} // namespace
void addFatalSignalCallback(std::function<void()> handler) {
gFatalSignalCallbackRegistry->add(std::move(handler));
}
namespace {
std::atomic<bool> gAlreadyInstalled;
} // namespace
void installFatalSignalHandler() {
if (gAlreadyInstalled.exchange(true)) {
// Already done.
return;
}
gFatalSignalCallbackRegistry->markInstalled();
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags |= SA_SIGINFO;
sa.sa_sigaction = &signalHandler;
for (auto p = kFatalSignals; p->name; ++p) {
CHECK_ERR(sigaction(p->number, &sa, &p->oldAction));
}
}
}} // namespaces
/*
* Copyright 2013 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.
*/
#ifndef FOLLY_SYMBOLIZER_SIGNALHANDLER_H_
#define FOLLY_SYMBOLIZER_SIGNALHANDLER_H_
#include <functional>
namespace folly { namespace symbolizer {
/**
* Install handler for fatal signals. The list of signals being handled is in
* SignalHandler.cpp.
*
* The handler will dump signal and time information followed by a stack trace
* to stderr, and then call the callbacks registered below.
*/
void installFatalSignalHandler();
/**
* Add a callback to be run when receiving a fatal signal. They will also
* be called by LOG(FATAL) and abort() (as those raise SIGABRT internally).
*
* These callbacks must be async-signal-safe, so don't even think of using
* LOG(...) or printf or malloc / new or doing anything even remotely fun.
*
* All these fatal callback must be added before calling
* installFatalSignalHandler().
*/
void addFatalSignalCallback(std::function<void()> callback);
}} // namespaces
#endif /* FOLLY_SYMBOLIZER_SIGNALHANDLER_H_ */
...@@ -30,30 +30,102 @@ namespace folly { ...@@ -30,30 +30,102 @@ namespace folly {
namespace symbolizer { namespace symbolizer {
/** /**
* Convert an address to symbol name and source location. * Address information: symbol name and location.
*
* Note that both name and location are references in the Symbolizer object,
* which must outlive this AddressInfo object.
*/
struct AddressInfo {
/* implicit */ AddressInfo(uintptr_t a=0, bool sf=false)
: address(a),
isSignalFrame(sf),
found(false) { }
uintptr_t address;
bool isSignalFrame;
bool found;
StringPiece name;
Dwarf::LocationInfo location;
};
/**
* Get the current stack trace into addresses, which has room for at least
* maxAddresses entries. Skip the first (topmost) skip entries.
* Returns the number of entries in addresses on success, -1 on failure.
*/ */
ssize_t getStackTrace(AddressInfo* addresses,
size_t maxAddresses,
size_t skip=0);
class Symbolizer { class Symbolizer {
public: public:
Symbolizer() : fileCount_(0) { }
/**
* Symbolize given addresses.
*/
void symbolize(AddressInfo* addresses, size_t addressCount);
/** /**
* Symbolize an instruction pointer address, returning the symbol name * Shortcut to symbolize one address.
* and file/line number information.
*
* The returned StringPiece objects are valid for the lifetime of
* this Symbolizer object.
*/ */
bool symbolize(uintptr_t address, folly::StringPiece& symbolName, bool symbolize(AddressInfo& address) {
Dwarf::LocationInfo& location); symbolize(&address, 1);
return address.found;
}
private:
// We can't allocate memory, so we'll preallocate room.
// "1023 shared libraries should be enough for everyone"
static constexpr size_t kMaxFiles = 1024;
size_t fileCount_;
ElfFile files_[kMaxFiles];
};
static void write(std::ostream& out, uintptr_t address, /**
folly::StringPiece symbolName, * Print a list of symbolized addresses. Base class.
const Dwarf::LocationInfo& location); */
class SymbolizePrinter {
public:
void print(const AddressInfo& ainfo);
void print(const AddressInfo* addresses, size_t addressCount);
virtual ~SymbolizePrinter() { }
private: private:
ElfFile& getFile(const std::string& name); virtual void doPrint(StringPiece sp) = 0;
// cache open ELF files
std::unordered_map<std::string, ElfFile> elfFiles_;
}; };
/**
* Print a list of symbolized addresses to a stream.
* Not reentrant. Do not use from signal handling code.
*/
class OStreamSymbolizePrinter : public SymbolizePrinter {
public:
explicit OStreamSymbolizePrinter(std::ostream& out) : out_(out) { }
private:
void doPrint(StringPiece sp) override;
std::ostream& out_;
};
/**
* Print a list of symbolized addresses to a file descriptor.
* Ignores errors. Async-signal-safe.
*/
class FDSymbolizePrinter : public SymbolizePrinter {
public:
explicit FDSymbolizePrinter(int fd) : fd_(fd) { }
private:
void doPrint(StringPiece sp) override;
int fd_;
};
/**
* Print an AddressInfo to a stream. Note that the Symbolizer that
* symbolized the address must outlive the AddressInfo. Just like
* OStreamSymbolizePrinter (which it uses internally), this is not
* reentrant; do not use from signal handling code.
*/
std::ostream& operator<<(std::ostream& out, const AddressInfo& ainfo);
} // namespace symbolizer } // namespace symbolizer
} // namespace folly } // namespace folly
......
/*
* Copyright 2013 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.
*/
#include "folly/experimental/symbolizer/LineReader.h"
#include <glog/logging.h>
#include <gtest/gtest.h>
#include "folly/FileUtil.h"
#include "folly/experimental/TestUtil.h"
namespace folly { namespace symbolizer { namespace test {
using folly::test::TemporaryFile;
void writeAll(int fd, const char* str) {
ssize_t n = strlen(str);
CHECK_EQ(n, writeFull(fd, str, n));
}
void expect(LineReader& lr, const char* expected) {
StringPiece line;
size_t expectedLen = strlen(expected);
EXPECT_EQ(expectedLen != 0 ? LineReader::kReading : LineReader::kEof,
lr.readLine(line));
EXPECT_EQ(expectedLen, line.size());
EXPECT_EQ(std::string(expected, expectedLen), line.str());
}
TEST(LineReader, Simple) {
TemporaryFile file;
int fd = file.fd();
writeAll(fd,
"Meow\n"
"Hello world\n"
"This is a long line. It is longer than the other lines.\n"
"\n"
"Incomplete last line");
{
CHECK_ERR(lseek(fd, 0, SEEK_SET));
char buf[10];
LineReader lr(fd, buf, sizeof(buf));
expect(lr, "Meow\n");
expect(lr, "Hello worl");
expect(lr, "d\n");
expect(lr, "This is a ");
expect(lr, "long line.");
expect(lr, " It is lon");
expect(lr, "ger than t");
expect(lr, "he other l");
expect(lr, "ines.\n");
expect(lr, "\n");
expect(lr, "Incomplete");
expect(lr, " last line");
expect(lr, "");
}
{
CHECK_ERR(lseek(fd, 0, SEEK_SET));
char buf[80];
LineReader lr(fd, buf, sizeof(buf));
expect(lr, "Meow\n");
expect(lr, "Hello world\n");
expect(lr, "This is a long line. It is longer than the other lines.\n");
expect(lr, "\n");
expect(lr, "Incomplete last line");
expect(lr, "");
}
}
}}} // namespaces
/*
* Copyright 2013 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.
*/
#include "folly/experimental/symbolizer/test/SignalHandlerTest.h"
#include "folly/experimental/symbolizer/SignalHandler.h"
#include <gtest/gtest.h>
#include "folly/FileUtil.h"
#include "folly/Range.h"
namespace folly { namespace symbolizer { namespace test {
namespace {
void print(StringPiece sp) {
writeFull(STDERR_FILENO, sp.data(), sp.size());
}
void callback1() {
print("Callback1\n");
}
void callback2() {
print("Callback2\n");
}
} // namespace
TEST(SignalHandler, Simple) {
addFatalSignalCallback(callback1);
addFatalSignalCallback(callback2);
installFatalSignalHandler();
EXPECT_DEATH(
failHard(),
"^\\*\\*\\* Aborted at [0-9]+ \\(Unix time, try 'date -d @[0-9]+'\\) "
"\\*\\*\\*\n"
"\\*\\*\\* Signal 11 \\(SIGSEGV\\) \\(0x2a\\) received by PID [0-9]+ "
"\\(TID 0x[0-9a-f]+\\), stack trace: \\*\\*\\*\n"
".*\n"
" @ [0-9a-f]+ folly::symbolizer::test::SignalHandler_Simple_Test"
"::TestBody\\(\\)\n"
".*\n"
" @ [0-9a-f]+ main\n"
".*\n"
"Callback1\n"
"Callback2\n"
);
}
}}} // namespaces
/*
* Copyright 2013 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.
*/
#ifndef FOLLY_SYMBOLIZER_TEST_SIGNALHANDLERTEST_H_
#define FOLLY_SYMBOLIZER_TEST_SIGNALHANDLERTEST_H_
namespace folly { namespace symbolizer { namespace test {
inline void failHard() {
*(volatile char*)42; // SIGSEGV
}
}}} // namespaces
#endif /* FOLLY_SYMBOLIZER_TEST_SIGNALHANDLERTEST_H_ */
/*
* Copyright 2013 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.
*/
#include "folly/experimental/symbolizer/Symbolizer.h"
#include <gtest/gtest.h>
#include "folly/Range.h"
#include "folly/String.h"
namespace folly { namespace symbolizer { namespace test {
void foo() {
}
TEST(Symbolizer, Single) {
AddressInfo a(reinterpret_cast<uintptr_t>(foo));
Symbolizer symbolizer;
ASSERT_TRUE(symbolizer.symbolize(a));
EXPECT_EQ("folly::symbolizer::test::foo()", demangle(a.name.str().c_str()));
auto path = a.location.file.toString();
folly::StringPiece basename(path);
auto pos = basename.rfind('/');
if (pos != folly::StringPiece::npos) {
basename.advance(pos + 1);
}
EXPECT_EQ("SymbolizerTest.cpp", basename.str());
}
}}} // namespaces
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