From 6835d019b22a4b643189d4ace2f8cae375ce89e5 Mon Sep 17 00:00:00 2001 From: Tamas Petz Date: Wed, 22 Jan 2020 14:22:41 +0100 Subject: [PATCH] libunwindstack: support for Armv8.3-A Pointer Authentication This patch adds support for handling return addresses signed with pointer authentication. It simply strips the authentication code without verifying its correctness, and thus works with both A and B keys and through key-change boundaries. Additons: * DW_CFA_AARCH64_negate_ra_state: new CFA operation. * RA_SIGN_STATE: new pseudo register. * Pass the arch to DwarfCfa so that the new op is only executed on aarch64. The stripping uses the xpaclri instruction. This is a hint space instruction which is compatible with pre Armv8.3-A devices. For cases where it cannot be used, a mask can be set instead. Test: libunwindstack_test Without this patch all UnwindTest.* testcases should fail if compiled with Pointer Authentication. The tests should be executed with both -mbranch-protection=pac-ret and pac-ret+leaf flags so that either some or all functions have pointer authentication instructions. Change-Id: Id7c3f1d0e2fc7fccb19bd1430826264405a9df7c --- libunwindstack/DwarfCfa.cpp | 36 ++++++++++- libunwindstack/DwarfCfa.h | 13 +++- libunwindstack/DwarfSection.cpp | 29 ++++++--- libunwindstack/RegsArm64.cpp | 53 +++++++++++++++- .../include/unwindstack/DwarfLocation.h | 1 + .../include/unwindstack/DwarfSection.h | 11 ++-- .../include/unwindstack/MachineArm64.h | 7 +++ libunwindstack/include/unwindstack/Regs.h | 4 ++ .../include/unwindstack/RegsArm64.h | 15 +++++ libunwindstack/tests/DwarfCfaLogTest.cpp | 30 +++++++-- libunwindstack/tests/DwarfCfaTest.cpp | 61 +++++++++++++++++-- libunwindstack/tests/DwarfSectionImplTest.cpp | 7 ++- libunwindstack/tests/DwarfSectionTest.cpp | 46 ++++++++------ libunwindstack/tests/RegsTest.cpp | 8 +++ .../tests/VerifyBionicTerminationTest.cpp | 2 +- libunwindstack/tools/unwind_info.cpp | 2 +- libunwindstack/tools/unwind_reg_info.cpp | 20 ++++-- 17 files changed, 288 insertions(+), 57 deletions(-) diff --git a/libunwindstack/DwarfCfa.cpp b/libunwindstack/DwarfCfa.cpp index c128b9bb7..c6db209db 100644 --- a/libunwindstack/DwarfCfa.cpp +++ b/libunwindstack/DwarfCfa.cpp @@ -26,7 +26,9 @@ #include #include +#include #include +#include #include "DwarfCfa.h" #include "DwarfEncoding.h" @@ -204,8 +206,12 @@ template bool DwarfCfa::LogInstruction(uint32_t indent, uint64_t cfa_offset, uint8_t op, uint64_t* cur_pc) { const auto* cfa = &DwarfCfaInfo::kTable[op]; - if (cfa->name[0] == '\0') { - log(indent, "Illegal"); + if (cfa->name[0] == '\0' || (arch_ != ARCH_ARM64 && op == 0x2d)) { + if (op == 0x2d) { + log(indent, "Illegal (Only valid on aarch64)"); + } else { + log(indent, "Illegal"); + } log(indent, "Raw Data: 0x%02x", op); return true; } @@ -514,6 +520,24 @@ bool DwarfCfa::cfa_gnu_negative_offset_extended(dwarf_loc_regs_t* l return true; } +template +bool DwarfCfa::cfa_aarch64_negate_ra_state(dwarf_loc_regs_t* loc_regs) { + // Only supported on aarch64. + if (arch_ != ARCH_ARM64) { + last_error_.code = DWARF_ERROR_ILLEGAL_VALUE; + return false; + } + + auto cfa_location = loc_regs->find(Arm64Reg::ARM64_PREG_RA_SIGN_STATE); + if (cfa_location == loc_regs->end()) { + (*loc_regs)[Arm64Reg::ARM64_PREG_RA_SIGN_STATE] = {.type = DWARF_LOCATION_PSEUDO_REGISTER, + .values = {1}}; + } else { + cfa_location->second.values[0] ^= 1; + } + return true; +} + const DwarfCfaInfo::Info DwarfCfaInfo::kTable[64] = { { // 0x00 DW_CFA_nop @@ -699,7 +723,13 @@ const DwarfCfaInfo::Info DwarfCfaInfo::kTable[64] = { {"", 0, 0, {}, {}}, // 0x2a illegal cfa {"", 0, 0, {}, {}}, // 0x2b illegal cfa {"", 0, 0, {}, {}}, // 0x2c illegal cfa - {"", 0, 0, {}, {}}, // 0x2d DW_CFA_GNU_window_save (Treat as illegal) + { + "DW_CFA_AARCH64_negate_ra_state", // 0x2d DW_CFA_AARCH64_negate_ra_state + 3, + 0, + {}, + {}, + }, { "DW_CFA_GNU_args_size", // 0x2e DW_CFA_GNU_args_size 2, diff --git a/libunwindstack/DwarfCfa.h b/libunwindstack/DwarfCfa.h index 569c17cef..d627e156b 100644 --- a/libunwindstack/DwarfCfa.h +++ b/libunwindstack/DwarfCfa.h @@ -31,6 +31,9 @@ namespace unwindstack { +// Forward declarations. +enum ArchEnum : uint8_t; + // DWARF Standard home: http://dwarfstd.org/ // This code is based on DWARF 4: http://http://dwarfstd.org/doc/DWARF4.pdf // See section 6.4.2.1 for a description of the DW_CFA_xxx values. @@ -72,7 +75,8 @@ class DwarfCfa { typedef typename std::make_signed::type SignedType; public: - DwarfCfa(DwarfMemory* memory, const DwarfFde* fde) : memory_(memory), fde_(fde) {} + DwarfCfa(DwarfMemory* memory, const DwarfFde* fde, ArchEnum arch) + : memory_(memory), fde_(fde), arch_(arch) {} virtual ~DwarfCfa() = default; bool GetLocationInfo(uint64_t pc, uint64_t start_offset, uint64_t end_offset, @@ -99,6 +103,7 @@ class DwarfCfa { DwarfErrorData last_error_; DwarfMemory* memory_; const DwarfFde* fde_; + ArchEnum arch_; AddressType cur_pc_; const dwarf_loc_regs_t* cie_loc_regs_ = nullptr; @@ -128,6 +133,7 @@ class DwarfCfa { bool cfa_val_offset_sf(dwarf_loc_regs_t*); bool cfa_val_expression(dwarf_loc_regs_t*); bool cfa_gnu_negative_offset_extended(dwarf_loc_regs_t*); + bool cfa_aarch64_negate_ra_state(dwarf_loc_regs_t*); using process_func = bool (DwarfCfa::*)(dwarf_loc_regs_t*); constexpr static process_func kCallbackTable[64] = { @@ -221,8 +227,9 @@ class DwarfCfa { nullptr, // 0x2c illegal cfa nullptr, - // 0x2d DW_CFA_GNU_window_save (Treat this as illegal) - nullptr, + // 0x2d DW_CFA_AARCH64_negate_ra_state (aarch64 only) + // DW_CFA_GNU_window_save on other architectures. + &DwarfCfa::cfa_aarch64_negate_ra_state, // 0x2e DW_CFA_GNU_args_size &DwarfCfa::cfa_nop, // 0x2f DW_CFA_GNU_negative_offset_extended diff --git a/libunwindstack/DwarfSection.cpp b/libunwindstack/DwarfSection.cpp index 18bd490f9..9e2a3cda7 100644 --- a/libunwindstack/DwarfSection.cpp +++ b/libunwindstack/DwarfSection.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -49,7 +50,7 @@ bool DwarfSection::Step(uint64_t pc, Regs* regs, Memory* process_memory, bool* f // Now get the location information for this pc. dwarf_loc_regs_t loc_regs; - if (!GetCfaLocationInfo(pc, fde, &loc_regs)) { + if (!GetCfaLocationInfo(pc, fde, &loc_regs, regs->Arch())) { return false; } loc_regs.cie = fde->cie; @@ -464,6 +465,13 @@ bool DwarfSectionImpl::EvalRegister(const DwarfLocation* loc, uint3 eval_info->return_address_undefined = true; } break; + case DWARF_LOCATION_PSEUDO_REGISTER: { + if (!eval_info->regs_info.regs->SetPseudoRegister(reg, loc->values[0])) { + last_error_.code = DWARF_ERROR_ILLEGAL_VALUE; + return false; + } + break; + } default: break; } @@ -491,6 +499,10 @@ bool DwarfSectionImpl::Eval(const DwarfCie* cie, Memory* regular_me // Always set the dex pc to zero when evaluating. cur_regs->set_dex_pc(0); + // Reset necessary pseudo registers before evaluation. + // This is needed for ARM64, for example. + regs->ResetPseudoRegisters(); + EvalInfo eval_info{.loc_regs = &loc_regs, .cie = cie, .regular_memory = regular_memory, @@ -527,8 +539,10 @@ bool DwarfSectionImpl::Eval(const DwarfCie* cie, Memory* regular_me AddressType* reg_ptr; if (reg >= cur_regs->total_regs()) { - // Skip this unknown register. - continue; + if (entry.second.type != DWARF_LOCATION_PSEUDO_REGISTER) { + // Skip this unknown register. + continue; + } } reg_ptr = eval_info.regs_info.Save(reg); @@ -554,8 +568,8 @@ bool DwarfSectionImpl::Eval(const DwarfCie* cie, Memory* regular_me template bool DwarfSectionImpl::GetCfaLocationInfo(uint64_t pc, const DwarfFde* fde, - dwarf_loc_regs_t* loc_regs) { - DwarfCfa cfa(&memory_, fde); + dwarf_loc_regs_t* loc_regs, ArchEnum arch) { + DwarfCfa cfa(&memory_, fde, arch); // Look for the cached copy of the cie data. auto reg_entry = cie_loc_regs_.find(fde->cie_offset); @@ -576,8 +590,9 @@ bool DwarfSectionImpl::GetCfaLocationInfo(uint64_t pc, const DwarfF } template -bool DwarfSectionImpl::Log(uint8_t indent, uint64_t pc, const DwarfFde* fde) { - DwarfCfa cfa(&memory_, fde); +bool DwarfSectionImpl::Log(uint8_t indent, uint64_t pc, const DwarfFde* fde, + ArchEnum arch) { + DwarfCfa cfa(&memory_, fde, arch); // Always print the cie information. const DwarfCie* cie = fde->cie; diff --git a/libunwindstack/RegsArm64.cpp b/libunwindstack/RegsArm64.cpp index 5b7431a57..b496187cb 100644 --- a/libunwindstack/RegsArm64.cpp +++ b/libunwindstack/RegsArm64.cpp @@ -30,7 +30,10 @@ namespace unwindstack { RegsArm64::RegsArm64() - : RegsImpl(ARM64_REG_LAST, Location(LOCATION_REGISTER, ARM64_REG_LR)) {} + : RegsImpl(ARM64_REG_LAST, Location(LOCATION_REGISTER, ARM64_REG_LR)) { + ResetPseudoRegisters(); + pac_mask_ = 0; +} ArchEnum RegsArm64::Arch() { return ARCH_ARM64; @@ -45,6 +48,23 @@ uint64_t RegsArm64::sp() { } void RegsArm64::set_pc(uint64_t pc) { + // If the target is aarch64 then the return address may have been + // signed using the Armv8.3-A Pointer Authentication extension. The + // original return address can be restored by stripping out the + // authentication code using a mask or xpaclri. xpaclri is a NOP on + // pre-Armv8.3-A architectures. + if ((0 != pc) && IsRASigned()) { + if (pac_mask_) { + pc &= ~pac_mask_; +#if defined(__aarch64__) + } else { + register uint64_t x30 __asm("x30") = pc; + // This is XPACLRI. + asm("hint 0x7" : "+r"(x30)); + pc = x30; +#endif + } + } regs_[ARM64_REG_PC] = pc; } @@ -144,6 +164,37 @@ bool RegsArm64::StepIfSignalHandler(uint64_t elf_offset, Elf* elf, Memory* proce return true; } +void RegsArm64::ResetPseudoRegisters(void) { + // DWARF for AArch64 says RA_SIGN_STATE should be initialized to 0. + this->SetPseudoRegister(Arm64Reg::ARM64_PREG_RA_SIGN_STATE, 0); +} + +bool RegsArm64::SetPseudoRegister(uint16_t id, uint64_t value) { + if ((id >= Arm64Reg::ARM64_PREG_FIRST) && (id < Arm64Reg::ARM64_PREG_LAST)) { + pseudo_regs_[id - Arm64Reg::ARM64_PREG_FIRST] = value; + return true; + } + return false; +} + +bool RegsArm64::GetPseudoRegister(uint16_t id, uint64_t* value) { + if ((id >= Arm64Reg::ARM64_PREG_FIRST) && (id < Arm64Reg::ARM64_PREG_LAST)) { + *value = pseudo_regs_[id - Arm64Reg::ARM64_PREG_FIRST]; + return true; + } + return false; +} + +bool RegsArm64::IsRASigned() { + uint64_t value; + auto result = this->GetPseudoRegister(Arm64Reg::ARM64_PREG_RA_SIGN_STATE, &value); + return (result && (value != 0)); +} + +void RegsArm64::SetPACMask(uint64_t mask) { + pac_mask_ = mask; +} + Regs* RegsArm64::Clone() { return new RegsArm64(*this); } diff --git a/libunwindstack/include/unwindstack/DwarfLocation.h b/libunwindstack/include/unwindstack/DwarfLocation.h index 3d50ccf36..bf45bc752 100644 --- a/libunwindstack/include/unwindstack/DwarfLocation.h +++ b/libunwindstack/include/unwindstack/DwarfLocation.h @@ -33,6 +33,7 @@ enum DwarfLocationEnum : uint8_t { DWARF_LOCATION_REGISTER, DWARF_LOCATION_EXPRESSION, DWARF_LOCATION_VAL_EXPRESSION, + DWARF_LOCATION_PSEUDO_REGISTER, }; struct DwarfLocation { diff --git a/libunwindstack/include/unwindstack/DwarfSection.h b/libunwindstack/include/unwindstack/DwarfSection.h index c244749c1..af823da5f 100644 --- a/libunwindstack/include/unwindstack/DwarfSection.h +++ b/libunwindstack/include/unwindstack/DwarfSection.h @@ -31,6 +31,7 @@ namespace unwindstack { // Forward declarations. +enum ArchEnum : uint8_t; class Memory; class Regs; template @@ -90,13 +91,14 @@ class DwarfSection { virtual bool Eval(const DwarfCie*, Memory*, const dwarf_loc_regs_t&, Regs*, bool*) = 0; - virtual bool Log(uint8_t indent, uint64_t pc, const DwarfFde* fde) = 0; + virtual bool Log(uint8_t indent, uint64_t pc, const DwarfFde* fde, ArchEnum arch) = 0; virtual void GetFdes(std::vector* fdes) = 0; virtual const DwarfFde* GetFdeFromPc(uint64_t pc) = 0; - virtual bool GetCfaLocationInfo(uint64_t pc, const DwarfFde* fde, dwarf_loc_regs_t* loc_regs) = 0; + virtual bool GetCfaLocationInfo(uint64_t pc, const DwarfFde* fde, dwarf_loc_regs_t* loc_regs, + ArchEnum arch) = 0; virtual uint64_t GetCieOffsetFromFde32(uint32_t pointer) = 0; @@ -140,9 +142,10 @@ class DwarfSectionImpl : public DwarfSection { bool Eval(const DwarfCie* cie, Memory* regular_memory, const dwarf_loc_regs_t& loc_regs, Regs* regs, bool* finished) override; - bool GetCfaLocationInfo(uint64_t pc, const DwarfFde* fde, dwarf_loc_regs_t* loc_regs) override; + bool GetCfaLocationInfo(uint64_t pc, const DwarfFde* fde, dwarf_loc_regs_t* loc_regs, + ArchEnum arch) override; - bool Log(uint8_t indent, uint64_t pc, const DwarfFde* fde) override; + bool Log(uint8_t indent, uint64_t pc, const DwarfFde* fde, ArchEnum arch) override; protected: bool GetNextCieOrFde(const DwarfFde** fde_entry); diff --git a/libunwindstack/include/unwindstack/MachineArm64.h b/libunwindstack/include/unwindstack/MachineArm64.h index e95333510..358e3d989 100644 --- a/libunwindstack/include/unwindstack/MachineArm64.h +++ b/libunwindstack/include/unwindstack/MachineArm64.h @@ -60,6 +60,13 @@ enum Arm64Reg : uint16_t { ARM64_REG_SP = ARM64_REG_R31, ARM64_REG_LR = ARM64_REG_R30, + + // Pseudo registers. These are not machine registers. + + // AARCH64 Return address signed state pseudo-register + ARM64_PREG_RA_SIGN_STATE = 34, + ARM64_PREG_FIRST = ARM64_PREG_RA_SIGN_STATE, + ARM64_PREG_LAST, }; } // namespace unwindstack diff --git a/libunwindstack/include/unwindstack/Regs.h b/libunwindstack/include/unwindstack/Regs.h index a367e6cf1..5f4256567 100644 --- a/libunwindstack/include/unwindstack/Regs.h +++ b/libunwindstack/include/unwindstack/Regs.h @@ -64,6 +64,10 @@ class Regs { uint64_t dex_pc() { return dex_pc_; } void set_dex_pc(uint64_t dex_pc) { dex_pc_ = dex_pc; } + virtual void ResetPseudoRegisters() {} + virtual bool SetPseudoRegister(uint16_t, uint64_t) { return false; } + virtual bool GetPseudoRegister(uint16_t, uint64_t*) { return false; } + virtual bool StepIfSignalHandler(uint64_t elf_offset, Elf* elf, Memory* process_memory) = 0; virtual bool SetPcFromReturnAddress(Memory* process_memory) = 0; diff --git a/libunwindstack/include/unwindstack/RegsArm64.h b/libunwindstack/include/unwindstack/RegsArm64.h index 2b3ddeb77..bf7ab1527 100644 --- a/libunwindstack/include/unwindstack/RegsArm64.h +++ b/libunwindstack/include/unwindstack/RegsArm64.h @@ -22,6 +22,7 @@ #include #include +#include #include namespace unwindstack { @@ -48,11 +49,25 @@ class RegsArm64 : public RegsImpl { void set_pc(uint64_t pc) override; void set_sp(uint64_t sp) override; + void ResetPseudoRegisters() override; + + bool SetPseudoRegister(uint16_t id, uint64_t value) override; + + bool GetPseudoRegister(uint16_t id, uint64_t* value) override; + + bool IsRASigned(); + + void SetPACMask(uint64_t mask); + Regs* Clone() override final; static Regs* Read(void* data); static Regs* CreateFromUcontext(void* ucontext); + + protected: + uint64_t pseudo_regs_[Arm64Reg::ARM64_PREG_LAST - Arm64Reg::ARM64_PREG_FIRST]; + uint64_t pac_mask_; }; } // namespace unwindstack diff --git a/libunwindstack/tests/DwarfCfaLogTest.cpp b/libunwindstack/tests/DwarfCfaLogTest.cpp index def408875..2b5a8dc83 100644 --- a/libunwindstack/tests/DwarfCfaLogTest.cpp +++ b/libunwindstack/tests/DwarfCfaLogTest.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "DwarfCfa.h" @@ -57,7 +58,7 @@ class DwarfCfaLogTest : public ::testing::Test { fde_.pc_end = 0x2000; fde_.pc_end = 0x10000; fde_.cie = &cie_; - cfa_.reset(new DwarfCfa(dmem_.get(), &fde_)); + cfa_.reset(new DwarfCfa(dmem_.get(), &fde_, ARCH_UNKNOWN)); } MemoryFake memory_; @@ -72,8 +73,8 @@ TYPED_TEST_SUITE_P(DwarfCfaLogTest); TYPED_TEST_P(DwarfCfaLogTest, cfa_illegal) { for (uint8_t i = 0x17; i < 0x3f; i++) { - if (i == 0x2e || i == 0x2f) { - // Skip gnu extension ops. + if (i == 0x2d || i == 0x2e || i == 0x2f) { + // Skip gnu extension ops and aarch64 specialized op. continue; } this->memory_.SetMemory(0x2000, std::vector{i}); @@ -763,6 +764,26 @@ TYPED_TEST_P(DwarfCfaLogTest, cfa_register_override) { ASSERT_EQ("", GetFakeLogBuf()); } +TYPED_TEST_P(DwarfCfaLogTest, cfa_aarch64_negate_ra_state) { + // Verify that if the cfa op is handled properly depending on aarch. + this->memory_.SetMemory(0x2000, std::vector{0x2d}); + + ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2000, 0x2001)); + std::string expected = "4 unwind Illegal (Only valid on aarch64)\n"; + expected += "4 unwind Raw Data: 0x2d\n"; + ASSERT_EQ(expected, GetFakeLogPrint()); + ASSERT_EQ("", GetFakeLogBuf()); + + ResetLogs(); + this->cfa_.reset(new DwarfCfa(this->dmem_.get(), &this->fde_, ARCH_ARM64)); + + ASSERT_TRUE(this->cfa_->Log(0, this->fde_.pc_start, 0x2000, 0x2001)); + expected = "4 unwind DW_CFA_AARCH64_negate_ra_state\n"; + expected += "4 unwind Raw Data: 0x2d\n"; + ASSERT_EQ(expected, GetFakeLogPrint()); + ASSERT_EQ("", GetFakeLogBuf()); +} + REGISTER_TYPED_TEST_SUITE_P(DwarfCfaLogTest, cfa_illegal, cfa_nop, cfa_offset, cfa_offset_extended, cfa_offset_extended_sf, cfa_restore, cfa_restore_extended, cfa_set_loc, cfa_advance_loc, cfa_advance_loc1, cfa_advance_loc2, cfa_advance_loc4, @@ -771,7 +792,8 @@ REGISTER_TYPED_TEST_SUITE_P(DwarfCfaLogTest, cfa_illegal, cfa_nop, cfa_offset, c cfa_def_cfa_register, cfa_def_cfa_offset, cfa_def_cfa_offset_sf, cfa_def_cfa_expression, cfa_expression, cfa_val_offset, cfa_val_offset_sf, cfa_val_expression, cfa_gnu_args_size, - cfa_gnu_negative_offset_extended, cfa_register_override); + cfa_gnu_negative_offset_extended, cfa_register_override, + cfa_aarch64_negate_ra_state); typedef ::testing::Types DwarfCfaLogTestTypes; INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfCfaLogTest, DwarfCfaLogTestTypes); diff --git a/libunwindstack/tests/DwarfCfaTest.cpp b/libunwindstack/tests/DwarfCfaTest.cpp index 9c6ab05e6..ea7e70807 100644 --- a/libunwindstack/tests/DwarfCfaTest.cpp +++ b/libunwindstack/tests/DwarfCfaTest.cpp @@ -25,7 +25,9 @@ #include #include #include +#include #include +#include #include "DwarfCfa.h" @@ -55,7 +57,7 @@ class DwarfCfaTest : public ::testing::Test { fde_.pc_start = 0x2000; fde_.cie = &cie_; - cfa_.reset(new DwarfCfa(dmem_.get(), &fde_)); + cfa_.reset(new DwarfCfa(dmem_.get(), &fde_, ARCH_UNKNOWN)); } MemoryFake memory_; @@ -70,8 +72,8 @@ TYPED_TEST_SUITE_P(DwarfCfaTest); TYPED_TEST_P(DwarfCfaTest, cfa_illegal) { for (uint8_t i = 0x17; i < 0x3f; i++) { - if (i == 0x2e || i == 0x2f) { - // Skip gnu extension ops. + if (i == 0x2d || i == 0x2e || i == 0x2f) { + // Skip gnu extension ops and aarch64 specialized op. continue; } this->memory_.SetMemory(0x2000, std::vector{i}); @@ -952,6 +954,57 @@ TYPED_TEST_P(DwarfCfaTest, cfa_register_override) { ASSERT_EQ("", GetFakeLogBuf()); } +TYPED_TEST_P(DwarfCfaTest, cfa_aarch64_negate_ra_state) { + this->memory_.SetMemory(0x2000, std::vector{0x2d}); + dwarf_loc_regs_t loc_regs; + + ASSERT_FALSE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs)); + ASSERT_EQ(DWARF_ERROR_ILLEGAL_VALUE, this->cfa_->LastErrorCode()); + ASSERT_EQ(0x2001U, this->dmem_->cur_offset()); + + ASSERT_EQ("", GetFakeLogPrint()); + ASSERT_EQ("", GetFakeLogBuf()); + + ResetLogs(); + this->cfa_.reset(new DwarfCfa(this->dmem_.get(), &this->fde_, ARCH_ARM64)); + ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs)); + ASSERT_EQ(0x2001U, this->dmem_->cur_offset()); + + auto location = loc_regs.find(Arm64Reg::ARM64_PREG_RA_SIGN_STATE); + ASSERT_NE(loc_regs.end(), location); + ASSERT_EQ(DWARF_LOCATION_PSEUDO_REGISTER, location->second.type); + ASSERT_EQ(1U, location->second.values[0]); + + ASSERT_EQ("", GetFakeLogPrint()); + ASSERT_EQ("", GetFakeLogBuf()); + + // Verify that the value is set to 0 after another evaluation. + ResetLogs(); + ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs)); + ASSERT_EQ(0x2001U, this->dmem_->cur_offset()); + + location = loc_regs.find(Arm64Reg::ARM64_PREG_RA_SIGN_STATE); + ASSERT_NE(loc_regs.end(), location); + ASSERT_EQ(DWARF_LOCATION_PSEUDO_REGISTER, location->second.type); + ASSERT_EQ(0U, location->second.values[0]); + + ASSERT_EQ("", GetFakeLogPrint()); + ASSERT_EQ("", GetFakeLogBuf()); + + // Verify that the value is set to 1 again after a third op. + ResetLogs(); + ASSERT_TRUE(this->cfa_->GetLocationInfo(this->fde_.pc_start, 0x2000, 0x2001, &loc_regs)); + ASSERT_EQ(0x2001U, this->dmem_->cur_offset()); + + location = loc_regs.find(Arm64Reg::ARM64_PREG_RA_SIGN_STATE); + ASSERT_NE(loc_regs.end(), location); + ASSERT_EQ(DWARF_LOCATION_PSEUDO_REGISTER, location->second.type); + ASSERT_EQ(1U, location->second.values[0]); + + ASSERT_EQ("", GetFakeLogPrint()); + ASSERT_EQ("", GetFakeLogBuf()); +} + REGISTER_TYPED_TEST_SUITE_P(DwarfCfaTest, cfa_illegal, cfa_nop, cfa_offset, cfa_offset_extended, cfa_offset_extended_sf, cfa_restore, cfa_restore_extended, cfa_set_loc, cfa_advance_loc1, cfa_advance_loc2, cfa_advance_loc4, cfa_undefined, @@ -960,7 +1013,7 @@ REGISTER_TYPED_TEST_SUITE_P(DwarfCfaTest, cfa_illegal, cfa_nop, cfa_offset, cfa_ cfa_def_cfa_offset_sf, cfa_def_cfa_expression, cfa_expression, cfa_val_offset, cfa_val_offset_sf, cfa_val_expression, cfa_gnu_args_size, cfa_gnu_negative_offset_extended, - cfa_register_override); + cfa_register_override, cfa_aarch64_negate_ra_state); typedef ::testing::Types DwarfCfaTestTypes; INSTANTIATE_TYPED_TEST_SUITE_P(Libunwindstack, DwarfCfaTest, DwarfCfaTestTypes); diff --git a/libunwindstack/tests/DwarfSectionImplTest.cpp b/libunwindstack/tests/DwarfSectionImplTest.cpp index cac59b7e9..d57cd339d 100644 --- a/libunwindstack/tests/DwarfSectionImplTest.cpp +++ b/libunwindstack/tests/DwarfSectionImplTest.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "DwarfEncoding.h" @@ -505,7 +506,7 @@ TYPED_TEST_P(DwarfSectionImplTest, GetCfaLocationInfo_cie_not_cached) { this->memory_.SetMemory(0x6000, std::vector{0x09, 0x04, 0x03}); dwarf_loc_regs_t loc_regs; - ASSERT_TRUE(this->section_->GetCfaLocationInfo(0x100, &fde, &loc_regs)); + ASSERT_TRUE(this->section_->GetCfaLocationInfo(0x100, &fde, &loc_regs, ARCH_UNKNOWN)); ASSERT_EQ(2U, loc_regs.size()); auto entry = loc_regs.find(2); @@ -535,7 +536,7 @@ TYPED_TEST_P(DwarfSectionImplTest, GetCfaLocationInfo_cie_cached) { this->memory_.SetMemory(0x6000, std::vector{0x09, 0x04, 0x03}); dwarf_loc_regs_t loc_regs; - ASSERT_TRUE(this->section_->GetCfaLocationInfo(0x100, &fde, &loc_regs)); + ASSERT_TRUE(this->section_->GetCfaLocationInfo(0x100, &fde, &loc_regs, ARCH_UNKNOWN)); ASSERT_EQ(2U, loc_regs.size()); auto entry = loc_regs.find(6); @@ -560,7 +561,7 @@ TYPED_TEST_P(DwarfSectionImplTest, Log) { this->memory_.SetMemory(0x5000, std::vector{0x00}); this->memory_.SetMemory(0x6000, std::vector{0xc2}); - ASSERT_TRUE(this->section_->Log(2, 0x1000, &fde)); + ASSERT_TRUE(this->section_->Log(2, 0x1000, &fde, ARCH_UNKNOWN)); ASSERT_EQ( "4 unwind DW_CFA_nop\n" diff --git a/libunwindstack/tests/DwarfSectionTest.cpp b/libunwindstack/tests/DwarfSectionTest.cpp index 953dc7563..febd6d326 100644 --- a/libunwindstack/tests/DwarfSectionTest.cpp +++ b/libunwindstack/tests/DwarfSectionTest.cpp @@ -20,8 +20,10 @@ #include #include +#include #include "MemoryFake.h" +#include "RegsFake.h" namespace unwindstack { @@ -35,13 +37,14 @@ class MockDwarfSection : public DwarfSection { MOCK_METHOD(bool, Eval, (const DwarfCie*, Memory*, const dwarf_loc_regs_t&, Regs*, bool*), (override)); - MOCK_METHOD(bool, Log, (uint8_t, uint64_t, const DwarfFde*), (override)); + MOCK_METHOD(bool, Log, (uint8_t, uint64_t, const DwarfFde*, ArchEnum arch), (override)); MOCK_METHOD(void, GetFdes, (std::vector*), (override)); MOCK_METHOD(const DwarfFde*, GetFdeFromPc, (uint64_t), (override)); - MOCK_METHOD(bool, GetCfaLocationInfo, (uint64_t, const DwarfFde*, dwarf_loc_regs_t*), (override)); + MOCK_METHOD(bool, GetCfaLocationInfo, + (uint64_t, const DwarfFde*, dwarf_loc_regs_t*, ArchEnum arch), (override)); MOCK_METHOD(uint64_t, GetCieOffsetFromFde32, (uint32_t), (override)); @@ -56,8 +59,11 @@ class DwarfSectionTest : public ::testing::Test { MemoryFake memory_; std::unique_ptr section_; + static RegsFake regs_; }; +RegsFake DwarfSectionTest::regs_(10); + TEST_F(DwarfSectionTest, Step_fail_fde) { EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(nullptr)); @@ -73,7 +79,7 @@ TEST_F(DwarfSectionTest, Step_fail_cie_null) { EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(&fde)); bool finished; - ASSERT_FALSE(section_->Step(0x1000, nullptr, nullptr, &finished)); + ASSERT_FALSE(section_->Step(0x1000, ®s_, nullptr, &finished)); } TEST_F(DwarfSectionTest, Step_fail_cfa_location) { @@ -83,11 +89,11 @@ TEST_F(DwarfSectionTest, Step_fail_cfa_location) { fde.cie = &cie; EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(&fde)); - EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_)) + EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_, ::testing::_)) .WillOnce(::testing::Return(false)); bool finished; - ASSERT_FALSE(section_->Step(0x1000, nullptr, nullptr, &finished)); + ASSERT_FALSE(section_->Step(0x1000, ®s_, nullptr, &finished)); } TEST_F(DwarfSectionTest, Step_pass) { @@ -97,19 +103,19 @@ TEST_F(DwarfSectionTest, Step_pass) { fde.cie = &cie; EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(&fde)); - EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_)) + EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_, ::testing::_)) .WillOnce(::testing::Return(true)); MemoryFake process; - EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, nullptr, ::testing::_)) + EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, ®s_, ::testing::_)) .WillOnce(::testing::Return(true)); bool finished; - ASSERT_TRUE(section_->Step(0x1000, nullptr, &process, &finished)); + ASSERT_TRUE(section_->Step(0x1000, ®s_, &process, &finished)); } static bool MockGetCfaLocationInfo(::testing::Unused, const DwarfFde* fde, - dwarf_loc_regs_t* loc_regs) { + dwarf_loc_regs_t* loc_regs, ArchEnum) { loc_regs->pc_start = fde->pc_start; loc_regs->pc_end = fde->pc_end; return true; @@ -123,17 +129,17 @@ TEST_F(DwarfSectionTest, Step_cache) { fde.cie = &cie; EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(&fde)); - EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_)) + EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde, ::testing::_, ::testing::_)) .WillOnce(::testing::Invoke(MockGetCfaLocationInfo)); MemoryFake process; - EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, nullptr, ::testing::_)) + EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, ®s_, ::testing::_)) .WillRepeatedly(::testing::Return(true)); bool finished; - ASSERT_TRUE(section_->Step(0x1000, nullptr, &process, &finished)); - ASSERT_TRUE(section_->Step(0x1000, nullptr, &process, &finished)); - ASSERT_TRUE(section_->Step(0x1500, nullptr, &process, &finished)); + ASSERT_TRUE(section_->Step(0x1000, ®s_, &process, &finished)); + ASSERT_TRUE(section_->Step(0x1000, ®s_, &process, &finished)); + ASSERT_TRUE(section_->Step(0x1500, ®s_, &process, &finished)); } TEST_F(DwarfSectionTest, Step_cache_not_in_pc) { @@ -143,26 +149,26 @@ TEST_F(DwarfSectionTest, Step_cache_not_in_pc) { fde0.pc_end = 0x2000; fde0.cie = &cie; EXPECT_CALL(*section_, GetFdeFromPc(0x1000)).WillOnce(::testing::Return(&fde0)); - EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde0, ::testing::_)) + EXPECT_CALL(*section_, GetCfaLocationInfo(0x1000, &fde0, ::testing::_, ::testing::_)) .WillOnce(::testing::Invoke(MockGetCfaLocationInfo)); MemoryFake process; - EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, nullptr, ::testing::_)) + EXPECT_CALL(*section_, Eval(&cie, &process, ::testing::_, ®s_, ::testing::_)) .WillRepeatedly(::testing::Return(true)); bool finished; - ASSERT_TRUE(section_->Step(0x1000, nullptr, &process, &finished)); + ASSERT_TRUE(section_->Step(0x1000, ®s_, &process, &finished)); DwarfFde fde1{}; fde1.pc_start = 0x500; fde1.pc_end = 0x800; fde1.cie = &cie; EXPECT_CALL(*section_, GetFdeFromPc(0x600)).WillOnce(::testing::Return(&fde1)); - EXPECT_CALL(*section_, GetCfaLocationInfo(0x600, &fde1, ::testing::_)) + EXPECT_CALL(*section_, GetCfaLocationInfo(0x600, &fde1, ::testing::_, ::testing::_)) .WillOnce(::testing::Invoke(MockGetCfaLocationInfo)); - ASSERT_TRUE(section_->Step(0x600, nullptr, &process, &finished)); - ASSERT_TRUE(section_->Step(0x700, nullptr, &process, &finished)); + ASSERT_TRUE(section_->Step(0x600, ®s_, &process, &finished)); + ASSERT_TRUE(section_->Step(0x700, ®s_, &process, &finished)); } } // namespace unwindstack diff --git a/libunwindstack/tests/RegsTest.cpp b/libunwindstack/tests/RegsTest.cpp index e4fc6f070..acf72de2c 100644 --- a/libunwindstack/tests/RegsTest.cpp +++ b/libunwindstack/tests/RegsTest.cpp @@ -247,6 +247,14 @@ TEST_F(RegsTest, mips64_verify_sp_pc) { EXPECT_EQ(0xc200000000U, mips64.pc()); } +TEST_F(RegsTest, arm64_strip_pac_mask) { + RegsArm64 arm64; + arm64.SetPseudoRegister(Arm64Reg::ARM64_PREG_RA_SIGN_STATE, 1); + arm64.SetPACMask(0x007fff8000000000ULL); + arm64.set_pc(0x0020007214bb3a04ULL); + EXPECT_EQ(0x0000007214bb3a04ULL, arm64.pc()); +} + TEST_F(RegsTest, machine_type) { RegsArm arm_regs; EXPECT_EQ(ARCH_ARM, arm_regs.Arch()); diff --git a/libunwindstack/tests/VerifyBionicTerminationTest.cpp b/libunwindstack/tests/VerifyBionicTerminationTest.cpp index 6a3e91a2a..eb2b01d29 100644 --- a/libunwindstack/tests/VerifyBionicTerminationTest.cpp +++ b/libunwindstack/tests/VerifyBionicTerminationTest.cpp @@ -55,7 +55,7 @@ static DwarfLocationEnum GetReturnAddressLocation(uint64_t rel_pc, DwarfSection* return DWARF_LOCATION_INVALID; } dwarf_loc_regs_t regs; - if (!section->GetCfaLocationInfo(rel_pc, fde, ®s)) { + if (!section->GetCfaLocationInfo(rel_pc, fde, ®s, ARCH_UNKNOWN)) { return DWARF_LOCATION_INVALID; } diff --git a/libunwindstack/tools/unwind_info.cpp b/libunwindstack/tools/unwind_info.cpp index 7a6d8ba46..a5002f226 100644 --- a/libunwindstack/tools/unwind_info.cpp +++ b/libunwindstack/tools/unwind_info.cpp @@ -96,7 +96,7 @@ void DumpDwarfSection(Elf* elf, DwarfSection* section, uint64_t) { printf(" <%s>", name.c_str()); } printf("\n"); - if (!section->Log(2, UINT64_MAX, fde)) { + if (!section->Log(2, UINT64_MAX, fde, elf->arch())) { printf("Failed to process cfa information for entry at 0x%" PRIx64 "\n", fde->pc_start); } } diff --git a/libunwindstack/tools/unwind_reg_info.cpp b/libunwindstack/tools/unwind_reg_info.cpp index 0cbcac5a0..68e02735d 100644 --- a/libunwindstack/tools/unwind_reg_info.cpp +++ b/libunwindstack/tools/unwind_reg_info.cpp @@ -64,7 +64,8 @@ void PrintExpression(Memory* memory, uint8_t class_type, uint64_t end, uint64_t } } -void PrintRegInformation(DwarfSection* section, Memory* memory, uint64_t pc, uint8_t class_type) { +void PrintRegInformation(DwarfSection* section, Memory* memory, uint64_t pc, uint8_t class_type, + ArchEnum arch) { const DwarfFde* fde = section->GetFdeFromPc(pc); if (fde == nullptr) { printf(" No fde found.\n"); @@ -72,7 +73,7 @@ void PrintRegInformation(DwarfSection* section, Memory* memory, uint64_t pc, uin } dwarf_loc_regs_t regs; - if (!section->GetCfaLocationInfo(pc, fde, ®s)) { + if (!section->GetCfaLocationInfo(pc, fde, ®s, arch)) { printf(" Cannot get location information.\n"); return; } @@ -128,6 +129,11 @@ void PrintRegInformation(DwarfSection* section, Memory* memory, uint64_t pc, uin break; } + case DWARF_LOCATION_PSEUDO_REGISTER: { + printf("%" PRId64 " (pseudo)\n", loc->values[0]); + break; + } + case DWARF_LOCATION_UNDEFINED: printf("undefine\n"); break; @@ -199,7 +205,7 @@ int GetInfo(const char* file, uint64_t offset, uint64_t pc) { DwarfSection* section = interface->eh_frame(); if (section != nullptr) { printf("\neh_frame:\n"); - PrintRegInformation(section, elf.memory(), pc, elf.class_type()); + PrintRegInformation(section, elf.memory(), pc, elf.class_type(), elf.arch()); } else { printf("\nno eh_frame information\n"); } @@ -207,7 +213,7 @@ int GetInfo(const char* file, uint64_t offset, uint64_t pc) { section = interface->debug_frame(); if (section != nullptr) { printf("\ndebug_frame:\n"); - PrintRegInformation(section, elf.memory(), pc, elf.class_type()); + PrintRegInformation(section, elf.memory(), pc, elf.class_type(), elf.arch()); printf("\n"); } else { printf("\nno debug_frame information\n"); @@ -219,7 +225,8 @@ int GetInfo(const char* file, uint64_t offset, uint64_t pc) { section = gnu_debugdata_interface->eh_frame(); if (section != nullptr) { printf("\ngnu_debugdata (eh_frame):\n"); - PrintRegInformation(section, gnu_debugdata_interface->memory(), pc, elf.class_type()); + PrintRegInformation(section, gnu_debugdata_interface->memory(), pc, elf.class_type(), + elf.arch()); printf("\n"); } else { printf("\nno gnu_debugdata (eh_frame)\n"); @@ -228,7 +235,8 @@ int GetInfo(const char* file, uint64_t offset, uint64_t pc) { section = gnu_debugdata_interface->debug_frame(); if (section != nullptr) { printf("\ngnu_debugdata (debug_frame):\n"); - PrintRegInformation(section, gnu_debugdata_interface->memory(), pc, elf.class_type()); + PrintRegInformation(section, gnu_debugdata_interface->memory(), pc, elf.class_type(), + elf.arch()); printf("\n"); } else { printf("\nno gnu_debugdata (debug_frame)\n");