From fd3c895f771ba28e43eb2de7460942c2927d0679 Mon Sep 17 00:00:00 2001 From: Tudor Bosman Date: Wed, 27 Nov 2013 08:58:51 -0800 Subject: [PATCH] Async-signal-safe symbolizer, fatal signal handler Test Plan: test added Reviewed By: lucian@fb.com FB internal diff: D1076170 @override-unit-failures --- .../exception_tracer/ExceptionTracer.cpp | 14 +- folly/experimental/symbolizer/Dwarf.cpp | 65 +-- folly/experimental/symbolizer/Dwarf.h | 5 +- folly/experimental/symbolizer/Elf-inl.h | 4 +- folly/experimental/symbolizer/Elf.cpp | 81 ++-- folly/experimental/symbolizer/Elf.h | 48 +- folly/experimental/symbolizer/LineReader.cpp | 74 +++ folly/experimental/symbolizer/LineReader.h | 91 ++++ .../experimental/symbolizer/SignalHandler.cpp | 288 ++++++++++++ folly/experimental/symbolizer/SignalHandler.h | 50 ++ folly/experimental/symbolizer/Symbolizer.cpp | 433 ++++++++++++++---- folly/experimental/symbolizer/Symbolizer.h | 100 +++- .../symbolizer/test/LineReaderTest.cpp | 86 ++++ .../symbolizer/test/SignalHandlerTest.cpp | 67 +++ .../symbolizer/test/SignalHandlerTest.h | 29 ++ .../symbolizer/test/SymbolizerTest.cpp | 44 ++ 16 files changed, 1276 insertions(+), 203 deletions(-) create mode 100644 folly/experimental/symbolizer/LineReader.cpp create mode 100644 folly/experimental/symbolizer/LineReader.h create mode 100644 folly/experimental/symbolizer/SignalHandler.cpp create mode 100644 folly/experimental/symbolizer/SignalHandler.h create mode 100644 folly/experimental/symbolizer/test/LineReaderTest.cpp create mode 100644 folly/experimental/symbolizer/test/SignalHandlerTest.cpp create mode 100644 folly/experimental/symbolizer/test/SignalHandlerTest.h create mode 100644 folly/experimental/symbolizer/test/SymbolizerTest.cpp diff --git a/folly/experimental/exception_tracer/ExceptionTracer.cpp b/folly/experimental/exception_tracer/ExceptionTracer.cpp index a2bd9ef0..19b85ec2 100644 --- a/folly/experimental/exception_tracer/ExceptionTracer.cpp +++ b/folly/experimental/exception_tracer/ExceptionTracer.cpp @@ -54,15 +54,19 @@ std::ostream& operator<<(std::ostream& out, const ExceptionInfo& info) { << (info.frames.size() == 1 ? " frame" : " frames") << ")\n"; try { - Symbolizer symbolizer; - folly::StringPiece symbolName; - Dwarf::LocationInfo location; + std::vector addresses; + addresses.reserve(info.frames.size()); for (auto ip : info.frames) { // Symbolize the previous address because the IP might be in the // next function, per glog/src/signalhandler.cc - symbolizer.symbolize(ip-1, symbolName, location); - Symbolizer::write(out, ip, symbolName, location); + addresses.emplace_back(ip - 1); } + + Symbolizer symbolizer; + symbolizer.symbolize(addresses.data(), addresses.size()); + + OStreamSymbolizePrinter osp(out); + osp.print(addresses.data(), addresses.size()); } catch (const std::exception& e) { out << "\n !! caught " << folly::exceptionStr(e) << "\n"; } catch (...) { diff --git a/folly/experimental/symbolizer/Dwarf.cpp b/folly/experimental/symbolizer/Dwarf.cpp index 7ad5af1d..9e7c3d32 100644 --- a/folly/experimental/symbolizer/Dwarf.cpp +++ b/folly/experimental/symbolizer/Dwarf.cpp @@ -34,13 +34,13 @@ Dwarf::Section::Section(folly::StringPiece d) : is64Bit_(false), data_(d) { 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 typename std::enable_if::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)); @@ -85,7 +85,7 @@ uint64_t readOffset(folly::StringPiece& sp, bool is64Bit) { // 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; @@ -95,7 +95,7 @@ folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) { folly::StringPiece readNullTerminated(folly::StringPiece& sp) { const char* p = static_cast( 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; @@ -105,7 +105,7 @@ folly::StringPiece readNullTerminated(folly::StringPiece& sp) { 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); } } @@ -233,7 +233,7 @@ bool Dwarf::Section::next(folly::StringPiece& chunk) { auto initialLength = read(chunk); is64Bit_ = (initialLength == (uint32_t)-1); auto length = is64Bit_ ? read(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; @@ -279,7 +279,7 @@ 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; @@ -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( @@ -356,12 +356,12 @@ 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); @@ -381,13 +381,13 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const { bool found = false; while (!found && arangesSection.next(chunk)) { auto version = read(chunk); - enforce(version == 2, "invalid aranges version"); + FOLLY_SAFE_CHECK(version == 2, "invalid aranges version"); debugInfoOffset = readOffset(chunk, arangesSection.is64Bit()); auto addressSize = read(chunk); - enforce(addressSize == sizeof(uintptr_t), "invalid address size"); + FOLLY_SAFE_CHECK(addressSize == sizeof(uintptr_t), "invalid address size"); auto segmentSize = read(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 @@ -417,21 +417,22 @@ bool Dwarf::findAddress(uintptr_t address, LocationInfo& locationInfo) const { folly::StringPiece sp(info_); sp.advance(debugInfoOffset); Section debugInfoSection(sp); - enforce(debugInfoSection.next(chunk), "invalid debug info"); + FOLLY_SAFE_CHECK(debugInfoSection.next(chunk), "invalid debug info"); auto version = read(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(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 // 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"); // Read attributes, extracting the few we care about bool foundLineOffset = false; @@ -488,7 +489,7 @@ 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(); @@ -510,23 +511,24 @@ void Dwarf::LineNumberVM::reset() { void Dwarf::LineNumberVM::init() { version_ = read(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(header); if (version_ == 4) { // Version 2 and 3 records don't have this uint8_t maxOpsPerInstruction = read(header); - enforce(maxOpsPerInstruction == 1, "VLIW not supported"); + FOLLY_SAFE_CHECK(maxOpsPerInstruction == 1, "VLIW not supported"); } defaultIsStmt_ = read(header); lineBase_ = read(header); // yes, signed lineRange_ = read(header); opcodeBase_ = read(header); - enforce(opcodeBase_ != 0, "invalid opcode base"); + FOLLY_SAFE_CHECK(opcodeBase_ != 0, "invalid opcode base"); standardOpcodeLengths_ = reinterpret_cast(header.data()); header.advance(opcodeBase_ - 1); @@ -561,7 +563,7 @@ bool Dwarf::LineNumberVM::next(folly::StringPiece& program) { 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_) { @@ -578,7 +580,7 @@ Dwarf::LineNumberVM::FileName Dwarf::LineNumberVM::getFileName(uint64_t index) 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; @@ -590,7 +592,8 @@ folly::StringPiece Dwarf::LineNumberVM::getIncludeDirectory(uint64_t index) return folly::StringPiece(); } - enforce(index <= includeDirectoryCount_, "invalid include directory"); + FOLLY_SAFE_CHECK(index <= includeDirectoryCount_, + "invalid include directory"); folly::StringPiece includeDirectories = includeDirectories_; folly::StringPiece dir; @@ -638,13 +641,13 @@ bool Dwarf::LineNumberVM::nextDefineFile(folly::StringPiece& program, // 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"); + FOLLY_SAFE_CHECK(length != 0, "invalid extended opcode length"); read(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; } @@ -733,7 +736,7 @@ Dwarf::LineNumberVM::StepResult Dwarf::LineNumberVM::step( // 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(program); --length; diff --git a/folly/experimental/symbolizer/Dwarf.h b/folly/experimental/symbolizer/Dwarf.h index 3b261501..4a14f24c 100644 --- a/folly/experimental/symbolizer/Dwarf.h +++ b/folly/experimental/symbolizer/Dwarf.h @@ -41,9 +41,8 @@ namespace symbolizer { * actually support many of the version 4 features (such as VLIW, multiple * operations per instruction) * - * 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 - * uses the heap). This is on purpose: you can use the parser from + * Note that the DWARF record parser does not allocate heap memory at all. + * This is on purpose: you can use the parser from * memory-constrained situations (such as an exception handler for * 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 diff --git a/folly/experimental/symbolizer/Elf-inl.h b/folly/experimental/symbolizer/Elf-inl.h index cc4f51bd..66f3b94e 100644 --- a/folly/experimental/symbolizer/Elf-inl.h +++ b/folly/experimental/symbolizer/Elf-inl.h @@ -62,8 +62,8 @@ const char* ElfFile::iterateStrings(const ElfW(Shdr)& stringTable, Fn fn) template const ElfW(Sym)* ElfFile::iterateSymbols(const ElfW(Shdr)& section, Fn fn) const { - enforce(section.sh_entsize == sizeof(ElfW(Sym)), - "invalid entry size in symbol table"); + FOLLY_SAFE_CHECK(section.sh_entsize == sizeof(ElfW(Sym)), + "invalid entry size in symbol table"); const ElfW(Sym)* sym = &at(section.sh_offset); const ElfW(Sym)* end = sym + (section.sh_size / section.sh_entsize); diff --git a/folly/experimental/symbolizer/Elf.cpp b/folly/experimental/symbolizer/Elf.cpp index 33e20dde..818b5188 100644 --- a/folly/experimental/symbolizer/Elf.cpp +++ b/folly/experimental/symbolizer/Elf.cpp @@ -32,7 +32,7 @@ namespace folly { namespace symbolizer { -ElfFile::ElfFile() +ElfFile::ElfFile() noexcept : fd_(-1), file_(static_cast(MAP_FAILED)), length_(0), @@ -40,18 +40,33 @@ ElfFile::ElfFile() } ElfFile::ElfFile(const char* name, bool readOnly) - : fd_(open(name, (readOnly) ? O_RDONLY : O_RDWR)), + : fd_(-1), file_(static_cast(MAP_FAILED)), length_(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) { - folly::throwSystemError("open ", name); + if (msg) *msg = "open"; + return -1; } struct stat st; int r = fstat(fd_, &st); if (r == -1) { - folly::throwSystemError("fstat"); + if (msg) *msg = "fstat"; + return -1; } length_ = st.st_size; @@ -61,9 +76,11 @@ ElfFile::ElfFile(const char* name, bool readOnly) } file_ = static_cast(mmap(nullptr, length_, prot, MAP_SHARED, fd_, 0)); if (file_ == MAP_FAILED) { - folly::throwSystemError("mmap"); + if (msg) *msg = "mmap"; + return -1; } init(); + return 0; } ElfFile::~ElfFile() { @@ -112,18 +129,18 @@ void ElfFile::init() { auto& elfHeader = this->elfHeader(); // Validate ELF magic numbers - enforce(elfHeader.e_ident[EI_MAG0] == ELFMAG0 && - elfHeader.e_ident[EI_MAG1] == ELFMAG1 && - elfHeader.e_ident[EI_MAG2] == ELFMAG2 && - elfHeader.e_ident[EI_MAG3] == ELFMAG3, - "invalid ELF magic"); + FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_MAG0] == ELFMAG0 && + elfHeader.e_ident[EI_MAG1] == ELFMAG1 && + elfHeader.e_ident[EI_MAG2] == ELFMAG2 && + elfHeader.e_ident[EI_MAG3] == ELFMAG3, + "invalid ELF magic"); // Validate ELF class (32/64 bits) #define EXPECTED_CLASS P1(ELFCLASS, __ELF_NATIVE_CLASS) #define P1(a, b) P2(a, b) #define P2(a, b) a ## b - enforce(elfHeader.e_ident[EI_CLASS] == EXPECTED_CLASS, - "invalid ELF class"); + FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_CLASS] == EXPECTED_CLASS, + "invalid ELF class"); #undef P1 #undef P2 #undef EXPECTED_CLASS @@ -136,24 +153,24 @@ void ElfFile::init() { #else # error Unsupported byte order #endif - enforce(elfHeader.e_ident[EI_DATA] == EXPECTED_ENCODING, - "invalid ELF encoding"); + FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_DATA] == EXPECTED_ENCODING, + "invalid ELF encoding"); #undef EXPECTED_ENCODING // Validate ELF version (1) - enforce(elfHeader.e_ident[EI_VERSION] == EV_CURRENT && - elfHeader.e_version == EV_CURRENT, - "invalid ELF version"); + FOLLY_SAFE_CHECK(elfHeader.e_ident[EI_VERSION] == EV_CURRENT && + elfHeader.e_version == EV_CURRENT, + "invalid ELF version"); // We only support executable and shared object files - enforce(elfHeader.e_type == ET_EXEC || elfHeader.e_type == ET_DYN, - "invalid ELF file type"); + FOLLY_SAFE_CHECK(elfHeader.e_type == ET_EXEC || elfHeader.e_type == ET_DYN, + "invalid ELF file type"); - enforce(elfHeader.e_phnum != 0, "no program header!"); - enforce(elfHeader.e_phentsize == sizeof(ElfW(Phdr)), - "invalid program header entry size"); - enforce(elfHeader.e_shentsize == sizeof(ElfW(Shdr)), - "invalid section header entry size"); + FOLLY_SAFE_CHECK(elfHeader.e_phnum != 0, "no program header!"); + FOLLY_SAFE_CHECK(elfHeader.e_phentsize == sizeof(ElfW(Phdr)), + "invalid program header entry size"); + FOLLY_SAFE_CHECK(elfHeader.e_shentsize == sizeof(ElfW(Shdr)), + "invalid section header entry size"); const ElfW(Phdr)* programHeader = &at(elfHeader.e_phoff); bool foundBase = false; @@ -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 { - enforce(idx < elfHeader().e_shnum, "invalid section index"); + FOLLY_SAFE_CHECK(idx < elfHeader().e_shnum, "invalid section index"); return &at(elfHeader().e_shoff + idx * sizeof(ElfW(Shdr))); } @@ -180,19 +197,21 @@ folly::StringPiece ElfFile::getSectionBody(const ElfW(Shdr)& section) 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; // First and last bytes must be 0 - enforce(stringTable.sh_size == 0 || - (start[0] == '\0' && start[stringTable.sh_size - 1] == '\0'), - "invalid string table"); + FOLLY_SAFE_CHECK(stringTable.sh_size == 0 || + (start[0] == '\0' && start[stringTable.sh_size - 1] == '\0'), + "invalid string table"); } const char* ElfFile::getString(const ElfW(Shdr)& stringTable, size_t offset) const { 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; } diff --git a/folly/experimental/symbolizer/Elf.h b/folly/experimental/symbolizer/Elf.h index 256e359c..4ad99df9 100644 --- a/folly/experimental/symbolizer/Elf.h +++ b/folly/experimental/symbolizer/Elf.h @@ -26,20 +26,14 @@ #include #include +#include "folly/Conv.h" #include "folly/Likely.h" #include "folly/Range.h" -#include "folly/Conv.h" +#include "folly/SafeAssert.h" namespace folly { namespace symbolizer { -template -inline void enforce(bool v, Args... args) { - if (UNLIKELY(!v)) { - throw std::runtime_error(folly::to(args...)); - } -} - /** * ELF file parser. * @@ -49,8 +43,20 @@ inline void enforce(bool v, Args... args) { */ class ElfFile { 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); + + // 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&& other); @@ -124,6 +130,8 @@ class ElfFile { * Find symbol definition by address. * Note that this is the file virtual address, so you need to undo * any relocation that might have happened. + * + * Returns {nullptr, nullptr} if not found. */ typedef std::pair Symbol; Symbol getDefinitionByAddress(uintptr_t address) const; @@ -133,6 +141,8 @@ class ElfFile { * * If a symbol with this name cannot be found, a Symbol * 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; @@ -142,7 +152,7 @@ class ElfFile { template const T& getSymbolValue(const ElfW(Sym)* symbol) const { 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(*section, symbol->st_value); } @@ -161,7 +171,7 @@ class ElfFile { template const T& getAddressValue(const ElfW(Addr) addr) const { 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(*section, addr); } @@ -185,8 +195,8 @@ class ElfFile { template const typename std::enable_if::value, T>::type& at(ElfW(Off) offset) const { - enforce(offset + sizeof(T) <= length_, - "Offset is not contained within our mmapped file"); + FOLLY_SAFE_CHECK(offset + sizeof(T) <= length_, + "Offset is not contained within our mmapped file"); return *reinterpret_cast(file_ + offset); } @@ -202,11 +212,13 @@ class ElfFile { // 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 // nobody wants this. We can always add it later. - enforce(elfHeader().e_type == ET_EXEC || elfHeader().e_type == ET_DYN, - "Only exectuables and shared objects are supported"); - enforce(addr >= section.sh_addr && - (addr + sizeof(T)) <= (section.sh_addr + section.sh_size), - "Address is not contained within the provided segment"); + FOLLY_SAFE_CHECK( + elfHeader().e_type == ET_EXEC || elfHeader().e_type == ET_DYN, + "Only exectuables and shared objects are supported"); + FOLLY_SAFE_CHECK( + addr >= section.sh_addr && + (addr + sizeof(T)) <= (section.sh_addr + section.sh_size), + "Address is not contained within the provided segment"); return at(section.sh_offset + (addr - section.sh_addr)); } diff --git a/folly/experimental/symbolizer/LineReader.cpp b/folly/experimental/symbolizer/LineReader.cpp new file mode 100644 index 00000000..ee303391 --- /dev/null +++ b/folly/experimental/symbolizer/LineReader.cpp @@ -0,0 +1,74 @@ +/* + * 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 + +#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(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 + diff --git a/folly/experimental/symbolizer/LineReader.h b/folly/experimental/symbolizer/LineReader.h new file mode 100644 index 00000000..2527f63b --- /dev/null +++ b/folly/experimental/symbolizer/LineReader.h @@ -0,0 +1,91 @@ +/* + * 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 + +#include + +#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_ */ + diff --git a/folly/experimental/symbolizer/SignalHandler.cpp b/folly/experimental/symbolizer/SignalHandler.cpp new file mode 100644 index 00000000..f66d0133 --- /dev/null +++ b/folly/experimental/symbolizer/SignalHandler.cpp @@ -0,0 +1,288 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 Func; + + FatalSignalCallbackRegistry(); + + void add(Func func); + void markInstalled(); + void run(); + + private: + std::atomic installed_; + std::mutex mutex_; + std::vector handlers_; +}; + +FatalSignalCallbackRegistry::FatalSignalCallbackRegistry() + : installed_(false) { +} + +void FatalSignalCallbackRegistry::add(Func func) { + std::lock_guard 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 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(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 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 handler) { + gFatalSignalCallbackRegistry->add(std::move(handler)); +} + +namespace { + +std::atomic 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 + diff --git a/folly/experimental/symbolizer/SignalHandler.h b/folly/experimental/symbolizer/SignalHandler.h new file mode 100644 index 00000000..2c13b194 --- /dev/null +++ b/folly/experimental/symbolizer/SignalHandler.h @@ -0,0 +1,50 @@ +/* + * 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 + +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 callback); + + +}} // namespaces + +#endif /* FOLLY_SYMBOLIZER_SIGNALHANDLER_H_ */ + diff --git a/folly/experimental/symbolizer/Symbolizer.cpp b/folly/experimental/symbolizer/Symbolizer.cpp index 57bea3bf..79345c8b 100644 --- a/folly/experimental/symbolizer/Symbolizer.cpp +++ b/folly/experimental/symbolizer/Symbolizer.cpp @@ -14,149 +14,384 @@ * limitations under the License. */ +// Must be first to ensure that UNW_LOCAL_ONLY is defined +#define UNW_LOCAL_ONLY 1 +#include #include "folly/experimental/symbolizer/Symbolizer.h" -#include -#include +#include + +#include "folly/Conv.h" +#include "folly/FileUtil.h" +#include "folly/String.h" #include "folly/experimental/symbolizer/Elf.h" #include "folly/experimental/symbolizer/Dwarf.h" -#include "folly/Range.h" -#include "folly/FBString.h" -#include "folly/String.h" -#include "folly/experimental/Gen.h" -#include "folly/experimental/FileGen.h" -#include "folly/experimental/StringGen.h" +#include "folly/experimental/symbolizer/LineReader.h" namespace folly { namespace symbolizer { namespace { -StringPiece sp(const boost::csub_match& m) { - return StringPiece(m.first, m.second); -} -uint64_t fromHex(StringPiece s) { - // Make a copy; we need a null-terminated string for strtoull - fbstring str(s.data(), s.size()); - const char* p = str.c_str(); - char* end; - uint64_t val = strtoull(p, &end, 16); - CHECK(*p != '\0' && *end == '\0'); +/** + * Read a hex value. + */ +uintptr_t readHex(StringPiece& sp) { + uintptr_t val = 0; + const char* p = sp.begin(); + for (; p != sp.end(); ++p) { + unsigned int v; + if (*p >= '0' && *p <= '9') { + v = (*p - '0'); + } else if (*p >= 'a' && *p <= 'f') { + v = (*p - 'a') + 10; + } else if (*p >= 'A' && *p <= 'F') { + v = (*p - 'A') + 10; + } else { + break; + } + val = (val << 4) + v; + } + sp.assign(p, sp.end()); return val; } -struct MappedFile { - uintptr_t begin; - uintptr_t end; - std::string name; -}; +/** + * Skip over non-space characters. + */ +void skipNS(StringPiece& sp) { + const char* p = sp.begin(); + for (; p != sp.end() && (*p != ' ' && *p != '\t'); ++p) { } + sp.assign(p, sp.end()); +} -} // namespace +/** + * Skip over space and tab characters. + */ +void skipWS(StringPiece& sp) { + const char* p = sp.begin(); + for (; p != sp.end() && (*p == ' ' || *p == '\t'); ++p) { } + sp.assign(p, sp.end()); +} -bool Symbolizer::symbolize(uintptr_t address, StringPiece& symbolName, - Dwarf::LocationInfo& location) { - symbolName.clear(); - location = Dwarf::LocationInfo(); - - // Entry in /proc/self/maps - static const boost::regex mapLineRegex( - "([[:xdigit:]]+)-([[:xdigit:]]+)" // from-to - "\\s+" - "[\\w-]+" // permissions - "\\s+" - "([[:xdigit:]]+)" // offset - "\\s+" - "[[:xdigit:]]+:[[:xdigit:]]+" // device, minor:major - "\\s+" - "\\d+" // inode - "\\s*" - "(.*)"); // file name - - boost::cmatch match; - - MappedFile foundFile; - bool error = gen::byLine("/proc/self/maps") | - [&] (StringPiece line) -> bool { - CHECK(boost::regex_match(line.begin(), line.end(), match, mapLineRegex)); - uint64_t begin = fromHex(sp(match[1])); - uint64_t end = fromHex(sp(match[2])); - uint64_t fileOffset = fromHex(sp(match[3])); - if (fileOffset != 0) { - return true; // main mapping starts at 0 - } +/** + * Parse a line from /proc/self/maps + */ +bool parseProcMapsLine(StringPiece line, + uintptr_t& from, uintptr_t& to, + StringPiece& fileName) { + // from to perm offset dev inode path + // 00400000-00405000 r-xp 00000000 08:03 35291182 /bin/cat + if (line.empty()) { + return false; + } - if (begin <= address && address < end) { - foundFile.begin = begin; - foundFile.end = end; - foundFile.name.assign(match[4].first, match[4].second); - return false; - } + // Remove trailing newline, if any + if (line.back() == '\n') { + line.pop_back(); + } - return true; - }; + // from + from = readHex(line); + if (line.empty() || line.front() != '-') { + return false; + } + line.pop_front(); - if (error) { + // to + to = readHex(line); + if (line.empty() || line.front() != ' ') { return false; } + line.pop_front(); - auto& elfFile = getFile(foundFile.name); - // Undo relocation - uintptr_t origAddress = address - foundFile.begin + elfFile.getBaseAddress(); + // perms + skipNS(line); + if (line.empty() || line.front() != ' ') { + return false; + } + line.pop_front(); - auto sym = elfFile.getDefinitionByAddress(origAddress); - if (!sym.first) { + uintptr_t fileOffset = readHex(line); + if (line.empty() || line.front() != ' ') { return false; } + line.pop_front(); + if (fileOffset != 0) { + return false; // main mapping starts at 0 + } - auto name = elfFile.getSymbolName(sym); - if (name) { - symbolName = name; + // dev + skipNS(line); + if (line.empty() || line.front() != ' ') { + return false; } + line.pop_front(); - Dwarf(&elfFile).findAddress(origAddress, location); + // inode + skipNS(line); + if (line.empty() || line.front() != ' ') { + return false; + } + + skipWS(line); + if (line.empty()) { + fileName.clear(); + return true; + } + + fileName = line; return true; } -ElfFile& Symbolizer::getFile(const std::string& name) { - auto pos = elfFiles_.find(name); - if (pos != elfFiles_.end()) { - return pos->second; +} // namespace + +ssize_t getStackTrace(AddressInfo* addresses, + size_t maxAddresses, + size_t skip) { + unw_context_t uctx; + int r = unw_getcontext(&uctx); + if (r < 0) { + return -1; } - return elfFiles_.insert( - std::make_pair(name, ElfFile(name.c_str()))).first->second; + unw_cursor_t cursor; + size_t idx = 0; + bool first = true; + while (idx < maxAddresses) { + if (first) { + first = false; + r = unw_init_local(&cursor, &uctx); + } else { + r = unw_step(&cursor); + if (r == 0) { + break; + } + } + if (r < 0) { + return -1; + } + + if (skip != 0) { + --skip; + continue; + } + unw_word_t ip; + int rr = unw_get_reg(&cursor, UNW_REG_IP, &ip); + if (rr < 0) { + return -1; + } + + // If error, assume not a signal frame + rr = unw_is_signal_frame(&cursor); + + addresses[idx++] = AddressInfo(ip, (rr > 0)); + } + + if (r < 0) { + return -1; + } + + return idx; } -void Symbolizer::write(std::ostream& out, uintptr_t address, - StringPiece symbolName, - const Dwarf::LocationInfo& location) { - char buf[20]; - sprintf(buf, "%#18jx", address); - out << " @ " << buf; +void Symbolizer::symbolize(AddressInfo* addresses, size_t addressCount) { + size_t remaining = 0; + for (size_t i = 0; i < addressCount; ++i) { + auto& ainfo = addresses[i]; + if (!ainfo.found) { + ++remaining; + ainfo.name.clear(); + ainfo.location = Dwarf::LocationInfo(); + } + } + + if (remaining == 0) { // we're done + return; + } + + int fd = openNoInt("/proc/self/maps", O_RDONLY); + if (fd == -1) { + return; + } + + char buf[PATH_MAX + 100]; // Long enough for any line + LineReader reader(fd, buf, sizeof(buf)); - if (!symbolName.empty()) { - out << " " << demangle(symbolName.toString().c_str()); + char fileNameBuf[PATH_MAX]; - std::string file; - if (location.hasFileAndLine) { - file = location.file.toString(); - out << " " << file << ":" << location.line; + while (remaining != 0) { + StringPiece line; + if (reader.readLine(line) != LineReader::kReading) { + break; } - std::string mainFile; - if (location.hasMainFile) { - mainFile = location.mainFile.toString(); - if (!location.hasFileAndLine || file != mainFile) { - out << "\n (compiling " - << location.mainFile << ")"; + // Parse line + uintptr_t from; + uintptr_t to; + StringPiece fileName; + if (!parseProcMapsLine(line, from, to, fileName)) { + continue; + } + + bool first = true; + ElfFile* elfFile = nullptr; + + // See if any addresses are here + for (size_t i = 0; i < addressCount; ++i) { + auto& ainfo = addresses[i]; + if (ainfo.found) { + continue; + } + + uintptr_t address = ainfo.address; + + // If the next address (closer to the top of the stack) was a signal + // frame, then this is the *resume* address, which is the address + // after the location where the signal was caught. This might be in + // the next function, so subtract 1 before symbolizing. + if (i != 0 && addresses[i-1].isSignalFrame) { + --address; + } + + if (from > address || address >= to) { + continue; + } + + // Found + ainfo.found = true; + --remaining; + + // Open the file on first use + if (first) { + first = false; + if (fileCount_ < kMaxFiles && + !fileName.empty() && + fileName.size() < sizeof(fileNameBuf)) { + memcpy(fileNameBuf, fileName.data(), fileName.size()); + fileNameBuf[fileName.size()] = '\0'; + auto& f = files_[fileCount_++]; + if (f.openNoThrow(fileNameBuf) != -1) { + elfFile = &f; + } + } + } + + if (!elfFile) { + continue; + } + + // Undo relocation + uintptr_t fileAddress = address - from + elfFile->getBaseAddress(); + auto sym = elfFile->getDefinitionByAddress(fileAddress); + if (!sym.first) { + continue; } + auto name = elfFile->getSymbolName(sym); + if (name) { + ainfo.name = name; + } + + Dwarf(elfFile).findAddress(fileAddress, ainfo.location); } + } + + closeNoInt(fd); +} + +namespace { +const char kHexChars[] = "0123456789abcdef"; +} // namespace + +void SymbolizePrinter::print(const AddressInfo& ainfo) { + uintptr_t address = ainfo.address; + // Can't use sprintf, not async-signal-safe + static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?"); + char buf[] = " @ 0000000000000000"; + char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t)); + const char padBuf[] = " "; + folly::StringPiece pad(padBuf, + sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t))); + char* p = end; + *p-- = '\0'; + while (address != 0) { + *p-- = kHexChars[address & 0xf]; + address >>= 4; + } + doPrint(folly::StringPiece(buf, end)); + + char mangledBuf[1024]; + if (!ainfo.found) { + doPrint(" (not found)\n"); + return; + } + + if (ainfo.name.empty()) { + doPrint(" (unknown)\n"); + } else if (ainfo.name.size() >= sizeof(mangledBuf)) { + doPrint(" "); + doPrint(ainfo.name); + doPrint("\n"); } else { - out << " (unknown)"; + memcpy(mangledBuf, ainfo.name.data(), ainfo.name.size()); + mangledBuf[ainfo.name.size()] = '\0'; + + char demangledBuf[1024]; + demangle(mangledBuf, demangledBuf, sizeof(demangledBuf)); + doPrint(" "); + doPrint(demangledBuf); + doPrint("\n"); + } + + char fileBuf[PATH_MAX]; + fileBuf[0] = '\0'; + if (ainfo.location.hasFileAndLine) { + ainfo.location.file.toBuffer(fileBuf, sizeof(fileBuf)); + doPrint(pad); + doPrint(fileBuf); + + char buf[22]; + uint32_t n = uint64ToBufferUnsafe(ainfo.location.line, buf); + doPrint(":"); + doPrint(StringPiece(buf, n)); + doPrint("\n"); } - out << "\n"; + + if (ainfo.location.hasMainFile) { + char mainFileBuf[PATH_MAX]; + mainFileBuf[0] = '\0'; + ainfo.location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf)); + if (!ainfo.location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) { + doPrint(pad); + doPrint("-> "); + doPrint(mainFileBuf); + doPrint("\n"); + } + } +} + +void SymbolizePrinter::print(const AddressInfo* addresses, + size_t addressCount) { + for (size_t i = 0; i < addressCount; ++i) { + auto& ainfo = addresses[i]; + print(ainfo); + } +} + +void OStreamSymbolizePrinter::doPrint(StringPiece sp) { + out_ << sp; +} + +void FDSymbolizePrinter::doPrint(StringPiece sp) { + writeFull(fd_, sp.data(), sp.size()); +} + +std::ostream& operator<<(std::ostream& out, const AddressInfo& ainfo) { + OStreamSymbolizePrinter osp(out); + osp.print(ainfo); + return out; } } // namespace symbolizer diff --git a/folly/experimental/symbolizer/Symbolizer.h b/folly/experimental/symbolizer/Symbolizer.h index 24362622..307ca252 100644 --- a/folly/experimental/symbolizer/Symbolizer.h +++ b/folly/experimental/symbolizer/Symbolizer.h @@ -30,30 +30,102 @@ namespace folly { 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 { public: + Symbolizer() : fileCount_(0) { } + + /** + * Symbolize given addresses. + */ + void symbolize(AddressInfo* addresses, size_t addressCount); + /** - * Symbolize an instruction pointer address, returning the symbol name - * and file/line number information. - * - * The returned StringPiece objects are valid for the lifetime of - * this Symbolizer object. + * Shortcut to symbolize one address. */ - bool symbolize(uintptr_t address, folly::StringPiece& symbolName, - Dwarf::LocationInfo& location); + bool symbolize(AddressInfo& address) { + 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, - const Dwarf::LocationInfo& location); +/** + * Print a list of symbolized addresses. Base class. + */ +class SymbolizePrinter { + public: + void print(const AddressInfo& ainfo); + void print(const AddressInfo* addresses, size_t addressCount); + virtual ~SymbolizePrinter() { } private: - ElfFile& getFile(const std::string& name); - // cache open ELF files - std::unordered_map elfFiles_; + virtual void doPrint(StringPiece sp) = 0; }; +/** + * 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 folly diff --git a/folly/experimental/symbolizer/test/LineReaderTest.cpp b/folly/experimental/symbolizer/test/LineReaderTest.cpp new file mode 100644 index 00000000..01a3dd3d --- /dev/null +++ b/folly/experimental/symbolizer/test/LineReaderTest.cpp @@ -0,0 +1,86 @@ +/* + * 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 +#include + +#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 + diff --git a/folly/experimental/symbolizer/test/SignalHandlerTest.cpp b/folly/experimental/symbolizer/test/SignalHandlerTest.cpp new file mode 100644 index 00000000..89f81591 --- /dev/null +++ b/folly/experimental/symbolizer/test/SignalHandlerTest.cpp @@ -0,0 +1,67 @@ +/* + * 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 + +#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 diff --git a/folly/experimental/symbolizer/test/SignalHandlerTest.h b/folly/experimental/symbolizer/test/SignalHandlerTest.h new file mode 100644 index 00000000..3eefbd0f --- /dev/null +++ b/folly/experimental/symbolizer/test/SignalHandlerTest.h @@ -0,0 +1,29 @@ +/* + * 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_ */ + diff --git a/folly/experimental/symbolizer/test/SymbolizerTest.cpp b/folly/experimental/symbolizer/test/SymbolizerTest.cpp new file mode 100644 index 00000000..0ff9cfde --- /dev/null +++ b/folly/experimental/symbolizer/test/SymbolizerTest.cpp @@ -0,0 +1,44 @@ +/* + * 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 + +#include "folly/Range.h" +#include "folly/String.h" + +namespace folly { namespace symbolizer { namespace test { + +void foo() { +} + +TEST(Symbolizer, Single) { + AddressInfo a(reinterpret_cast(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 -- 2.34.1