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

Support inline class member functions in stack trace.

Summary:
Actually class inline functions exist in both .debug_info and .debug_types sections. In this diff, only the information in .debug_info is used.

Step 1. Find the DW_TAG_inlined_subroutine debug info entry (DIE) based on the given address.
Step 2. Find the DW_TAG_subprogram DIE based on the DW_AT_abstract_origin attr in inlined_subroutine, but this DIE is just a declaration.
Step 3. Find the actual definition DW_TAG_subprogram DIE based on the DW_AT_specification attr.

```
.debug_info

0x00003657:           DW_TAG_subprogram [56] *   ---- step 3
                        DW_AT_linkage_name [DW_FORM_strp] ( .debug_str[0x0002932d] = "_ZN5folly10symbolizer4test24ClassWithInlineFunctions9inlineBarERNS0_10FrameArrayILm100EEE")
                        DW_AT_name [DW_FORM_strp] ( .debug_str[0x00014064] = "inlineBar")
                        DW_AT_decl_file [DW_FORM_data1] (0x18)
                        DW_AT_decl_line [DW_FORM_data1] (0xa1)
                        DW_AT_declaration [DW_FORM_flag_present]  (true)
                        DW_AT_external [DW_FORM_flag_present] (true)
                        DW_AT_accessibility [DW_FORM_data1] (0x01)

…

0x0000cd74:   DW_TAG_subprogram [203] * .   ---- step 2
                DW_AT_specification [DW_FORM_ref4]  (cu + 0x358f => {0x00003657})
                DW_AT_inline [DW_FORM_data1]  (0x01)
                DW_AT_object_pointer [DW_FORM_ref4] (cu + 0xccb7 => {0x0000cd7f})

…
0x0000cda0:   DW_TAG_subprogram [181] *
                DW_AT_low_pc [DW_FORM_addr] (0x00000000002a4980)
                DW_AT_high_pc [DW_FORM_data4] (0x00001e22)
                DW_AT_frame_base [DW_FORM_exprloc]  (<0x1> 56 )
                DW_AT_object_pointer [DW_FORM_ref4] (cu + 0xccf0 => {0x0000cdb8})
                DW_AT_specification [DW_FORM_ref4]  (cu + 0x312f => {0x000031f7})

…

0x0000ce02:     DW_TAG_inlined_subroutine [157] *               ---- step 1
                  DW_AT_abstract_origin [DW_FORM_ref4]  (cu + 0xccac => {0x0000cd74})
                  DW_AT_low_pc [DW_FORM_addr] (0x00000000002a4f66)
                  DW_AT_high_pc [DW_FORM_data4] (0x000000c0)
                  DW_AT_call_file [DW_FORM_data1] (0x18)
                  DW_AT_call_line [DW_FORM_data1] (0xe6)

.debug_types

0x00020fdf:         DW_TAG_class_type [33] *
                      DW_AT_calling_convention [DW_FORM_data1]  (0x05)
                      DW_AT_name [DW_FORM_strp] ( .debug_str[0x00067094] = "ClassWithInlineFunctions")
                      DW_AT_byte_size [DW_FORM_data1] (0x08)
                      DW_AT_decl_file [DW_FORM_data1] (0x18)
                      DW_AT_decl_line [DW_FORM_data1] (0x9f)

…

0x00020ff5:           DW_TAG_subprogram [56] *
                        DW_AT_linkage_name [DW_FORM_strp] ( .debug_str[0x0002932d] = "_ZN5folly10symbolizer4test24ClassWithInlineFunctions9inlineBarERNS0_10FrameArrayILm100EEE")
                        DW_AT_name [DW_FORM_strp] ( .debug_str[0x00014064] = "inlineBar")
                        DW_AT_decl_file [DW_FORM_data1] (0x18)
                        DW_AT_decl_line [DW_FORM_data1] (0xa1)
                        DW_AT_declaration [DW_FORM_flag_present]  (true)
                        DW_AT_external [DW_FORM_flag_present] (true)
                        DW_AT_accessibility [DW_FORM_data1] (0x01)

```

Reviewed By: luciang

Differential Revision: D19644033

fbshipit-source-id: fa5d6dc46dbb1a417b40c1d06d9eaf9178661d21
parent 88cc7c93
......@@ -637,9 +637,31 @@ detail::Die Dwarf::getDieAtOffset(
die.abbr = !cu.abbrCache.empty() && die.code < kMaxAbbreviationEntries
? cu.abbrCache[die.code - 1]
: getAbbreviation(die.code, cu.abbrevOffset);
return die;
}
detail::Die Dwarf::findDefinitionDie(
const detail::CompilationUnit& cu,
const detail::Die& die) const {
// Find the real definition instead of declaration.
folly::Optional<uint64_t> offset;
forEachAttribute(cu, die, [&](const detail::Attribute& attr) {
// Incomplete, non-defining, or separate declaration corresponding to
// a declaration
if (attr.spec.name == DW_AT_specification) {
offset = boost::get<uint64_t>(attr.attrValue);
return false;
}
return true;
});
if (!offset.hasValue()) {
return die;
}
return getDieAtOffset(cu, cu.offset + offset.value());
}
size_t Dwarf::forEachChild(
const detail::CompilationUnit& cu,
const detail::Die& die,
......@@ -695,10 +717,9 @@ void Dwarf::findSubProgramDieForAddress(
detail::Die& subprogram) const {
forEachChild(cu, die, [&](const detail::Die& childDie) {
if (childDie.abbr.tag == DW_TAG_subprogram) {
uint64_t lowPc = 0;
uint64_t highPc = 0;
bool isHighPcAddr = false;
StringPiece name;
folly::Optional<uint64_t> lowPc;
folly::Optional<uint64_t> highPc;
folly::Optional<bool> isHighPcAddr;
forEachAttribute(cu, childDie, [&](const detail::Attribute& attr) {
switch (attr.spec.name) {
// Here DW_AT_ranges is not supported since it requires looking up
......@@ -712,15 +733,16 @@ void Dwarf::findSubProgramDieForAddress(
isHighPcAddr = (attr.spec.form == DW_FORM_addr);
highPc = boost::get<uint64_t>(attr.attrValue);
break;
case DW_AT_name:
name = boost::get<StringPiece>(attr.attrValue);
break;
}
// Iterate through all attributes until find all above.
return true;
});
if (address > lowPc &&
address < (isHighPcAddr ? highPc : lowPc + highPc)) {
if (!lowPc || !highPc || !isHighPcAddr) {
// Missing required fields. Keep searching other children.
return true;
}
if (address >= *lowPc &&
address < (*isHighPcAddr ? *highPc : *lowPc + *highPc)) {
subprogram = childDie;
return false;
}
......@@ -746,12 +768,12 @@ void Dwarf::findInlinedSubroutineDieForAddress(
if (childDie.abbr.tag != DW_TAG_inlined_subroutine) {
return true; // Skip DIE that is not inlined_subroutine.
}
uint64_t lowPc = 0;
uint64_t highPc = 0;
bool isHighPcAddr = false;
uint64_t origin = 0;
uint64_t originRefType = 0;
uint64_t callLine = 0;
folly::Optional<uint64_t> lowPc;
folly::Optional<uint64_t> highPc;
folly::Optional<bool> isHighPcAddr;
folly::Optional<uint64_t> origin;
folly::Optional<uint64_t> originRefType;
folly::Optional<uint64_t> callLine;
forEachAttribute(cu, childDie, [&](const detail::Attribute& attr) {
switch (attr.spec.name) {
// Here DW_AT_ranges is not supported since it requires looking up
......@@ -776,20 +798,35 @@ void Dwarf::findInlinedSubroutineDieForAddress(
// Iterate through all until find all above attributes.
return true;
});
if (address < lowPc || address > (isHighPcAddr ? highPc : lowPc + highPc)) {
if (!lowPc || !highPc || !isHighPcAddr || !origin || !originRefType ||
!callLine) {
// Missing required fields. Keep searching other children.
return true;
}
if (address < *lowPc ||
address >= (*isHighPcAddr ? *highPc : *lowPc + *highPc)) {
// Address doesn't match. Keep searching other children.
return true;
}
isrLoc[0].line = callLine;
if (originRefType == DW_FORM_ref1 || originRefType == DW_FORM_ref2 ||
originRefType == DW_FORM_ref4 || originRefType == DW_FORM_ref8 ||
originRefType == DW_FORM_ref_udata) {
isrLoc[0].die = getDieAtOffset(cu, cu.offset + origin);
isrLoc[0].line = *callLine;
if (*originRefType == DW_FORM_ref1 || *originRefType == DW_FORM_ref2 ||
*originRefType == DW_FORM_ref4 || *originRefType == DW_FORM_ref8 ||
*originRefType == DW_FORM_ref_udata) {
// These reference types identify any debugging information entry within
// the containing unit. This type of reference is an offset from the first
// byte of the compilation header for the compilation unit containing the
// reference.
isrLoc[0].die =
findDefinitionDie(cu, getDieAtOffset(cu, cu.offset + *origin));
isrLoc.advance(1);
findInlinedSubroutineDieForAddress(cu, childDie, address, isrLoc);
} else if (originRefType == DW_FORM_ref_addr) {
auto srcu = findCompilationUnit(debugInfo_, origin);
isrLoc[0].die = getDieAtOffset(cu, origin);
} else if (*originRefType == DW_FORM_ref_addr) {
// DW_FORM_ref_addr identifies any debugging information entry
// within a .debug_info section; in particular, it may refer to an entry
// in a different compilation unit from the unit containing the reference,
// and may refer to an entry in a different shared object.
auto srcu = findCompilationUnit(debugInfo_, *origin);
isrLoc[0].die = findDefinitionDie(srcu, getDieAtOffset(cu, *origin));
isrLoc.advance(1);
findInlinedSubroutineDieForAddress(srcu, childDie, address, isrLoc);
}
......
......@@ -149,6 +149,13 @@ class Dwarf {
detail::Die getDieAtOffset(const detail::CompilationUnit& cu, uint64_t offset)
const;
/**
* Find the actual definition DIE instead of declaration for the given die.
*/
detail::Die findDefinitionDie(
const detail::CompilationUnit& cu,
const detail::Die& die) 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
......
......@@ -134,7 +134,9 @@ TEST(SymbolizerTest, SymbolCache) {
namespace {
size_t kQsortCallLineNo = 0;
size_t kFooCallLineNo = 0;
size_t kFooCallByStandaloneBarLineNo = 0;
size_t kFooCallByClassBarLineNo = 0;
size_t kFooCallByClassStaticBarLineNo = 0;
template <size_t kNumFrames = 100>
FOLLY_ALWAYS_INLINE void inlineFoo(FrameArray<kNumFrames>& frames) {
......@@ -148,12 +150,58 @@ FOLLY_ALWAYS_INLINE void inlineFoo(FrameArray<kNumFrames>& frames) {
template <size_t kNumFrames = 100>
FOLLY_ALWAYS_INLINE void inlineBar(FrameArray<kNumFrames>& frames) {
kFooCallLineNo = __LINE__ + 1;
kFooCallByStandaloneBarLineNo = __LINE__ + 1;
inlineFoo(frames);
}
void verifyStackTrace(
const FrameArray<100>& frames,
const std::string& barName,
size_t barLine) {
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(kQsortCallLineNo, frames.frames[5].location.line);
EXPECT_EQ(barName, std::string(frames.frames[6].name));
EXPECT_EQ(
"folly/experimental/symbolizer/test/SymbolizerTest.cpp",
std::string(frames.frames[6].location.file.toString()));
EXPECT_EQ(barLine, frames.frames[6].location.line);
}
template <size_t kNumFrames = 100>
void compareFrames(
const FrameArray<kNumFrames>& frames1,
const FrameArray<kNumFrames>& frames2) {
EXPECT_EQ(frames1.frameCount, frames2.frameCount);
for (size_t i = 0; i < frames1.frameCount; i++) {
EXPECT_STREQ(frames1.frames[i].name, frames2.frames[i].name);
}
}
} // namespace
class ClassWithInlineFunctions {
public:
FOLLY_ALWAYS_INLINE void inlineBar(FrameArray<100>& frames) const {
kFooCallByClassBarLineNo = __LINE__ + 1;
inlineFoo(frames);
}
FOLLY_ALWAYS_INLINE static void staticInlineBar(FrameArray<100>& frames) {
kFooCallByClassStaticBarLineNo = __LINE__ + 1;
inlineFoo(frames);
}
// Dummy non-inline function.
size_t dummy() const {
return dummy_;
}
size_t dummy_ = 0;
};
TEST(SymbolizerTest, InlineFunctionBasic) {
Symbolizer symbolizer(nullptr, LocationInfoMode::FULL_WITH_INLINE, 0);
......@@ -180,25 +228,34 @@ TEST(SymbolizerTest, InlineFunctionBasic) {
// Frame: _ZN7testing8UnitTest3RunEv
// Frame: _Z13RUN_ALL_TESTSv
// clang-format on
EXPECT_TRUE(frames.frameCount == 14 || frames.frameCount == 15);
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(kQsortCallLineNo, 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(kFooCallLineNo, frames.frames[6].location.line);
verifyStackTrace(frames, "inlineBar<100>", kFooCallByStandaloneBarLineNo);
FrameArray<100> frames2;
inlineBar<100>(frames2);
symbolizer.symbolize(frames2);
EXPECT_EQ(frames.frameCount, frames2.frameCount);
for (size_t i = 0; i < frames.frameCount; i++) {
EXPECT_STREQ(frames.frames[i].name, frames2.frames[i].name);
}
compareFrames(frames, frames2);
}
TEST(SymbolizerTest, InlineClassMemberFunction) {
Symbolizer symbolizer(nullptr, LocationInfoMode::FULL_WITH_INLINE, 0);
FrameArray<100> frames;
ClassWithInlineFunctions obj;
obj.inlineBar(frames);
symbolizer.symbolize(frames);
verifyStackTrace(frames, "inlineBar", kFooCallByClassBarLineNo);
}
TEST(SymbolizerTest, StaticInlineClassMemberFunction) {
Symbolizer symbolizer(nullptr, LocationInfoMode::FULL_WITH_INLINE, 0);
FrameArray<100> frames;
ClassWithInlineFunctions::staticInlineBar(frames);
symbolizer.symbolize(frames);
verifyStackTrace(frames, "staticInlineBar", kFooCallByClassStaticBarLineNo);
}
// No inline frames should be filled because of no extra frames.
......@@ -213,9 +270,7 @@ TEST(SymbolizerTest, InlineFunctionBasicNoExtraFrames) {
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);
}
compareFrames<8>(frames, frames2);
}
TEST(SymbolizerTest, InlineFunctionWithCache) {
......@@ -225,25 +280,12 @@ TEST(SymbolizerTest, InlineFunctionWithCache) {
inlineBar<100>(frames);
symbolizer.symbolize(frames);
EXPECT_TRUE(frames.frameCount == 14 || frames.frameCount == 15);
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(kQsortCallLineNo, 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(kFooCallLineNo, frames.frames[6].location.line);
verifyStackTrace(frames, "inlineBar<100>", kFooCallByStandaloneBarLineNo);
FrameArray<100> frames2;
inlineBar<100>(frames2);
symbolizer.symbolize(frames2);
EXPECT_EQ(frames.frameCount, frames2.frameCount);
for (size_t i = 0; i < frames.frameCount; i++) {
EXPECT_STREQ(frames.frames[i].name, frames2.frames[i].name);
}
compareFrames(frames, frames2);
}
} // namespace test
......
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