<< (info.frames.size() == 1 ? " frame" : " frames")
<< ")\n";
try {
- Symbolizer symbolizer;
- folly::StringPiece symbolName;
- Dwarf::LocationInfo location;
+ std::vector<AddressInfo> 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 (...) {
namespace {
// All following read* functions read from a StringPiece, advancing the
-// StringPiece, and throwing an exception if there's not enough room
+// StringPiece, and aborting if there's not enough room.
// Read (bitwise) one object of type T
template <class T>
typename std::enable_if<std::is_pod<T>::value, T>::type
read(folly::StringPiece& sp) {
- enforce(sp.size() >= sizeof(T), "underflow");
+ FOLLY_SAFE_CHECK(sp.size() >= sizeof(T), "underflow");
T x;
memcpy(&x, sp.data(), sizeof(T));
sp.advance(sizeof(T));
// Read "len" bytes
folly::StringPiece readBytes(folly::StringPiece& sp, uint64_t len) {
- enforce(len >= sp.size(), "invalid string length");
+ FOLLY_SAFE_CHECK(len >= sp.size(), "invalid string length");
folly::StringPiece ret(sp.data(), len);
sp.advance(len);
return ret;
folly::StringPiece readNullTerminated(folly::StringPiece& sp) {
const char* p = static_cast<const char*>(
memchr(sp.data(), 0, sp.size()));
- enforce(p, "invalid null-terminated string");
+ FOLLY_SAFE_CHECK(p, "invalid null-terminated string");
folly::StringPiece ret(sp.data(), p);
sp.assign(p + 1, sp.end());
return ret;
void skipPadding(folly::StringPiece& sp, const char* start, size_t alignment) {
size_t remainder = (sp.data() - start) % alignment;
if (remainder) {
- enforce(alignment - remainder <= sp.size(), "invalid padding");
+ FOLLY_SAFE_CHECK(alignment - remainder <= sp.size(), "invalid padding");
sp.advance(alignment - remainder);
}
}
auto initialLength = read<uint32_t>(chunk);
is64Bit_ = (initialLength == (uint32_t)-1);
auto length = is64Bit_ ? read<uint64_t>(chunk) : initialLength;
- enforce(length <= chunk.size(), "invalid DWARF section");
+ FOLLY_SAFE_CHECK(length <= chunk.size(), "invalid DWARF section");
chunk.reset(chunk.data(), length);
data_.assign(chunk.end(), data_.end());
return true;
// attributes
const char* attributeBegin = section.data();
for (;;) {
- enforce(!section.empty(), "invalid attribute section");
+ FOLLY_SAFE_CHECK(!section.empty(), "invalid attribute section");
auto attr = readAttribute(section);
if (attr.name == 0 && attr.form == 0) {
break;
}
}
- throw std::runtime_error("could not find abbreviation code");
+ FOLLY_SAFE_CHECK(false, "could not find abbreviation code");
}
Dwarf::AttributeValue Dwarf::readAttributeValue(
case DW_FORM_indirect: // form is explicitly specified
return readAttributeValue(sp, readULEB(sp), is64Bit);
default:
- throw std::runtime_error("invalid attribute form");
+ FOLLY_SAFE_CHECK(false, "invalid attribute form");
}
}
folly::StringPiece Dwarf::getStringFromStringSection(uint64_t offset) const {
- enforce(offset < strings_.size(), "invalid strp offset");
+ FOLLY_SAFE_CHECK(offset < strings_.size(), "invalid strp offset");
folly::StringPiece sp(strings_);
sp.advance(offset);
return readNullTerminated(sp);
bool found = false;
while (!found && arangesSection.next(chunk)) {
auto version = read<uint16_t>(chunk);
- enforce(version == 2, "invalid aranges version");
+ FOLLY_SAFE_CHECK(version == 2, "invalid aranges version");
debugInfoOffset = readOffset(chunk, arangesSection.is64Bit());
auto addressSize = read<uint8_t>(chunk);
- enforce(addressSize == sizeof(uintptr_t), "invalid address size");
+ FOLLY_SAFE_CHECK(addressSize == sizeof(uintptr_t), "invalid address size");
auto segmentSize = read<uint8_t>(chunk);
- enforce(segmentSize == 0, "segmented architecture not supported");
+ FOLLY_SAFE_CHECK(segmentSize == 0, "segmented architecture not supported");
// Padded to a multiple of 2 addresses.
// Strangely enough, this is the only place in the DWARF spec that requires
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<uint16_t>(chunk);
- enforce(version >= 2 && version <= 4, "invalid info version");
+ FOLLY_SAFE_CHECK(version >= 2 && version <= 4, "invalid info version");
uint64_t abbrevOffset = readOffset(chunk, debugInfoSection.is64Bit());
auto addressSize = read<uint8_t>(chunk);
- enforce(addressSize == sizeof(uintptr_t), "invalid address size");
+ FOLLY_SAFE_CHECK(addressSize == sizeof(uintptr_t), "invalid address size");
// We survived so far. The first (and only) DIE should be
// DW_TAG_compile_unit
// 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;
folly::StringPiece compilationDirectory)
: compilationDirectory_(compilationDirectory) {
Section section(data);
- enforce(section.next(data_), "invalid line number VM");
+ FOLLY_SAFE_CHECK(section.next(data_), "invalid line number VM");
is64Bit_ = section.is64Bit();
init();
reset();
void Dwarf::LineNumberVM::init() {
version_ = read<uint16_t>(data_);
- enforce(version_ >= 2 && version_ <= 4, "invalid version in line number VM");
+ FOLLY_SAFE_CHECK(version_ >= 2 && version_ <= 4,
+ "invalid version in line number VM");
uint64_t headerLength = readOffset(data_, is64Bit_);
- enforce(headerLength <= data_.size(),
- "invalid line number VM header length");
+ FOLLY_SAFE_CHECK(headerLength <= data_.size(),
+ "invalid line number VM header length");
folly::StringPiece header(data_.data(), headerLength);
data_.assign(header.end(), data_.end());
minLength_ = read<uint8_t>(header);
if (version_ == 4) { // Version 2 and 3 records don't have this
uint8_t maxOpsPerInstruction = read<uint8_t>(header);
- enforce(maxOpsPerInstruction == 1, "VLIW not supported");
+ FOLLY_SAFE_CHECK(maxOpsPerInstruction == 1, "VLIW not supported");
}
defaultIsStmt_ = read<uint8_t>(header);
lineBase_ = read<int8_t>(header); // yes, signed
lineRange_ = read<uint8_t>(header);
opcodeBase_ = read<uint8_t>(header);
- enforce(opcodeBase_ != 0, "invalid opcode base");
+ FOLLY_SAFE_CHECK(opcodeBase_ != 0, "invalid opcode base");
standardOpcodeLengths_ = reinterpret_cast<const uint8_t*>(header.data());
header.advance(opcodeBase_ - 1);
Dwarf::LineNumberVM::FileName Dwarf::LineNumberVM::getFileName(uint64_t index)
const {
- enforce(index != 0, "invalid file index 0");
+ FOLLY_SAFE_CHECK(index != 0, "invalid file index 0");
FileName fn;
if (index <= fileNameCount_) {
folly::StringPiece program = data_;
for (; index; --index) {
- enforce(nextDefineFile(program, fn), "invalid file index");
+ FOLLY_SAFE_CHECK(nextDefineFile(program, fn), "invalid file index");
}
return fn;
return folly::StringPiece();
}
- enforce(index <= includeDirectoryCount_, "invalid include directory");
+ FOLLY_SAFE_CHECK(index <= includeDirectoryCount_,
+ "invalid include directory");
folly::StringPiece includeDirectories = includeDirectories_;
folly::StringPiece dir;
// Extended opcode
auto length = readULEB(program);
// the opcode itself should be included in the length, so length >= 1
- enforce(length != 0, "invalid extended opcode length");
+ FOLLY_SAFE_CHECK(length != 0, "invalid extended opcode length");
read<uint8_t>(program); // extended opcode
--length;
if (opcode == DW_LNE_define_file) {
- enforce(readFileName(program, fn),
- "invalid empty file in DW_LNE_define_file");
+ FOLLY_SAFE_CHECK(readFileName(program, fn),
+ "invalid empty file in DW_LNE_define_file");
return true;
}
// Extended opcode
auto length = readULEB(program);
// the opcode itself should be included in the length, so length >= 1
- enforce(length != 0, "invalid extende opcode length");
+ FOLLY_SAFE_CHECK(length != 0, "invalid extended opcode length");
auto extendedOpcode = read<uint8_t>(program);
--length;
* 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
template <class Fn>
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<ElfW(Sym)>(section.sh_offset);
const ElfW(Sym)* end = sym + (section.sh_size / section.sh_entsize);
namespace folly {
namespace symbolizer {
-ElfFile::ElfFile()
+ElfFile::ElfFile() noexcept
: fd_(-1),
file_(static_cast<char*>(MAP_FAILED)),
length_(0),
}
ElfFile::ElfFile(const char* name, bool readOnly)
- : fd_(open(name, (readOnly) ? O_RDONLY : O_RDWR)),
+ : fd_(-1),
file_(static_cast<char*>(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;
}
file_ = static_cast<char*>(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() {
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
#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<ElfW(Phdr)>(elfHeader.e_phoff);
bool foundBase = false;
}
}
- 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<ElfW(Shdr)>(elfHeader().e_shoff + idx * sizeof(ElfW(Shdr)));
}
}
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;
}
#include <stdexcept>
#include <system_error>
+#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 <class... Args>
-inline void enforce(bool v, Args... args) {
- if (UNLIKELY(!v)) {
- throw std::runtime_error(folly::to<std::string>(args...));
- }
-}
-
/**
* ELF file parser.
*
*/
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);
* 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<const ElfW(Shdr)*, const ElfW(Sym)*> Symbol;
Symbol getDefinitionByAddress(uintptr_t address) const;
*
* 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.
+ *
+ * Returns {nullptr, nullptr} if not found.
*/
Symbol getSymbolByName(const char* name) const;
template <class T>
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<T>(*section, symbol->st_value);
}
template <class T>
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<T>(*section, addr);
}
template <class T>
const typename std::enable_if<std::is_pod<T>::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<T*>(file_ + offset);
}
// 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<T>(section.sh_offset + (addr - section.sh_addr));
}
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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_ */
+
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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_ */
+
* limitations under the License.
*/
+// Must be first to ensure that UNW_LOCAL_ONLY is defined
+#define UNW_LOCAL_ONLY 1
+#include <libunwind.h>
#include "folly/experimental/symbolizer/Symbolizer.h"
-#include <boost/regex.hpp>
-#include <glog/logging.h>
+#include <limits.h>
+
+#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
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<std::string, ElfFile> 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
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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_ */
+
--- /dev/null
+/*
+ * 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