Commit 380c720b authored by Matt Ma's avatar Matt Ma Committed by Facebook Github Bot

folly/experimental/symbolizer: symbolize inlined functions

Summary:
The previous approach (D16760775) reserved space for inlined function info in each `SymbolizedFrame`.

```
struct SymbolizedFrame {
  Dwarf::LocationInfo location;
  Dwarf::LocationInfo inlineLocations[Dwarf::kMaxLocationInfoPerFrame];
  ...
```

That increased the size of `SymbolizedFrame` and `FrameArray` and lead to stack overflow in some already deep stacks.
```
template <size_t N>
struct FrameArray {
  FrameArray() {}

  size_t frameCount = 0;
  uintptr_t addresses[N];
  SymbolizedFrame frames[N];
};
```

To avoid allocate more space on stack, changed to use extra frames to store inline calls:
- Usually the callers allocate `FrameArray<100>` frames, but the stack trace is usually smaller than 100
- Use the unused slots to fill in inlined function info:
 -- each function gets at most `kMaxLocationInfoPerFrame` (currently 3) inlined entries
 -- when the available buffer fills up no more inlined functions are filled in.

To find the inline calling stack, we need first need to find the subprogram Debug Info Entry (with tag DW_TAG_subprogram) with the given address, then recursively find all the inline subroutines (with tag DW_TAG_inlined_subroutine) in the call stack. Sadly debug info has no index we can use for jump, and a linear search over debug info entries (and their attributes) is needed during the process, which would cause performance regression.

```
buck run mode/opt folly/experimental/symbolizer/test:dwarf_benchmark -- --benchmark

============================================================================
folly/experimental/symbolizer/test/DwarfBenchmark.cpprelative  time/iter  iters/s
============================================================================
DwarfFindAddressFast                                         4.03us  248.36K
DwarfFindAddressFull                                         4.03us  248.18K
DwarfFindAddressFullWithInline                             293.23us    3.41K
============================================================================
```

Reviewed By: luciang

Differential Revision: D17586385

