X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=blobdiff_plain;f=folly%2Fexperimental%2Fsymbolizer%2FSymbolizer.cpp;h=13fe68fa38d4ab054f7b039ccbd301a1353d0941;hp=79345c8b6eee49670e697560f53f62d019f2943a;hb=cb3a7e4572affb970e7491a94c9503b6a2745d1e;hpb=fd3c895f771ba28e43eb2de7460942c2927d0679 diff --git a/folly/experimental/symbolizer/Symbolizer.cpp b/folly/experimental/symbolizer/Symbolizer.cpp index 79345c8b..13fe68fa 100644 --- a/folly/experimental/symbolizer/Symbolizer.cpp +++ b/folly/experimental/symbolizer/Symbolizer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2013 Facebook, Inc. + * Copyright 2016 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,197 +14,82 @@ * 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 #include +#include +#include -#include "folly/Conv.h" -#include "folly/FileUtil.h" -#include "folly/String.h" +#ifdef __GNUC__ +#include +#include +#endif -#include "folly/experimental/symbolizer/Elf.h" -#include "folly/experimental/symbolizer/Dwarf.h" -#include "folly/experimental/symbolizer/LineReader.h" +#include +#include +#include +#include -namespace folly { -namespace symbolizer { - -namespace { +#include +#include +#include -/** - * 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; -} -/** - * 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()); -} - -/** - * 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()); -} - -/** - * Parse a line from /proc/self/maps +/* + * This is declared in `link.h' on Linux platforms, but apparently not on the + * Mac version of the file. It's harmless to declare again, in any case. + * + * Note that declaring it with `extern "C"` results in linkage conflicts. */ -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; - } - - // Remove trailing newline, if any - if (line.back() == '\n') { - line.pop_back(); - } - - // from - from = readHex(line); - if (line.empty() || line.front() != '-') { - return false; - } - line.pop_front(); - - // to - to = readHex(line); - if (line.empty() || line.front() != ' ') { - return false; - } - line.pop_front(); - - // perms - skipNS(line); - if (line.empty() || line.front() != ' ') { - return false; - } - line.pop_front(); - - 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 - } - - // dev - skipNS(line); - if (line.empty() || line.front() != ' ') { - return false; - } - line.pop_front(); +extern struct r_debug _r_debug; - // inode - skipNS(line); - if (line.empty() || line.front() != ' ') { - return false; - } +namespace folly { +namespace symbolizer { - skipWS(line); - if (line.empty()) { - fileName.clear(); - return true; - } +namespace { - fileName = line; - return true; +ElfCache* defaultElfCache() { + static constexpr size_t defaultCapacity = 500; + static auto cache = new ElfCache(defaultCapacity); + return cache; } } // 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; - } +void SymbolizedFrame::set(const std::shared_ptr& file, + uintptr_t address, + Dwarf::LocationInfoMode mode) { + clear(); + found = true; - 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)); + address += file->getBaseAddress(); + auto sym = file->getDefinitionByAddress(address); + if (!sym.first) { + return; } - if (r < 0) { - return -1; - } + file_ = file; + name = file->getSymbolName(sym); - return idx; + Dwarf(file.get()).findAddress(address, location, mode); } -void Symbolizer::symbolize(AddressInfo* addresses, size_t addressCount) { +Symbolizer::Symbolizer(ElfCacheBase* cache, Dwarf::LocationInfoMode mode) + : cache_(cache ?: defaultElfCache()), mode_(mode) { +} + +void Symbolizer::symbolize(const uintptr_t* addresses, + SymbolizedFrame* frames, + size_t addrCount) { size_t remaining = 0; - for (size_t i = 0; i < addressCount; ++i) { - auto& ainfo = addresses[i]; - if (!ainfo.found) { + for (size_t i = 0; i < addrCount; ++i) { + auto& frame = frames[i]; + if (!frame.found) { ++remaining; - ainfo.name.clear(); - ainfo.location = Dwarf::LocationInfo(); + frame.clear(); } } @@ -212,186 +97,284 @@ void Symbolizer::symbolize(AddressInfo* addresses, size_t addressCount) { return; } - int fd = openNoInt("/proc/self/maps", O_RDONLY); - if (fd == -1) { + if (_r_debug.r_version != 1) { return; } - char buf[PATH_MAX + 100]; // Long enough for any line - LineReader reader(fd, buf, sizeof(buf)); - - char fileNameBuf[PATH_MAX]; - - while (remaining != 0) { - StringPiece line; - if (reader.readLine(line) != LineReader::kReading) { - break; - } - - // Parse line - uintptr_t from; - uintptr_t to; - StringPiece fileName; - if (!parseProcMapsLine(line, from, to, fileName)) { + char selfPath[PATH_MAX + 8]; + ssize_t selfSize; + if ((selfSize = readlink("/proc/self/exe", selfPath, PATH_MAX + 1)) == -1) { + // Something has gone terribly wrong. + return; + } + selfPath[selfSize] = '\0'; + + for (auto lmap = _r_debug.r_map; + lmap != nullptr && remaining != 0; + lmap = lmap->l_next) { + // The empty string is used in place of the filename for the link_map + // corresponding to the running executable. Additionally, the `l_addr' is + // 0 and the link_map appears to be first in the list---but none of this + // behavior appears to be documented, so checking for the empty string is + // as good as anything. + auto const objPath = lmap->l_name[0] != '\0' ? lmap->l_name : selfPath; + + auto const elfFile = cache_->getFile(objPath); + if (!elfFile) { 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) { + // Get the address at which the object is loaded. We have to use the ELF + // header for the running executable, since its `l_addr' is zero, but we + // should use `l_addr' for everything else---in particular, if the object + // is position-independent, getBaseAddress() (which is p_vaddr) will be 0. + auto const base = lmap->l_addr != 0 + ? lmap->l_addr + : elfFile->getBaseAddress(); + + for (size_t i = 0; i < addrCount && remaining != 0; ++i) { + auto& frame = frames[i]; + if (frame.found) { continue; } - uintptr_t address = ainfo.address; + auto const addr = addresses[i]; + // Get the unrelocated, ELF-relative address. + auto const adjusted = addr - base; - // 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 (elfFile->getSectionContainingAddress(adjusted)) { + frame.set(elfFile, adjusted, mode_); + --remaining; } - - 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"; +constexpr char kHexChars[] = "0123456789abcdef"; +constexpr auto kAddressColor = SymbolizePrinter::Color::BLUE; +constexpr auto kFunctionColor = SymbolizePrinter::Color::PURPLE; +constexpr auto kFileColor = SymbolizePrinter::Color::DEFAULT; } // namespace -void SymbolizePrinter::print(const AddressInfo& ainfo) { - uintptr_t address = ainfo.address; +constexpr char AddressFormatter::bufTemplate[]; +constexpr std::array + SymbolizePrinter::kColorMap; + +AddressFormatter::AddressFormatter() { + memcpy(buf_, bufTemplate, sizeof(buf_)); +} + +folly::StringPiece AddressFormatter::format(uintptr_t 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* end = buf_ + sizeof(buf_) - 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 folly::StringPiece(buf_, end); +} + +void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) { + if (options_ & TERSE) { + printTerse(address, frame); return; } - if (ainfo.name.empty()) { - doPrint(" (unknown)\n"); - } else if (ainfo.name.size() >= sizeof(mangledBuf)) { - doPrint(" "); - doPrint(ainfo.name); - doPrint("\n"); - } else { - memcpy(mangledBuf, ainfo.name.data(), ainfo.name.size()); - mangledBuf[ainfo.name.size()] = '\0'; + SCOPE_EXIT { color(Color::DEFAULT); }; - char demangledBuf[1024]; - demangle(mangledBuf, demangledBuf, sizeof(demangledBuf)); - doPrint(" "); - doPrint(demangledBuf); - doPrint("\n"); + if (!(options_ & NO_FRAME_ADDRESS)) { + color(kAddressColor); + + AddressFormatter formatter; + doPrint(formatter.format(address)); + } + + const char padBuf[] = " "; + folly::StringPiece pad(padBuf, + sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t))); + + color(kFunctionColor); + if (!frame.found) { + doPrint(" (not found)"); + return; } - 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"); + if (!frame.name || frame.name[0] == '\0') { + doPrint(" (unknown)"); + } else { + char demangledBuf[2048]; + demangle(frame.name, demangledBuf, sizeof(demangledBuf)); + doPrint(" "); + doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf); } - 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); + if (!(options_ & NO_FILE_AND_LINE)) { + color(kFileColor); + char fileBuf[PATH_MAX]; + fileBuf[0] = '\0'; + if (frame.location.hasFileAndLine) { + frame.location.file.toBuffer(fileBuf, sizeof(fileBuf)); doPrint("\n"); + doPrint(pad); + doPrint(fileBuf); + + char buf[22]; + uint32_t n = uint64ToBufferUnsafe(frame.location.line, buf); + doPrint(":"); + doPrint(StringPiece(buf, n)); + } + + if (frame.location.hasMainFile) { + char mainFileBuf[PATH_MAX]; + mainFileBuf[0] = '\0'; + frame.location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf)); + if (!frame.location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) { + doPrint("\n"); + doPrint(pad); + doPrint("-> "); + doPrint(mainFileBuf); + } } } } -void SymbolizePrinter::print(const AddressInfo* addresses, - size_t addressCount) { - for (size_t i = 0; i < addressCount; ++i) { - auto& ainfo = addresses[i]; - print(ainfo); +void SymbolizePrinter::color(SymbolizePrinter::Color color) { + if ((options_ & COLOR) == 0 && + ((options_ & COLOR_IF_TTY) == 0 || !isTty_)) { + return; + } + if (color < 0 || color >= kColorMap.size()) { + return; } + doPrint(kColorMap[color]); +} + +void SymbolizePrinter::println(uintptr_t address, + const SymbolizedFrame& frame) { + print(address, frame); + doPrint("\n"); +} + +void SymbolizePrinter::printTerse(uintptr_t address, + const SymbolizedFrame& frame) { + if (frame.found && frame.name && frame.name[0] != '\0') { + char demangledBuf[2048] = {0}; + demangle(frame.name, demangledBuf, sizeof(demangledBuf)); + doPrint(demangledBuf[0] == '\0' ? frame.name : demangledBuf); + } else { + // Can't use sprintf, not async-signal-safe + static_assert(sizeof(uintptr_t) <= 8, "huge uintptr_t?"); + char buf[] = "0x0000000000000000"; + char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t)); + char* p = end; + *p-- = '\0'; + while (address != 0) { + *p-- = kHexChars[address & 0xf]; + address >>= 4; + } + doPrint(StringPiece(buf, end)); + } +} + +void SymbolizePrinter::println(const uintptr_t* addresses, + const SymbolizedFrame* frames, + size_t frameCount) { + for (size_t i = 0; i < frameCount; ++i) { + println(addresses[i], frames[i]); + } +} + +namespace { + +int getFD(const std::ios& stream) { +#ifdef __GNUC__ + std::streambuf* buf = stream.rdbuf(); + using namespace __gnu_cxx; + + { + auto sbuf = dynamic_cast*>(buf); + if (sbuf) { + return fileno(sbuf->file()); + } + } + { + auto sbuf = dynamic_cast*>(buf); + if (sbuf) { + return sbuf->fd(); + } + } +#endif // __GNUC__ + return -1; +} + +bool isColorfulTty(int options, int fd) { + if ((options & SymbolizePrinter::TERSE) != 0 || + (options & SymbolizePrinter::COLOR_IF_TTY) == 0 || + fd < 0 || !::isatty(fd)) { + return false; + } + auto term = ::getenv("TERM"); + return !(term == nullptr || term[0] == '\0' || strcmp(term, "dumb") == 0); +} + +} // anonymous namespace + +OStreamSymbolizePrinter::OStreamSymbolizePrinter(std::ostream& out, int options) + : SymbolizePrinter(options, isColorfulTty(options, getFD(out))), + out_(out) { } void OStreamSymbolizePrinter::doPrint(StringPiece sp) { out_ << sp; } +FDSymbolizePrinter::FDSymbolizePrinter(int fd, int options, size_t bufferSize) + : SymbolizePrinter(options, isColorfulTty(options, fd)), + fd_(fd), + buffer_(bufferSize ? IOBuf::create(bufferSize) : nullptr) { +} + +FDSymbolizePrinter::~FDSymbolizePrinter() { + flush(); +} + void FDSymbolizePrinter::doPrint(StringPiece sp) { - writeFull(fd_, sp.data(), sp.size()); + if (buffer_) { + if (sp.size() > buffer_->tailroom()) { + flush(); + writeFull(fd_, sp.data(), sp.size()); + } else { + memcpy(buffer_->writableTail(), sp.data(), sp.size()); + buffer_->append(sp.size()); + } + } else { + writeFull(fd_, sp.data(), sp.size()); + } +} + +void FDSymbolizePrinter::flush() { + if (buffer_ && !buffer_->empty()) { + writeFull(fd_, buffer_->data(), buffer_->length()); + buffer_->clear(); + } +} + +FILESymbolizePrinter::FILESymbolizePrinter(FILE* file, int options) + : SymbolizePrinter(options, isColorfulTty(options, fileno(file))), + file_(file) { +} + +void FILESymbolizePrinter::doPrint(StringPiece sp) { + fwrite(sp.data(), 1, sp.size(), file_); } -std::ostream& operator<<(std::ostream& out, const AddressInfo& ainfo) { - OStreamSymbolizePrinter osp(out); - osp.print(ainfo); - return out; +void StringSymbolizePrinter::doPrint(StringPiece sp) { + buf_.append(sp.data(), sp.size()); } } // namespace symbolizer