Add support for CFI based stack walking on Arm64.

This CL adds CFI based stack walking support for Arm64 to BreakPad along with
unit tests.  The Arm64 CFI stack walker is based on the Arm CFI stack walker

BUG=367367,335641,354405
R=blundell@chromium.org, mark@chromium.org

Review URL: https://breakpad.appspot.com/1664002

git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1325 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
rmcilroy@chromium.org 2014-05-06 09:18:30 +00:00
parent 44ba0b2050
commit a789d1d26b
3 changed files with 420 additions and 5 deletions

View File

@ -78,8 +78,77 @@ StackFrame* StackwalkerARM64::GetContextFrame() {
StackFrameARM64* StackwalkerARM64::GetCallerByCFIFrameInfo(
const vector<StackFrame*> &frames,
CFIFrameInfo* cfi_frame_info) {
// Obtaining the stack frame from CFI info is not yet supported for ARM64.
return NULL;
StackFrameARM64* last_frame = static_cast<StackFrameARM64*>(frames.back());
static const char* register_names[] = {
"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
"x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
"x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23",
"x24", "x25", "x26", "x27", "x28", "x29", "x30", "sp",
"pc", NULL
};
// Populate a dictionary with the valid register values in last_frame.
CFIFrameInfo::RegisterValueMap<uint64_t> callee_registers;
for (int i = 0; register_names[i]; i++) {
if (last_frame->context_validity & StackFrameARM64::RegisterValidFlag(i))
callee_registers[register_names[i]] = last_frame->context.iregs[i];
}
// Use the STACK CFI data to recover the caller's register values.
CFIFrameInfo::RegisterValueMap<uint64_t> caller_registers;
if (!cfi_frame_info->FindCallerRegs(callee_registers, *memory_,
&caller_registers)) {
return NULL;
}
// Construct a new stack frame given the values the CFI recovered.
scoped_ptr<StackFrameARM64> frame(new StackFrameARM64());
for (int i = 0; register_names[i]; i++) {
CFIFrameInfo::RegisterValueMap<uint64_t>::iterator entry =
caller_registers.find(register_names[i]);
if (entry != caller_registers.end()) {
// We recovered the value of this register; fill the context with the
// value from caller_registers.
frame->context_validity |= StackFrameARM64::RegisterValidFlag(i);
frame->context.iregs[i] = entry->second;
} else if (19 <= i && i <= 29 && (last_frame->context_validity &
StackFrameARM64::RegisterValidFlag(i))) {
// If the STACK CFI data doesn't mention some callee-saves register, and
// it is valid in the callee, assume the callee has not yet changed it.
// Registers r19 through r29 are callee-saves, according to the Procedure
// Call Standard for the ARM AARCH64 Architecture, which the Linux ABI
// follows.
frame->context_validity |= StackFrameARM64::RegisterValidFlag(i);
frame->context.iregs[i] = last_frame->context.iregs[i];
}
}
// If the CFI doesn't recover the PC explicitly, then use .ra.
if (!(frame->context_validity & StackFrameARM64::CONTEXT_VALID_PC)) {
CFIFrameInfo::RegisterValueMap<uint64_t>::iterator entry =
caller_registers.find(".ra");
if (entry != caller_registers.end()) {
frame->context_validity |= StackFrameARM64::CONTEXT_VALID_PC;
frame->context.iregs[MD_CONTEXT_ARM64_REG_PC] = entry->second;
}
}
// If the CFI doesn't recover the SP explicitly, then use .cfa.
if (!(frame->context_validity & StackFrameARM64::CONTEXT_VALID_SP)) {
CFIFrameInfo::RegisterValueMap<uint64_t>::iterator entry =
caller_registers.find(".cfa");
if (entry != caller_registers.end()) {
frame->context_validity |= StackFrameARM64::CONTEXT_VALID_SP;
frame->context.iregs[MD_CONTEXT_ARM64_REG_SP] = entry->second;
}
}
// If we didn't recover the PC and the SP, then the frame isn't very useful.
static const uint64_t essentials = (StackFrameARM64::CONTEXT_VALID_SP
| StackFrameARM64::CONTEXT_VALID_PC);
if ((frame->context_validity & essentials) != essentials)
return NULL;
frame->trust = StackFrame::FRAME_TRUST_CFI;
return frame.release();
}
StackFrameARM64* StackwalkerARM64::GetCallerByStackScan(

View File

@ -63,7 +63,9 @@ class StackwalkerARM64 : public Stackwalker {
// Change the context validity mask of the frame returned by
// GetContextFrame to VALID. This is only for use by unit tests; the
// default behavior is correct for all application code.
void SetContextFrameValidity(int valid) { context_frame_validity_ = valid; }
void SetContextFrameValidity(uint64_t valid) {
context_frame_validity_ = valid;
}
private:
// Implementation of Stackwalker, using arm64 context and stack conventions.

View File

@ -76,8 +76,7 @@ class StackwalkerARM64Fixture {
// for tests to play with.
module1(0x40000000, 0x10000, "module1", "version1"),
module2(0x50000000, 0x10000, "module2", "version2") {
// Identify the system as an iOS system, since that is the only platform
// for which ARM64 support is currently enabled.
// Identify the system as an iOS system.
system_info.os = "iOS";
system_info.os_short = "ios";
system_info.cpu = "arm64";
@ -534,3 +533,348 @@ TEST_F(GetFramesByFramePointer, OnlyFramePointer) {
EXPECT_EQ(frame2_sp.Value(), frame2->context.iregs[MD_CONTEXT_ARM64_REG_SP]);
EXPECT_EQ(0U, frame2->context.iregs[MD_CONTEXT_ARM64_REG_FP]);
}
struct CFIFixture: public StackwalkerARM64Fixture {
CFIFixture() {
// Provide a bunch of STACK CFI records; we'll walk to the caller
// from every point in this series, expecting to find the same set
// of register values.
SetModuleSymbols(&module1,
// The youngest frame's function.
"FUNC 4000 1000 10 enchiridion\n"
// Initially, nothing has been pushed on the stack,
// and the return address is still in the link
// register (x30).
"STACK CFI INIT 4000 100 .cfa: sp 0 + .ra: x30\n"
// Push x19, x20, the frame pointer and the link register.
"STACK CFI 4001 .cfa: sp 32 + .ra: .cfa -8 + ^"
" x19: .cfa -32 + ^ x20: .cfa -24 + ^ "
" x29: .cfa -16 + ^\n"
// Save x19..x22 in x0..x3: verify that we populate
// the youngest frame with all the values we have.
"STACK CFI 4002 x19: x0 x20: x1 x21: x2 x22: x3\n"
// Restore x19..x22. Save the non-callee-saves register x1.
"STACK CFI 4003 .cfa: sp 40 + x1: .cfa 40 - ^"
" x19: x19 x20: x20 x21: x21 x22: x22\n"
// Move the .cfa back eight bytes, to point at the return
// address, and restore the sp explicitly.
"STACK CFI 4005 .cfa: sp 32 + x1: .cfa 32 - ^"
" x29: .cfa 8 - ^ .ra: .cfa ^ sp: .cfa 8 +\n"
// Recover the PC explicitly from a new stack slot;
// provide garbage for the .ra.
"STACK CFI 4006 .cfa: sp 40 + pc: .cfa 40 - ^\n"
// The calling function.
"FUNC 5000 1000 10 epictetus\n"
// Mark it as end of stack.
"STACK CFI INIT 5000 1000 .cfa: 0 .ra: 0\n"
// A function whose CFI makes the stack pointer
// go backwards.
"FUNC 6000 1000 20 palinal\n"
"STACK CFI INIT 6000 1000 .cfa: sp 8 - .ra: x30\n"
// A function with CFI expressions that can't be
// evaluated.
"FUNC 7000 1000 20 rhetorical\n"
"STACK CFI INIT 7000 1000 .cfa: moot .ra: ambiguous\n");
// Provide some distinctive values for the caller's registers.
expected.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x0000000040005510L;
expected.iregs[MD_CONTEXT_ARM64_REG_SP] = 0x0000000080000000L;
expected.iregs[19] = 0x5e68b5d5b5d55e68L;
expected.iregs[20] = 0x34f3ebd1ebd134f3L;
expected.iregs[21] = 0x74bca31ea31e74bcL;
expected.iregs[22] = 0x16b32dcb2dcb16b3L;
expected.iregs[23] = 0x21372ada2ada2137L;
expected.iregs[24] = 0x557dbbbbbbbb557dL;
expected.iregs[25] = 0x8ca748bf48bf8ca7L;
expected.iregs[26] = 0x21f0ab46ab4621f0L;
expected.iregs[27] = 0x146732b732b71467L;
expected.iregs[28] = 0xa673645fa673645fL;
expected.iregs[MD_CONTEXT_ARM64_REG_FP] = 0xe11081128112e110L;
// Expect CFI to recover all callee-saves registers. Since CFI is the
// only stack frame construction technique we have, aside from the
// context frame itself, there's no way for us to have a set of valid
// registers smaller than this.
expected_validity = (StackFrameARM64::CONTEXT_VALID_PC |
StackFrameARM64::CONTEXT_VALID_SP |
StackFrameARM64::CONTEXT_VALID_X19 |
StackFrameARM64::CONTEXT_VALID_X20 |
StackFrameARM64::CONTEXT_VALID_X21 |
StackFrameARM64::CONTEXT_VALID_X22 |
StackFrameARM64::CONTEXT_VALID_X23 |
StackFrameARM64::CONTEXT_VALID_X24 |
StackFrameARM64::CONTEXT_VALID_X25 |
StackFrameARM64::CONTEXT_VALID_X26 |
StackFrameARM64::CONTEXT_VALID_X27 |
StackFrameARM64::CONTEXT_VALID_X28 |
StackFrameARM64::CONTEXT_VALID_FP);
// By default, context frames provide all registers, as normal.
context_frame_validity = StackFrameARM64::CONTEXT_VALID_ALL;
// By default, registers are unchanged.
raw_context = expected;
}
// Walk the stack, using stack_section as the contents of the stack
// and raw_context as the current register values. (Set the stack
// pointer to the stack's starting address.) Expect two stack
// frames; in the older frame, expect the callee-saves registers to
// have values matching those in 'expected'.
void CheckWalk() {
RegionFromSection();
raw_context.iregs[MD_CONTEXT_ARM64_REG_SP] = stack_section.start().Value();
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerARM64 walker(&system_info, &raw_context, &stack_region,
&modules, &frame_symbolizer);
walker.SetContextFrameValidity(context_frame_validity);
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());
frames = call_stack.frames();
ASSERT_EQ(2U, frames->size());
StackFrameARM64 *frame0 = static_cast<StackFrameARM64 *>(frames->at(0));
EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
ASSERT_EQ(context_frame_validity, frame0->context_validity);
EXPECT_EQ("enchiridion", frame0->function_name);
EXPECT_EQ(0x0000000040004000UL, frame0->function_base);
StackFrameARM64 *frame1 = static_cast<StackFrameARM64 *>(frames->at(1));
EXPECT_EQ(StackFrame::FRAME_TRUST_CFI, frame1->trust);
ASSERT_EQ(expected_validity, frame1->context_validity);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_X1)
EXPECT_EQ(expected.iregs[1], frame1->context.iregs[1]);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_X19)
EXPECT_EQ(expected.iregs[19], frame1->context.iregs[19]);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_X20)
EXPECT_EQ(expected.iregs[20], frame1->context.iregs[20]);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_X21)
EXPECT_EQ(expected.iregs[21], frame1->context.iregs[21]);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_X22)
EXPECT_EQ(expected.iregs[22], frame1->context.iregs[22]);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_X23)
EXPECT_EQ(expected.iregs[23], frame1->context.iregs[23]);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_X24)
EXPECT_EQ(expected.iregs[24], frame1->context.iregs[24]);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_X25)
EXPECT_EQ(expected.iregs[25], frame1->context.iregs[25]);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_X26)
EXPECT_EQ(expected.iregs[26], frame1->context.iregs[26]);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_X27)
EXPECT_EQ(expected.iregs[27], frame1->context.iregs[27]);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_X28)
EXPECT_EQ(expected.iregs[28], frame1->context.iregs[28]);
if (expected_validity & StackFrameARM64::CONTEXT_VALID_FP)
EXPECT_EQ(expected.iregs[MD_CONTEXT_ARM64_REG_FP],
frame1->context.iregs[MD_CONTEXT_ARM64_REG_FP]);
// We would never have gotten a frame in the first place if the SP
// and PC weren't valid or ->instruction weren't set.
EXPECT_EQ(expected.iregs[MD_CONTEXT_ARM64_REG_SP],
frame1->context.iregs[MD_CONTEXT_ARM64_REG_SP]);
EXPECT_EQ(expected.iregs[MD_CONTEXT_ARM64_REG_PC],
frame1->context.iregs[MD_CONTEXT_ARM64_REG_PC]);
EXPECT_EQ(expected.iregs[MD_CONTEXT_ARM64_REG_PC],
frame1->instruction + 4);
EXPECT_EQ("epictetus", frame1->function_name);
}
// The values we expect to find for the caller's registers.
MDRawContextARM64 expected;
// The validity mask for expected.
uint64_t expected_validity;
// The validity mask to impose on the context frame.
uint64_t context_frame_validity;
};
class CFI: public CFIFixture, public Test { };
TEST_F(CFI, At4000) {
stack_section.start() = expected.iregs[MD_CONTEXT_ARM64_REG_SP];
raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x0000000040004000L;
raw_context.iregs[MD_CONTEXT_ARM64_REG_LR] = 0x0000000040005510L;
CheckWalk();
}
TEST_F(CFI, At4001) {
Label frame1_sp = expected.iregs[MD_CONTEXT_ARM64_REG_SP];
stack_section
.D64(0x5e68b5d5b5d55e68L) // saved x19
.D64(0x34f3ebd1ebd134f3L) // saved x20
.D64(0xe11081128112e110L) // saved fp
.D64(0x0000000040005510L) // return address
.Mark(&frame1_sp); // This effectively sets stack_section.start().
raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x0000000040004001L;
// distinct callee x19, x20 and fp
raw_context.iregs[19] = 0xadc9f635a635adc9L;
raw_context.iregs[20] = 0x623135ac35ac6231L;
raw_context.iregs[MD_CONTEXT_ARM64_REG_FP] = 0x5fc4be14be145fc4L;
CheckWalk();
}
// As above, but unwind from a context that has only the PC and SP.
TEST_F(CFI, At4001LimitedValidity) {
Label frame1_sp = expected.iregs[MD_CONTEXT_ARM64_REG_SP];
stack_section
.D64(0x5e68b5d5b5d55e68L) // saved x19
.D64(0x34f3ebd1ebd134f3L) // saved x20
.D64(0xe11081128112e110L) // saved fp
.D64(0x0000000040005510L) // return address
.Mark(&frame1_sp); // This effectively sets stack_section.start().
context_frame_validity =
StackFrameARM64::CONTEXT_VALID_PC | StackFrameARM64::CONTEXT_VALID_SP;
raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x0000000040004001L;
raw_context.iregs[MD_CONTEXT_ARM64_REG_FP] = 0x5fc4be14be145fc4L;
expected_validity = (StackFrameARM64::CONTEXT_VALID_PC
| StackFrameARM64::CONTEXT_VALID_SP
| StackFrameARM64::CONTEXT_VALID_FP
| StackFrameARM64::CONTEXT_VALID_X19
| StackFrameARM64::CONTEXT_VALID_X20);
CheckWalk();
}
TEST_F(CFI, At4002) {
Label frame1_sp = expected.iregs[MD_CONTEXT_ARM64_REG_SP];
stack_section
.D64(0xff3dfb81fb81ff3dL) // no longer saved x19
.D64(0x34f3ebd1ebd134f3L) // no longer saved x20
.D64(0xe11081128112e110L) // saved fp
.D64(0x0000000040005510L) // return address
.Mark(&frame1_sp); // This effectively sets stack_section.start().
raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x0000000040004002L;
raw_context.iregs[0] = 0x5e68b5d5b5d55e68L; // saved x19
raw_context.iregs[1] = 0x34f3ebd1ebd134f3L; // saved x20
raw_context.iregs[2] = 0x74bca31ea31e74bcL; // saved x21
raw_context.iregs[3] = 0x16b32dcb2dcb16b3L; // saved x22
raw_context.iregs[19] = 0xadc9f635a635adc9L; // distinct callee x19
raw_context.iregs[20] = 0x623135ac35ac6231L; // distinct callee x20
raw_context.iregs[21] = 0xac4543564356ac45L; // distinct callee x21
raw_context.iregs[22] = 0x2561562f562f2561L; // distinct callee x22
// distinct callee fp
raw_context.iregs[MD_CONTEXT_ARM64_REG_FP] = 0x5fc4be14be145fc4L;
CheckWalk();
}
TEST_F(CFI, At4003) {
Label frame1_sp = expected.iregs[MD_CONTEXT_ARM64_REG_SP];
stack_section
.D64(0xdd5a48c848c8dd5aL) // saved x1 (even though it's not callee-saves)
.D64(0xff3dfb81fb81ff3dL) // no longer saved x19
.D64(0x34f3ebd1ebd134f3L) // no longer saved x20
.D64(0xe11081128112e110L) // saved fp
.D64(0x0000000040005510L) // return address
.Mark(&frame1_sp); // This effectively sets stack_section.start().
raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x0000000040004003L;
// distinct callee x1 and fp
raw_context.iregs[1] = 0xfb756319fb756319L;
raw_context.iregs[MD_CONTEXT_ARM64_REG_FP] = 0x5fc4be14be145fc4L;
// caller's x1
expected.iregs[1] = 0xdd5a48c848c8dd5aL;
expected_validity |= StackFrameARM64::CONTEXT_VALID_X1;
CheckWalk();
}
// We have no new rule at module offset 0x4004, so the results here should
// be the same as those at module offset 0x4003.
TEST_F(CFI, At4004) {
Label frame1_sp = expected.iregs[MD_CONTEXT_ARM64_REG_SP];
stack_section
.D64(0xdd5a48c848c8dd5aL) // saved x1 (even though it's not callee-saves)
.D64(0xff3dfb81fb81ff3dL) // no longer saved x19
.D64(0x34f3ebd1ebd134f3L) // no longer saved x20
.D64(0xe11081128112e110L) // saved fp
.D64(0x0000000040005510L) // return address
.Mark(&frame1_sp); // This effectively sets stack_section.start().
raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x0000000040004004L;
// distinct callee x1 and fp
raw_context.iregs[1] = 0xfb756319fb756319L;
raw_context.iregs[MD_CONTEXT_ARM64_REG_FP] = 0x5fc4be14be145fc4L;
// caller's x1
expected.iregs[1] = 0xdd5a48c848c8dd5aL;
expected_validity |= StackFrameARM64::CONTEXT_VALID_X1;
CheckWalk();
}
// Here we move the .cfa, but provide an explicit rule to recover the SP,
// so again there should be no change in the registers recovered.
TEST_F(CFI, At4005) {
Label frame1_sp = expected.iregs[MD_CONTEXT_ARM64_REG_SP];
stack_section
.D64(0xdd5a48c848c8dd5aL) // saved x1 (even though it's not callee-saves)
.D64(0xff3dfb81fb81ff3dL) // no longer saved x19
.D64(0x34f3ebd1ebd134f3L) // no longer saved x20
.D64(0xe11081128112e110L) // saved fp
.D64(0x0000000040005510L) // return address
.Mark(&frame1_sp); // This effectively sets stack_section.start().
raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x0000000040004005L;
raw_context.iregs[1] = 0xfb756319fb756319L; // distinct callee x1
expected.iregs[1] = 0xdd5a48c848c8dd5aL; // caller's x1
expected_validity |= StackFrameARM64::CONTEXT_VALID_X1;
CheckWalk();
}
// Here we provide an explicit rule for the PC, and have the saved .ra be
// bogus.
TEST_F(CFI, At4006) {
Label frame1_sp = expected.iregs[MD_CONTEXT_ARM64_REG_SP];
stack_section
.D64(0x0000000040005510L) // saved pc
.D64(0xdd5a48c848c8dd5aL) // saved x1 (even though it's not callee-saves)
.D64(0xff3dfb81fb81ff3dL) // no longer saved x19
.D64(0x34f3ebd1ebd134f3L) // no longer saved x20
.D64(0xe11081128112e110L) // saved fp
.D64(0xf8d157835783f8d1L) // .ra rule recovers this, which is garbage
.Mark(&frame1_sp); // This effectively sets stack_section.start().
raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x0000000040004006L;
raw_context.iregs[1] = 0xfb756319fb756319L; // distinct callee x1
expected.iregs[1] = 0xdd5a48c848c8dd5aL; // caller's x1
expected_validity |= StackFrameARM64::CONTEXT_VALID_X1;
CheckWalk();
}
// Check that we reject rules that would cause the stack pointer to
// move in the wrong direction.
TEST_F(CFI, RejectBackwards) {
raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x0000000040006000L;
raw_context.iregs[MD_CONTEXT_ARM64_REG_SP] = 0x0000000080000000L;
raw_context.iregs[MD_CONTEXT_ARM64_REG_LR] = 0x0000000040005510L;
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerARM64 walker(&system_info, &raw_context, &stack_region, &modules,
&frame_symbolizer);
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());
frames = call_stack.frames();
ASSERT_EQ(1U, frames->size());
}
// Check that we reject rules whose expressions' evaluation fails.
TEST_F(CFI, RejectBadExpressions) {
raw_context.iregs[MD_CONTEXT_ARM64_REG_PC] = 0x0000000040007000L;
raw_context.iregs[MD_CONTEXT_ARM64_REG_SP] = 0x0000000080000000L;
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerARM64 walker(&system_info, &raw_context, &stack_region, &modules,
&frame_symbolizer);
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());
frames = call_stack.frames();
ASSERT_EQ(1U, frames->size());
}