fbshipit-source-id: 1b84b3f3a576573ce24092b433a501a3bdf76be0
parent e4d37c6c
......@@ -83,7 +83,7 @@ void printExceptionInfo(
symbolizer.symbolize(addresses, frames.data(), frameCount);
OStreamSymbolizePrinter osp(out, options);
osp.println(addresses, frames.data(), frameCount);
osp.println(frames.data(), frameCount);
}
} catch (const std::exception& e) {
out << "\n !! caught " << folly::exceptionStr(e) << "\n";
......
This diff is collapsed.
This diff is collapsed.
......@@ -65,7 +65,8 @@ ElfCache* defaultElfCache() {
void SymbolizedFrame::set(
const std::shared_ptr<ElfFile>& file,
uintptr_t address,
Dwarf::LocationInfoMode mode) {
Dwarf::LocationInfoMode mode,
folly::Range<Dwarf::LocationInfo*> inlineLocations) {
clear();
found = true;
......@@ -77,7 +78,7 @@ void SymbolizedFrame::set(
file_ = file;
name = file->getSymbolName(sym);
Dwarf(file.get()).findAddress(address, location, mode);
Dwarf(file.get()).findAddress(address, mode, location, inlineLocations);
}
Symbolizer::Symbolizer(
......@@ -91,9 +92,10 @@ Symbolizer::Symbolizer(
}
void Symbolizer::symbolize(
const uintptr_t* addresses,
SymbolizedFrame* frames,
size_t addrCount) {
folly::Range<const uintptr_t*> addrs,
folly::Range<SymbolizedFrame*> frames) {
size_t addrCount = addrs.size();
size_t frameCount = frames.size();
size_t remaining = 0;
for (size_t i = 0; i < addrCount; ++i) {
auto& frame = frames[i];
......@@ -119,6 +121,10 @@ void Symbolizer::symbolize(
}
selfPath[selfSize] = '\0';
for (size_t i = 0; i < addrCount; i++) {
frames[i].addr = addrs[i];
}
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
......@@ -139,8 +145,10 @@ void Symbolizer::symbolize(
continue;
}
auto const addr = addresses[i];
if (symbolCache_) {
auto const addr = frame.addr;
// Mode FULL_WITH_INLINE (may have multiple frames for an address) is not
// supported in symbolCache_.
if (symbolCache_ && mode_ != Dwarf::LocationInfoMode::FULL_WITH_INLINE) {
// Need a write lock, because EvictingCacheMap brings found item to
// front of eviction list.
auto lockedSymbolCache = symbolCache_->wlock();
......@@ -157,9 +165,49 @@ void Symbolizer::symbolize(
auto const adjusted = addr - lmap->l_addr;
if (elfFile->getSectionContainingAddress(adjusted)) {
frame.set(elfFile, adjusted, mode_);
if (mode_ == Dwarf::LocationInfoMode::FULL_WITH_INLINE &&
frameCount > addrCount) {
size_t maxInline = std::min<size_t>(
Dwarf::kMaxInlineLocationInfoPerFrame, frameCount - addrCount);
Dwarf::LocationInfo inlineLocations[maxInline];
folly::Range<Dwarf::LocationInfo*> inlineLocRange(
inlineLocations, maxInline);
frame.set(elfFile, adjusted, mode_, inlineLocRange);
// Find out how many LocationInfo were filled in.
size_t numInlined = std::distance(
inlineLocRange.begin(),
std::find_if(
inlineLocRange.begin(), inlineLocRange.end(), [&](auto loc) {
return !loc.hasFileAndLine;
}));
// Move the rest of the frames to make space for inlined location info
std::move_backward(
frames.begin() + i,
frames.begin() + addrCount,
frames.begin() + addrCount + numInlined);
// Insert inlined location info in the space we opened up above. The
// inlineLocations contains inline functions in call sequence, and
// here we store them reversely in frames (caller in later frame).
for (size_t k = 0; k < numInlined; k++) {
frames[i + k].found = true;
frames[i + k].addr = addr;
frames[i + k].location = inlineLocations[numInlined - k - 1];
frames[i + k].name =
inlineLocations[numInlined - k - 1].name.data();
}
// Skip over the newly added inlined items.
i += numInlined;
addrCount += numInlined;
} else {
frame.set(elfFile, adjusted, mode_);
}
--remaining;
if (symbolCache_) {
if (symbolCache_ &&
mode_ != Dwarf::LocationInfoMode::FULL_WITH_INLINE) {
// 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);
......@@ -198,9 +246,9 @@ folly::StringPiece AddressFormatter::format(uintptr_t address) {
return folly::StringPiece(buf_, end);
}
void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
void SymbolizePrinter::print(const SymbolizedFrame& frame) {
if (options_ & TERSE) {
printTerse(address, frame);
printTerse(frame);
return;
}
......@@ -212,7 +260,7 @@ void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
color(kAddressColor);
AddressFormatter formatter;
doPrint(formatter.format(address));
doPrint(formatter.format(frame.addr));
}
const char padBuf[] = " ";
......@@ -274,16 +322,12 @@ void SymbolizePrinter::color(SymbolizePrinter::Color color) {
doPrint(kColorMap[color]);
}
void SymbolizePrinter::println(
uintptr_t address,
const SymbolizedFrame& frame) {
print(address, frame);
void SymbolizePrinter::println(const SymbolizedFrame& frame) {
print(frame);
doPrint("\n");
}
void SymbolizePrinter::printTerse(
uintptr_t address,
const SymbolizedFrame& frame) {
void SymbolizePrinter::printTerse(const SymbolizedFrame& frame) {
if (frame.found && frame.name && frame.name[0] != '\0') {
char demangledBuf[2048] = {0};
demangle(frame.name, demangledBuf, sizeof(demangledBuf));
......@@ -295,6 +339,7 @@ void SymbolizePrinter::printTerse(
char* end = buf + sizeof(buf) - 1 - (16 - 2 * sizeof(uintptr_t));
char* p = end;
*p-- = '\0';
auto address = frame.addr;
while (address != 0) {
*p-- = kHexChars[address & 0xf];
address >>= 4;
......@@ -304,11 +349,10 @@ void SymbolizePrinter::printTerse(
}
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]);
println(frames[i]);
}
}
......
......@@ -47,13 +47,15 @@ struct SymbolizedFrame {
void set(
const std::shared_ptr<ElfFile>& file,
uintptr_t address,
Dwarf::LocationInfoMode mode);
Dwarf::LocationInfoMode mode,
folly::Range<Dwarf::LocationInfo*> inlineLocations = {});
void clear() {
*this = SymbolizedFrame();
}
bool found = false;
uintptr_t addr = 0;
const char* name = nullptr;
Dwarf::LocationInfo location;
......@@ -127,24 +129,45 @@ class Symbolizer {
ElfCacheBase* cache,
Dwarf::LocationInfoMode mode = kDefaultLocationInfoMode,
size_t symbolCacheSize = 0);
/**
* Symbolize given addresses.
* Symbolize given addresses.
*
* - all entries in @addrs will be symbolized (if possible, e.g. if they're
* valid code addresses)
*
* - if `mode_ == FULL_WITH_INLINE` and `frames.size() > addrs.size()` then at
* most `frames.size() - addrs.size()` additional inlined functions will
* also be symbolized (at most `kMaxInlineLocationInfoPerFrame` per @addr
* entry).
*/
void symbolize(
folly::Range<const uintptr_t*> addrs,
folly::Range<SymbolizedFrame*> frames);
void symbolize(
const uintptr_t* addresses,
SymbolizedFrame* frames,
size_t frameCount);
size_t frameCount) {
symbolize(
folly::Range<const uintptr_t*>(addresses, frameCount),
folly::Range<SymbolizedFrame*>(frames, frameCount));
}
template <size_t N>
void symbolize(FrameArray<N>& fa) {
symbolize(fa.addresses, fa.frames, fa.frameCount);
symbolize(
folly::Range<const uintptr_t*>(fa.addresses, fa.frameCount),
folly::Range<SymbolizedFrame*>(fa.frames, N));
}
/**
* Shortcut to symbolize one address.
*/
bool symbolize(uintptr_t address, SymbolizedFrame& frame) {
symbolize(&address, &frame, 1);
symbolize(
folly::Range<const uintptr_t*>(&address, 1),
folly::Range<SymbolizedFrame*>(&frame, 1));
return frame.found;
}
......@@ -180,22 +203,19 @@ class AddressFormatter {
class SymbolizePrinter {
public:
/**
* Print one address, no ending newline.
* Print one frame, no ending newline.
*/
void print(uintptr_t address, const SymbolizedFrame& frame);
void print(const SymbolizedFrame& frame);
/**
* Print one address with ending newline.
* Print one frame with ending newline.
*/
void println(uintptr_t address, const SymbolizedFrame& frame);
void println(const SymbolizedFrame& frame);
/**
* Print multiple addresses on separate lines.
* Print multiple frames on separate lines.
*/
void println(
const uintptr_t* addresses,
const SymbolizedFrame* frames,
size_t frameCount);
void println(const SymbolizedFrame* frames, size_t frameCount);
/**
* Print a string, no endling newline.
......@@ -205,13 +225,13 @@ class SymbolizePrinter {
}
/**
* Print multiple addresses on separate lines, skipping the first
* Print multiple frames on separate lines, skipping the first
* skip addresses.
*/
template <size_t N>
void println(const FrameArray<N>& fa, size_t skip = 0) {
if (skip < fa.frameCount) {
println(fa.addresses + skip, fa.frames + skip, fa.frameCount - skip);
println(fa.frames + skip, fa.frameCount - skip);
}
}
......@@ -252,7 +272,7 @@ class SymbolizePrinter {
const bool isTty_;
private:
void printTerse(uintptr_t address, const SymbolizedFrame& frame);
void printTerse(const SymbolizedFrame& frame);
virtual void doPrint(StringPiece sp) = 0;
static constexpr std::array<const char*, Color::NUM> kColorMap = {{
......
......@@ -15,6 +15,7 @@
*/
#include <folly/Benchmark.h>
#include <folly/Range.h>
#include <folly/experimental/symbolizer/Dwarf.h>
#include <folly/portability/GFlags.h>
......@@ -33,7 +34,9 @@ void run(Dwarf::LocationInfoMode mode, size_t n) {
suspender.dismiss();
for (size_t i = 0; i < n; i++) {
Dwarf::LocationInfo info;
dwarf.findAddress(uintptr_t(&dummy), info, mode);
auto inlineInfo = std::
array<Dwarf::LocationInfo, Dwarf::kMaxInlineLocationInfoPerFrame>();
dwarf.findAddress(uintptr_t(&dummy), mode, info, folly::range(inlineInfo));
}
}
......@@ -47,6 +50,10 @@ BENCHMARK(DwarfFindAddressFull, n) {
run(folly::symbolizer::Dwarf::LocationInfoMode::FULL, n);
}
BENCHMARK(DwarfFindAddressFullWithInline, n) {
run(folly::symbolizer::Dwarf::LocationInfoMode::FULL_WITH_INLINE, n);
}
int main(int argc, char* argv[]) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
......
......@@ -16,6 +16,7 @@
#include <folly/experimental/symbolizer/Symbolizer.h>
#include <array>
#include <cstdlib>
#include <folly/Range.h>
......@@ -46,10 +47,11 @@ TEST(Symbolizer, Single) {
EXPECT_EQ("SymbolizerTest.cpp", basename.str());
}
FrameArray<100>* framesToFill{nullptr};
void* framesToFill{nullptr};
template <size_t kNumFrames = 100>
int comparator(const void* ap, const void* bp) {
getStackTrace(*framesToFill);
getStackTrace(*static_cast<FrameArray<kNumFrames>*>(framesToFill));
int a = *static_cast<const int*>(ap);
int b = *static_cast<const int*>(bp);
......@@ -61,9 +63,9 @@ FOLLY_NOINLINE void bar();
void bar(FrameArray<100>& frames) {
framesToFill = &frames;
int a[2] = {1, 2};
std::array<int, 2> a = {1, 2};
// Use qsort, which is in a different library
qsort(a, 2, sizeof(int), comparator);
qsort(a.data(), 2, sizeof(int), comparator<100>);
framesToFill = nullptr;
}
......@@ -127,6 +129,69 @@ TEST(SymbolizerTest, SymbolCache) {
}
}
namespace {
template <size_t kNumFrames = 100>
FOLLY_ALWAYS_INLINE void inlineFoo(FrameArray<kNumFrames>& frames) {
framesToFill = &frames;
std::array<int, 2> a = {1, 2};
// Use qsort, which is in a different library
qsort(a.data(), 2, sizeof(int), comparator<kNumFrames>);
framesToFill = nullptr;
}
template <size_t kNumFrames = 100>
FOLLY_ALWAYS_INLINE void inlineBar(FrameArray<kNumFrames>& frames) {
inlineFoo(frames);
}
} // namespace
TEST(SymbolizerTest, InlineFunctionBasic) {
Symbolizer symbolizer(
nullptr, Dwarf::LocationInfoMode::FULL_WITH_INLINE, 100);
FrameArray<100> frames;
inlineBar<100>(frames);
symbolizer.symbolize(frames);
EXPECT_EQ("inlineFoo<100>", std::string(frames.frames[5].name));
EXPECT_EQ(
"folly/experimental/symbolizer/test/SymbolizerTest.cpp",
std::string(frames.frames[5].location.file.toString()));
EXPECT_EQ(139, frames.frames[5].location.line);
EXPECT_EQ("inlineBar<100>", std::string(frames.frames[6].name));
EXPECT_EQ(
"folly/experimental/symbolizer/test/SymbolizerTest.cpp",
std::string(frames.frames[6].location.file.toString()));
EXPECT_EQ(145, frames.frames[6].location.line);
FrameArray<100> frames2;
inlineBar<100>(frames2);
symbolizer.symbolize(frames2);
for (size_t i = 0; i < frames.frameCount; i++) {
EXPECT_STREQ(frames.frames[i].name, frames2.frames[i].name);
}
}
// No inline frames should be filled because of no extra frames.
TEST(SymbolizerTest, InlineFunctionBasicNoExtraFrames) {
Symbolizer symbolizer(
nullptr, Dwarf::LocationInfoMode::FULL_WITH_INLINE, 100);
FrameArray<8> frames;
inlineBar<8>(frames);
symbolizer.symbolize(frames);
Symbolizer symbolizer2(nullptr, Dwarf::LocationInfoMode::FULL, 100);
FrameArray<8> frames2;
inlineBar<8>(frames2);
symbolizer2.symbolize(frames2);
for (size_t i = 0; i < frames.frameCount; i++) {
EXPECT_STREQ(frames.frames[i].name, frames2.frames[i].name);
}
}
} // namespace test
} // namespace symbolizer
} // 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