Fix stack walking using StackwalkerAddressList.
When building a stack trace using StackwalkerAddressList, if there are inlined frames then the stack trace will skip over the following frames, leading to missing frames in the symbolized stacktraces. Bug: 314930064 Change-Id: I5c7a1b2e7c2f728e27b2082e77ebe953808f38bc Reviewed-on: https://chromium-review.googlesource.com/c/breakpad/breakpad/+/5087692 Reviewed-by: Joshua Peraza <jperaza@chromium.org>
This commit is contained in:
parent
f49c2f1a20
commit
11ec9c3288
3 changed files with 110 additions and 21 deletions
|
@ -56,7 +56,8 @@ StackwalkerAddressList::StackwalkerAddressList(
|
|||
StackFrameSymbolizer* frame_symbolizer)
|
||||
: Stackwalker(NULL, NULL, modules, frame_symbolizer),
|
||||
frames_(frames),
|
||||
frame_count_(frame_count) {
|
||||
frame_count_(frame_count),
|
||||
next_frame_index_(0) {
|
||||
assert(frames);
|
||||
assert(frame_symbolizer);
|
||||
}
|
||||
|
@ -68,26 +69,22 @@ StackFrame* StackwalkerAddressList::GetContextFrame() {
|
|||
StackFrame* frame = new StackFrame();
|
||||
frame->instruction = frames_[0];
|
||||
frame->trust = StackFrame::FRAME_TRUST_PREWALKED;
|
||||
|
||||
next_frame_index_ = 1;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
StackFrame* StackwalkerAddressList::GetCallerFrame(const CallStack* stack,
|
||||
StackFrame* StackwalkerAddressList::GetCallerFrame(const CallStack*,
|
||||
bool stack_scan_allowed) {
|
||||
if (!stack) {
|
||||
BPLOG(ERROR) << "Can't get caller frame without stack";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t frame_index = stack->frames()->size();
|
||||
|
||||
// There are no more frames to fetch.
|
||||
if (frame_index >= frame_count_)
|
||||
if (next_frame_index_ >= frame_count_)
|
||||
return NULL;
|
||||
|
||||
// All frames have the highest level of trust because they were
|
||||
// explicitly provided.
|
||||
StackFrame* frame = new StackFrame();
|
||||
frame->instruction = frames_[frame_index];
|
||||
frame->instruction = frames_[next_frame_index_++];
|
||||
frame->trust = StackFrame::FRAME_TRUST_PREWALKED;
|
||||
return frame;
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ class StackwalkerAddressList : public Stackwalker {
|
|||
|
||||
const uint64_t* frames_;
|
||||
size_t frame_count_;
|
||||
size_t next_frame_index_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
|
|
@ -107,18 +107,32 @@ class StackwalkerAddressListTest : public testing::Test {
|
|||
Return(MockSymbolSupplier::FOUND)));
|
||||
}
|
||||
|
||||
void CheckCallStack(const CallStack& call_stack) {
|
||||
void CheckCallStack(const CallStack& call_stack, bool allow_inline=false) {
|
||||
const std::vector<StackFrame*>* frames = call_stack.frames();
|
||||
ASSERT_EQ(arraysize(kDummyFrames), frames->size());
|
||||
for (size_t i = 0; i < arraysize(kDummyFrames); ++i) {
|
||||
ASSERT_EQ(kDummyFrames[i], frames->at(i)->instruction);
|
||||
ASSERT_EQ(StackFrame::FRAME_TRUST_PREWALKED, frames->at(i)->trust);
|
||||
ASSERT_LE(arraysize(kDummyFrames), frames->size());
|
||||
|
||||
// Iterate through the produced stack frames, validating that all of the
|
||||
// prewalked frames match the expected input frames. Inlined frames are only
|
||||
// allowed if `allow_inline` is set to true.
|
||||
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < frames->size(); ++i) {
|
||||
if (j <= 2) {
|
||||
ASSERT_EQ(static_cast<const CodeModule*>(&module2), frames->at(i)->module);
|
||||
} else {
|
||||
ASSERT_EQ(static_cast<const CodeModule*>(&module1), frames->at(i)->module);
|
||||
}
|
||||
|
||||
ASSERT_EQ(kDummyFrames[j], frames->at(i)->instruction);
|
||||
if (frames->at(i)->trust == StackFrame::FRAME_TRUST_PREWALKED) {
|
||||
// Only move on to the next "real" stack frame if this isn't an
|
||||
// inlined stack frame.
|
||||
++j;
|
||||
} else {
|
||||
ASSERT_TRUE(allow_inline);
|
||||
ASSERT_EQ(StackFrame::FRAME_TRUST_INLINE, frames->at(i)->trust);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(static_cast<const CodeModule*>(&module2), frames->at(0)->module);
|
||||
ASSERT_EQ(static_cast<const CodeModule*>(&module2), frames->at(1)->module);
|
||||
ASSERT_EQ(static_cast<const CodeModule*>(&module2), frames->at(2)->module);
|
||||
ASSERT_EQ(static_cast<const CodeModule*>(&module1), frames->at(3)->module);
|
||||
ASSERT_EQ(static_cast<const CodeModule*>(&module1), frames->at(4)->module);
|
||||
}
|
||||
|
||||
MockCodeModule module1;
|
||||
|
@ -199,3 +213,80 @@ TEST_F(StackwalkerAddressListTest, ScanWithSymbols) {
|
|||
ASSERT_EQ("mod1func1", frames->at(4)->function_name);
|
||||
ASSERT_EQ(0x40001000u, frames->at(4)->function_base);
|
||||
}
|
||||
|
||||
TEST_F(StackwalkerAddressListTest, ScanWithInlining) {
|
||||
// File : FILE number(dex) name
|
||||
// Function: FUNC address(hex) size(hex) parameter_size(hex) name
|
||||
// Inline: : INLINE_ORIGIN number(dec) name
|
||||
// : INLINE depth(dec) line(dec) filenum(dec) inlinenum(dec)
|
||||
// address(hex) size(hex)
|
||||
// Line : address(hex) size(hex) line(dec) filenum(dec)
|
||||
SetModuleSymbols(&module2,
|
||||
"FILE 1 module2.cc\n"
|
||||
"INLINE_ORIGIN 0 mod2inlinefunc1\n"
|
||||
"INLINE_ORIGIN 1 mod2inlinefunc2\n"
|
||||
"INLINE_ORIGIN 2 mod2inlinefunc3\n"
|
||||
"FUNC 3000 100 10 mod2func3\n"
|
||||
"INLINE 0 1 1 1 3000 10\n"
|
||||
"INLINE 1 1 1 2 3000 10\n"
|
||||
"3000 10 1 1\n"
|
||||
"FUNC 2000 200 10 mod2func2\n"
|
||||
"INLINE 0 1 1 0 2000 10\n"
|
||||
"FUNC 1000 300 10 mod2func1\n");
|
||||
SetModuleSymbols(&module1,
|
||||
"FUNC 2000 200 10 mod1func2\n"
|
||||
"FUNC 1000 300 10 mod1func1\n");
|
||||
|
||||
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
|
||||
StackwalkerAddressList walker(kDummyFrames, arraysize(kDummyFrames),
|
||||
&modules, &frame_symbolizer);
|
||||
|
||||
CallStack call_stack;
|
||||
vector<const CodeModule*> modules_without_symbols;
|
||||
vector<const CodeModule*> modules_with_corrupt_symbols;
|
||||
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
|
||||
&modules_with_corrupt_symbols));
|
||||
|
||||
ASSERT_EQ(0u, modules_without_symbols.size());
|
||||
ASSERT_EQ(0u, modules_with_corrupt_symbols.size());
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(CheckCallStack(call_stack, /*allow_inline=*/true));
|
||||
|
||||
const std::vector<StackFrame*>* frames = call_stack.frames();
|
||||
|
||||
// We have full file/line information for the first function call, including
|
||||
// inline functions
|
||||
ASSERT_EQ("mod2inlinefunc3", frames->at(0)->function_name);
|
||||
ASSERT_EQ(0x50003000u, frames->at(0)->function_base);
|
||||
ASSERT_EQ("module2.cc", frames->at(0)->source_file_name);
|
||||
ASSERT_EQ(1, frames->at(0)->source_line);
|
||||
ASSERT_EQ(0x50003000u, frames->at(0)->source_line_base);
|
||||
|
||||
ASSERT_EQ("mod2inlinefunc2", frames->at(1)->function_name);
|
||||
ASSERT_EQ(0x50003000u, frames->at(1)->function_base);
|
||||
ASSERT_EQ("module2.cc", frames->at(1)->source_file_name);
|
||||
ASSERT_EQ(1, frames->at(1)->source_line);
|
||||
ASSERT_EQ(0x50003000u, frames->at(1)->source_line_base);
|
||||
|
||||
ASSERT_EQ("mod2func3", frames->at(2)->function_name);
|
||||
ASSERT_EQ(0x50003000u, frames->at(2)->function_base);
|
||||
ASSERT_EQ("module2.cc", frames->at(2)->source_file_name);
|
||||
ASSERT_EQ(1, frames->at(2)->source_line);
|
||||
ASSERT_EQ(0x50003000u, frames->at(2)->source_line_base);
|
||||
|
||||
// Here we have inlining information, but no file/line information.
|
||||
ASSERT_EQ("mod2inlinefunc1", frames->at(3)->function_name);
|
||||
ASSERT_EQ(0x50002000u, frames->at(3)->function_base);
|
||||
|
||||
ASSERT_EQ("mod2func2", frames->at(4)->function_name);
|
||||
ASSERT_EQ(0x50002000u, frames->at(4)->function_base);
|
||||
|
||||
ASSERT_EQ("mod2func1", frames->at(5)->function_name);
|
||||
ASSERT_EQ(0x50001000u, frames->at(5)->function_base);
|
||||
|
||||
ASSERT_EQ("mod1func2", frames->at(6)->function_name);
|
||||
ASSERT_EQ(0x40002000u, frames->at(6)->function_base);
|
||||
|
||||
ASSERT_EQ("mod1func1", frames->at(7)->function_name);
|
||||
ASSERT_EQ(0x40001000u, frames->at(7)->function_base);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue