From 01596d3bc13b5f6aa7fd708ee131579921eba010 Mon Sep 17 00:00:00 2001 From: "qsr@chromium.org" Date: Mon, 17 Oct 2011 13:42:35 +0000 Subject: Use frame pointer to walk ARM stack on iOS. Review URL: http://breakpad.appspot.com/314001 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@869 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/google_breakpad/common/minidump_cpu_arm.h | 9 +- src/processor/stackwalker.cc | 8 +- src/processor/stackwalker_arm.cc | 79 ++++++++-- src/processor/stackwalker_arm.h | 12 +- src/processor/stackwalker_arm_unittest.cc | 202 ++++++++++++++++++++++++-- 5 files changed, 280 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/google_breakpad/common/minidump_cpu_arm.h b/src/google_breakpad/common/minidump_cpu_arm.h index aa1c88f5..dd071293 100644 --- a/src/google_breakpad/common/minidump_cpu_arm.h +++ b/src/google_breakpad/common/minidump_cpu_arm.h @@ -120,10 +120,11 @@ typedef struct { * purpose. */ enum MDARMRegisterNumbers { - MD_CONTEXT_ARM_REG_FP = 11, - MD_CONTEXT_ARM_REG_SP = 13, - MD_CONTEXT_ARM_REG_LR = 14, - MD_CONTEXT_ARM_REG_PC = 15 + MD_CONTEXT_ARM_REG_IOS_FP = 7, + MD_CONTEXT_ARM_REG_FP = 11, + MD_CONTEXT_ARM_REG_SP = 13, + MD_CONTEXT_ARM_REG_LR = 14, + MD_CONTEXT_ARM_REG_PC = 15 }; /* For (MDRawContextARM).context_flags. These values indicate the type of diff --git a/src/processor/stackwalker.cc b/src/processor/stackwalker.cc index 9ddb60ae..2bd333ac 100644 --- a/src/processor/stackwalker.cc +++ b/src/processor/stackwalker.cc @@ -44,6 +44,7 @@ #include "google_breakpad/processor/source_line_resolver_interface.h" #include "google_breakpad/processor/stack_frame.h" #include "google_breakpad/processor/symbol_supplier.h" +#include "google_breakpad/processor/system_info.h" #include "processor/linked_ptr.h" #include "processor/logging.h" #include "processor/scoped_ptr.h" @@ -187,10 +188,13 @@ Stackwalker* Stackwalker::StackwalkerForCPU( break; case MD_CONTEXT_ARM: + int fp_register = -1; + if (system_info->os_short == "ios") + fp_register = MD_CONTEXT_ARM_REG_IOS_FP; cpu_stackwalker = new StackwalkerARM(system_info, context->GetContextARM(), - memory, modules, supplier, - resolver); + fp_register, memory, modules, + supplier, resolver); break; } diff --git a/src/processor/stackwalker_arm.cc b/src/processor/stackwalker_arm.cc index 3b91caff..0a3c522d 100644 --- a/src/processor/stackwalker_arm.cc +++ b/src/processor/stackwalker_arm.cc @@ -33,7 +33,6 @@ // // Author: Mark Mentovai, Ted Mielczarek, Jim Blandy - #include "google_breakpad/processor/call_stack.h" #include "google_breakpad/processor/memory_region.h" #include "google_breakpad/processor/source_line_resolver_interface.h" @@ -48,12 +47,13 @@ namespace google_breakpad { StackwalkerARM::StackwalkerARM(const SystemInfo *system_info, const MDRawContextARM *context, + int fp_register, MemoryRegion *memory, const CodeModules *modules, SymbolSupplier *supplier, SourceLineResolverInterface *resolver) : Stackwalker(system_info, memory, modules, supplier, resolver), - context_(context), + context_(context), fp_register_(fp_register), context_frame_validity_(StackFrameARM::CONTEXT_VALID_ALL) { } @@ -70,7 +70,7 @@ StackFrame* StackwalkerARM::GetContextFrame() { frame->context = *context_; frame->context_validity = context_frame_validity_; frame->trust = StackFrame::FRAME_TRUST_CONTEXT; - frame->instruction = frame->context.iregs[15]; + frame->instruction = frame->context.iregs[MD_CONTEXT_ARM_REG_PC]; return frame; } @@ -125,8 +125,18 @@ StackFrameARM *StackwalkerARM::GetCallerByCFIFrameInfo( CFIFrameInfo::RegisterValueMap::iterator entry = caller_registers.find(".ra"); if (entry != caller_registers.end()) { - frame->context_validity |= StackFrameARM::CONTEXT_VALID_PC; - frame->context.iregs[MD_CONTEXT_ARM_REG_PC] = entry->second; + if (fp_register_ == -1) { + frame->context_validity |= StackFrameARM::CONTEXT_VALID_PC; + frame->context.iregs[MD_CONTEXT_ARM_REG_PC] = entry->second; + } else { + // The CFI updated the link register and not the program counter. + // Handle getting the program counter from the link register. + frame->context_validity |= StackFrameARM::CONTEXT_VALID_PC; + frame->context_validity |= StackFrameARM::CONTEXT_VALID_LR; + frame->context.iregs[MD_CONTEXT_ARM_REG_LR] = entry->second; + frame->context.iregs[MD_CONTEXT_ARM_REG_PC] = + last_frame->context.iregs[MD_CONTEXT_ARM_REG_LR]; + } } } // If the CFI doesn't recover the SP explicitly, then use .cfa. @@ -154,7 +164,7 @@ StackFrameARM *StackwalkerARM::GetCallerByStackScan( StackFrameARM *last_frame = static_cast(frames.back()); u_int32_t last_sp = last_frame->context.iregs[MD_CONTEXT_ARM_REG_SP]; u_int32_t caller_sp, caller_pc; - + if (!ScanForReturnAddress(last_sp, &caller_sp, &caller_pc)) { // No plausible return address was found. return NULL; @@ -179,6 +189,52 @@ StackFrameARM *StackwalkerARM::GetCallerByStackScan( return frame; } +StackFrameARM *StackwalkerARM::GetCallerByFramePointer( + const vector &frames) { + StackFrameARM *last_frame = static_cast(frames.back()); + + if (!(last_frame->context_validity & + StackFrameARM::RegisterValidFlag(fp_register_))) { + return NULL; + } + + u_int32_t last_fp = last_frame->context.iregs[fp_register_]; + + u_int32_t caller_fp = 0; + if (last_fp && !memory_->GetMemoryAtAddress(last_fp, &caller_fp)) { + BPLOG(ERROR) << "Unable to read caller_fp from last_fp: 0x" + << std::hex << last_fp; + return NULL; + } + + u_int32_t caller_lr = 0; + if (last_fp && !memory_->GetMemoryAtAddress(last_fp + 4, &caller_lr)) { + BPLOG(ERROR) << "Unable to read caller_lr from last_fp + 4: 0x" + << std::hex << (last_fp + 4); + return NULL; + } + + u_int32_t caller_sp = last_fp ? last_fp + 8 : + last_frame->context.iregs[MD_CONTEXT_ARM_REG_SP]; + + // Create a new stack frame (ownership will be transferred to the caller) + // and fill it in. + StackFrameARM *frame = new StackFrameARM(); + + frame->trust = StackFrame::FRAME_TRUST_FP; + frame->context = last_frame->context; + frame->context.iregs[fp_register_] = caller_fp; + frame->context.iregs[MD_CONTEXT_ARM_REG_SP] = caller_sp; + frame->context.iregs[MD_CONTEXT_ARM_REG_PC] = + last_frame->context.iregs[MD_CONTEXT_ARM_REG_LR]; + frame->context.iregs[MD_CONTEXT_ARM_REG_LR] = caller_lr; + frame->context_validity = StackFrameARM::CONTEXT_VALID_PC | + StackFrameARM::CONTEXT_VALID_LR | + StackFrameARM::RegisterValidFlag(fp_register_) | + StackFrameARM::CONTEXT_VALID_SP; + return frame; +} + StackFrame* StackwalkerARM::GetCallerFrame(const CallStack *stack) { if (!memory_ || !stack) { BPLOG(ERROR) << "Can't get caller frame without memory or stack"; @@ -196,10 +252,13 @@ StackFrame* StackwalkerARM::GetCallerFrame(const CallStack *stack) { frame.reset(GetCallerByCFIFrameInfo(frames, cfi_frame_info.get())); // If CFI failed, or there wasn't CFI available, fall back - // to stack scanning. - if (!frame.get()) { + // to frame pointer, if this is configured. + if (fp_register_ >= 0 && !frame.get()) + frame.reset(GetCallerByFramePointer(frames)); + + // If everuthing failed, fall back to stack scanning. + if (!frame.get()) frame.reset(GetCallerByStackScan(frames)); - } // If nothing worked, tell the caller. if (!frame.get()) @@ -225,7 +284,7 @@ StackFrame* StackwalkerARM::GetCallerFrame(const CallStack *stack) { // match up with the line that contains the function call. Callers that // require the exact return address value may access // frame->context.iregs[MD_CONTEXT_ARM_REG_PC]. - frame->instruction = frame->context.iregs[MD_CONTEXT_ARM_REG_PC] - 1; + frame->instruction = frame->context.iregs[MD_CONTEXT_ARM_REG_PC] - 2; return frame.release(); } diff --git a/src/processor/stackwalker_arm.h b/src/processor/stackwalker_arm.h index 830579b2..5390c752 100644 --- a/src/processor/stackwalker_arm.h +++ b/src/processor/stackwalker_arm.h @@ -40,7 +40,6 @@ #ifndef PROCESSOR_STACKWALKER_ARM_H__ #define PROCESSOR_STACKWALKER_ARM_H__ - #include "google_breakpad/common/breakpad_types.h" #include "google_breakpad/common/minidump_format.h" #include "google_breakpad/processor/stackwalker.h" @@ -57,6 +56,7 @@ class StackwalkerARM : public Stackwalker { // to the base Stackwalker constructor. StackwalkerARM(const SystemInfo *system_info, const MDRawContextARM *context, + int fp_register, MemoryRegion *memory, const CodeModules *modules, SymbolSupplier *supplier, @@ -78,8 +78,12 @@ class StackwalkerARM : public Stackwalker { StackFrameARM *GetCallerByCFIFrameInfo(const vector &frames, CFIFrameInfo *cfi_frame_info); + // Use the frame pointer. The caller takes ownership of the returned frame. + // Return NULL on failure. + StackFrameARM *GetCallerByFramePointer(const vector &frames); + // Scan the stack for plausible return addresses. The caller takes ownership - // of the returned frame. Return NULL on failure. + // of the returned frame. Return NULL on failure. StackFrameARM *GetCallerByStackScan(const vector &frames); // Stores the CPU context corresponding to the youngest stack frame, to @@ -90,6 +94,10 @@ class StackwalkerARM : public Stackwalker { // CONTEXT_VALID_ALL in real use; it is only changeable for the sake of // unit tests. int context_frame_validity_; + + // The register to use a as frame pointer. The value is -1 if frame pointer + // cannot be used. + int fp_register_; }; diff --git a/src/processor/stackwalker_arm_unittest.cc b/src/processor/stackwalker_arm_unittest.cc index cb7ce631..6a623c27 100644 --- a/src/processor/stackwalker_arm_unittest.cc +++ b/src/processor/stackwalker_arm_unittest.cc @@ -116,7 +116,7 @@ class StackwalkerARMFixture { for (size_t i = 0; i < sizeof(*raw_context); i++) reinterpret_cast(raw_context)[i] = (x += 17); } - + SystemInfo system_info; MDRawContextARM raw_context; Section stack_section; @@ -136,7 +136,7 @@ TEST_F(SanityCheck, NoResolver) { // Since we have no call frame information, and all unwinding // requires call frame information, the stack walk will end after // the first frame. - StackwalkerARM walker(&system_info, &raw_context, &stack_region, &modules, + StackwalkerARM walker(&system_info, &raw_context, -1, &stack_region, &modules, NULL, NULL); // This should succeed even without a resolver or supplier. ASSERT_TRUE(walker.Walk(&call_stack)); @@ -154,7 +154,7 @@ TEST_F(GetContextFrame, Simple) { // Since we have no call frame information, and all unwinding // requires call frame information, the stack walk will end after // the first frame. - StackwalkerARM walker(&system_info, &raw_context, &stack_region, &modules, + StackwalkerARM walker(&system_info, &raw_context, -1, &stack_region, &modules, &supplier, &resolver); ASSERT_TRUE(walker.Walk(&call_stack)); frames = call_stack.frames(); @@ -201,7 +201,7 @@ TEST_F(GetCallerFrame, ScanWithoutSymbols) { raw_context.iregs[MD_CONTEXT_ARM_REG_PC] = 0x40005510; raw_context.iregs[MD_CONTEXT_ARM_REG_SP] = stack_section.start().Value(); - StackwalkerARM walker(&system_info, &raw_context, &stack_region, &modules, + StackwalkerARM walker(&system_info, &raw_context, -1, &stack_region, &modules, &supplier, &resolver); ASSERT_TRUE(walker.Walk(&call_stack)); frames = call_stack.frames(); @@ -264,7 +264,7 @@ TEST_F(GetCallerFrame, ScanWithFunctionSymbols) { // The calling frame's function. "FUNC 100 400 10 marsupial\n"); - StackwalkerARM walker(&system_info, &raw_context, &stack_region, &modules, + StackwalkerARM walker(&system_info, &raw_context, -1, &stack_region, &modules, &supplier, &resolver); ASSERT_TRUE(walker.Walk(&call_stack)); frames = call_stack.frames(); @@ -374,8 +374,8 @@ struct CFIFixture: public StackwalkerARMFixture { RegionFromSection(); raw_context.iregs[MD_CONTEXT_ARM_REG_SP] = stack_section.start().Value(); - StackwalkerARM walker(&system_info, &raw_context, &stack_region, &modules, - &supplier, &resolver); + StackwalkerARM walker(&system_info, &raw_context, -1, &stack_region, + &modules, &supplier, &resolver); walker.SetContextFrameValidity(context_frame_validity); ASSERT_TRUE(walker.Walk(&call_stack)); frames = call_stack.frames(); @@ -417,7 +417,7 @@ struct CFIFixture: public StackwalkerARMFixture { EXPECT_EQ(expected.iregs[MD_CONTEXT_ARM_REG_PC], frame1->context.iregs[MD_CONTEXT_ARM_REG_PC]); EXPECT_EQ(expected.iregs[MD_CONTEXT_ARM_REG_PC], - frame1->instruction + 1); + frame1->instruction + 2); EXPECT_EQ("epictetus", frame1->function_name); } @@ -564,9 +564,9 @@ TEST_F(CFI, At4006) { // move in the wrong direction. TEST_F(CFI, RejectBackwards) { raw_context.iregs[MD_CONTEXT_ARM_REG_PC] = 0x40006000; - raw_context.iregs[MD_CONTEXT_ARM_REG_SP] = 0x80000000; + raw_context.iregs[MD_CONTEXT_ARM_REG_SP] = 0x80000000; raw_context.iregs[MD_CONTEXT_ARM_REG_LR] = 0x40005510; - StackwalkerARM walker(&system_info, &raw_context, &stack_region, &modules, + StackwalkerARM walker(&system_info, &raw_context, -1, &stack_region, &modules, &supplier, &resolver); ASSERT_TRUE(walker.Walk(&call_stack)); frames = call_stack.frames(); @@ -576,11 +576,189 @@ TEST_F(CFI, RejectBackwards) { // Check that we reject rules whose expressions' evaluation fails. TEST_F(CFI, RejectBadExpressions) { raw_context.iregs[MD_CONTEXT_ARM_REG_PC] = 0x40007000; - raw_context.iregs[MD_CONTEXT_ARM_REG_SP] = 0x80000000; - StackwalkerARM walker(&system_info, &raw_context, &stack_region, &modules, + raw_context.iregs[MD_CONTEXT_ARM_REG_SP] = 0x80000000; + StackwalkerARM walker(&system_info, &raw_context, -1, &stack_region, &modules, &supplier, &resolver); ASSERT_TRUE(walker.Walk(&call_stack)); frames = call_stack.frames(); ASSERT_EQ(1U, frames->size()); } +class StackwalkerARMFixtureIOS : public StackwalkerARMFixture { + public: + StackwalkerARMFixtureIOS() { + system_info.os = "iOS"; + system_info.os_short = "ios"; + } +}; + +class GetFramesByFramePointer: public StackwalkerARMFixtureIOS, public Test { }; + +TEST_F(GetFramesByFramePointer, OnlyFramePointer) { + stack_section.start() = 0x80000000; + u_int32_t return_address1 = 0x50000100; + u_int32_t return_address2 = 0x50000900; + Label frame1_sp, frame2_sp; + Label frame1_fp, frame2_fp; + stack_section + // frame 0 + .Append(32, 0) // Whatever values on the stack. + .D32(0x0000000D) // junk that's not + .D32(0xF0000000) // a return address. + + .Mark(&frame1_fp) // Next fp will point to the next value. + .D32(frame2_fp) // Save current frame pointer. + .D32(return_address2) // Save current link register. + .Mark(&frame1_sp) + + // frame 1 + .Append(32, 0) // Whatever values on the stack. + .D32(0x0000000D) // junk that's not + .D32(0xF0000000) // a return address. + + .Mark(&frame2_fp) + .D32(0) + .D32(0) + .Mark(&frame2_sp) + + // frame 2 + .Append(32, 0) // Whatever values on the stack. + .D32(0x0000000D) // junk that's not + .D32(0xF0000000); // a return address. + RegionFromSection(); + + + raw_context.iregs[MD_CONTEXT_ARM_REG_PC] = 0x40005510; + raw_context.iregs[MD_CONTEXT_ARM_REG_LR] = return_address1; + raw_context.iregs[MD_CONTEXT_ARM_REG_IOS_FP] = frame1_fp.Value(); + raw_context.iregs[MD_CONTEXT_ARM_REG_SP] = stack_section.start().Value(); + + StackwalkerARM walker(&system_info, &raw_context, MD_CONTEXT_ARM_REG_IOS_FP, + &stack_region, &modules, &supplier, &resolver); + + ASSERT_TRUE(walker.Walk(&call_stack)); + frames = call_stack.frames(); + ASSERT_EQ(3U, frames->size()); + + StackFrameARM *frame0 = static_cast(frames->at(0)); + EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust); + ASSERT_EQ(StackFrameARM::CONTEXT_VALID_ALL, frame0->context_validity); + EXPECT_EQ(0, memcmp(&raw_context, &frame0->context, sizeof(raw_context))); + + StackFrameARM *frame1 = static_cast(frames->at(1)); + EXPECT_EQ(StackFrame::FRAME_TRUST_FP, frame1->trust); + ASSERT_EQ((StackFrameARM::CONTEXT_VALID_PC | + StackFrameARM::CONTEXT_VALID_LR | + StackFrameARM::RegisterValidFlag(MD_CONTEXT_ARM_REG_IOS_FP) | + StackFrameARM::CONTEXT_VALID_SP), + frame1->context_validity); + EXPECT_EQ(return_address1, frame1->context.iregs[MD_CONTEXT_ARM_REG_PC]); + EXPECT_EQ(return_address2, frame1->context.iregs[MD_CONTEXT_ARM_REG_LR]); + EXPECT_EQ(frame1_sp.Value(), frame1->context.iregs[MD_CONTEXT_ARM_REG_SP]); + EXPECT_EQ(frame2_fp.Value(), + frame1->context.iregs[MD_CONTEXT_ARM_REG_IOS_FP]); + + StackFrameARM *frame2 = static_cast(frames->at(2)); + EXPECT_EQ(StackFrame::FRAME_TRUST_FP, frame2->trust); + ASSERT_EQ((StackFrameARM::CONTEXT_VALID_PC | + StackFrameARM::CONTEXT_VALID_LR | + StackFrameARM::RegisterValidFlag(MD_CONTEXT_ARM_REG_IOS_FP) | + StackFrameARM::CONTEXT_VALID_SP), + frame2->context_validity); + EXPECT_EQ(return_address2, frame2->context.iregs[MD_CONTEXT_ARM_REG_PC]); + EXPECT_EQ(0, frame2->context.iregs[MD_CONTEXT_ARM_REG_LR]); + EXPECT_EQ(frame2_sp.Value(), frame2->context.iregs[MD_CONTEXT_ARM_REG_SP]); + EXPECT_EQ(0, frame2->context.iregs[MD_CONTEXT_ARM_REG_IOS_FP]); +} + +TEST_F(GetFramesByFramePointer, FramePointerAndCFI) { + // Provide the standatd STACK CFI records that is obtained when exmining an + // executable produced by XCode. + SetModuleSymbols(&module1, + // Adding a function in CFI. + "FUNC 4000 1000 10 enchiridion\n" + + "STACK CFI INIT 4000 100 .cfa: sp 0 + .ra: lr\n" + "STACK CFI 4001 .cfa: sp 8 + .ra: .cfa -4 + ^" + " r7: .cfa -8 + ^\n" + "STACK CFI 4002 .cfa: r7 8 +\n" + ); + + stack_section.start() = 0x80000000; + u_int32_t return_address1 = 0x40004010; + u_int32_t return_address2 = 0x50000900; + Label frame1_sp, frame2_sp; + Label frame1_fp, frame2_fp; + stack_section + // frame 0 + .Append(32, 0) // Whatever values on the stack. + .D32(0x0000000D) // junk that's not + .D32(0xF0000000) // a return address. + + .Mark(&frame1_fp) // Next fp will point to the next value. + .D32(frame2_fp) // Save current frame pointer. + .D32(return_address2) // Save current link register. + .Mark(&frame1_sp) + + // frame 1 + .Append(32, 0) // Whatever values on the stack. + .D32(0x0000000D) // junk that's not + .D32(0xF0000000) // a return address. + + .Mark(&frame2_fp) + .D32(0) + .D32(0) + .Mark(&frame2_sp) + + // frame 2 + .Append(32, 0) // Whatever values on the stack. + .D32(0x0000000D) // junk that's not + .D32(0xF0000000); // a return address. + RegionFromSection(); + + + raw_context.iregs[MD_CONTEXT_ARM_REG_PC] = 0x50000400; + raw_context.iregs[MD_CONTEXT_ARM_REG_LR] = return_address1; + raw_context.iregs[MD_CONTEXT_ARM_REG_IOS_FP] = frame1_fp.Value(); + raw_context.iregs[MD_CONTEXT_ARM_REG_SP] = stack_section.start().Value(); + + StackwalkerARM walker(&system_info, &raw_context, MD_CONTEXT_ARM_REG_IOS_FP, + &stack_region, &modules, &supplier, &resolver); + + ASSERT_TRUE(walker.Walk(&call_stack)); + frames = call_stack.frames(); + ASSERT_EQ(3U, frames->size()); + + StackFrameARM *frame0 = static_cast(frames->at(0)); + EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust); + ASSERT_EQ(StackFrameARM::CONTEXT_VALID_ALL, frame0->context_validity); + EXPECT_EQ(0, memcmp(&raw_context, &frame0->context, sizeof(raw_context))); + + StackFrameARM *frame1 = static_cast(frames->at(1)); + EXPECT_EQ(StackFrame::FRAME_TRUST_FP, frame1->trust); + ASSERT_EQ((StackFrameARM::CONTEXT_VALID_PC | + StackFrameARM::CONTEXT_VALID_LR | + StackFrameARM::RegisterValidFlag(MD_CONTEXT_ARM_REG_IOS_FP) | + StackFrameARM::CONTEXT_VALID_SP), + frame1->context_validity); + EXPECT_EQ(return_address1, frame1->context.iregs[MD_CONTEXT_ARM_REG_PC]); + EXPECT_EQ(return_address2, frame1->context.iregs[MD_CONTEXT_ARM_REG_LR]); + EXPECT_EQ(frame1_sp.Value(), frame1->context.iregs[MD_CONTEXT_ARM_REG_SP]); + EXPECT_EQ(frame2_fp.Value(), + frame1->context.iregs[MD_CONTEXT_ARM_REG_IOS_FP]); + EXPECT_EQ("enchiridion", frame1->function_name); + EXPECT_EQ(0x40004000U, frame1->function_base); + + + StackFrameARM *frame2 = static_cast(frames->at(2)); + EXPECT_EQ(StackFrame::FRAME_TRUST_CFI, frame2->trust); + ASSERT_EQ((StackFrameARM::CONTEXT_VALID_PC | + StackFrameARM::CONTEXT_VALID_LR | + StackFrameARM::RegisterValidFlag(MD_CONTEXT_ARM_REG_IOS_FP) | + StackFrameARM::CONTEXT_VALID_SP), + frame2->context_validity); + EXPECT_EQ(return_address2, frame2->context.iregs[MD_CONTEXT_ARM_REG_PC]); + EXPECT_EQ(0, frame2->context.iregs[MD_CONTEXT_ARM_REG_LR]); + EXPECT_EQ(frame2_sp.Value(), frame2->context.iregs[MD_CONTEXT_ARM_REG_SP]); + EXPECT_EQ(0, frame2->context.iregs[MD_CONTEXT_ARM_REG_IOS_FP]); +} -- cgit v1.2.1