Commit 72cf4378 authored by Martin Martin's avatar Martin Martin Committed by Facebook Github Bot

Create a "fast" stack trace printer that caches symbol lookups.

Summary: Create a "fast" stack trace printer that caches symbol lookups.

Reviewed By: yfeldblum

Differential Revision: D6000178

fbshipit-source-id: e81be1db21cfcd2cd26524c9ef6262337153ae36
parent daeedd94
...@@ -33,7 +33,6 @@ ...@@ -33,7 +33,6 @@
#include <folly/ScopeGuard.h> #include <folly/ScopeGuard.h>
#include <folly/experimental/symbolizer/ElfCache.h> #include <folly/experimental/symbolizer/ElfCache.h>
#include <folly/experimental/symbolizer/Symbolizer.h> #include <folly/experimental/symbolizer/Symbolizer.h>
#include <folly/portability/PThread.h>
#include <folly/portability/SysSyscall.h> #include <folly/portability/SysSyscall.h>
#include <folly/portability/Unistd.h> #include <folly/portability/Unistd.h>
...@@ -128,7 +127,7 @@ void callPreviousSignalHandler(int signum) { ...@@ -128,7 +127,7 @@ void callPreviousSignalHandler(int signum) {
// in our signal handler at a time. // in our signal handler at a time.
// //
// Leak it so we don't have to worry about destruction order // Leak it so we don't have to worry about destruction order
StackTracePrinter* gStackTracePrinter = new StackTracePrinter(); SafeStackTracePrinter* gStackTracePrinter = new SafeStackTracePrinter();
void printDec(uint64_t val) { void printDec(uint64_t val) {
char buf[20]; char buf[20];
......
...@@ -79,8 +79,15 @@ void SymbolizedFrame::set( ...@@ -79,8 +79,15 @@ void SymbolizedFrame::set(
Dwarf(file.get()).findAddress(address, location, mode); Dwarf(file.get()).findAddress(address, location, mode);
} }
Symbolizer::Symbolizer(ElfCacheBase* cache, Dwarf::LocationInfoMode mode) Symbolizer::Symbolizer(
: cache_(cache ? cache : defaultElfCache()), mode_(mode) {} ElfCacheBase* cache,
Dwarf::LocationInfoMode mode,
size_t symbolCacheSize)
: cache_(cache ? cache : defaultElfCache()), mode_(mode) {
if (symbolCacheSize > 0) {
symbolCache_.emplace(folly::in_place, symbolCacheSize);
}
}
void Symbolizer::symbolize( void Symbolizer::symbolize(
const uintptr_t* addresses, const uintptr_t* addresses,
...@@ -139,12 +146,29 @@ void Symbolizer::symbolize( ...@@ -139,12 +146,29 @@ void Symbolizer::symbolize(
} }
auto const addr = addresses[i]; auto const addr = addresses[i];
if (symbolCache_) {
// Need a write lock, because EvictingCacheMap brings found item to
// front of eviction list.
auto lockedSymbolCache = symbolCache_->wlock();
auto const iter = lockedSymbolCache->find(addr);
if (iter != lockedSymbolCache->end()) {
frame = iter->second;
continue;
}
}
// Get the unrelocated, ELF-relative address. // Get the unrelocated, ELF-relative address.
auto const adjusted = addr - base; auto const adjusted = addr - base;
if (elfFile->getSectionContainingAddress(adjusted)) { if (elfFile->getSectionContainingAddress(adjusted)) {
frame.set(elfFile, adjusted, mode_); frame.set(elfFile, adjusted, mode_);
--remaining; --remaining;
if (symbolCache_) {
// frame may already have been set here. That's ok, we'll just
// overwrite, which doesn't cause a correctness problem.
symbolCache_->wlock()->set(addr, frame);
}
} }
} }
} }
...@@ -378,7 +402,9 @@ void StringSymbolizePrinter::doPrint(StringPiece sp) { ...@@ -378,7 +402,9 @@ void StringSymbolizePrinter::doPrint(StringPiece sp) {
buf_.append(sp.data(), sp.size()); buf_.append(sp.data(), sp.size());
} }
StackTracePrinter::StackTracePrinter(size_t minSignalSafeElfCacheSize, int fd) SafeStackTracePrinter::SafeStackTracePrinter(
size_t minSignalSafeElfCacheSize,
int fd)
: fd_(fd), : fd_(fd),
elfCache_(std::max(countLoadedElfFiles(), minSignalSafeElfCacheSize)), elfCache_(std::max(countLoadedElfFiles(), minSignalSafeElfCacheSize)),
printer_( printer_(
...@@ -387,12 +413,12 @@ StackTracePrinter::StackTracePrinter(size_t minSignalSafeElfCacheSize, int fd) ...@@ -387,12 +413,12 @@ StackTracePrinter::StackTracePrinter(size_t minSignalSafeElfCacheSize, int fd)
size_t(64) << 10), // 64KiB size_t(64) << 10), // 64KiB
addresses_(std::make_unique<FrameArray<kMaxStackTraceDepth>>()) {} addresses_(std::make_unique<FrameArray<kMaxStackTraceDepth>>()) {}
void StackTracePrinter::flush() { void SafeStackTracePrinter::flush() {
printer_.flush(); printer_.flush();
fsyncNoInt(fd_); fsyncNoInt(fd_);
} }
void StackTracePrinter::printStackTrace(bool symbolize) { void SafeStackTracePrinter::printStackTrace(bool symbolize) {
SCOPE_EXIT { SCOPE_EXIT {
flush(); flush();
}; };
...@@ -408,7 +434,7 @@ void StackTracePrinter::printStackTrace(bool symbolize) { ...@@ -408,7 +434,7 @@ void StackTracePrinter::printStackTrace(bool symbolize) {
// Skip the top 2 frames: // Skip the top 2 frames:
// getStackTraceSafe // getStackTraceSafe
// StackTracePrinter::printStackTrace (here) // SafeStackTracePrinter::printStackTrace (here)
// //
// Leaving signalHandler on the stack for clarity, I think. // Leaving signalHandler on the stack for clarity, I think.
printer_.println(*addresses_, 2); printer_.println(*addresses_, 2);
...@@ -422,5 +448,51 @@ void StackTracePrinter::printStackTrace(bool symbolize) { ...@@ -422,5 +448,51 @@ void StackTracePrinter::printStackTrace(bool symbolize) {
} }
} }
FastStackTracePrinter::FastStackTracePrinter(
std::unique_ptr<SymbolizePrinter> printer,
size_t elfCacheSize,
size_t symbolCacheSize)
: elfCache_(
elfCacheSize == 0
? nullptr
: new ElfCache{std::max(countLoadedElfFiles(), elfCacheSize)}),
printer_(std::move(printer)),
symbolizer_(
elfCache_ ? elfCache_.get() : defaultElfCache(),
Dwarf::LocationInfoMode::FULL,
symbolCacheSize) {}
FastStackTracePrinter::~FastStackTracePrinter() {}
void FastStackTracePrinter::printStackTrace(bool symbolize) {
SCOPE_EXIT {
printer_->flush();
};
FrameArray<kMaxStackTraceDepth> addresses;
if (!getStackTraceSafe(addresses)) {
printer_->print("(error retrieving stack trace)\n");
} else if (symbolize) {
symbolizer_.symbolize(addresses);
// Skip the top 2 frames:
// getStackTraceSafe
// FastStackTracePrinter::printStackTrace (here)
printer_->println(addresses, 2);
} else {
printer_->print("(safe mode, symbolizer not available)\n");
AddressFormatter formatter;
for (size_t i = 0; i < addresses.frameCount; ++i) {
printer_->print(formatter.format(addresses.addresses[i]));
printer_->print("\n");
}
}
}
void FastStackTracePrinter::flush() {
printer_->flush();
}
} // namespace symbolizer } // namespace symbolizer
} // namespace folly } // namespace folly
...@@ -22,8 +22,11 @@ ...@@ -22,8 +22,11 @@
#include <string> #include <string>
#include <folly/FBString.h> #include <folly/FBString.h>
#include <folly/Optional.h>
#include <folly/Range.h> #include <folly/Range.h>
#include <folly/String.h> #include <folly/String.h>
#include <folly/Synchronized.h>
#include <folly/container/EvictingCacheMap.h>
#include <folly/experimental/symbolizer/Dwarf.h> #include <folly/experimental/symbolizer/Dwarf.h>
#include <folly/experimental/symbolizer/Elf.h> #include <folly/experimental/symbolizer/Elf.h>
#include <folly/experimental/symbolizer/ElfCache.h> #include <folly/experimental/symbolizer/ElfCache.h>
...@@ -122,8 +125,8 @@ class Symbolizer { ...@@ -122,8 +125,8 @@ class Symbolizer {
explicit Symbolizer( explicit Symbolizer(
ElfCacheBase* cache, ElfCacheBase* cache,
Dwarf::LocationInfoMode mode = kDefaultLocationInfoMode); Dwarf::LocationInfoMode mode = kDefaultLocationInfoMode,
size_t symbolCacheSize = 0);
/** /**
* Symbolize given addresses. * Symbolize given addresses.
*/ */
...@@ -148,6 +151,9 @@ class Symbolizer { ...@@ -148,6 +151,9 @@ class Symbolizer {
private: private:
ElfCacheBase* const cache_; ElfCacheBase* const cache_;
const Dwarf::LocationInfoMode mode_; const Dwarf::LocationInfoMode mode_;
using SymbolCache = EvictingCacheMap<uintptr_t, SymbolizedFrame>;
folly::Optional<Synchronized<SymbolCache>> symbolCache_;
}; };
/** /**
...@@ -209,6 +215,12 @@ class SymbolizePrinter { ...@@ -209,6 +215,12 @@ class SymbolizePrinter {
} }
} }
/**
* If output buffered inside this class, send it to the output stream, so that
* any output done in other ways appears after this.
*/
virtual void flush() {}
virtual ~SymbolizePrinter() {} virtual ~SymbolizePrinter() {}
enum Options { enum Options {
...@@ -276,7 +288,7 @@ class FDSymbolizePrinter : public SymbolizePrinter { ...@@ -276,7 +288,7 @@ class FDSymbolizePrinter : public SymbolizePrinter {
public: public:
explicit FDSymbolizePrinter(int fd, int options = 0, size_t bufferSize = 0); explicit FDSymbolizePrinter(int fd, int options = 0, size_t bufferSize = 0);
~FDSymbolizePrinter() override; ~FDSymbolizePrinter() override;
void flush(); virtual void flush() override;
private: private:
void doPrint(StringPiece sp) override; void doPrint(StringPiece sp) override;
...@@ -328,7 +340,7 @@ class StringSymbolizePrinter : public SymbolizePrinter { ...@@ -328,7 +340,7 @@ class StringSymbolizePrinter : public SymbolizePrinter {
* descriptor is more important than performance. * descriptor is more important than performance.
* *
* Make sure to create one of these on startup, not in the signal handler, as * Make sure to create one of these on startup, not in the signal handler, as
* the constructo allocates on the heap, whereas the other methods don't. Best * the constructor allocates on the heap, whereas the other methods don't. Best
* practice is to just leak this object, rather than worry about destruction * practice is to just leak this object, rather than worry about destruction
* order. * order.
* *
...@@ -336,11 +348,11 @@ class StringSymbolizePrinter : public SymbolizePrinter { ...@@ -336,11 +348,11 @@ class StringSymbolizePrinter : public SymbolizePrinter {
* threads at the same time, you need to do your own locking to ensure you don't * threads at the same time, you need to do your own locking to ensure you don't
* call these methods from multiple threads. They are signal safe, however. * call these methods from multiple threads. They are signal safe, however.
*/ */
class StackTracePrinter { class SafeStackTracePrinter {
public: public:
static constexpr size_t kDefaultMinSignalSafeElfCacheSize = 500; static constexpr size_t kDefaultMinSignalSafeElfCacheSize = 500;
explicit StackTracePrinter( explicit SafeStackTracePrinter(
size_t minSignalSafeElfCacheSize = kDefaultMinSignalSafeElfCacheSize, size_t minSignalSafeElfCacheSize = kDefaultMinSignalSafeElfCacheSize,
int fd = STDERR_FILENO); int fd = STDERR_FILENO);
...@@ -348,6 +360,9 @@ class StackTracePrinter { ...@@ -348,6 +360,9 @@ class StackTracePrinter {
* Only allocates on the stack and is signal-safe but not thread-safe. Don't * Only allocates on the stack and is signal-safe but not thread-safe. Don't
* call printStackTrace() on the same StackTracePrinter object from multiple * call printStackTrace() on the same StackTracePrinter object from multiple
* threads at the same time. * threads at the same time.
*
* This is NOINLINE to make sure it shows up in the stack we grab, which makes
* it easy to skip printing it.
*/ */
FOLLY_NOINLINE void printStackTrace(bool symbolize); FOLLY_NOINLINE void printStackTrace(bool symbolize);
...@@ -367,5 +382,38 @@ class StackTracePrinter { ...@@ -367,5 +382,38 @@ class StackTracePrinter {
std::unique_ptr<FrameArray<kMaxStackTraceDepth>> addresses_; std::unique_ptr<FrameArray<kMaxStackTraceDepth>> addresses_;
}; };
/**
* Use this class to print a stack trace from normal code. It will malloc and
* won't flush or sync.
*
* These methods are thread safe, through locking. However, they are not signal
* safe.
*/
class FastStackTracePrinter {
public:
static constexpr size_t kDefaultSymbolCacheSize = 10000;
explicit FastStackTracePrinter(
std::unique_ptr<SymbolizePrinter> printer,
size_t elfCacheSize = 0, // 0 means "use the default elf cache instance."
size_t symbolCacheSize = kDefaultSymbolCacheSize);
~FastStackTracePrinter();
/**
* This is NOINLINE to make sure it shows up in the stack we grab, which makes
* it easy to skip printing it.
*/
FOLLY_NOINLINE void printStackTrace(bool symbolize);
void flush();
private:
static constexpr size_t kMaxStackTraceDepth = 100;
const std::unique_ptr<ElfCache> elfCache_;
const std::unique_ptr<SymbolizePrinter> printer_;
Symbolizer symbolizer_;
};
} // namespace symbolizer } // namespace symbolizer
} // namespace folly } // namespace folly
...@@ -14,6 +14,9 @@ ...@@ -14,6 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
#include <cstring>
#include <folly/experimental/TestUtil.h>
#include <folly/experimental/symbolizer/StackTrace.h> #include <folly/experimental/symbolizer/StackTrace.h>
#include <folly/experimental/symbolizer/Symbolizer.h> #include <folly/experimental/symbolizer/Symbolizer.h>
...@@ -88,3 +91,75 @@ TEST(StackTraceTest, Signal) { ...@@ -88,3 +91,75 @@ TEST(StackTraceTest, Signal) {
raise(SIGUSR1); raise(SIGUSR1);
EXPECT_TRUE(handled); EXPECT_TRUE(handled);
} }
ssize_t read_all(int fd, uint8_t* buffer, size_t size) {
uint8_t* pos = buffer;
ssize_t bytes_read;
do {
bytes_read = read(fd, pos, size);
if (bytes_read < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
continue;
}
return bytes_read;
}
pos += bytes_read;
size -= bytes_read;
} while (bytes_read > 0 && size > 0);
return pos - buffer;
}
// Returns the position in the file after done reading.
off_t get_stack_trace(int fd, size_t file_pos, uint8_t* buffer, size_t count) {
off_t rv = lseek(fd, file_pos, SEEK_SET);
assert(rv == (off_t)file_pos);
// Subtract 1 from size of buffer to hold nullptr.
ssize_t bytes_read = read_all(fd, buffer, count - 1);
assert(bytes_read > 0);
buffer[bytes_read] = '\0';
return lseek(fd, 0, SEEK_CUR);
}
template <class StackTracePrinter>
void testStackTracePrinter(StackTracePrinter& printer, int fd) {
ASSERT_GT(fd, 0);
printer.printStackTrace(true);
printer.flush();
std::array<uint8_t, 4000> first;
off_t pos = get_stack_trace(fd, 0, first.data(), first.size());
ASSERT_GT(pos, 0);
printer.printStackTrace(true);
printer.flush();
std::array<uint8_t, 4000> second;
get_stack_trace(fd, pos, second.data(), second.size());
// The first two lines refer to this stack frame, which is different in the
// two cases, so strip those off. The rest should be equal.
ASSERT_STREQ(
strchr(strchr((const char*)first.data(), '\n') + 1, '\n') + 1,
strchr(strchr((const char*)second.data(), '\n') + 1, '\n') + 1);
}
TEST(StackTraceTest, SafeStackTracePrinter) {
test::TemporaryFile file;
SafeStackTracePrinter printer{10, file.fd()};
testStackTracePrinter<SafeStackTracePrinter>(printer, file.fd());
}
TEST(StackTraceTest, FastStackTracePrinter) {
test::TemporaryFile file;
FastStackTracePrinter printer{
std::make_unique<FDSymbolizePrinter>(file.fd())};
testStackTracePrinter<FastStackTracePrinter>(printer, file.fd());
}
...@@ -53,10 +53,10 @@ TEST(Symbolizer, Single) { ...@@ -53,10 +53,10 @@ TEST(Symbolizer, Single) {
} }
} }
FrameArray<100> goldenFrames; FrameArray<100>* framesToFill{nullptr};
int comparator(const void* ap, const void* bp) { int comparator(const void* ap, const void* bp) {
getStackTrace(goldenFrames); getStackTrace(*framesToFill);
int a = *static_cast<const int*>(ap); int a = *static_cast<const int*>(ap);
int b = *static_cast<const int*>(bp); int b = *static_cast<const int*>(bp);
...@@ -66,10 +66,12 @@ int comparator(const void* ap, const void* bp) { ...@@ -66,10 +66,12 @@ int comparator(const void* ap, const void* bp) {
// Test stack frames... // Test stack frames...
FOLLY_NOINLINE void bar(); FOLLY_NOINLINE void bar();
void bar() { void bar(FrameArray<100>& frames) {
framesToFill = &frames;
int a[2] = {1, 2}; int a[2] = {1, 2};
// Use qsort, which is in a different library // Use qsort, which is in a different library
qsort(a, 2, sizeof(int), comparator); qsort(a, 2, sizeof(int), comparator);
framesToFill = nullptr;
} }
class ElfCacheTest : public testing::Test { class ElfCacheTest : public testing::Test {
...@@ -78,8 +80,10 @@ class ElfCacheTest : public testing::Test { ...@@ -78,8 +80,10 @@ class ElfCacheTest : public testing::Test {
}; };
// Capture "golden" stack trace with default-configured Symbolizer // Capture "golden" stack trace with default-configured Symbolizer
FrameArray<100> goldenFrames;
void ElfCacheTest::SetUp() { void ElfCacheTest::SetUp() {
bar(); bar(goldenFrames);
Symbolizer symbolizer; Symbolizer symbolizer;
symbolizer.symbolize(goldenFrames); symbolizer.symbolize(goldenFrames);
// At least 3 stack frames from us + getStackTrace() // At least 3 stack frames from us + getStackTrace()
...@@ -114,6 +118,22 @@ TEST_F(ElfCacheTest, SignalSafeElfCache) { ...@@ -114,6 +118,22 @@ TEST_F(ElfCacheTest, SignalSafeElfCache) {
runElfCacheTest(symbolizer); runElfCacheTest(symbolizer);
} }
} }
TEST(SymbolizerTest, SymbolCache) {
Symbolizer symbolizer(nullptr, Dwarf::LocationInfoMode::FULL, 100);
FrameArray<100> frames;
bar(frames);
symbolizer.symbolize(frames);
FrameArray<100> frames2;
bar(frames2);
symbolizer.symbolize(frames2);
for (size_t i = 0; i < frames.frameCount; i++) {
EXPECT_STREQ(frames.frames[i].name, frames2.frames[i].name);
}
}
} // namespace test } // namespace test
} // namespace symbolizer } // namespace symbolizer
} // namespace folly } // namespace folly
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment