Commit 1c76bae6 authored by Matt Ma's avatar Matt Ma Committed by Facebook Github Bot

Support inline functions in folly stack trace symbolizer

Summary:
Basic idea is to find the subprogram for a given stack trace address, scan the inlined subroutines of that subprogram recursively for all inline function calls, then find the name and location of these inline function calls.

Now only pure inline functions are supported, and class inline functions are not. Class inline functions needs a lookup path "caller->obj->type", then lookup types in .debug_types, which is more complex.

The debug info parser idea was stolen from hphp/tools/debug-parser/dwarfstate.h

Not sure if we want to add a flag to enable this feature. We can discuss over review.

Reviewed By: luciang

Differential Revision: D16760775

fbshipit-source-id: 1a7c23486605e7d8837070a697a875789ae41416
parent cffb3628
This diff is collapsed.
......@@ -20,6 +20,7 @@
#include <boost/variant.hpp>
#include <folly/Optional.h>
#include <folly/Range.h>
#include <folly/experimental/symbolizer/Elf.h>
......@@ -115,9 +116,21 @@ class Dwarf {
FAST,
// Scan all CU in .debug_info (slow!) on .debug_aranges lookup failure.
FULL,
// Scan .debug_info (super slower, use with caution) for inline functions in
// addition to FULL.
FULL_WITH_INLINE,
};
// More than one location info may exist if current frame is an inline
// function call.
static const uint32_t kMaxLocationInfoPerFrame = 5;
struct LocationInfo {
// Whether the location info is filled.
bool empty = true;
// Function name in call stack.
folly::StringPiece name;
bool hasMainFile = false;
Path mainFile;
......@@ -126,21 +139,19 @@ class Dwarf {
uint64_t line = 0;
};
/**
* Find the file and line number information corresponding to address.
*/
bool findAddress(uintptr_t address, LocationInfo& info, LocationInfoMode mode)
const;
// Find the file and line number information corresponding to address.
bool findAddress(
uintptr_t address,
LocationInfoMode mode,
LocationInfo& info,
LocationInfo inlineInfo[],
uint32_t maxNumInline = kMaxLocationInfoPerFrame) const;
private:
static bool
findDebugInfoOffset(uintptr_t address, StringPiece aranges, uint64_t& offset);
void init();
bool findLocation(
uintptr_t address,
StringPiece& infoEntry,
LocationInfo& info) const;
const ElfFile* elf_;
......@@ -169,20 +180,80 @@ class Dwarf {
folly::StringPiece data_;
};
struct AttributeSpec {
uint64_t name = 0;
uint64_t form = 0;
explicit operator bool() const {
return name != 0 || form != 0;
}
};
// Abbreviation for a Debugging Information Entry.
struct DIEAbbreviation {
uint64_t code;
uint64_t tag;
bool hasChildren;
struct Attribute {
uint64_t name;
uint64_t form;
};
bool hasChildren = false;
folly::StringPiece attributes;
};
// Maximum number of DIEAbbreviation to cache in a compilation unit. Used to
// speed up inline function lookup.
static const uint32_t kMaxAbbreviationEntries = 1000;
// A top level chunk in the .debug_info that contains a compilation unit.
struct CompilationUnit {
// Offset in .debug_info of this compilation unit.
uint64_t offset;
uint64_t size;
bool is64Bit;
uint8_t version;
uint8_t addrSize;
uint64_t abbrevOffset;
// Offset in .debug_info for the first DIE in this compilation unit.
uint64_t firstDie;
// Only the CompilationUnit that contains the caller functions needs this
// cache.
// Indexed by (abbr.code - 1) if (abbr.code - 1) < abbrCache.size();
folly::Range<DIEAbbreviation*> abbrCache;
};
// Debugging information entry to define a low-level representation of a
// source program. Each debugging information entry consists of an identifying
// tag and a series of attributes. An entry, or group of entries together,
// provide a description of a corresponding entity in the source program.
struct Die {
const CompilationUnit* cu;
// Offset within debug info.
uint64_t offset;
bool is64Bit;
uint64_t code;
// Offset from start to first attribute
uint8_t attrOffset;
// If we know where the next sibling is (eg via DW_AT_sibling), and
// it fits in uint32_t, the delta we need to add to offset to get
// there; otherwise zero.
uint32_t siblingDelta;
// If we know where the next die is, and it fits in uint32_t, this
// is the delta we need to add to offset to get there; otherwise zero.
// if there are no children, this will be the same as sibling.
uint32_t nextDieDelta;
DIEAbbreviation abbr;
};
struct Attribute {
AttributeSpec spec;
Die* die;
boost::variant<uint64_t, folly::StringPiece> attrValue;
};
struct CodeLocation {
CompilationUnit cu;
Die die;
uint64_t line;
};
// Interpreter for the line number bytecode VM
class LineNumberVM {
public:
......@@ -192,6 +263,12 @@ class Dwarf {
bool findAddress(uintptr_t address, Path& file, uint64_t& line);
// Gets full file name at given index including directory.
Path getFullFileName(uint64_t index) const {
auto fn = getFileName(index);
return Path({}, getIncludeDirectory(fn.directoryIndex), fn.relativeName);
}
private:
void init();
void reset();
......@@ -261,27 +338,82 @@ class Dwarf {
uint64_t discriminator_;
};
// Read an abbreviation from a StringPiece, return true if at end; advance sp
using AttributeValue = boost::variant<uint64_t, folly::StringPiece>;
// Finds the Compilation Unit starting at offset.
void getCompilationUnit(
Dwarf::CompilationUnit& cu,
uint64_t offset,
Dwarf::DIEAbbreviation* abbrs) const;
// Finds the Compilation Unit that contains offset.
CompilationUnit findCompilationUnit(uint64_t offset) const;
// cu must exist during the life cycle of created Die.
Die getDieAtOffset(const CompilationUnit& cu, uint64_t offset) const;
Die getNextSibling(Die& die) const;
// Reads an abbreviation from a StringPiece, return true if at end; advance sp
static bool readAbbreviation(folly::StringPiece& sp, DIEAbbreviation& abbr);
// Get abbreviation corresponding to a code, in the chunk starting at
// Gets abbreviation corresponding to a code, in the chunk starting at
// offset in the .debug_abbrev section
DIEAbbreviation getAbbreviation(uint64_t code, uint64_t offset) const;
// Read one attribute <name, form> pair, advance sp; returns <0, 0> at end.
static DIEAbbreviation::Attribute readAttribute(folly::StringPiece& sp);
// Reads attribute name and form from sp.
static AttributeSpec readAttributeSpec(folly::StringPiece& sp);
// Reads attribute value of given DIE
Attribute readAttribute(Die& die, AttributeSpec& spec, folly::StringPiece& sp)
const;
// Read one attribute value, advance sp
typedef boost::variant<uint64_t, folly::StringPiece> AttributeValue;
AttributeValue
readAttributeValue(folly::StringPiece& sp, uint64_t form, bool is64Bit) const;
// Finds a subprogram debugging info entry that contains a given address among
// children of given die. Depth first search.
void findSubProgramDieForAddress(
Dwarf::Die& die,
uint64_t address,
Dwarf::Die& subprogram) const;
// Finds inlined subroutine DIEs and their caller lines that contains a given
// address among children of given die. Depth first search.
void findInlinedSubroutineDieForAddress(
const CompilationUnit& cu,
Dwarf::Die& die,
uint64_t address,
CodeLocation* isrLoc,
uint32_t& numFound) const;
// Iterates over all children of a debugging info entry, calling the given
// callable for each. Iteration is stopped early if any of the calls return
// false.
template <typename F>
void forEachChild(Die& die, F&& f) const;
// Iterates over all attributes of a debugging info entry, calling the given
// callable for each. Iteration is stopped early if any of the calls return
// false.
template <typename F>
void forEachAttribute(Die& die, F&& f) const;
// Iterate over all attributes of the given DIE, and move to its first child
// if any or its next sibling.
void moveToNextDie(Dwarf::Die& die) const;
// Get an ELF section by name, return true if found
bool getSection(const char* name, folly::StringPiece* section) const;
// Get a string from the .debug_str section
// Gets a string from the .debug_str section
folly::StringPiece getStringFromStringSection(uint64_t offset) const;
// Finds location info (file and line) for a given address in the given
// compilation unit.
bool findLocation(
uintptr_t address,
const LocationInfoMode mode,
const CompilationUnit& unit,
LocationInfo& info,
LocationInfo inlineInfo[],
uint32_t maxNumInline = kMaxLocationInfoPerFrame) const;
folly::StringPiece info_; // .debug_info
folly::StringPiece abbrev_; // .debug_abbrev
folly::StringPiece aranges_; // .debug_aranges
......
......@@ -76,8 +76,15 @@ void SymbolizedFrame::set(
file_ = file;
name = file->getSymbolName(sym);
Dwarf(file.get()).findAddress(address, location, mode);
location.name = name;
Dwarf(file.get())
.findAddress(
address,
mode,
location,
inlineLocations,
Dwarf::kMaxLocationInfoPerFrame);
}
Symbolizer::Symbolizer(
......@@ -215,52 +222,19 @@ void SymbolizePrinter::print(uintptr_t address, const SymbolizedFrame& frame) {
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;
}
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 (!(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);
}
printLocationInfo(frame.location, false);
for (const Dwarf::LocationInfo& location : frame.inlineLocations) {
if (location.empty) {
break;
}
doPrint("\n");
printLocationInfo(location, true);
}
}
......@@ -312,6 +286,60 @@ void SymbolizePrinter::println(
}
}
void SymbolizePrinter::printLocationInfo(
const Dwarf::LocationInfo& location,
bool isInline) {
const char padBuf[] = " ";
folly::StringPiece pad(
padBuf, sizeof(padBuf) - 1 - (16 - 2 * sizeof(uintptr_t)));
// Inline function has no address in stack trace.
if (isInline) {
doPrint(pad);
}
color(kFunctionColor);
if (location.name.empty()) {
doPrint(" (unknown)");
} else if (isInline) {
doPrint(location.name);
} else {
char demangledBuf[2048];
demangle(location.name.data(), demangledBuf, sizeof(demangledBuf));
doPrint(" ");
doPrint(demangledBuf[0] == '\0' ? location.name : demangledBuf);
}
if (!(options_ & NO_FILE_AND_LINE)) {
color(kFileColor);
char fileBuf[PATH_MAX];
fileBuf[0] = '\0';
if (location.hasFileAndLine) {
location.file.toBuffer(fileBuf, sizeof(fileBuf));
doPrint("\n");
doPrint(pad);
doPrint(fileBuf);
char buf[22];
uint32_t n = uint64ToBufferUnsafe(location.line, buf);
doPrint(":");
doPrint(StringPiece(buf, n));
}
if (location.hasMainFile) {
char mainFileBuf[PATH_MAX];
mainFileBuf[0] = '\0';
location.mainFile.toBuffer(mainFileBuf, sizeof(mainFileBuf));
if (!location.hasFileAndLine || strcmp(fileBuf, mainFileBuf)) {
doPrint("\n");
doPrint(pad);
doPrint("-> ");
doPrint(mainFileBuf);
}
}
}
}
namespace {
int getFD(const std::ios& stream) {
......
......@@ -56,6 +56,12 @@ struct SymbolizedFrame {
bool found = false;
const char* name = nullptr;
Dwarf::LocationInfo location;
// If stack trace happens on an inline function call, more than one locations
// need to be printed. First element is always the location of caller
// function, and the subsequent locations are inline function locations if
// applicable. std::vector is not used here in favor of signal-safe and memory
// constrained situations like std::bad_alloc.
Dwarf::LocationInfo inlineLocations[Dwarf::kMaxLocationInfoPerFrame];
/**
* Demangle the name and return it. Not async-signal-safe; allocates memory.
......@@ -253,6 +259,9 @@ class SymbolizePrinter {
private:
void printTerse(uintptr_t address, const SymbolizedFrame& frame);
void printLocationInfo(
const Dwarf::LocationInfo& locationInfo,
bool isInline);
virtual void doPrint(StringPiece sp) = 0;
static constexpr std::array<const char*, Color::NUM> kColorMap = {{
......
......@@ -33,7 +33,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::kMaxLocationInfoPerFrame>();
dwarf.findAddress(uintptr_t(&dummy), mode, info, inlineInfo.data());
}
}
......@@ -47,6 +49,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]);
......
......@@ -14,6 +14,7 @@
* limitations under the License.
*/
#include <folly/experimental/symbolizer/test/SymbolizerTest.h>
#include <folly/experimental/symbolizer/Symbolizer.h>
#include <cstdlib>
......@@ -46,16 +47,6 @@ TEST(Symbolizer, Single) {
EXPECT_EQ("SymbolizerTest.cpp", basename.str());
}
FrameArray<100>* framesToFill{nullptr};
int comparator(const void* ap, const void* bp) {
getStackTrace(*framesToFill);
int a = *static_cast<const int*>(ap);
int b = *static_cast<const int*>(bp);
return a < b ? -1 : a > b ? 1 : 0;
}
// Test stack frames...
FOLLY_NOINLINE void bar();
......@@ -127,6 +118,78 @@ TEST(SymbolizerTest, SymbolCache) {
}
}
namespace {
FOLLY_ALWAYS_INLINE void inlineBar(FrameArray<100>& frames) {
inlineFoo(frames);
}
} // namespace
TEST(SymbolizerTest, InlineFunctionBasic) {
Symbolizer symbolizer(
nullptr, Dwarf::LocationInfoMode::FULL_WITH_INLINE, 100);
FrameArray<100> frames;
inlineBar(frames);
symbolizer.symbolize(frames);
StringSymbolizePrinter printer;
// Stack trace for SymbolizerTest_InlineFunctionBasic_Test function call.
printer.print(4, frames.frames[4]);
EXPECT_STREQ(
printer.str().c_str(),
R"( @ 0000000000000004 folly::symbolizer::test::SymbolizerTest_InlineFunctionBasic_Test::TestBody()
./folly/experimental/symbolizer/test/SymbolizerTest.h:134
-> ./folly/experimental/symbolizer/test/SymbolizerTest.cpp
inlineBar
folly/experimental/symbolizer/test/SymbolizerTest.cpp:124
inlineFoo
folly/experimental/symbolizer/test/SymbolizerTest.h:39)");
}
TEST(SymbolizerTest, InlineFunctionInSeparateFile) {
Symbolizer symbolizer(
nullptr, Dwarf::LocationInfoMode::FULL_WITH_INLINE, 100);
FrameArray<100> frames;
inlineFunctionInSeparateFile(frames);
symbolizer.symbolize(frames);
StringSymbolizePrinter printer;
// Stack trace for SymbolizerTest_InlineFunctionInSeparateFile_Test function
// call.
printer.print(4, frames.frames[4]);
EXPECT_STREQ(
printer.str().c_str(),
R"( @ 0000000000000004 folly::symbolizer::test::SymbolizerTest_InlineFunctionInSeparateFile_Test::TestBody()
./folly/experimental/symbolizer/test/SymbolizerTest.h:155
-> ./folly/experimental/symbolizer/test/SymbolizerTest.cpp
inlineFunctionInSeparateFile
folly/experimental/symbolizer/test/SymbolizerTest.h:44
inlineFoo
folly/experimental/symbolizer/test/SymbolizerTest.h:39)");
}
// Not supported yet.
TEST(SymbolizerTest, InlineFunctionInClass) {
Symbolizer symbolizer(
nullptr, Dwarf::LocationInfoMode::FULL_WITH_INLINE, 100);
FrameArray<100> frames;
ClassWithInlineFunction cif;
cif.inlineFunctionInClass(frames);
symbolizer.symbolize(frames);
StringSymbolizePrinter printer;
// Stack trace for SymbolizerTest_InlineFunctionInClass_Test function call.
printer.print(4, frames.frames[4]);
EXPECT_STREQ(
printer.str().c_str(),
R"( @ 0000000000000004 folly::symbolizer::test::SymbolizerTest_InlineFunctionInClass_Test::TestBody()
./folly/experimental/symbolizer/test/SymbolizerTest.h:39
-> ./folly/experimental/symbolizer/test/SymbolizerTest.cpp)");
}
} // namespace test
} // namespace symbolizer
} // namespace folly
......
/*
* Copyright 2019-present 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.
*/
#pragma once
#include <folly/container/Array.h>
#include <folly/experimental/symbolizer/Symbolizer.h>
namespace folly {
namespace symbolizer {
namespace test {
FrameArray<100>* framesToFill{nullptr};
int comparator(const void* ap, const void* bp) {
getStackTrace(*framesToFill);
int a = *static_cast<const int*>(ap);
int b = *static_cast<const int*>(bp);
return a < b ? -1 : a > b ? 1 : 0;
}
FOLLY_ALWAYS_INLINE void inlineFoo(FrameArray<100>& frames) {
framesToFill = &frames;
int a[2] = {1, 2};
// Use qsort, which is in a different library
qsort(a, 2, sizeof(int), comparator);
framesToFill = nullptr;
}
FOLLY_ALWAYS_INLINE void inlineFunctionInSeparateFile(FrameArray<100>& frames) {
inlineFoo(frames);
}
class ClassWithInlineFunction {
public:
FOLLY_ALWAYS_INLINE void inlineFunctionInClass(FrameArray<100>& frames) {
inlineFoo(frames);
}
};
} // 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