| //===-- NativeRegisterContextLinux_arm64.cpp ------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #if defined(__arm64__) || defined(__aarch64__) | |
| #include "NativeRegisterContextLinux_arm64.h" | |
| #include "Plugins/Process/Linux/NativeProcessLinux.h" | |
| #include "Plugins/Process/Linux/NativeRegisterContextLinux_arm.h" | |
| #include "Plugins/Process/Linux/NativeRegisterContextLinux_arm64dbreg.h" | |
| #include "Plugins/Process/Linux/Procfs.h" | |
| #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" | |
| #include "Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h" | |
| #include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h" | |
| #include "Plugins/Process/Utility/RegisterTypeDetector_arm64.h" | |
| #include "lldb/Host/HostInfo.h" | |
| #include "lldb/Host/common/NativeProcessProtocol.h" | |
| #include "lldb/Utility/DataBufferHeap.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/RegisterValue.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "llvm/BinaryFormat/ELF.h" | |
| #include <mutex> | |
| #include <optional> | |
| // System includes - They have to be included after framework includes because | |
| // they define some macros which collide with variable names in other modules. | |
| #include <sys/ptrace.h> | |
| #include <sys/uio.h> | |
| #ifndef HWCAP_PACA | |
| #define HWCAP_PACA (1 << 30) | |
| #endif | |
| #ifndef HWCAP_GCS | |
| #define HWCAP_GCS (1UL << 32) | |
| #endif | |
| #ifndef HWCAP2_MTE | |
| #define HWCAP2_MTE (1 << 18) | |
| #endif | |
| #ifndef HWCAP2_FPMR | |
| #define HWCAP2_FPMR (1UL << 48) | |
| #endif | |
| #ifndef HWCAP2_POE | |
| #define HWCAP2_POE (1ULL << 63) | |
| #endif | |
| #ifndef PTRACE_GETREGSET | |
| #define PTRACE_GETREGSET 0x4204 | |
| #endif | |
| #ifndef PTRACE_PEEKMTETAGS | |
| #define PTRACE_PEEKMTETAGS 33 | |
| #endif | |
| #ifndef PTRACE_POKEMTETAGS | |
| #define PTRACE_POKEMTETAGS 34 | |
| #endif | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| using namespace lldb_private::process_linux; | |
| NativeRegisterContextLinux_arm64::RegisterSetType | |
| NativeRegisterContextLinux_arm64::GetInvalidationMask( | |
| const RegisterSetType set) const { | |
| switch (set) { | |
| case RegisterSetType::FPMR: | |
| case RegisterSetType::GPR: | |
| case RegisterSetType::GCS: | |
| case RegisterSetType::MTE: | |
| case RegisterSetType::PAC: | |
| case RegisterSetType::POE: | |
| case RegisterSetType::TLS: | |
| return set; | |
| case RegisterSetType::SVE_HEADER: | |
| case RegisterSetType::SVE: | |
| case RegisterSetType::FPR: | |
| return RegisterSetType::SVE_HEADER | RegisterSetType::SVE | | |
| // SVE registers overlap FP registers in hardware. | |
| RegisterSetType::FPR; | |
| case RegisterSetType::ZA_HEADER: | |
| case RegisterSetType::ZA: | |
| case RegisterSetType::ZT: | |
| // In the Linux ptrace ABI, writes that enable ZA or ZT result in | |
| // both ZA and ZT being enabled. | |
| return RegisterSetType::ZA_HEADER | RegisterSetType::ZA | | |
| RegisterSetType::ZT; | |
| } | |
| } | |
| unsigned int NativeRegisterContextLinux_arm64::GetPtraceSet( | |
| NativeRegisterContextLinux_arm64::RegisterSetType set) const { | |
| switch (set) { | |
| case RegisterSetType::GPR: | |
| return llvm::ELF::NT_PRSTATUS; | |
| case RegisterSetType::FPR: | |
| return llvm::ELF::NT_FPREGSET; | |
| case RegisterSetType::SVE: | |
| case RegisterSetType::SVE_HEADER: | |
| switch (m_sve_state) { | |
| case SVEState::Streaming: | |
| case SVEState::StreamingFPSIMD: | |
| return llvm::ELF::NT_ARM_SSVE; | |
| default: | |
| return llvm::ELF::NT_ARM_SVE; | |
| } | |
| case RegisterSetType::PAC: | |
| return llvm::ELF::NT_ARM_PAC_MASK; | |
| case RegisterSetType::MTE: | |
| return llvm::ELF::NT_ARM_TAGGED_ADDR_CTRL; | |
| case RegisterSetType::TLS: | |
| return llvm::ELF::NT_ARM_TLS; | |
| case RegisterSetType::ZA: | |
| case RegisterSetType::ZA_HEADER: | |
| return llvm::ELF::NT_ARM_ZA; | |
| case RegisterSetType::ZT: | |
| return llvm::ELF::NT_ARM_ZT; | |
| case RegisterSetType::FPMR: | |
| return llvm::ELF::NT_ARM_FPMR; | |
| case RegisterSetType::GCS: | |
| return llvm::ELF::NT_ARM_GCS; | |
| case RegisterSetType::POE: | |
| return llvm::ELF::NT_ARM_POE; | |
| } | |
| llvm_unreachable("No ptrace set for this RegisterType."); | |
| } | |
| size_t NativeRegisterContextLinux_arm64::GetSetSize( | |
| NativeRegisterContextLinux_arm64::RegisterSetType set) const { | |
| switch (set) { | |
| case RegisterSetType::GPR: | |
| // Returns sizeof arm64 GPR ptrace buffer, which is different | |
| // from GetGPRSize which returns sizeof RegisterInfoPOSIX_arm64::GPR. | |
| return sizeof(m_gpr_arm64); | |
| case RegisterSetType::FPR: | |
| return sizeof(m_fpr); | |
| case RegisterSetType::SVE: | |
| return m_sve_ptrace_payload.size(); | |
| case RegisterSetType::SVE_HEADER: | |
| return sizeof(m_sve_header); | |
| case RegisterSetType::PAC: | |
| return sizeof(m_pac_mask); | |
| case RegisterSetType::MTE: | |
| return sizeof(m_mte_ctrl_reg); | |
| case RegisterSetType::TLS: | |
| return m_tls_size; | |
| case RegisterSetType::ZA: | |
| return m_za_ptrace_payload.size(); | |
| case RegisterSetType::ZA_HEADER: | |
| return sizeof(m_za_header); | |
| case RegisterSetType::ZT: | |
| return m_zt_reg.size(); | |
| case RegisterSetType::FPMR: | |
| return sizeof(m_fpmr_reg); | |
| case RegisterSetType::GCS: | |
| return sizeof(m_gcs_regs); | |
| case RegisterSetType::POE: | |
| return sizeof(m_poe_regs); | |
| } | |
| llvm_unreachable("No set size for this RegisterType."); | |
| } | |
| void *NativeRegisterContextLinux_arm64::GetSetBuffer( | |
| NativeRegisterContextLinux_arm64::RegisterSetType set) { | |
| switch (set) { | |
| case RegisterSetType::GPR: | |
| return &m_gpr_arm64; | |
| case RegisterSetType::FPR: | |
| return &m_fpr; | |
| case RegisterSetType::SVE: | |
| return m_sve_ptrace_payload.data(); | |
| case RegisterSetType::SVE_HEADER: | |
| return &m_sve_header; | |
| case RegisterSetType::PAC: | |
| return &m_pac_mask; | |
| case RegisterSetType::MTE: | |
| return &m_mte_ctrl_reg; | |
| case RegisterSetType::TLS: | |
| return &m_tls_regs; | |
| case RegisterSetType::ZA: | |
| return m_za_ptrace_payload.data(); | |
| case RegisterSetType::ZA_HEADER: | |
| return &m_za_header; | |
| case RegisterSetType::ZT: | |
| return m_zt_reg.data(); | |
| case RegisterSetType::FPMR: | |
| return &m_fpmr_reg; | |
| case RegisterSetType::GCS: | |
| return &m_gcs_regs; | |
| case RegisterSetType::POE: | |
| return &m_poe_regs; | |
| } | |
| llvm_unreachable("No set buffer for this RegisterType."); | |
| } | |
| // A NativeRegisterContext is constructed per thread, but all threads' registers | |
| // will contain the same fields. Therefore this mutex prevents each instance | |
| // competing with the other, and subsequent instances from having to detect the | |
| // fields all over again. | |
| static std::mutex g_register_type_detector_mutex; | |
| static Arm64RegisterTypeDetector g_register_type_detector; | |
| std::unique_ptr<NativeRegisterContextLinux> | |
| NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux( | |
| const ArchSpec &target_arch, NativeThreadLinux &native_thread) { | |
| switch (target_arch.GetMachine()) { | |
| case llvm::Triple::arm: | |
| return std::make_unique<NativeRegisterContextLinux_arm>(target_arch, | |
| native_thread); | |
| case llvm::Triple::aarch64: { | |
| // Configure register sets supported by this AArch64 target. | |
| // Read SVE header to check for SVE support. | |
| struct sve::user_sve_header sve_header; | |
| struct iovec ioVec; | |
| ioVec.iov_base = &sve_header; | |
| ioVec.iov_len = sizeof(sve_header); | |
| unsigned int regset = llvm::ELF::NT_ARM_SVE; | |
| Flags opt_regsets; | |
| if (NativeProcessLinux::PtraceWrapper(PTRACE_GETREGSET, | |
| native_thread.GetID(), ®set, | |
| &ioVec, sizeof(sve_header)) | |
| .Success()) | |
| opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskSVE); | |
| // We may have the Scalable Matrix Extension (SME) which adds a | |
| // streaming SVE mode. Systems can have SVE and/or SME. | |
| ioVec.iov_len = sizeof(sve_header); | |
| regset = llvm::ELF::NT_ARM_SSVE; | |
| if (NativeProcessLinux::PtraceWrapper(PTRACE_GETREGSET, | |
| native_thread.GetID(), ®set, | |
| &ioVec, sizeof(sve_header)) | |
| .Success()) | |
| opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskSSVE); | |
| sve::user_za_header za_header; | |
| ioVec.iov_base = &za_header; | |
| ioVec.iov_len = sizeof(za_header); | |
| regset = llvm::ELF::NT_ARM_ZA; | |
| if (NativeProcessLinux::PtraceWrapper(PTRACE_GETREGSET, | |
| native_thread.GetID(), ®set, | |
| &ioVec, sizeof(za_header)) | |
| .Success()) | |
| opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskZA); | |
| // SME's ZT0 is a 512 bit register. | |
| std::array<uint8_t, 64> zt_reg; | |
| ioVec.iov_base = zt_reg.data(); | |
| ioVec.iov_len = zt_reg.size(); | |
| regset = llvm::ELF::NT_ARM_ZT; | |
| if (NativeProcessLinux::PtraceWrapper(PTRACE_GETREGSET, | |
| native_thread.GetID(), ®set, | |
| &ioVec, zt_reg.size()) | |
| .Success()) | |
| opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskZT); | |
| NativeProcessLinux &process = native_thread.GetProcess(); | |
| std::optional<uint64_t> auxv_at_hwcap = | |
| process.GetAuxValue(AuxVector::AUXV_AT_HWCAP); | |
| if (auxv_at_hwcap && (*auxv_at_hwcap & HWCAP_PACA)) | |
| opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskPAuth); | |
| std::optional<uint64_t> auxv_at_hwcap2 = | |
| process.GetAuxValue(AuxVector::AUXV_AT_HWCAP2); | |
| if (auxv_at_hwcap2) { | |
| if (*auxv_at_hwcap2 & HWCAP2_MTE) | |
| opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskMTE); | |
| if (*auxv_at_hwcap2 & HWCAP2_FPMR) | |
| opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskFPMR); | |
| if (*auxv_at_hwcap & HWCAP_GCS) | |
| opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskGCS); | |
| if (*auxv_at_hwcap2 & HWCAP2_POE) | |
| opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskPOE); | |
| } | |
| opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskTLS); | |
| std::optional<uint64_t> auxv_at_hwcap3 = | |
| process.GetAuxValue(AuxVector::AUXV_AT_HWCAP3); | |
| std::lock_guard<std::mutex> lock(g_register_type_detector_mutex); | |
| if (!g_register_type_detector.HasDetected()) | |
| g_register_type_detector.DetectTypes(auxv_at_hwcap.value_or(0), | |
| auxv_at_hwcap2.value_or(0), | |
| auxv_at_hwcap3.value_or(0)); | |
| auto register_info_up = | |
| std::make_unique<RegisterInfoPOSIX_arm64>(target_arch, opt_regsets); | |
| return std::make_unique<NativeRegisterContextLinux_arm64>( | |
| target_arch, native_thread, std::move(register_info_up)); | |
| } | |
| default: | |
| llvm_unreachable("have no register context for architecture"); | |
| } | |
| } | |
| llvm::Expected<ArchSpec> | |
| NativeRegisterContextLinux::DetermineArchitecture(lldb::tid_t tid) { | |
| return DetermineArchitectureViaGPR( | |
| tid, RegisterInfoPOSIX_arm64::GetGPRSizeStatic()); | |
| } | |
| NativeRegisterContextLinux_arm64::NativeRegisterContextLinux_arm64( | |
| const ArchSpec &target_arch, NativeThreadProtocol &native_thread, | |
| std::unique_ptr<RegisterInfoPOSIX_arm64> register_info_up) | |
| : NativeRegisterContextRegisterInfo(native_thread, | |
| register_info_up.release()), | |
| NativeRegisterContextLinux(native_thread) { | |
| g_register_type_detector.UpdateRegisterInfo( | |
| GetRegisterInfoInterface().GetRegisterInfo(), | |
| GetRegisterInfoInterface().GetRegisterCount()); | |
| // 16 is just a maximum value, query hardware for actual watchpoint count | |
| m_max_hwp_supported = 16; | |
| m_max_hbp_supported = 16; | |
| // SME adds the tpidr2 register | |
| m_tls_size = GetRegisterInfo().IsSSVEPresent() ? sizeof(m_tls_regs) | |
| : sizeof(m_tls_regs.tpidr_reg); | |
| if (GetRegisterInfo().IsSVEPresent() || GetRegisterInfo().IsSSVEPresent()) | |
| m_sve_state = SVEState::Unknown; | |
| else | |
| m_sve_state = SVEState::Disabled; | |
| } | |
| RegisterInfoPOSIX_arm64 & | |
| NativeRegisterContextLinux_arm64::GetRegisterInfo() const { | |
| return static_cast<RegisterInfoPOSIX_arm64 &>(*m_register_info_interface_up); | |
| } | |
| uint32_t NativeRegisterContextLinux_arm64::GetRegisterSetCount() const { | |
| return GetRegisterInfo().GetRegisterSetCount(); | |
| } | |
| const RegisterSet * | |
| NativeRegisterContextLinux_arm64::GetRegisterSet(uint32_t set_index) const { | |
| return GetRegisterInfo().GetRegisterSet(set_index); | |
| } | |
| uint32_t NativeRegisterContextLinux_arm64::GetUserRegisterCount() const { | |
| uint32_t count = 0; | |
| for (uint32_t set_index = 0; set_index < GetRegisterSetCount(); ++set_index) | |
| count += GetRegisterSet(set_index)->num_registers; | |
| return count; | |
| } | |
| Status | |
| NativeRegisterContextLinux_arm64::ReadRegister(const RegisterInfo *reg_info, | |
| RegisterValue ®_value) { | |
| Status error; | |
| if (!reg_info) { | |
| error = Status::FromErrorString("reg_info NULL"); | |
| return error; | |
| } | |
| const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB]; | |
| if (reg == LLDB_INVALID_REGNUM) | |
| return Status::FromErrorStringWithFormat( | |
| "no lldb regnum for %s", | |
| reg_info && reg_info->name ? reg_info->name : "<unknown register>"); | |
| uint8_t *src; | |
| uint32_t offset = LLDB_INVALID_INDEX32; | |
| uint64_t sve_vg; | |
| std::vector<uint8_t> sve_reg_non_live; | |
| if (GetRegisterInfo().IsGPR(reg)) { | |
| error = ReadGPR(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset; | |
| assert(offset < GetGPRSize()); | |
| src = (uint8_t *)GetGPRBuffer() + offset; | |
| } else if (GetRegisterInfo().IsFPR(reg)) { | |
| if (m_sve_state == SVEState::Disabled || | |
| m_sve_state == SVEState::StreamingFPSIMD) { | |
| // FP registers come from the FP register set when: | |
| // * We only have SVE in streaming mode, and we are in non-streaming mode. | |
| // * We only have SIMD, no SVE in any mode. | |
| error = ReadFPR(); | |
| if (error.Fail()) | |
| return error; | |
| offset = CalculateFprOffset(reg_info, | |
| m_sve_state == SVEState::StreamingFPSIMD); | |
| assert(offset < GetFPRSize()); | |
| src = (uint8_t *)GetFPRBuffer() + offset; | |
| } else { | |
| // SVE or SSVE enabled, we will read and cache SVE ptrace data. | |
| // In SIMD or Full mode, the data comes from the SVE regset. In streaming | |
| // mode it comes from the streaming SVE regset. | |
| error = ReadAllSVE(); | |
| if (error.Fail()) | |
| return error; | |
| // FPSR and FPCR will be located right after Z registers in | |
| // SVEState::FPSIMD while in SVEState::Full or SVEState::Streaming they | |
| // will be located at the end of register data after an alignment | |
| // correction based on currently selected vector length. | |
| uint32_t sve_reg_num = LLDB_INVALID_REGNUM; | |
| if (reg == GetRegisterInfo().GetRegNumFPSR()) { | |
| sve_reg_num = reg; | |
| if (m_sve_state == SVEState::Full || m_sve_state == SVEState::Streaming) | |
| offset = sve::PTraceFPSROffset(sve::vq_from_vl(m_sve_header.vl)); | |
| else if (m_sve_state == SVEState::FPSIMD) | |
| offset = sve::ptrace_fpsimd_offset + (32 * 16); | |
| } else if (reg == GetRegisterInfo().GetRegNumFPCR()) { | |
| sve_reg_num = reg; | |
| if (m_sve_state == SVEState::Full || m_sve_state == SVEState::Streaming) | |
| offset = sve::PTraceFPCROffset(sve::vq_from_vl(m_sve_header.vl)); | |
| else if (m_sve_state == SVEState::FPSIMD) | |
| offset = sve::ptrace_fpsimd_offset + (32 * 16) + 4; | |
| } else { | |
| // Extract SVE Z register value register number for this reg_info | |
| if (reg_info->value_regs && | |
| reg_info->value_regs[0] != LLDB_INVALID_REGNUM) | |
| sve_reg_num = reg_info->value_regs[0]; | |
| offset = CalculateSVEOffset(GetRegisterInfoAtIndex(sve_reg_num)); | |
| } | |
| assert(offset < GetSetSize(RegisterSetType::SVE)); | |
| src = (uint8_t *)GetSetBuffer(RegisterSetType::SVE) + offset; | |
| } | |
| } else if (GetRegisterInfo().IsTLSReg(reg)) { | |
| error = ReadTLS(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetTLSOffset(); | |
| assert(offset < GetSetSize(RegisterSetType::TLS)); | |
| src = (uint8_t *)GetSetBuffer(RegisterSetType::TLS) + offset; | |
| } else if (GetRegisterInfo().IsSVEReg(reg)) { | |
| if (m_sve_state == SVEState::Disabled || m_sve_state == SVEState::Unknown) | |
| return Status::FromErrorString("SVE disabled or not supported"); | |
| if (GetRegisterInfo().IsSVERegVG(reg)) { | |
| error = ReadSVEHeader(); | |
| if (error.Fail()) | |
| return error; | |
| sve_vg = GetSVERegVG(); | |
| src = (uint8_t *)&sve_vg; | |
| } else if (m_sve_state == SVEState::StreamingFPSIMD) { | |
| // When we only have streaming SVE and we are in non-streaming mode, | |
| // we cannot read streaming SVE registers. | |
| // P and FFR show as 0s. | |
| if (GetRegisterInfo().IsSVEPReg(reg) || | |
| GetRegisterInfo().IsSVERegFFR(reg)) { | |
| std::vector<uint8_t> fake_reg(reg_info->byte_size, 0); | |
| reg_value.SetFromMemoryData(*reg_info, &fake_reg[0], | |
| reg_info->byte_size, eByteOrderLittle, | |
| error); | |
| return error; | |
| } | |
| // For Z registers, zero extend the 128-bit FP register to Z register | |
| // size. | |
| error = ReadFPR(); | |
| if (error.Fail()) | |
| return error; | |
| // As we told the client we have Z registers, our own internal offsets | |
| // are set as if we were using an SVE context. We need to work out | |
| // an offset within the FP context instead: | |
| // struct user_fpsimd_state { | |
| // __uint128_t vregs[32]; | |
| // __u32 fpsr; | |
| // __u32 fpcr; | |
| // __u32 __reserved[2]; | |
| // }; | |
| const uint32_t z_num = reg - GetRegisterInfo().GetRegNumSVEZ0(); | |
| offset = z_num * 16; | |
| assert(offset < GetFPRSize()); | |
| src = (uint8_t *)GetFPRBuffer() + offset; | |
| // Copy from FP into a fake Z value. | |
| std::vector<uint8_t> fake_z(reg_info->byte_size, 0); | |
| std::memcpy(&fake_z[0], src, 16 /* 128 bits */); | |
| reg_value.SetFromMemoryData(*reg_info, &fake_z[0], reg_info->byte_size, | |
| eByteOrderLittle, error); | |
| return error; | |
| } else { | |
| // SVE enabled, we will read and cache SVE ptrace data | |
| error = ReadAllSVE(); | |
| if (error.Fail()) | |
| return error; | |
| if (m_sve_state == SVEState::FPSIMD) { | |
| // In FPSIMD state SVE payload mirrors legacy fpsimd struct and so | |
| // just copy 16 bytes of v register to the start of z register. All | |
| // other SVE register will be set to zero. | |
| sve_reg_non_live.resize(reg_info->byte_size, 0); | |
| src = sve_reg_non_live.data(); | |
| if (GetRegisterInfo().IsSVEZReg(reg)) { | |
| offset = CalculateSVEOffset(reg_info); | |
| assert(offset < GetSetSize(RegisterSetType::SVE)); | |
| ::memcpy(sve_reg_non_live.data(), | |
| (uint8_t *)GetSetBuffer(RegisterSetType::SVE) + offset, 16); | |
| } | |
| } else { | |
| offset = CalculateSVEOffset(reg_info); | |
| assert(offset < GetSetSize(RegisterSetType::SVE)); | |
| src = (uint8_t *)GetSetBuffer(RegisterSetType::SVE) + offset; | |
| } | |
| } | |
| } else if (GetRegisterInfo().IsPAuthReg(reg)) { | |
| error = ReadPAuthMask(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetPAuthOffset(); | |
| assert(offset < GetSetSize(RegisterSetType::PAC)); | |
| src = (uint8_t *)GetSetBuffer(RegisterSetType::PAC) + offset; | |
| } else if (GetRegisterInfo().IsMTEReg(reg)) { | |
| error = ReadMTEControl(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetMTEOffset(); | |
| assert(offset < GetSetSize(RegisterSetType::MTE)); | |
| src = (uint8_t *)GetSetBuffer(RegisterSetType::MTE) + offset; | |
| } else if (GetRegisterInfo().IsSMEReg(reg)) { | |
| if (GetRegisterInfo().IsSMERegZA(reg)) { | |
| error = ReadZAHeader(); | |
| if (error.Fail()) | |
| return error; | |
| // If there is only a header and no registers, ZA is inactive. Read as 0 | |
| // in this case. | |
| if (m_za_header.size == sizeof(m_za_header)) { | |
| // This will get reconfigured/reset later, so we are safe to use it. | |
| // ZA is a square of VL * VL and the ptrace buffer also includes the | |
| // header itself. | |
| m_za_ptrace_payload.resize(((m_za_header.vl) * (m_za_header.vl)) + | |
| GetSetSize(RegisterSetType::ZA_HEADER)); | |
| std::fill(m_za_ptrace_payload.begin(), m_za_ptrace_payload.end(), 0); | |
| } else { | |
| // ZA is active, read the real register. | |
| error = ReadZA(); | |
| if (error.Fail()) | |
| return error; | |
| } | |
| // ZA is part of the SME set but uses a separate member buffer for | |
| // storage. Therefore its effective byte offset is always 0 even if it | |
| // isn't 0 within the SME register set. | |
| src = (uint8_t *)GetSetBuffer(RegisterSetType::ZA) + | |
| GetSetSize(RegisterSetType::ZA_HEADER); | |
| } else if (GetRegisterInfo().IsSMERegZT(reg)) { | |
| // Unlike ZA, the kernel will return register data for ZT0 when ZA is not | |
| // enabled. This data will be all 0s so we don't have to invent anything | |
| // like we did for ZA. | |
| error = ReadZT(); | |
| if (error.Fail()) | |
| return error; | |
| src = (uint8_t *)GetSetBuffer(RegisterSetType::ZT); | |
| } else { | |
| error = ReadSMESVG(); | |
| if (error.Fail()) | |
| return error; | |
| // This is a psuedo so it never fails. | |
| ReadSMEControl(); | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetSMEOffset(); | |
| assert(offset < GetSMEPseudoBufferSize()); | |
| src = (uint8_t *)GetSMEPseudoBuffer() + offset; | |
| } | |
| } else if (GetRegisterInfo().IsFPMRReg(reg)) { | |
| error = ReadFPMR(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetFPMROffset(); | |
| assert(offset < GetSetSize(RegisterSetType::FPMR)); | |
| src = (uint8_t *)GetSetBuffer(RegisterSetType::FPMR) + offset; | |
| } else if (GetRegisterInfo().IsGCSReg(reg)) { | |
| error = ReadGCS(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetGCSOffset(); | |
| assert(offset < GetSetSize(RegisterSetType::GCS)); | |
| src = (uint8_t *)GetSetBuffer(RegisterSetType::GCS) + offset; | |
| } else if (GetRegisterInfo().IsPOEReg(reg)) { | |
| error = ReadPOE(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetPOEOffset(); | |
| assert(offset < GetSetSize(RegisterSetType::POE)); | |
| src = (uint8_t *)GetSetBuffer(RegisterSetType::POE) + offset; | |
| } else | |
| return Status::FromErrorString( | |
| "failed - register wasn't recognized to be a GPR or an FPR, " | |
| "write strategy unknown"); | |
| reg_value.SetFromMemoryData(*reg_info, src, reg_info->byte_size, | |
| eByteOrderLittle, error); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteRegister( | |
| const RegisterInfo *reg_info, const RegisterValue ®_value) { | |
| Status error; | |
| if (!reg_info) | |
| return Status::FromErrorString("reg_info NULL"); | |
| const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB]; | |
| if (reg == LLDB_INVALID_REGNUM) | |
| return Status::FromErrorStringWithFormat( | |
| "no lldb regnum for %s", | |
| reg_info && reg_info->name ? reg_info->name : "<unknown register>"); | |
| uint8_t *dst; | |
| uint32_t offset = LLDB_INVALID_INDEX32; | |
| if (GetRegisterInfo().IsGPR(reg)) { | |
| error = ReadGPR(); | |
| if (error.Fail()) | |
| return error; | |
| assert(reg_info->byte_offset < GetGPRSize()); | |
| dst = (uint8_t *)GetGPRBuffer() + reg_info->byte_offset; | |
| ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); | |
| return WriteGPR(); | |
| } else if (GetRegisterInfo().IsFPR(reg)) { | |
| if (m_sve_state == SVEState::Disabled || | |
| m_sve_state == SVEState::StreamingFPSIMD) { | |
| // SVE is not present, or we only have it in streaming mode and are | |
| // currently outside of streaming mode. Take normal route for FPU register | |
| // access. | |
| error = ReadFPR(); | |
| if (error.Fail()) | |
| return error; | |
| offset = CalculateFprOffset(reg_info, | |
| m_sve_state == SVEState::StreamingFPSIMD); | |
| assert(offset < GetFPRSize()); | |
| dst = (uint8_t *)GetFPRBuffer() + offset; | |
| ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); | |
| return WriteFPR(); | |
| } else { | |
| // SVE enabled, we will read and cache SVE ptrace data. | |
| error = ReadAllSVE(); | |
| if (error.Fail()) | |
| return error; | |
| // FPSR and FPCR will be located right after Z registers in | |
| // SVEState::FPSIMD while in SVEState::Full or SVEState::Streaming they | |
| // will be located at the end of register data after an alignment | |
| // correction based on currently selected vector length. | |
| uint32_t sve_reg_num = LLDB_INVALID_REGNUM; | |
| if (reg == GetRegisterInfo().GetRegNumFPSR()) { | |
| sve_reg_num = reg; | |
| if (m_sve_state == SVEState::Full || m_sve_state == SVEState::Streaming) | |
| offset = sve::PTraceFPSROffset(sve::vq_from_vl(m_sve_header.vl)); | |
| else if (m_sve_state == SVEState::FPSIMD) | |
| offset = sve::ptrace_fpsimd_offset + (32 * 16); | |
| } else if (reg == GetRegisterInfo().GetRegNumFPCR()) { | |
| sve_reg_num = reg; | |
| if (m_sve_state == SVEState::Full || m_sve_state == SVEState::Streaming) | |
| offset = sve::PTraceFPCROffset(sve::vq_from_vl(m_sve_header.vl)); | |
| else if (m_sve_state == SVEState::FPSIMD) | |
| offset = sve::ptrace_fpsimd_offset + (32 * 16) + 4; | |
| } else { | |
| // Extract SVE Z register value register number for this reg_info | |
| if (reg_info->value_regs && | |
| reg_info->value_regs[0] != LLDB_INVALID_REGNUM) | |
| sve_reg_num = reg_info->value_regs[0]; | |
| offset = CalculateSVEOffset(GetRegisterInfoAtIndex(sve_reg_num)); | |
| } | |
| assert(offset < GetSetSize(RegisterSetType::SVE)); | |
| dst = (uint8_t *)GetSetBuffer(RegisterSetType::SVE) + offset; | |
| ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); | |
| return WriteAllSVE(); | |
| } | |
| } else if (GetRegisterInfo().IsSVEReg(reg)) { | |
| if (m_sve_state == SVEState::Disabled || m_sve_state == SVEState::Unknown) { | |
| return Status::FromErrorString("SVE disabled or not supported"); | |
| } else if (m_sve_state == SVEState::StreamingFPSIMD) { | |
| // When a target has SVE (in any state), the client is told that it has | |
| // real SVE registers and that the FP registers are just subregisters | |
| // of those SVE registers. This means that any FP write will be converted | |
| // into an SVE write. | |
| // | |
| // If we get here, it did that, but we are outside of streaming mode | |
| // on an SME only system. Meaning there's no way at all to write to actual | |
| // SVE registers. | |
| // | |
| // Instead we will extract the bottom 128 bits of the register, | |
| // write that via the standard FP route and then return the fake SVE | |
| // values as usual. | |
| // | |
| // We can only do this for Z registers. P, FFR and VG have no SIMD | |
| // equivalent. | |
| if (GetRegisterInfo().IsSVERegVG(reg) || | |
| GetRegisterInfo().IsSVEPReg(reg) || | |
| GetRegisterInfo().IsSVERegFFR(reg)) | |
| return Status::FromErrorString( | |
| "Cannot write SVE VG, P or FFR registers while outside of " | |
| "streaming mode."); | |
| // We have told the client that we only have Z registers and the V | |
| // registers are subsets of Z. This means that the V byte offsets are | |
| // actually for the SVE register context, which we cannot access right | |
| // now. That is, v0 is offset 16, v1 is 16+vlen, and so on. So we will | |
| // manually patch this data into the FP context and write it. | |
| error = ReadFPR(); | |
| if (error.Fail()) | |
| return error; | |
| uint32_t z_num = reg - GetRegisterInfo().GetRegNumSVEZ0(); | |
| offset = z_num * 16; | |
| assert(offset < GetFPRSize()); | |
| dst = (uint8_t *)GetFPRBuffer() + offset; | |
| // If we get here we must have a Z register. Assume we have 16 bytes aka | |
| // 128 bits at least, enough to fill an FP V register. | |
| ::memcpy(dst, reg_value.GetBytes(), 16); | |
| return WriteFPR(); | |
| } else { | |
| // Target has SVE enabled, we will read and cache SVE ptrace data | |
| error = ReadAllSVE(); | |
| if (error.Fail()) | |
| return error; | |
| if (GetRegisterInfo().IsSVERegVG(reg)) { | |
| uint64_t vg_value = reg_value.GetAsUInt64(); | |
| if (sve::vl_valid(vg_value * 8)) { | |
| if (IsValid(RegisterSetType::SVE_HEADER) && vg_value == GetSVERegVG()) | |
| return error; | |
| SetSVERegVG(vg_value); | |
| error = WriteSVEHeader(); | |
| if (error.Success()) { | |
| // Changing VG during streaming mode also changes the size of ZA. | |
| if (m_sve_state == SVEState::Streaming) | |
| Invalidate(RegisterSetType::ZA_HEADER); | |
| ConfigureRegisterContext(); | |
| } | |
| if (IsValid(RegisterSetType::SVE_HEADER) && vg_value == GetSVERegVG()) | |
| return error; | |
| } | |
| return Status::FromErrorString("SVE vector length update failed."); | |
| } | |
| // If target supports SVE but currently in FPSIMD mode. | |
| if (m_sve_state == SVEState::FPSIMD) { | |
| // Here we will check if writing this SVE register enables | |
| // SVEState::Full | |
| bool set_sve_state_full = false; | |
| const uint8_t *reg_bytes = (const uint8_t *)reg_value.GetBytes(); | |
| if (GetRegisterInfo().IsSVEZReg(reg)) { | |
| for (uint32_t i = 16; i < reg_info->byte_size; i++) { | |
| if (reg_bytes[i]) { | |
| set_sve_state_full = true; | |
| break; | |
| } | |
| } | |
| } else if (GetRegisterInfo().IsSVEPReg(reg) || | |
| reg == GetRegisterInfo().GetRegNumSVEFFR()) { | |
| for (uint32_t i = 0; i < reg_info->byte_size; i++) { | |
| if (reg_bytes[i]) { | |
| set_sve_state_full = true; | |
| break; | |
| } | |
| } | |
| } | |
| if (!set_sve_state_full && GetRegisterInfo().IsSVEZReg(reg)) { | |
| // We are writing a Z register which is zero beyond 16 bytes so copy | |
| // first 16 bytes only as SVE payload mirrors legacy fpsimd structure | |
| offset = CalculateSVEOffset(reg_info); | |
| assert(offset < GetSetSize(RegisterSetType::SVE)); | |
| dst = (uint8_t *)GetSetBuffer(RegisterSetType::SVE) + offset; | |
| ::memcpy(dst, reg_value.GetBytes(), 16); | |
| return WriteAllSVE(); | |
| } else | |
| return Status::FromErrorString( | |
| "SVE state change operation not supported"); | |
| } else { | |
| offset = CalculateSVEOffset(reg_info); | |
| assert(offset < GetSetSize(RegisterSetType::SVE)); | |
| dst = (uint8_t *)GetSetBuffer(RegisterSetType::SVE) + offset; | |
| ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); | |
| return WriteAllSVE(); | |
| } | |
| } | |
| } else if (GetRegisterInfo().IsMTEReg(reg)) { | |
| error = ReadMTEControl(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetMTEOffset(); | |
| assert(offset < GetSetSize(RegisterSetType::MTE)); | |
| dst = (uint8_t *)GetSetBuffer(RegisterSetType::MTE) + offset; | |
| ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); | |
| return WriteMTEControl(); | |
| } else if (GetRegisterInfo().IsTLSReg(reg)) { | |
| error = ReadTLS(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetTLSOffset(); | |
| assert(offset < GetSetSize(RegisterSetType::TLS)); | |
| dst = (uint8_t *)GetSetBuffer(RegisterSetType::TLS) + offset; | |
| ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); | |
| return WriteTLS(); | |
| } else if (GetRegisterInfo().IsSMEReg(reg)) { | |
| if (GetRegisterInfo().IsSMERegZA(reg)) { | |
| error = ReadZA(); | |
| if (error.Fail()) | |
| return error; | |
| // ZA is part of the SME set but not stored with the other SME registers. | |
| // So its byte offset is effectively always 0. | |
| dst = (uint8_t *)GetSetBuffer(RegisterSetType::ZA) + | |
| GetSetSize(RegisterSetType::ZA_HEADER); | |
| ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); | |
| // While this is writing a header that contains a vector length, the only | |
| // way to change that is via the vg register. So here we assume the length | |
| // will always be the current length and no reconfigure is needed. | |
| return WriteZA(); | |
| } else if (GetRegisterInfo().IsSMERegZT(reg)) { | |
| error = ReadZT(); | |
| if (error.Fail()) | |
| return error; | |
| dst = (uint8_t *)GetSetBuffer(RegisterSetType::ZT); | |
| ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); | |
| return WriteZT(); | |
| } else | |
| return Status::FromErrorString( | |
| "Writing to SVG or SVCR is not supported."); | |
| } else if (GetRegisterInfo().IsFPMRReg(reg)) { | |
| error = ReadFPMR(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetFPMROffset(); | |
| assert(offset < GetSetSize(RegisterSetType::FPMR)); | |
| dst = (uint8_t *)GetSetBuffer(RegisterSetType::FPMR) + offset; | |
| ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); | |
| return WriteFPMR(); | |
| } else if (GetRegisterInfo().IsGCSReg(reg)) { | |
| error = ReadGCS(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetGCSOffset(); | |
| assert(offset < GetSetSize(RegisterSetType::GCS)); | |
| dst = (uint8_t *)GetSetBuffer(RegisterSetType::GCS) + offset; | |
| ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); | |
| return WriteGCS(); | |
| } else if (GetRegisterInfo().IsPOEReg(reg)) { | |
| error = ReadPOE(); | |
| if (error.Fail()) | |
| return error; | |
| offset = reg_info->byte_offset - GetRegisterInfo().GetPOEOffset(); | |
| assert(offset < GetSetSize(RegisterSetType::POE)); | |
| dst = (uint8_t *)GetSetBuffer(RegisterSetType::POE) + offset; | |
| ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); | |
| return WritePOE(); | |
| } | |
| return Status::FromErrorString("Failed to write register value"); | |
| } | |
| uint8_t *NativeRegisterContextLinux_arm64::AddRegisterSetType( | |
| uint8_t *dst, RegisterSetType register_set_type) { | |
| std::memcpy(dst, ®ister_set_type, sizeof(register_set_type)); | |
| return dst + sizeof(RegisterSetType); | |
| } | |
| static uint8_t *AddSavedRegistersData(uint8_t *dst, void *src, size_t size) { | |
| ::memcpy(dst, src, size); | |
| return dst + size; | |
| } | |
| uint8_t *NativeRegisterContextLinux_arm64::AddSavedRegisters( | |
| uint8_t *dst, RegisterSetType register_set_type, void *src, size_t size) { | |
| dst = AddRegisterSetType(dst, register_set_type); | |
| return AddSavedRegistersData(dst, src, size); | |
| } | |
| Status | |
| NativeRegisterContextLinux_arm64::CacheAllRegisters(uint32_t &cached_size) { | |
| Status error; | |
| cached_size = sizeof(RegisterSetType) + GetGPRBufferSize(); | |
| error = ReadGPR(); | |
| if (error.Fail()) | |
| return error; | |
| if (GetRegisterInfo().IsZAPresent()) { | |
| error = ReadZAHeader(); | |
| if (error.Fail()) | |
| return error; | |
| // Use header size here because the buffer may contain fake data when ZA is | |
| // disabled. We do not want to write this fake data (all 0s) because this | |
| // would tell the kernel that we want ZA to become active. Which is the | |
| // opposite of what we want in the case where it is currently inactive. | |
| cached_size += sizeof(RegisterSetType) + m_za_header.size; | |
| // For the same reason, we need to force it to be re-read so that it will | |
| // always contain the real header. | |
| Invalidate(RegisterSetType::ZA); | |
| error = ReadZA(); | |
| if (error.Fail()) | |
| return error; | |
| // We will only be restoring ZT data if ZA is active. As writing to an | |
| // inactive ZT enables ZA, which may not be desireable. | |
| if ( | |
| // If we have ZT0, or in other words, if we have SME2. | |
| GetRegisterInfo().IsZTPresent() && | |
| // And ZA is active, which means that ZT0 is also active. | |
| m_za_header.size > sizeof(m_za_header)) { | |
| cached_size += sizeof(RegisterSetType) + GetSetSize(RegisterSetType::ZT); | |
| // The kernel handles an inactive ZT0 for us, and it will read as 0s if | |
| // inactive (unlike ZA where we fake that behaviour). | |
| error = ReadZT(); | |
| if (error.Fail()) | |
| return error; | |
| } | |
| } | |
| // If SVE is enabled we need not copy FPR separately, unless we are in the | |
| // non-streaming mode of a streaming only process (as its non-streaming mode | |
| // is FPSIMD, rather than SVE). | |
| if ((GetRegisterInfo().IsSVEPresent() || GetRegisterInfo().IsSSVEPresent()) && | |
| m_sve_state != SVEState::StreamingFPSIMD) { | |
| // Store mode and register data. | |
| cached_size += sizeof(RegisterSetType) + sizeof(m_sve_state) + | |
| GetSetSize(RegisterSetType::SVE); | |
| error = ReadAllSVE(); | |
| } else { | |
| cached_size += sizeof(RegisterSetType) + GetFPRSize(); | |
| error = ReadFPR(); | |
| } | |
| if (error.Fail()) | |
| return error; | |
| if (GetRegisterInfo().IsMTEPresent()) { | |
| cached_size += sizeof(RegisterSetType) + GetSetSize(RegisterSetType::MTE); | |
| error = ReadMTEControl(); | |
| if (error.Fail()) | |
| return error; | |
| } | |
| if (GetRegisterInfo().IsFPMRPresent()) { | |
| cached_size += sizeof(RegisterSetType) + GetSetSize(RegisterSetType::FPMR); | |
| error = ReadFPMR(); | |
| if (error.Fail()) | |
| return error; | |
| } | |
| if (GetRegisterInfo().IsGCSPresent()) { | |
| cached_size += sizeof(RegisterSetType) + GetSetSize(RegisterSetType::GCS); | |
| error = ReadGCS(); | |
| if (error.Fail()) | |
| return error; | |
| } | |
| if (GetRegisterInfo().IsPOEPresent()) { | |
| cached_size += sizeof(RegisterSetType) + GetSetSize(RegisterSetType::POE); | |
| error = ReadPOE(); | |
| if (error.Fail()) | |
| return error; | |
| } | |
| // tpidr is always present but tpidr2 depends on SME. | |
| cached_size += sizeof(RegisterSetType) + GetSetSize(RegisterSetType::TLS); | |
| error = ReadTLS(); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadAllRegisterValues( | |
| lldb::WritableDataBufferSP &data_sp) { | |
| // AArch64 register data must contain GPRs and either FPR or SVE registers. | |
| // SVE registers can be non-streaming (aka SVE) or streaming (aka SSVE). | |
| // Finally an optional MTE register. Pointer Authentication (PAC) registers | |
| // are read-only and will be skipped. | |
| // In order to create register data checkpoint we first read all register | |
| // values if not done already and calculate total size of register set data. | |
| // We store all register values in data_sp by copying full PTrace data that | |
| // corresponds to register sets enabled by current register context. | |
| uint32_t reg_data_byte_size = 0; | |
| Status error = CacheAllRegisters(reg_data_byte_size); | |
| if (error.Fail()) | |
| return error; | |
| data_sp.reset(new DataBufferHeap(reg_data_byte_size, 0)); | |
| uint8_t *dst = data_sp->GetBytes(); | |
| dst = AddSavedRegisters(dst, RegisterSetType::GPR, GetGPRBuffer(), | |
| GetGPRBufferSize()); | |
| // Streaming SVE and the ZA register both use the streaming vector length. | |
| // When you change this, the kernel will invalidate parts of the process | |
| // state. Therefore we need a specific order of restoration for each mode, if | |
| // we also have ZA to restore. | |
| // | |
| // Streaming mode enabled, ZA enabled: | |
| // * Write streaming registers. This sets SVCR.SM and clears SVCR.ZA. | |
| // * Write ZA, this set SVCR.ZA. The register data we provide is written to | |
| // ZA. | |
| // * Result is SVCR.SM and SVCR.ZA set, with the expected data in both | |
| // register sets. | |
| // | |
| // Streaming mode disabled, ZA enabled: | |
| // * Write ZA. This sets SVCR.ZA, and the ZA content. In the majority of cases | |
| // the streaming vector length is changing, so the thread is converted into | |
| // an FPSIMD thread if it is not already one. This also clears SVCR.SM. | |
| // * Write SVE registers, which also clears SVCR.SM but most importantly, puts | |
| // us into full SVE mode instead of FPSIMD mode (where the registers are | |
| // actually the 128 bit Neon registers). | |
| // * Result is we have SVCR.SM = 0, SVCR.ZA = 1 and the expected register | |
| // state. | |
| // | |
| // Restoring in different orders leads to things like the SVE registers being | |
| // truncated due to the FPSIMD mode and ZA being disabled or filled with 0s | |
| // (disabled and 0s looks the same from inside lldb since we fake the value | |
| // when it's disabled). | |
| // | |
| // For more information on this, look up the uses of the relevant NT_ARM_ | |
| // constants and the functions vec_set_vector_length, sve_set_common and | |
| // za_set in the Linux Kernel. | |
| if ((m_sve_state != SVEState::Streaming) && GetRegisterInfo().IsZAPresent()) { | |
| // Use the header size not the buffer size, as we may be using the buffer | |
| // for fake data, which we do not want to write out. | |
| assert(m_za_header.size <= GetSetSize(RegisterSetType::ZA)); | |
| dst = | |
| AddSavedRegisters(dst, RegisterSetType::ZA, | |
| GetSetBuffer(RegisterSetType::ZA), m_za_header.size); | |
| } | |
| if ((GetRegisterInfo().IsSVEPresent() || GetRegisterInfo().IsSSVEPresent()) && | |
| m_sve_state != SVEState::StreamingFPSIMD) { | |
| dst = AddRegisterSetType(dst, RegisterSetType::SVE); | |
| std::memcpy(dst, &m_sve_state, sizeof(m_sve_state)); | |
| dst += sizeof(m_sve_state); | |
| dst = AddSavedRegistersData(dst, GetSetBuffer(RegisterSetType::SVE), | |
| GetSetSize(RegisterSetType::SVE)); | |
| } else { | |
| dst = AddSavedRegisters(dst, RegisterSetType::FPR, GetFPRBuffer(), | |
| GetFPRSize()); | |
| } | |
| if ((m_sve_state == SVEState::Streaming) && GetRegisterInfo().IsZAPresent()) { | |
| assert(m_za_header.size <= GetSetSize(RegisterSetType::ZA)); | |
| dst = | |
| AddSavedRegisters(dst, RegisterSetType::ZA, | |
| GetSetBuffer(RegisterSetType::ZA), m_za_header.size); | |
| } | |
| // If ZT0 is present and we are going to be restoring an active ZA (which | |
| // implies an active ZT0), then restore ZT0 after ZA has been set. This | |
| // prevents us enabling ZA accidentally after the restore of ZA disabled it. | |
| // If we leave ZA/ZT0 inactive and read ZT0, the kernel returns 0s. Therefore | |
| // there's nothing for us to restore if ZA was originally inactive. | |
| if ( | |
| // If we have SME2 and therefore ZT0. | |
| GetRegisterInfo().IsZTPresent() && | |
| // And ZA is enabled. | |
| m_za_header.size > sizeof(m_za_header)) | |
| dst = AddSavedRegisters(dst, RegisterSetType::ZT, | |
| GetSetBuffer(RegisterSetType::ZT), | |
| GetSetSize(RegisterSetType::ZT)); | |
| if (GetRegisterInfo().IsMTEPresent()) { | |
| dst = AddSavedRegisters(dst, RegisterSetType::MTE, | |
| GetSetBuffer(RegisterSetType::MTE), | |
| GetSetSize(RegisterSetType::MTE)); | |
| } | |
| if (GetRegisterInfo().IsFPMRPresent()) { | |
| dst = AddSavedRegisters(dst, RegisterSetType::FPMR, | |
| GetSetBuffer(RegisterSetType::FPMR), | |
| GetSetSize(RegisterSetType::FPMR)); | |
| } | |
| if (GetRegisterInfo().IsGCSPresent()) { | |
| dst = AddSavedRegisters(dst, RegisterSetType::GCS, | |
| GetSetBuffer(RegisterSetType::GCS), | |
| GetSetSize(RegisterSetType::GCS)); | |
| } | |
| if (GetRegisterInfo().IsPOEPresent()) { | |
| dst = AddSavedRegisters(dst, RegisterSetType::POE, | |
| GetSetBuffer(RegisterSetType::POE), | |
| GetSetSize(RegisterSetType::POE)); | |
| } | |
| dst = AddSavedRegisters(dst, RegisterSetType::TLS, | |
| GetSetBuffer(RegisterSetType::TLS), | |
| GetSetSize(RegisterSetType::TLS)); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::RestoreRegisters( | |
| void *buffer, const uint8_t **src, size_t len, | |
| const NativeRegisterContextLinux_arm64::RegisterSetType set, | |
| std::function<Status()> writer) { | |
| ::memcpy(buffer, *src, len); | |
| MakeValid(set); | |
| *src += len; | |
| return writer(); | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteAllRegisterValues( | |
| const lldb::DataBufferSP &data_sp) { | |
| // AArch64 register data must contain GPRs, either FPR or SVE registers | |
| // (which can be streaming or non-streaming) and optional MTE register. | |
| // Pointer Authentication (PAC) registers are read-only and will be skipped. | |
| // We store all register values in data_sp by copying full PTrace data that | |
| // corresponds to register sets enabled by current register context. In order | |
| // to restore from register data checkpoint we will first restore GPRs, based | |
| // on size of remaining register data either SVE or FPRs should be restored | |
| // next. SVE is not enabled if we have register data size less than or equal | |
| // to size of GPR + FPR + MTE. | |
| Status error; | |
| if (!data_sp) { | |
| error = Status::FromErrorStringWithFormat( | |
| "NativeRegisterContextLinux_arm64::%s invalid data_sp provided", | |
| __FUNCTION__); | |
| return error; | |
| } | |
| const uint8_t *src = data_sp->GetBytes(); | |
| if (src == nullptr) { | |
| error = Status::FromErrorStringWithFormat( | |
| "NativeRegisterContextLinux_arm64::%s " | |
| "DataBuffer::GetBytes() returned a null " | |
| "pointer", | |
| __FUNCTION__); | |
| return error; | |
| } | |
| uint64_t reg_data_min_size = | |
| GetGPRBufferSize() + GetFPRSize() + 2 * (sizeof(RegisterSetType)); | |
| if (data_sp->GetByteSize() < reg_data_min_size) { | |
| error = Status::FromErrorStringWithFormat( | |
| "NativeRegisterContextLinux_arm64::%s data_sp contained insufficient " | |
| "register data bytes, expected at least %" PRIu64 ", actual %" PRIu64, | |
| __FUNCTION__, reg_data_min_size, data_sp->GetByteSize()); | |
| return error; | |
| } | |
| const uint8_t *end = src + data_sp->GetByteSize(); | |
| while (src < end) { | |
| RegisterSetType kind; | |
| std::memcpy(&kind, src, sizeof(kind)); | |
| src += sizeof(RegisterSetType); | |
| switch (kind) { | |
| case RegisterSetType::GPR: | |
| error = RestoreRegisters( | |
| GetGPRBuffer(), &src, GetGPRBufferSize(), kind, | |
| std::bind(&NativeRegisterContextLinux_arm64::WriteGPR, this)); | |
| break; | |
| case RegisterSetType::SVE: | |
| // Restore to the correct mode, streaming or not. | |
| std::memcpy(&m_sve_state, src, sizeof(m_sve_state)); | |
| src += sizeof(m_sve_state); | |
| // First write SVE header. We do not use RestoreRegisters because we do | |
| // not want src to be modified yet. | |
| ::memcpy(GetSetBuffer(RegisterSetType::SVE_HEADER), src, | |
| GetSetSize(RegisterSetType::SVE_HEADER)); | |
| if (!sve::vl_valid(m_sve_header.vl)) { | |
| Invalidate(RegisterSetType::SVE_HEADER); | |
| error = Status::FromErrorStringWithFormat( | |
| "NativeRegisterContextLinux_arm64::%s " | |
| "Invalid SVE header in data_sp", | |
| __FUNCTION__); | |
| return error; | |
| } | |
| MakeValid(RegisterSetType::SVE_HEADER); | |
| error = WriteSVEHeader(); | |
| if (error.Fail()) | |
| return error; | |
| // SVE header has been written configure SVE vector length if needed. | |
| // This could change ZA data too, but that will be restored again later | |
| // anyway. | |
| ConfigureRegisterContext(); | |
| // Write header and register data, incrementing src this time. | |
| error = RestoreRegisters( | |
| GetSetBuffer(RegisterSetType::SVE), &src, | |
| GetSetSize(RegisterSetType::SVE), kind, | |
| std::bind(&NativeRegisterContextLinux_arm64::WriteAllSVE, this)); | |
| break; | |
| case RegisterSetType::FPR: { | |
| Invalidate(RegisterSetType::SVE_HEADER); | |
| m_sve_state = SVEState::Unknown; | |
| ConfigureRegisterContext(); | |
| // If we are on an SME only system and currently in streaming mode, about | |
| // to restore non-streaming FP data. | |
| if (!GetRegisterInfo().IsSVEPresent() && | |
| GetRegisterInfo().IsSSVEPresent() && | |
| m_sve_state == SVEState::Streaming) { | |
| // We can only restore this data on kernel versions >= 6.19, so | |
| // attempt it and if it fails, we will skip restoring the data. | |
| // | |
| // To attempt the restore we write FPSIMD format data to NT_ARM_SVE, | |
| // with the vector length set to 0. If supported, this will switch | |
| // modes from streaming to non-streaming and update the FP registers | |
| // with the values we provided. | |
| // | |
| // This interface is only used by LLDB in this one specific | |
| // circumstance. | |
| size_t data_size = sve::ptrace_fpsimd_offset + GetFPRSize(); | |
| // NT_ARM_SVE data must be a multiple of 128 bits, and the FPU data size | |
| // is not, round up. | |
| data_size = | |
| (data_size + sve::vq_bytes - 1) / sve::vq_bytes * sve::vq_bytes; | |
| std::vector<uint8_t> sve_fpsimd_data(data_size); | |
| sve::user_sve_header *header = | |
| reinterpret_cast<sve::user_sve_header *>(sve_fpsimd_data.data()); | |
| std::memset(header, 0, sizeof(sve::user_sve_header)); | |
| header->size = sve_fpsimd_data.size(); | |
| // VL = 0 tells the process to exit streaming mode. | |
| header->vl = 0; | |
| header->flags = sve::ptrace_regs_fpsimd; | |
| std::memcpy(&sve_fpsimd_data[sve::ptrace_fpsimd_offset], src, | |
| GetFPRSize()); | |
| struct iovec ioVec; | |
| ioVec.iov_base = sve_fpsimd_data.data(); | |
| ioVec.iov_len = sve_fpsimd_data.size(); | |
| // Even though the system does not have SVE, NT_ARM_SVE is used when | |
| // exiting streaming mode. | |
| error = WriteRegisterSet(&ioVec, sve_fpsimd_data.size(), | |
| llvm::ELF::NT_ARM_SVE); | |
| // Consume FP register set. | |
| src += GetFPRSize(); | |
| if (error.Success()) { | |
| Invalidate(RegisterSetType::FPR); | |
| m_sve_state = SVEState::Unknown; | |
| ConfigureRegisterContext(); | |
| } | |
| // Else we failed to restore these registers, but we will try to restore | |
| // the others. | |
| } else { | |
| error = RestoreRegisters( | |
| GetFPRBuffer(), &src, GetFPRSize(), kind, | |
| std::bind(&NativeRegisterContextLinux_arm64::WriteFPR, this)); | |
| } | |
| break; | |
| } | |
| case RegisterSetType::MTE: | |
| error = RestoreRegisters( | |
| GetSetBuffer(RegisterSetType::MTE), &src, | |
| GetSetSize(RegisterSetType::MTE), kind, | |
| std::bind(&NativeRegisterContextLinux_arm64::WriteMTEControl, this)); | |
| break; | |
| case RegisterSetType::TLS: | |
| error = RestoreRegisters( | |
| GetSetBuffer(RegisterSetType::TLS), &src, | |
| GetSetSize(RegisterSetType::TLS), kind, | |
| std::bind(&NativeRegisterContextLinux_arm64::WriteTLS, this)); | |
| break; | |
| case RegisterSetType::ZA: | |
| // To enable or disable ZA you write the regset with or without register | |
| // data. The kernel detects this by looking at the ioVec's length, not the | |
| // ZA header size you pass in. Therefore we must write header and register | |
| // data (if present) in one go every time. Read the header only first just | |
| // to get the size. | |
| ::memcpy(GetSetBuffer(RegisterSetType::ZA_HEADER), src, | |
| GetSetSize(RegisterSetType::ZA_HEADER)); | |
| // Read the header and register data. Can't use the buffer size here, it | |
| // may be incorrect due to being filled with dummy data previously. Resize | |
| // this so WriteZA uses the correct size. | |
| m_za_ptrace_payload.resize(m_za_header.size); | |
| ::memcpy(GetSetBuffer(RegisterSetType::ZA), src, | |
| GetSetSize(RegisterSetType::ZA)); | |
| MakeValid(RegisterSetType::ZA); | |
| error = WriteZA(); | |
| if (error.Fail()) | |
| return error; | |
| // Update size of ZA, which resizes the ptrace payload potentially | |
| // trashing our copy of the data we just wrote. | |
| ConfigureRegisterContext(); | |
| // ZA buffer now has proper size, read back the data we wrote above, from | |
| // ptrace. | |
| error = ReadZA(); | |
| src += GetSetSize(RegisterSetType::ZA); | |
| break; | |
| case RegisterSetType::ZT: | |
| // Doing this would activate an inactive ZA, however we will only get here | |
| // if the state we are restoring had an active ZA. Restoring ZT0 will | |
| // always come after restoring ZA. | |
| error = RestoreRegisters( | |
| GetSetBuffer(RegisterSetType::ZT), &src, | |
| GetSetSize(RegisterSetType::ZT), kind, | |
| std::bind(&NativeRegisterContextLinux_arm64::WriteZT, this)); | |
| break; | |
| case RegisterSetType::FPMR: | |
| error = RestoreRegisters( | |
| GetSetBuffer(RegisterSetType::FPMR), &src, | |
| GetSetSize(RegisterSetType::FPMR), kind, | |
| std::bind(&NativeRegisterContextLinux_arm64::WriteFPMR, this)); | |
| break; | |
| case RegisterSetType::GCS: { | |
| // It is not permitted to enable GCS via ptrace. We can disable it, but | |
| // to keep things simple we will not revert any change to the | |
| // PR_SHADOW_STACK_ENABLE bit. Instead patch in the current enable bit | |
| // into the registers we are about to restore. | |
| Invalidate(RegisterSetType::GCS); | |
| error = ReadGCS(); | |
| if (error.Fail()) | |
| return error; | |
| uint64_t enable_bit = m_gcs_regs.features_enabled & 1UL; | |
| gcs_regs new_gcs_regs; | |
| std::memcpy(&new_gcs_regs, src, sizeof(new_gcs_regs)); | |
| new_gcs_regs.features_enabled = | |
| (new_gcs_regs.features_enabled & ~1UL) | enable_bit; | |
| const uint8_t *new_gcs_src = | |
| reinterpret_cast<const uint8_t *>(&new_gcs_regs); | |
| error = RestoreRegisters( | |
| GetSetBuffer(RegisterSetType::GCS), &new_gcs_src, | |
| GetSetSize(RegisterSetType::GCS), kind, | |
| std::bind(&NativeRegisterContextLinux_arm64::WriteGCS, this)); | |
| src += GetSetSize(RegisterSetType::GCS); | |
| break; | |
| } | |
| case RegisterSetType::POE: | |
| error = RestoreRegisters( | |
| GetSetBuffer(RegisterSetType::POE), &src, | |
| GetSetSize(RegisterSetType::POE), kind, | |
| std::bind(&NativeRegisterContextLinux_arm64::WritePOE, this)); | |
| break; | |
| case RegisterSetType::PAC: | |
| case RegisterSetType::SVE_HEADER: | |
| case RegisterSetType::ZA_HEADER: | |
| // These are not saved or restored. | |
| break; | |
| } | |
| if (error.Fail()) | |
| return error; | |
| } | |
| return error; | |
| } | |
| llvm::Error NativeRegisterContextLinux_arm64::ReadHardwareDebugInfo() { | |
| if (!m_refresh_hwdebug_info) { | |
| return llvm::Error::success(); | |
| } | |
| ::pid_t tid = m_thread.GetID(); | |
| Status error = arm64::ReadHardwareDebugInfo(tid, m_max_hwp_supported, | |
| m_max_hbp_supported); | |
| if (error.Fail()) | |
| return error.ToError(); | |
| m_refresh_hwdebug_info = false; | |
| return llvm::Error::success(); | |
| } | |
| llvm::Error | |
| NativeRegisterContextLinux_arm64::WriteHardwareDebugRegs(DREGType hwbType) { | |
| uint32_t max_supported = | |
| (hwbType == eDREGTypeWATCH) ? m_max_hwp_supported : m_max_hbp_supported; | |
| auto ®s = (hwbType == eDREGTypeWATCH) ? m_hwp_regs : m_hbp_regs; | |
| return arm64::WriteHardwareDebugRegs(hwbType, m_thread.GetID(), max_supported, | |
| regs) | |
| .ToError(); | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadGPR() { | |
| Status error; | |
| if (IsValid(RegisterSetType::GPR)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetGPRBuffer(); | |
| ioVec.iov_len = GetGPRBufferSize(); | |
| error = ReadRegisterSet(&ioVec, GetGPRBufferSize(), | |
| GetPtraceSet(RegisterSetType::GPR)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::GPR); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteGPR() { | |
| Status error = ReadGPR(); | |
| if (error.Fail()) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetGPRBuffer(); | |
| ioVec.iov_len = GetGPRBufferSize(); | |
| Invalidate(RegisterSetType::GPR); | |
| return WriteRegisterSet(&ioVec, GetGPRBufferSize(), | |
| GetPtraceSet(RegisterSetType::GPR)); | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadFPR() { | |
| Status error; | |
| if (IsValid(RegisterSetType::FPR)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetFPRBuffer(); | |
| ioVec.iov_len = GetFPRSize(); | |
| error = | |
| ReadRegisterSet(&ioVec, GetFPRSize(), GetPtraceSet(RegisterSetType::FPR)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::FPR); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteFPR() { | |
| Status error = ReadFPR(); | |
| if (error.Fail()) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetFPRBuffer(); | |
| ioVec.iov_len = GetFPRSize(); | |
| Invalidate(RegisterSetType::FPR); | |
| return WriteRegisterSet(&ioVec, GetFPRSize(), | |
| GetPtraceSet(RegisterSetType::FPR)); | |
| } | |
| void NativeRegisterContextLinux_arm64::InvalidateAllRegisters() { | |
| m_validity = static_cast<RegisterSetType>(0); | |
| // Update SVE and ZA registers in case there is change in configuration. | |
| ConfigureRegisterContext(); | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadSVEHeader() { | |
| Status error; | |
| if (IsValid(RegisterSetType::SVE_HEADER)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::SVE_HEADER); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::SVE_HEADER); | |
| error = ReadRegisterSet(&ioVec, GetSetSize(RegisterSetType::SVE_HEADER), | |
| GetPtraceSet(RegisterSetType::SVE_HEADER)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::SVE_HEADER); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadPAuthMask() { | |
| Status error; | |
| if (IsValid(RegisterSetType::PAC)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::PAC); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::PAC); | |
| error = ReadRegisterSet(&ioVec, GetSetSize(RegisterSetType::PAC), | |
| GetPtraceSet(RegisterSetType::PAC)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::PAC); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteSVEHeader() { | |
| Status error; | |
| error = ReadSVEHeader(); | |
| if (error.Fail()) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::SVE_HEADER); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::SVE_HEADER); | |
| Invalidate(RegisterSetType::SVE_HEADER); | |
| return WriteRegisterSet(&ioVec, GetSetSize(RegisterSetType::SVE_HEADER), | |
| GetPtraceSet(RegisterSetType::SVE_HEADER)); | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadAllSVE() { | |
| Status error; | |
| if (IsValid(RegisterSetType::SVE)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::SVE); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::SVE); | |
| error = ReadRegisterSet(&ioVec, GetSetSize(RegisterSetType::SVE), | |
| GetPtraceSet(RegisterSetType::SVE)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::SVE); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteAllSVE() { | |
| Status error; | |
| error = ReadAllSVE(); | |
| if (error.Fail()) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::SVE); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::SVE); | |
| Invalidate(RegisterSetType::SVE); | |
| return WriteRegisterSet(&ioVec, GetSetSize(RegisterSetType::SVE), | |
| GetPtraceSet(RegisterSetType::SVE)); | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadSMEControl() { | |
| // The real register is SVCR and is accessible from EL0. However we don't want | |
| // to have to JIT code into the target process so we'll just recreate it using | |
| // what we know from ptrace. | |
| // Bit 0 indicates whether streaming mode is active. | |
| m_sme_pseudo_regs.ctrl_reg = m_sve_state == SVEState::Streaming; | |
| // Bit 1 indicates whether the array storage is active. | |
| // It is active if we can read the header and the size field tells us that | |
| // there is register data following it. | |
| Status error = ReadZAHeader(); | |
| if (error.Success() && (m_za_header.size > sizeof(m_za_header))) | |
| m_sme_pseudo_regs.ctrl_reg |= 2; | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadMTEControl() { | |
| Status error; | |
| if (IsValid(RegisterSetType::MTE)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::MTE); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::MTE); | |
| error = ReadRegisterSet(&ioVec, GetSetSize(RegisterSetType::MTE), | |
| GetPtraceSet(RegisterSetType::MTE)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::MTE); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteMTEControl() { | |
| Status error; | |
| error = ReadMTEControl(); | |
| if (error.Fail()) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::MTE); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::MTE); | |
| Invalidate(RegisterSetType::MTE); | |
| return WriteRegisterSet(&ioVec, GetSetSize(RegisterSetType::MTE), | |
| GetPtraceSet(RegisterSetType::MTE)); | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadTLS() { | |
| Status error; | |
| if (IsValid(RegisterSetType::TLS)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::TLS); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::TLS); | |
| error = ReadRegisterSet(&ioVec, GetSetSize(RegisterSetType::TLS), | |
| GetPtraceSet(RegisterSetType::TLS)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::TLS); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteTLS() { | |
| Status error; | |
| error = ReadTLS(); | |
| if (error.Fail()) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::TLS); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::TLS); | |
| Invalidate(RegisterSetType::TLS); | |
| return WriteRegisterSet(&ioVec, GetSetSize(RegisterSetType::TLS), | |
| GetPtraceSet(RegisterSetType::TLS)); | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadGCS() { | |
| Status error; | |
| if (IsValid(RegisterSetType::GCS)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::GCS); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::GCS); | |
| error = ReadRegisterSet(&ioVec, GetSetSize(RegisterSetType::GCS), | |
| GetPtraceSet(RegisterSetType::GCS)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::GCS); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteGCS() { | |
| Status error; | |
| error = ReadGCS(); | |
| if (error.Fail()) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::GCS); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::GCS); | |
| Invalidate(RegisterSetType::GCS); | |
| return WriteRegisterSet(&ioVec, GetSetSize(RegisterSetType::GCS), | |
| GetPtraceSet(RegisterSetType::GCS)); | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadZAHeader() { | |
| Status error; | |
| if (IsValid(RegisterSetType::ZA_HEADER)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::ZA_HEADER); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::ZA_HEADER); | |
| error = ReadRegisterSet(&ioVec, GetSetSize(RegisterSetType::ZA_HEADER), | |
| GetPtraceSet(RegisterSetType::ZA)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::ZA_HEADER); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadZA() { | |
| Status error; | |
| if (IsValid(RegisterSetType::ZA)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::ZA); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::ZA); | |
| error = ReadRegisterSet(&ioVec, GetSetSize(RegisterSetType::ZA), | |
| GetPtraceSet(RegisterSetType::ZA)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::ZA); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteZA() { | |
| // Note that because the ZA ptrace payload contains the header also, this | |
| // method will write both. This is done because writing only the header | |
| // will disable ZA, even if .size in the header is correct for an enabled ZA. | |
| Status error; | |
| error = ReadZA(); | |
| if (error.Fail()) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::ZA); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::ZA); | |
| Invalidate(RegisterSetType::ZA); | |
| return WriteRegisterSet(&ioVec, GetSetSize(RegisterSetType::ZA), | |
| GetPtraceSet(RegisterSetType::ZA)); | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadZT() { | |
| Status error; | |
| if (IsValid(RegisterSetType::ZT)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::ZT); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::ZT); | |
| error = ReadRegisterSet(&ioVec, GetSetSize(RegisterSetType::ZT), | |
| GetPtraceSet(RegisterSetType::ZT)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::ZT); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteZT() { | |
| Status error; | |
| error = ReadZT(); | |
| if (error.Fail()) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::ZT); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::ZT); | |
| Invalidate(RegisterSetType::ZT); | |
| return WriteRegisterSet(&ioVec, GetSetSize(RegisterSetType::ZT), | |
| GetPtraceSet(RegisterSetType::ZT)); | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadFPMR() { | |
| Status error; | |
| if (IsValid(RegisterSetType::FPMR)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::FPMR); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::FPMR); | |
| error = ReadRegisterSet(&ioVec, GetSetSize(RegisterSetType::FPMR), | |
| GetPtraceSet(RegisterSetType::FPMR)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::FPMR); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WriteFPMR() { | |
| Status error; | |
| error = ReadFPMR(); | |
| if (error.Fail()) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::FPMR); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::FPMR); | |
| Invalidate(RegisterSetType::FPMR); | |
| return WriteRegisterSet(&ioVec, GetSetSize(RegisterSetType::FPMR), | |
| GetPtraceSet(RegisterSetType::FPMR)); | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadPOE() { | |
| Status error; | |
| if (IsValid(RegisterSetType::POE)) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::POE); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::POE); | |
| error = ReadRegisterSet(&ioVec, GetSetSize(RegisterSetType::POE), | |
| GetPtraceSet(RegisterSetType::POE)); | |
| if (error.Success()) | |
| MakeValid(RegisterSetType::POE); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_arm64::WritePOE() { | |
| Status error; | |
| error = ReadPOE(); | |
| if (error.Fail()) | |
| return error; | |
| struct iovec ioVec; | |
| ioVec.iov_base = GetSetBuffer(RegisterSetType::POE); | |
| ioVec.iov_len = GetSetSize(RegisterSetType::POE); | |
| Invalidate(RegisterSetType::POE); | |
| return WriteRegisterSet(&ioVec, GetSetSize(RegisterSetType::POE), | |
| GetPtraceSet(RegisterSetType::POE)); | |
| } | |
| void NativeRegisterContextLinux_arm64::ConfigureRegisterContext() { | |
| // ConfigureRegisterContext gets called from InvalidateAllRegisters | |
| // on every stop and configures SVE vector length and whether we are in | |
| // streaming SVE mode. | |
| // If m_sve_state is set to SVEState::Disabled on first stop, code below will | |
| // be deemed non operational for the lifetime of current process. | |
| if (!IsValid(RegisterSetType::SVE_HEADER) && | |
| m_sve_state != SVEState::Disabled) { | |
| // Systems may have SVE and/or SME. If they are SME only, the SVE regset | |
| // cannot be read from but the SME one can. If they have both SVE and SME, | |
| // only the active mode will return valid register data. | |
| // Check for SME. | |
| Invalidate(RegisterSetType::SVE_HEADER); | |
| m_sve_state = SVEState::Streaming; | |
| Status error = ReadSVEHeader(); | |
| bool has_sme = error.Success(); | |
| bool sme_is_active = | |
| has_sme && | |
| ((m_sve_header.flags & sve::ptrace_regs_mask) == sve::ptrace_regs_sve); | |
| // Check for SVE. | |
| Invalidate(RegisterSetType::SVE_HEADER); | |
| m_sve_state = SVEState::Full; | |
| error = ReadSVEHeader(); | |
| bool has_sve = error.Success(); | |
| bool sve_is_active = | |
| has_sve && | |
| ((m_sve_header.flags & sve::ptrace_regs_mask) == sve::ptrace_regs_sve); | |
| // We do not check this for streaming mode because the streaming mode regset | |
| // will never be in FP format. | |
| bool fp_is_active = | |
| has_sve && ((m_sve_header.flags & sve::ptrace_regs_mask) == | |
| sve::ptrace_regs_fpsimd); | |
| if (sme_is_active) | |
| m_sve_state = SVEState::Streaming; | |
| else if (sve_is_active) | |
| m_sve_state = SVEState::Full; | |
| else if (fp_is_active) | |
| m_sve_state = SVEState::FPSIMD; | |
| else if (has_sme) { | |
| // We are in the non-streaming mode of an SME only system. | |
| m_sve_state = SVEState::StreamingFPSIMD; | |
| } else | |
| m_sve_state = SVEState::Disabled; | |
| if (m_sve_state == SVEState::Full || m_sve_state == SVEState::FPSIMD || | |
| m_sve_state == SVEState::Streaming || | |
| m_sve_state == SVEState::StreamingFPSIMD) { | |
| Invalidate(RegisterSetType::SVE_HEADER); | |
| error = ReadSVEHeader(); | |
| // On every stop we configure SVE vector length by calling | |
| // ConfigureVectorLengthSVE regardless of current SVEState of this thread. | |
| uint32_t vq = RegisterInfoPOSIX_arm64::eVectorQuadwordAArch64SVE; | |
| if (sve::vl_valid(m_sve_header.vl)) | |
| vq = sve::vq_from_vl(m_sve_header.vl); | |
| GetRegisterInfo().ConfigureVectorLengthSVE(vq); | |
| m_sve_ptrace_payload.resize(sve::PTraceSize(vq, sve::ptrace_regs_sve)); | |
| } | |
| } | |
| if (!IsValid(RegisterSetType::ZA_HEADER)) { | |
| Status error = ReadZAHeader(); | |
| if (error.Success()) { | |
| uint32_t vq = RegisterInfoPOSIX_arm64::eVectorQuadwordAArch64SVE; | |
| if (sve::vl_valid(m_za_header.vl)) | |
| vq = sve::vq_from_vl(m_za_header.vl); | |
| GetRegisterInfo().ConfigureVectorLengthZA(vq); | |
| m_za_ptrace_payload.resize(m_za_header.size); | |
| Invalidate(RegisterSetType::ZA); | |
| } | |
| } | |
| } | |
| uint32_t NativeRegisterContextLinux_arm64::CalculateFprOffset( | |
| const RegisterInfo *reg_info, bool streaming_fpsimd) const { | |
| uint32_t offset = reg_info->byte_offset - GetGPRSize(); | |
| if (!streaming_fpsimd) | |
| return offset; | |
| // If we're outside of streaming mode on a streaming only target, the offsets | |
| // are relative to an SVE context. We need the offset into the actual FPR | |
| // context: | |
| // struct user_fpsimd_state { | |
| // __uint128_t vregs[32]; | |
| // __u32 fpsr; | |
| // __u32 fpcr; | |
| // __u32 __reserved[2]; | |
| // }; | |
| const size_t fpsr_offset = 16 * 32; | |
| const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB]; | |
| if (reg == GetRegisterInfo().GetRegNumFPSR()) | |
| offset = fpsr_offset; | |
| else if (reg == GetRegisterInfo().GetRegNumFPCR()) | |
| offset = fpsr_offset + 4; | |
| else | |
| offset = 16 * (reg - GetRegisterInfo().GetRegNumFPV0()); | |
| return offset; | |
| } | |
| uint32_t NativeRegisterContextLinux_arm64::CalculateSVEOffset( | |
| const RegisterInfo *reg_info) const { | |
| // Start of Z0 data is after GPRs plus 8 bytes of vg register | |
| uint32_t sve_reg_offset = LLDB_INVALID_INDEX32; | |
| if (m_sve_state == SVEState::FPSIMD) { | |
| const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB]; | |
| sve_reg_offset = sve::ptrace_fpsimd_offset + | |
| (reg - GetRegisterInfo().GetRegNumSVEZ0()) * 16; | |
| // Between non-streaming and streaming mode, the layout is identical. | |
| } else if (m_sve_state == SVEState::Full || | |
| m_sve_state == SVEState::Streaming) { | |
| uint32_t sve_z0_offset = GetGPRSize() + 16; | |
| sve_reg_offset = | |
| sve::SigRegsOffset() + reg_info->byte_offset - sve_z0_offset; | |
| } | |
| return sve_reg_offset; | |
| } | |
| Status NativeRegisterContextLinux_arm64::ReadSMESVG() { | |
| // This register is the streaming vector length, so we will get it from | |
| // NT_ARM_ZA regardless of the current streaming mode. | |
| Status error = ReadZAHeader(); | |
| if (error.Success()) | |
| m_sme_pseudo_regs.svg_reg = m_za_header.vl / 8; | |
| return error; | |
| } | |
| std::vector<uint32_t> NativeRegisterContextLinux_arm64::GetExpeditedRegisters( | |
| ExpeditedRegs expType) const { | |
| std::vector<uint32_t> expedited_reg_nums = | |
| NativeRegisterContext::GetExpeditedRegisters(expType); | |
| // SVE, non-streaming vector length. | |
| if (m_sve_state == SVEState::FPSIMD || m_sve_state == SVEState::Full) | |
| expedited_reg_nums.push_back(GetRegisterInfo().GetRegNumSVEVG()); | |
| // SME, streaming vector length. This is used by the ZA register which is | |
| // present even when streaming mode is not enabled. | |
| if (GetRegisterInfo().IsSSVEPresent()) | |
| expedited_reg_nums.push_back(GetRegisterInfo().GetRegNumSMESVG()); | |
| return expedited_reg_nums; | |
| } | |
| llvm::Expected<NativeRegisterContextLinux::MemoryTaggingDetails> | |
| NativeRegisterContextLinux_arm64::GetMemoryTaggingDetails(int32_t type) { | |
| if (type == MemoryTagManagerAArch64MTE::eMTE_allocation) { | |
| return MemoryTaggingDetails{std::make_unique<MemoryTagManagerAArch64MTE>(), | |
| PTRACE_PEEKMTETAGS, PTRACE_POKEMTETAGS}; | |
| } | |
| return llvm::createStringError(llvm::inconvertibleErrorCode(), | |
| "Unknown AArch64 memory tag type %d", type); | |
| } | |
| lldb::addr_t NativeRegisterContextLinux_arm64::FixWatchpointHitAddress( | |
| lldb::addr_t hit_addr) { | |
| // Linux configures user-space virtual addresses with top byte ignored. | |
| // We set default value of mask such that top byte is masked out. | |
| lldb::addr_t mask = ~((1ULL << 56) - 1); | |
| // Try to read pointer authentication data_mask register and calculate a | |
| // consolidated data address mask after ignoring the top byte. | |
| if (ReadPAuthMask().Success()) | |
| mask |= m_pac_mask.data_mask; | |
| return hit_addr & ~mask; | |
| ; | |
| } | |
| #endif // defined (__arm64__) || defined (__aarch64__) |