diff --git a/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.cpp b/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.cpp --- a/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.cpp +++ b/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.cpp @@ -138,6 +138,13 @@ m_inst_emulator_up->GetRegisterInfo( eRegisterKindGeneric, LLDB_REGNUM_GENERIC_RA, ra_reg_info); + // cache the stack pointer register number (in whatever register + // numbering this UnwindPlan uses) for quick reference during + // instruction parsing. + RegisterInfo sp_reg_info; + m_inst_emulator_up->GetRegisterInfo( + eRegisterKindGeneric, LLDB_REGNUM_GENERIC_SP, ra_reg_info); + // The architecture dependent condition code of the last processed // instruction. EmulateInstruction::InstructionCondition last_condition = @@ -167,6 +174,23 @@ *newrow = *it->second.first; m_curr_row.reset(newrow); m_register_values = it->second.second; + // re-set the CFA register ivars to match the + // new m_curr_row. + if (sp_reg_info.name && + m_curr_row->GetCFAValue().IsRegisterPlusOffset()) { + uint32_t row_cfa_regnum = + m_curr_row->GetCFAValue().GetRegisterNumber(); + lldb::RegisterKind row_kind = + m_unwind_plan_ptr->GetRegisterKind(); + // set m_cfa_reg_info to the row's CFA reg. + m_inst_emulator_up->GetRegisterInfo(row_kind, row_cfa_regnum, + m_cfa_reg_info); + // set m_fp_is_cfa. + if (sp_reg_info.kinds[row_kind] == row_cfa_regnum) + m_fp_is_cfa = false; + else + m_fp_is_cfa = true; + } } m_inst_emulator_up->SetInstruction(inst->GetOpcode(), @@ -197,6 +221,23 @@ std::make_shared(*saved_state.first); m_curr_row->SetOffset(current_offset); m_register_values = saved_state.second; + // re-set the CFA register ivars to match the + // new m_curr_row. + if (sp_reg_info.name && + m_curr_row->GetCFAValue().IsRegisterPlusOffset()) { + uint32_t row_cfa_regnum = + m_curr_row->GetCFAValue().GetRegisterNumber(); + lldb::RegisterKind row_kind = + m_unwind_plan_ptr->GetRegisterKind(); + // set m_cfa_reg_info to the row's CFA reg. + m_inst_emulator_up->GetRegisterInfo(row_kind, row_cfa_regnum, + m_cfa_reg_info); + // set m_fp_is_cfa. + if (sp_reg_info.kinds[row_kind] == row_cfa_regnum) + m_fp_is_cfa = false; + else + m_fp_is_cfa = true; + } bool replace_existing = true; // The last instruction might already // created a row for this offset and diff --git a/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp b/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp --- a/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp +++ b/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp @@ -678,3 +678,103 @@ EXPECT_TRUE(regloc.IsSame()); } } + +TEST_F(TestArm64InstEmulation, TestCFARegisterTrackedAcrossJumps) { + ArchSpec arch("arm64-apple-ios10"); + std::unique_ptr engine( + static_cast( + UnwindAssemblyInstEmulation::CreateInstance(arch))); + ASSERT_NE(nullptr, engine); + + UnwindPlan::RowSP row_sp; + AddressRange sample_range; + UnwindPlan unwind_plan(eRegisterKindLLDB); + UnwindPlan::Row::RegisterLocation regloc; + + uint8_t data[] = { + // prologue + 0xf4, 0x4f, 0xbe, 0xa9, // 0: 0xa9be4ff4 stp x20, x19, [sp, #-0x20]! + 0xfd, 0x7b, 0x01, 0xa9, // 4: 0xa9017bfd stp x29, x30, [sp, #0x10] + 0xfd, 0x43, 0x00, 0x91, // 8: 0x910043fd add x29, sp, #0x10 + 0xff, 0x43, 0x00, 0xd1, // 12: 0xd10043ff sub sp, sp, #0x10 + // conditional branch over a mid-function epilogue + 0xeb, 0x00, 0x00, 0x54, // 16: 0x540000eb b.lt <+44> + // mid-function epilogue + 0x1f, 0x20, 0x03, 0xd5, // 20: 0xd503201f nop + 0xe0, 0x03, 0x13, 0xaa, // 24: 0xaa1303e0 mov x0, x19 + 0xbf, 0x43, 0x00, 0xd1, // 28: 0xd10043bf sub sp, x29, #0x10 + 0xfd, 0x7b, 0x41, 0xa9, // 32: 0xa9417bfd ldp x29, x30, [sp, #0x10] + 0xf4, 0x4f, 0xc2, 0xa8, // 36: 0xa8c24ff4 ldp x20, x19, [sp], #0x20 + 0xc0, 0x03, 0x5f, 0xd6, // 40: 0xd65f03c0 ret + // unwind state restored, we're using a frame pointer, let's change the + // stack pointer and see no change in how the CFA is computed + 0x1f, 0x20, 0x03, 0xd5, // 44: 0xd503201f nop + 0xff, 0x43, 0x00, 0xd1, // 48: 0xd10043ff sub sp, sp, #0x10 + 0x1f, 0x20, 0x03, 0xd5, // 52: 0xd503201f nop + // final epilogue + 0xe0, 0x03, 0x13, 0xaa, // 56: 0xaa1303e0 mov x0, x19 + 0xbf, 0x43, 0x00, 0xd1, // 60: 0xd10043bf sub sp, x29, #0x10 + 0xfd, 0x7b, 0x41, 0xa9, // 64: 0xa9417bfd ldp x29, x30, [sp, #0x10] + 0xf4, 0x4f, 0xc2, 0xa8, // 68: 0xa8c24ff4 ldp x20, x19, [sp], #0x20 + 0xc0, 0x03, 0x5f, 0xd6, // 72: 0xd65f03c0 ret + + 0x1f, 0x20, 0x03, 0xd5, // 52: 0xd503201f nop + }; + + // UnwindPlan we expect: + // row[0]: 0: CFA=sp +0 => + // row[1]: 4: CFA=sp+32 => x19=[CFA-24] x20=[CFA-32] + // row[2]: 8: CFA=sp+32 => x19=[CFA-24] x20=[CFA-32] fp=[CFA-16] lr=[CFA-8] + // row[3]: 12: CFA=fp+16 => x19=[CFA-24] x20=[CFA-32] fp=[CFA-16] lr=[CFA-8] + // row[4]: 32: CFA=sp+32 => x19=[CFA-24] x20=[CFA-32] fp=[CFA-16] lr=[CFA-8] + // row[5]: 36: CFA=sp+32 => x19=[CFA-24] x20=[CFA-32] fp= lr= + // row[6]: 40: CFA=sp +0 => x19= x20= fp= lr= + // row[7]: 44: CFA=fp+16 => x19=[CFA-24] x20=[CFA-32] fp=[CFA-16] lr=[CFA-8] + // row[8]: 64: CFA=sp+32 => x19=[CFA-24] x20=[CFA-32] fp=[CFA-16] lr=[CFA-8] + // row[9]: 68: CFA=sp+32 => x19=[CFA-24] x20=[CFA-32] fp= lr= + // row[10]: 72: CFA=sp +0 => x19= x20= fp= lr= + + // The specific bug we're looking for is this incorrect CFA definition, + // where the InstEmulation is using the $sp value mixed in with $fp, + // it looks like this: + // + // row[7]: 44: CFA=fp+16 => x19=[CFA-24] x20=[CFA-32] fp=[CFA-16] lr=[CFA-8] + // row[8]: 52: CFA=fp+64 => x19=[CFA-24] x20=[CFA-32] fp=[CFA-16] lr=[CFA-8] + // row[9]: 68: CFA=fp+64 => x19=[CFA-24] x20=[CFA-32] fp= lr= + + sample_range = AddressRange(0x1000, sizeof(data)); + + EXPECT_TRUE(engine->GetNonCallSiteUnwindPlanFromAssembly( + sample_range, data, sizeof(data), unwind_plan)); + + // Confirm CFA at mid-func epilogue 'ret' is $sp+0 + row_sp = unwind_plan.GetRowForFunctionOffset(40); + EXPECT_EQ(40ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(0, row_sp->GetCFAValue().GetOffset()); + + // After the 'ret', confirm we're back to the correct CFA of $fp+16 + row_sp = unwind_plan.GetRowForFunctionOffset(44); + EXPECT_EQ(44ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == gpr_fp_arm64); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(16, row_sp->GetCFAValue().GetOffset()); + + // Confirm that we have no additional UnwindPlan rows before the + // real epilogue -- we still get the Row at offset 44. + row_sp = unwind_plan.GetRowForFunctionOffset(60); + EXPECT_EQ(44ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == gpr_fp_arm64); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(16, row_sp->GetCFAValue().GetOffset()); + + // And in the epilogue, confirm that we start by switching back to + // defining the CFA in terms of $sp. + row_sp = unwind_plan.GetRowForFunctionOffset(64); + EXPECT_EQ(64ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(32, row_sp->GetCFAValue().GetOffset()); +} +