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:
Mark Brand 2023-12-05 12:57:29 +01:00 committed by Joshua Peraza
parent f49c2f1a20
commit 11ec9c3288
3 changed files with 110 additions and 21 deletions

View file

@ -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;
}

View file

@ -63,6 +63,7 @@ class StackwalkerAddressList : public Stackwalker {
const uint64_t* frames_;
size_t frame_count_;
size_t next_frame_index_;
};
} // namespace google_breakpad

View file

@ -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);
}