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( ...@@ -637,9 +637,31 @@ detail::Die Dwarf::getDieAtOffset(
die.abbr = !cu.abbrCache.empty() && die.code < kMaxAbbreviationEntries die.abbr = !cu.abbrCache.empty() && die.code < kMaxAbbreviationEntries
? cu.abbrCache[die.code - 1] ? cu.abbrCache[die.code - 1]
: getAbbreviation(die.code, cu.abbrevOffset); : getAbbreviation(die.code, cu.abbrevOffset);
return die; 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( size_t Dwarf::forEachChild(
const detail::CompilationUnit& cu, const detail::CompilationUnit& cu,
const detail::Die& die, const detail::Die& die,
...@@ -695,10 +717,9 @@ void Dwarf::findSubProgramDieForAddress( ...@@ -695,10 +717,9 @@ void Dwarf::findSubProgramDieForAddress(
detail::Die& subprogram) const { detail::Die& subprogram) const {
forEachChild(cu, die, [&](const detail::Die& childDie) { forEachChild(cu, die, [&](const detail::Die& childDie) {
if (childDie.abbr.tag == DW_TAG_subprogram) { if (childDie.abbr.tag == DW_TAG_subprogram) {
uint64_t lowPc = 0; folly::Optional<uint64_t> lowPc;
uint64_t highPc = 0; folly::Optional<uint64_t> highPc;
bool isHighPcAddr = false; folly::Optional<bool> isHighPcAddr;
StringPiece name;
forEachAttribute(cu, childDie, [&](const detail::Attribute& attr) { forEachAttribute(cu, childDie, [&](const detail::Attribute& attr) {
switch (attr.spec.name) { switch (attr.spec.name) {
// Here DW_AT_ranges is not supported since it requires looking up // Here DW_AT_ranges is not supported since it requires looking up
...@@ -712,15 +733,16 @@ void Dwarf::findSubProgramDieForAddress( ...@@ -712,15 +733,16 @@ void Dwarf::findSubProgramDieForAddress(
isHighPcAddr = (attr.spec.form == DW_FORM_addr); isHighPcAddr = (attr.spec.form == DW_FORM_addr);
highPc = boost::get<uint64_t>(attr.attrValue); highPc = boost::get<uint64_t>(attr.attrValue);
break; break;
case DW_AT_name:
name = boost::get<StringPiece>(attr.attrValue);
break;
} }
// Iterate through all attributes until find all above. // Iterate through all attributes until find all above.
return true; return true;
}); });
if (address > lowPc && if (!lowPc || !highPc || !isHighPcAddr) {
address < (isHighPcAddr ? highPc : lowPc + highPc)) { // Missing required fields. Keep searching other children.
return true;
}
if (address >= *lowPc &&
address < (*isHighPcAddr ? *highPc : *lowPc + *highPc)) {
subprogram = childDie; subprogram = childDie;
return false; return false;
} }
...@@ -746,12 +768,12 @@ void Dwarf::findInlinedSubroutineDieForAddress( ...@@ -746,12 +768,12 @@ void Dwarf::findInlinedSubroutineDieForAddress(
if (childDie.abbr.tag != DW_TAG_inlined_subroutine) { if (childDie.abbr.tag != DW_TAG_inlined_subroutine) {
return true; // Skip DIE that is not inlined_subroutine. return true; // Skip DIE that is not inlined_subroutine.
} }
uint64_t lowPc = 0; folly::Optional<uint64_t> lowPc;
uint64_t highPc = 0; folly::Optional<uint64_t> highPc;
bool isHighPcAddr = false; folly::Optional<bool> isHighPcAddr;
uint64_t origin = 0; folly::Optional<uint64_t> origin;
uint64_t originRefType = 0; folly::Optional<uint64_t> originRefType;
uint64_t callLine = 0; folly::Optional<uint64_t> callLine;
forEachAttribute(cu, childDie, [&](const detail::Attribute& attr) { forEachAttribute(cu, childDie, [&](const detail::Attribute& attr) {
switch (attr.spec.name) { switch (attr.spec.name) {
// Here DW_AT_ranges is not supported since it requires looking up // Here DW_AT_ranges is not supported since it requires looking up
...@@ -776,20 +798,35 @@ void Dwarf::findInlinedSubroutineDieForAddress( ...@@ -776,20 +798,35 @@ void Dwarf::findInlinedSubroutineDieForAddress(
// Iterate through all until find all above attributes. // Iterate through all until find all above attributes.
return true; 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. // Address doesn't match. Keep searching other children.
return true; return true;
} }
isrLoc[0].line = callLine; isrLoc[0].line = *callLine;
if (originRefType == DW_FORM_ref1 || originRefType == DW_FORM_ref2 || if (*originRefType == DW_FORM_ref1 || *originRefType == DW_FORM_ref2 ||
originRefType == DW_FORM_ref4 || originRefType == DW_FORM_ref8 || *originRefType == DW_FORM_ref4 || *originRefType == DW_FORM_ref8 ||
originRefType == DW_FORM_ref_udata) { *originRefType == DW_FORM_ref_udata) {
isrLoc[0].die = getDieAtOffset(cu, cu.offset + origin); // 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); isrLoc.advance(1);
findInlinedSubroutineDieForAddress(cu, childDie, address, isrLoc); findInlinedSubroutineDieForAddress(cu, childDie, address, isrLoc);
} else if (originRefType == DW_FORM_ref_addr) { } else if (*originRefType == DW_FORM_ref_addr) {
auto srcu = findCompilationUnit(debugInfo_, origin); // DW_FORM_ref_addr identifies any debugging information entry
isrLoc[0].die = getDieAtOffset(cu, origin); // 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); isrLoc.advance(1);
findInlinedSubroutineDieForAddress(srcu, childDie, address, isrLoc); findInlinedSubroutineDieForAddress(srcu, childDie, address, isrLoc);
} }
......
...@@ -149,6 +149,13 @@ class Dwarf { ...@@ -149,6 +149,13 @@ class Dwarf {
detail::Die getDieAtOffset(const detail::CompilationUnit& cu, uint64_t offset) detail::Die getDieAtOffset(const detail::CompilationUnit& cu, uint64_t offset)
const; 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 * 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 * callable for each. Iteration is stopped early if any of the calls return
......
...@@ -134,7 +134,9 @@ TEST(SymbolizerTest, SymbolCache) { ...@@ -134,7 +134,9 @@ TEST(SymbolizerTest, SymbolCache) {
namespace { namespace {
size_t kQsortCallLineNo = 0; 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> template <size_t kNumFrames = 100>
FOLLY_ALWAYS_INLINE void inlineFoo(FrameArray<kNumFrames>& frames) { FOLLY_ALWAYS_INLINE void inlineFoo(FrameArray<kNumFrames>& frames) {
...@@ -148,12 +150,58 @@ 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> template <size_t kNumFrames = 100>
FOLLY_ALWAYS_INLINE void inlineBar(FrameArray<kNumFrames>& frames) { FOLLY_ALWAYS_INLINE void inlineBar(FrameArray<kNumFrames>& frames) {
kFooCallLineNo = __LINE__ + 1; kFooCallByStandaloneBarLineNo = __LINE__ + 1;
inlineFoo(frames); 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 } // 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) { TEST(SymbolizerTest, InlineFunctionBasic) {
Symbolizer symbolizer(nullptr, LocationInfoMode::FULL_WITH_INLINE, 0); Symbolizer symbolizer(nullptr, LocationInfoMode::FULL_WITH_INLINE, 0);
...@@ -180,25 +228,34 @@ TEST(SymbolizerTest, InlineFunctionBasic) { ...@@ -180,25 +228,34 @@ TEST(SymbolizerTest, InlineFunctionBasic) {
// Frame: _ZN7testing8UnitTest3RunEv // Frame: _ZN7testing8UnitTest3RunEv
// Frame: _Z13RUN_ALL_TESTSv // Frame: _Z13RUN_ALL_TESTSv
// clang-format on // clang-format on
EXPECT_TRUE(frames.frameCount == 14 || frames.frameCount == 15); verifyStackTrace(frames, "inlineBar<100>", kFooCallByStandaloneBarLineNo);
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);
FrameArray<100> frames2; FrameArray<100> frames2;
inlineBar<100>(frames2); inlineBar<100>(frames2);
symbolizer.symbolize(frames2); symbolizer.symbolize(frames2);
EXPECT_EQ(frames.frameCount, frames2.frameCount);
for (size_t i = 0; i < frames.frameCount; i++) { compareFrames(frames, frames2);
EXPECT_STREQ(frames.frames[i].name, frames2.frames[i].name); }
}
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. // No inline frames should be filled because of no extra frames.
...@@ -213,9 +270,7 @@ TEST(SymbolizerTest, InlineFunctionBasicNoExtraFrames) { ...@@ -213,9 +270,7 @@ TEST(SymbolizerTest, InlineFunctionBasicNoExtraFrames) {
inlineBar<8>(frames2); inlineBar<8>(frames2);
symbolizer2.symbolize(frames2); symbolizer2.symbolize(frames2);
for (size_t i = 0; i < frames.frameCount; i++) { compareFrames<8>(frames, frames2);
EXPECT_STREQ(frames.frames[i].name, frames2.frames[i].name);
}
} }
TEST(SymbolizerTest, InlineFunctionWithCache) { TEST(SymbolizerTest, InlineFunctionWithCache) {
...@@ -225,25 +280,12 @@ TEST(SymbolizerTest, InlineFunctionWithCache) { ...@@ -225,25 +280,12 @@ TEST(SymbolizerTest, InlineFunctionWithCache) {
inlineBar<100>(frames); inlineBar<100>(frames);
symbolizer.symbolize(frames); symbolizer.symbolize(frames);
EXPECT_TRUE(frames.frameCount == 14 || frames.frameCount == 15); verifyStackTrace(frames, "inlineBar<100>", kFooCallByStandaloneBarLineNo);
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);
FrameArray<100> frames2; FrameArray<100> frames2;
inlineBar<100>(frames2); inlineBar<100>(frames2);
symbolizer.symbolize(frames2); symbolizer.symbolize(frames2);
EXPECT_EQ(frames.frameCount, frames2.frameCount); compareFrames(frames, frames2);
for (size_t i = 0; i < frames.frameCount; i++) {
EXPECT_STREQ(frames.frames[i].name, frames2.frames[i].name);
}
} }
} // namespace test } // 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