| //===-- NativeRegisterContextLinux_mips64.cpp ---------------*- C++ -*-===// | |
| // | |
| // 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(__mips__) | |
| #include "NativeRegisterContextLinux_mips64.h" | |
| #include "Plugins/Process/Linux/NativeProcessLinux.h" | |
| #include "Plugins/Process/Linux/Procfs.h" | |
| #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" | |
| #include "Plugins/Process/Utility/RegisterContextLinux_mips.h" | |
| #include "Plugins/Process/Utility/RegisterContextLinux_mips64.h" | |
| #include "lldb/Core/EmulateInstruction.h" | |
| #include "lldb/Host/Host.h" | |
| #include "lldb/Host/HostInfo.h" | |
| #include "lldb/Utility/DataBufferHeap.h" | |
| #include "lldb/Utility/LLDBAssert.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/RegisterValue.h" | |
| #include "lldb/Utility/Status.h" | |
| #include "lldb/lldb-enumerations.h" | |
| #include "lldb/lldb-private-enumerations.h" | |
| #define NT_MIPS_MSA 0x600 | |
| #define CONFIG5_FRE (1 << 8) | |
| #define SR_FR (1 << 26) | |
| #define NUM_REGISTERS 32 | |
| #include <asm/ptrace.h> | |
| #include <sys/ptrace.h> | |
| #ifndef PTRACE_GET_WATCH_REGS | |
| enum pt_watch_style { pt_watch_style_mips32, pt_watch_style_mips64 }; | |
| struct mips32_watch_regs { | |
| uint32_t watchlo[8]; | |
| uint16_t watchhi[8]; | |
| uint16_t watch_masks[8]; | |
| uint32_t num_valid; | |
| } __attribute__((aligned(8))); | |
| struct mips64_watch_regs { | |
| uint64_t watchlo[8]; | |
| uint16_t watchhi[8]; | |
| uint16_t watch_masks[8]; | |
| uint32_t num_valid; | |
| } __attribute__((aligned(8))); | |
| struct pt_watch_regs { | |
| enum pt_watch_style style; | |
| union { | |
| struct mips32_watch_regs mips32; | |
| struct mips64_watch_regs mips64; | |
| }; | |
| }; | |
| #define PTRACE_GET_WATCH_REGS 0xd0 | |
| #define PTRACE_SET_WATCH_REGS 0xd1 | |
| #endif | |
| #define W (1 << 0) | |
| #define R (1 << 1) | |
| #define I (1 << 2) | |
| #define IRW (I | R | W) | |
| #ifndef PTRACE_GETREGSET | |
| #define PTRACE_GETREGSET 0x4204 | |
| #endif | |
| struct pt_watch_regs default_watch_regs; | |
| using namespace lldb_private; | |
| using namespace lldb_private::process_linux; | |
| std::unique_ptr<NativeRegisterContextLinux> | |
| NativeRegisterContextLinux::CreateHostNativeRegisterContextLinux( | |
| const ArchSpec &target_arch, NativeThreadProtocol &native_thread) { | |
| return std::make_unique<NativeRegisterContextLinux_mips64>(target_arch, | |
| native_thread); | |
| } | |
| #define REG_CONTEXT_SIZE \ | |
| (GetRegisterInfoInterface().GetGPRSize() + sizeof(FPR_linux_mips) + \ | |
| sizeof(MSA_linux_mips)) | |
| // NativeRegisterContextLinux_mips64 members. | |
| static RegisterInfoInterface * | |
| CreateRegisterInfoInterface(const ArchSpec &target_arch) { | |
| if ((target_arch.GetMachine() == llvm::Triple::mips) || | |
| (target_arch.GetMachine() == llvm::Triple::mipsel)) { | |
| // 32-bit hosts run with a RegisterContextLinux_mips context. | |
| return new RegisterContextLinux_mips( | |
| target_arch, NativeRegisterContextLinux_mips64::IsMSAAvailable()); | |
| } else { | |
| return new RegisterContextLinux_mips64( | |
| target_arch, NativeRegisterContextLinux_mips64::IsMSAAvailable()); | |
| } | |
| } | |
| NativeRegisterContextLinux_mips64::NativeRegisterContextLinux_mips64( | |
| const ArchSpec &target_arch, NativeThreadProtocol &native_thread) | |
| : NativeRegisterContextLinux(native_thread, | |
| CreateRegisterInfoInterface(target_arch)) { | |
| switch (target_arch.GetMachine()) { | |
| case llvm::Triple::mips: | |
| case llvm::Triple::mipsel: | |
| m_reg_info.num_registers = k_num_registers_mips; | |
| m_reg_info.num_gpr_registers = k_num_gpr_registers_mips; | |
| m_reg_info.num_fpr_registers = k_num_fpr_registers_mips; | |
| m_reg_info.last_gpr = k_last_gpr_mips; | |
| m_reg_info.first_fpr = k_first_fpr_mips; | |
| m_reg_info.last_fpr = k_last_fpr_mips; | |
| m_reg_info.first_msa = k_first_msa_mips; | |
| m_reg_info.last_msa = k_last_msa_mips; | |
| break; | |
| case llvm::Triple::mips64: | |
| case llvm::Triple::mips64el: | |
| m_reg_info.num_registers = k_num_registers_mips64; | |
| m_reg_info.num_gpr_registers = k_num_gpr_registers_mips64; | |
| m_reg_info.num_fpr_registers = k_num_fpr_registers_mips64; | |
| m_reg_info.last_gpr = k_last_gpr_mips64; | |
| m_reg_info.first_fpr = k_first_fpr_mips64; | |
| m_reg_info.last_fpr = k_last_fpr_mips64; | |
| m_reg_info.first_msa = k_first_msa_mips64; | |
| m_reg_info.last_msa = k_last_msa_mips64; | |
| break; | |
| default: | |
| assert(false && "Unhandled target architecture."); | |
| break; | |
| } | |
| // Initialize m_iovec to point to the buffer and buffer size using the | |
| // conventions of Berkeley style UIO structures, as required by PTRACE | |
| // extensions. | |
| m_iovec.iov_base = &m_msa; | |
| m_iovec.iov_len = sizeof(MSA_linux_mips); | |
| // init h/w watchpoint addr map | |
| for (int index = 0; index <= MAX_NUM_WP; index++) | |
| hw_addr_map[index] = LLDB_INVALID_ADDRESS; | |
| ::memset(&m_gpr, 0, sizeof(GPR_linux_mips)); | |
| ::memset(&m_fpr, 0, sizeof(FPR_linux_mips)); | |
| ::memset(&m_msa, 0, sizeof(MSA_linux_mips)); | |
| } | |
| uint32_t NativeRegisterContextLinux_mips64::GetRegisterSetCount() const { | |
| switch (GetRegisterInfoInterface().GetTargetArchitecture().GetMachine()) { | |
| case llvm::Triple::mips64: | |
| case llvm::Triple::mips64el: { | |
| const auto context = static_cast<const RegisterContextLinux_mips64 &> | |
| (GetRegisterInfoInterface()); | |
| return context.GetRegisterSetCount(); | |
| } | |
| case llvm::Triple::mips: | |
| case llvm::Triple::mipsel: { | |
| const auto context = static_cast<const RegisterContextLinux_mips &> | |
| (GetRegisterInfoInterface()); | |
| return context.GetRegisterSetCount(); | |
| } | |
| default: | |
| llvm_unreachable("Unhandled target architecture."); | |
| } | |
| } | |
| lldb::addr_t NativeRegisterContextLinux_mips64::GetPCfromBreakpointLocation( | |
| lldb::addr_t fail_value) { | |
| Status error; | |
| RegisterValue pc_value; | |
| lldb::addr_t pc = fail_value; | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_BREAKPOINTS)); | |
| LLDB_LOG(log, "Reading PC from breakpoint location"); | |
| // PC register is at index 34 of the register array | |
| const RegisterInfo *const pc_info_p = GetRegisterInfoAtIndex(gpr_pc_mips64); | |
| error = ReadRegister(pc_info_p, pc_value); | |
| if (error.Success()) { | |
| pc = pc_value.GetAsUInt64(); | |
| // CAUSE register is at index 37 of the register array | |
| const RegisterInfo *const cause_info_p = | |
| GetRegisterInfoAtIndex(gpr_cause_mips64); | |
| RegisterValue cause_value; | |
| ReadRegister(cause_info_p, cause_value); | |
| uint64_t cause = cause_value.GetAsUInt64(); | |
| LLDB_LOG(log, "PC {0:x} cause {1:x}", pc, cause); | |
| /* | |
| * The breakpoint might be in a delay slot. In this case PC points | |
| * to the delayed branch instruction rather then the instruction | |
| * in the delay slot. If the CAUSE.BD flag is set then adjust the | |
| * PC based on the size of the branch instruction. | |
| */ | |
| if ((cause & (1 << 31)) != 0) { | |
| lldb::addr_t branch_delay = 0; | |
| branch_delay = | |
| 4; // FIXME - Adjust according to size of branch instruction at PC | |
| pc = pc + branch_delay; | |
| pc_value.SetUInt64(pc); | |
| WriteRegister(pc_info_p, pc_value); | |
| LLDB_LOG(log, "New PC {0:x}", pc); | |
| } | |
| } | |
| return pc; | |
| } | |
| const RegisterSet * | |
| NativeRegisterContextLinux_mips64::GetRegisterSet(uint32_t set_index) const { | |
| if (set_index >= GetRegisterSetCount()) | |
| return nullptr; | |
| switch (GetRegisterInfoInterface().GetTargetArchitecture().GetMachine()) { | |
| case llvm::Triple::mips64: | |
| case llvm::Triple::mips64el: { | |
| const auto context = static_cast<const RegisterContextLinux_mips64 &> | |
| (GetRegisterInfoInterface()); | |
| return context.GetRegisterSet(set_index); | |
| } | |
| case llvm::Triple::mips: | |
| case llvm::Triple::mipsel: { | |
| const auto context = static_cast<const RegisterContextLinux_mips &> | |
| (GetRegisterInfoInterface()); | |
| return context.GetRegisterSet(set_index); | |
| } | |
| default: | |
| llvm_unreachable("Unhandled target architecture."); | |
| } | |
| } | |
| lldb_private::Status | |
| NativeRegisterContextLinux_mips64::ReadRegister(const RegisterInfo *reg_info, | |
| RegisterValue ®_value) { | |
| Status error; | |
| if (!reg_info) { | |
| error.SetErrorString("reg_info NULL"); | |
| return error; | |
| } | |
| const uint32_t reg = reg_info->kinds[lldb::eRegisterKindLLDB]; | |
| uint8_t byte_size = reg_info->byte_size; | |
| if (reg == LLDB_INVALID_REGNUM) { | |
| // This is likely an internal register for lldb use only and should not be | |
| // directly queried. | |
| error.SetErrorStringWithFormat("register \"%s\" is an internal-only lldb " | |
| "register, cannot read directly", | |
| reg_info->name); | |
| return error; | |
| } | |
| if (IsMSA(reg) && !IsMSAAvailable()) { | |
| error.SetErrorString("MSA not available on this processor"); | |
| return error; | |
| } | |
| if (IsMSA(reg) || IsFPR(reg)) { | |
| uint8_t *src = nullptr; | |
| lldbassert(reg_info->byte_offset < sizeof(UserArea)); | |
| error = ReadCP1(); | |
| if (!error.Success()) { | |
| error.SetErrorString("failed to read co-processor 1 register"); | |
| return error; | |
| } | |
| if (IsFPR(reg)) { | |
| if (IsFR0() && (byte_size != 4)) { | |
| byte_size = 4; | |
| uint8_t ptrace_index; | |
| ptrace_index = reg_info->kinds[lldb::eRegisterKindProcessPlugin]; | |
| src = ReturnFPOffset(ptrace_index, reg_info->byte_offset); | |
| } else | |
| src = (uint8_t *)&m_fpr + reg_info->byte_offset - sizeof(m_gpr); | |
| } else | |
| src = (uint8_t *)&m_msa + reg_info->byte_offset - | |
| (sizeof(m_gpr) + sizeof(m_fpr)); | |
| switch (byte_size) { | |
| case 4: | |
| reg_value.SetUInt32(*(uint32_t *)src); | |
| break; | |
| case 8: | |
| reg_value.SetUInt64(*(uint64_t *)src); | |
| break; | |
| case 16: | |
| reg_value.SetBytes((const void *)src, 16, GetByteOrder()); | |
| break; | |
| default: | |
| assert(false && "Unhandled data size."); | |
| error.SetErrorStringWithFormat("unhandled byte size: %" PRIu32, | |
| reg_info->byte_size); | |
| break; | |
| } | |
| } else { | |
| error = ReadRegisterRaw(reg, reg_value); | |
| } | |
| return error; | |
| } | |
| lldb_private::Status NativeRegisterContextLinux_mips64::WriteRegister( | |
| const RegisterInfo *reg_info, const RegisterValue ®_value) { | |
| Status error; | |
| assert(reg_info && "reg_info is null"); | |
| const uint32_t reg_index = reg_info->kinds[lldb::eRegisterKindLLDB]; | |
| if (reg_index == LLDB_INVALID_REGNUM) | |
| return Status("no lldb regnum for %s", reg_info && reg_info->name | |
| ? reg_info->name | |
| : "<unknown register>"); | |
| if (IsMSA(reg_index) && !IsMSAAvailable()) { | |
| error.SetErrorString("MSA not available on this processor"); | |
| return error; | |
| } | |
| if (IsFPR(reg_index) || IsMSA(reg_index)) { | |
| uint8_t *dst = nullptr; | |
| uint64_t *src = nullptr; | |
| uint8_t byte_size = reg_info->byte_size; | |
| lldbassert(reg_info->byte_offset < sizeof(UserArea)); | |
| // Initialise the FP and MSA buffers by reading all co-processor 1 | |
| // registers | |
| ReadCP1(); | |
| if (IsFPR(reg_index)) { | |
| if (IsFR0() && (byte_size != 4)) { | |
| byte_size = 4; | |
| uint8_t ptrace_index; | |
| ptrace_index = reg_info->kinds[lldb::eRegisterKindProcessPlugin]; | |
| dst = ReturnFPOffset(ptrace_index, reg_info->byte_offset); | |
| } else | |
| dst = (uint8_t *)&m_fpr + reg_info->byte_offset - sizeof(m_gpr); | |
| } else | |
| dst = (uint8_t *)&m_msa + reg_info->byte_offset - | |
| (sizeof(m_gpr) + sizeof(m_fpr)); | |
| switch (byte_size) { | |
| case 4: | |
| *(uint32_t *)dst = reg_value.GetAsUInt32(); | |
| break; | |
| case 8: | |
| *(uint64_t *)dst = reg_value.GetAsUInt64(); | |
| break; | |
| case 16: | |
| src = (uint64_t *)reg_value.GetBytes(); | |
| *(uint64_t *)dst = *src; | |
| *(uint64_t *)(dst + 8) = *(src + 1); | |
| break; | |
| default: | |
| assert(false && "Unhandled data size."); | |
| error.SetErrorStringWithFormat("unhandled byte size: %" PRIu32, | |
| reg_info->byte_size); | |
| break; | |
| } | |
| error = WriteCP1(); | |
| if (!error.Success()) { | |
| error.SetErrorString("failed to write co-processor 1 register"); | |
| return error; | |
| } | |
| } else { | |
| error = WriteRegisterRaw(reg_index, reg_value); | |
| } | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_mips64::ReadAllRegisterValues( | |
| lldb::DataBufferSP &data_sp) { | |
| Status error; | |
| data_sp.reset(new DataBufferHeap(REG_CONTEXT_SIZE, 0)); | |
| error = ReadGPR(); | |
| if (!error.Success()) { | |
| error.SetErrorString("ReadGPR() failed"); | |
| return error; | |
| } | |
| error = ReadCP1(); | |
| if (!error.Success()) { | |
| error.SetErrorString("ReadCP1() failed"); | |
| return error; | |
| } | |
| uint8_t *dst = data_sp->GetBytes(); | |
| ::memcpy(dst, &m_gpr, GetRegisterInfoInterface().GetGPRSize()); | |
| dst += GetRegisterInfoInterface().GetGPRSize(); | |
| ::memcpy(dst, &m_fpr, GetFPRSize()); | |
| dst += GetFPRSize(); | |
| ::memcpy(dst, &m_msa, sizeof(MSA_linux_mips)); | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_mips64::WriteAllRegisterValues( | |
| const lldb::DataBufferSP &data_sp) { | |
| Status error; | |
| if (!data_sp) { | |
| error.SetErrorStringWithFormat( | |
| "NativeRegisterContextLinux_mips64::%s invalid data_sp provided", | |
| __FUNCTION__); | |
| return error; | |
| } | |
| if (data_sp->GetByteSize() != REG_CONTEXT_SIZE) { | |
| error.SetErrorStringWithFormat( | |
| "NativeRegisterContextLinux_mips64::%s data_sp contained mismatched " | |
| "data size, expected %" PRIu64 ", actual %" PRIu64, | |
| __FUNCTION__, REG_CONTEXT_SIZE, data_sp->GetByteSize()); | |
| return error; | |
| } | |
| uint8_t *src = data_sp->GetBytes(); | |
| if (src == nullptr) { | |
| error.SetErrorStringWithFormat("NativeRegisterContextLinux_mips64::%s " | |
| "DataBuffer::GetBytes() returned a null " | |
| "pointer", | |
| __FUNCTION__); | |
| return error; | |
| } | |
| ::memcpy(&m_gpr, src, GetRegisterInfoInterface().GetGPRSize()); | |
| src += GetRegisterInfoInterface().GetGPRSize(); | |
| ::memcpy(&m_fpr, src, GetFPRSize()); | |
| src += GetFPRSize(); | |
| ::memcpy(&m_msa, src, sizeof(MSA_linux_mips)); | |
| error = WriteGPR(); | |
| if (!error.Success()) { | |
| error.SetErrorStringWithFormat( | |
| "NativeRegisterContextLinux_mips64::%s WriteGPR() failed", | |
| __FUNCTION__); | |
| return error; | |
| } | |
| error = WriteCP1(); | |
| if (!error.Success()) { | |
| error.SetErrorStringWithFormat( | |
| "NativeRegisterContextLinux_mips64::%s WriteCP1() failed", | |
| __FUNCTION__); | |
| return error; | |
| } | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_mips64::ReadCP1() { | |
| Status error; | |
| uint8_t *src = nullptr; | |
| uint8_t *dst = nullptr; | |
| lldb::ByteOrder byte_order = GetByteOrder(); | |
| bool IsBigEndian = (byte_order == lldb::eByteOrderBig); | |
| if (IsMSAAvailable()) { | |
| error = NativeRegisterContextLinux::ReadRegisterSet( | |
| &m_iovec, sizeof(MSA_linux_mips), NT_MIPS_MSA); | |
| src = (uint8_t *)&m_msa + (IsBigEndian * 8); | |
| dst = (uint8_t *)&m_fpr; | |
| for (int i = 0; i < NUM_REGISTERS; i++) { | |
| // Copy fp values from msa buffer fetched via ptrace | |
| *(uint64_t *)dst = *(uint64_t *)src; | |
| src = src + 16; | |
| dst = dst + 8; | |
| } | |
| m_fpr.fir = m_msa.fir; | |
| m_fpr.fcsr = m_msa.fcsr; | |
| m_fpr.config5 = m_msa.config5; | |
| } else { | |
| error = NativeRegisterContextLinux::ReadFPR(); | |
| } | |
| return error; | |
| } | |
| uint8_t * | |
| NativeRegisterContextLinux_mips64::ReturnFPOffset(uint8_t reg_index, | |
| uint32_t byte_offset) { | |
| uint8_t *fp_buffer_ptr = nullptr; | |
| lldb::ByteOrder byte_order = GetByteOrder(); | |
| bool IsBigEndian = (byte_order == lldb::eByteOrderBig); | |
| if (reg_index % 2) { | |
| uint8_t offset_diff = (IsBigEndian) ? 8 : 4; | |
| fp_buffer_ptr = | |
| (uint8_t *)&m_fpr + byte_offset - offset_diff - sizeof(m_gpr); | |
| } else { | |
| fp_buffer_ptr = | |
| (uint8_t *)&m_fpr + byte_offset + 4 * (IsBigEndian) - sizeof(m_gpr); | |
| } | |
| return fp_buffer_ptr; | |
| } | |
| Status NativeRegisterContextLinux_mips64::WriteCP1() { | |
| Status error; | |
| uint8_t *src = nullptr; | |
| uint8_t *dst = nullptr; | |
| lldb::ByteOrder byte_order = GetByteOrder(); | |
| bool IsBigEndian = (byte_order == lldb::eByteOrderBig); | |
| if (IsMSAAvailable()) { | |
| dst = (uint8_t *)&m_msa + (IsBigEndian * 8); | |
| src = (uint8_t *)&m_fpr; | |
| for (int i = 0; i < NUM_REGISTERS; i++) { | |
| // Copy fp values to msa buffer for ptrace | |
| *(uint64_t *)dst = *(uint64_t *)src; | |
| dst = dst + 16; | |
| src = src + 8; | |
| } | |
| m_msa.fir = m_fpr.fir; | |
| m_msa.fcsr = m_fpr.fcsr; | |
| m_msa.config5 = m_fpr.config5; | |
| error = NativeRegisterContextLinux::WriteRegisterSet( | |
| &m_iovec, sizeof(MSA_linux_mips), NT_MIPS_MSA); | |
| } else { | |
| error = NativeRegisterContextLinux::WriteFPR(); | |
| } | |
| return error; | |
| } | |
| bool NativeRegisterContextLinux_mips64::IsFR0() { | |
| const RegisterInfo *const reg_info_p = GetRegisterInfoAtIndex(gpr_sr_mips64); | |
| RegisterValue reg_value; | |
| ReadRegister(reg_info_p, reg_value); | |
| uint64_t value = reg_value.GetAsUInt64(); | |
| return (!(value & SR_FR)); | |
| } | |
| bool NativeRegisterContextLinux_mips64::IsFRE() { | |
| const RegisterInfo *const reg_info_p = | |
| GetRegisterInfoAtIndex(gpr_config5_mips64); | |
| RegisterValue reg_value; | |
| ReadRegister(reg_info_p, reg_value); | |
| uint64_t config5 = reg_value.GetAsUInt64(); | |
| return (config5 & CONFIG5_FRE); | |
| } | |
| bool NativeRegisterContextLinux_mips64::IsFPR(uint32_t reg_index) const { | |
| return (m_reg_info.first_fpr <= reg_index && | |
| reg_index <= m_reg_info.last_fpr); | |
| } | |
| static uint32_t GetWatchHi(struct pt_watch_regs *regs, uint32_t index) { | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); | |
| if (regs->style == pt_watch_style_mips32) | |
| return regs->mips32.watchhi[index]; | |
| else if (regs->style == pt_watch_style_mips64) | |
| return regs->mips64.watchhi[index]; | |
| LLDB_LOG(log, "Invalid watch register style"); | |
| return 0; | |
| } | |
| static void SetWatchHi(struct pt_watch_regs *regs, uint32_t index, | |
| uint16_t value) { | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); | |
| if (regs->style == pt_watch_style_mips32) | |
| regs->mips32.watchhi[index] = value; | |
| else if (regs->style == pt_watch_style_mips64) | |
| regs->mips64.watchhi[index] = value; | |
| LLDB_LOG(log, "Invalid watch register style"); | |
| return; | |
| } | |
| static lldb::addr_t GetWatchLo(struct pt_watch_regs *regs, uint32_t index) { | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); | |
| if (regs->style == pt_watch_style_mips32) | |
| return regs->mips32.watchlo[index]; | |
| else if (regs->style == pt_watch_style_mips64) | |
| return regs->mips64.watchlo[index]; | |
| LLDB_LOG(log, "Invalid watch register style"); | |
| return LLDB_INVALID_ADDRESS; | |
| } | |
| static void SetWatchLo(struct pt_watch_regs *regs, uint32_t index, | |
| uint64_t value) { | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); | |
| if (regs->style == pt_watch_style_mips32) | |
| regs->mips32.watchlo[index] = (uint32_t)value; | |
| else if (regs->style == pt_watch_style_mips64) | |
| regs->mips64.watchlo[index] = value; | |
| else | |
| LLDB_LOG(log, "Invalid watch register style"); | |
| } | |
| static uint32_t GetIRWMask(struct pt_watch_regs *regs, uint32_t index) { | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); | |
| if (regs->style == pt_watch_style_mips32) | |
| return regs->mips32.watch_masks[index] & IRW; | |
| else if (regs->style == pt_watch_style_mips64) | |
| return regs->mips64.watch_masks[index] & IRW; | |
| LLDB_LOG(log, "Invalid watch register style"); | |
| return 0; | |
| } | |
| static uint32_t GetRegMask(struct pt_watch_regs *regs, uint32_t index) { | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); | |
| if (regs->style == pt_watch_style_mips32) | |
| return regs->mips32.watch_masks[index] & ~IRW; | |
| else if (regs->style == pt_watch_style_mips64) | |
| return regs->mips64.watch_masks[index] & ~IRW; | |
| LLDB_LOG(log, "Invalid watch register style"); | |
| return 0; | |
| } | |
| static lldb::addr_t GetRangeMask(lldb::addr_t mask) { | |
| lldb::addr_t mask_bit = 1; | |
| while (mask_bit < mask) { | |
| mask = mask | mask_bit; | |
| mask_bit <<= 1; | |
| } | |
| return mask; | |
| } | |
| static int GetVacantWatchIndex(struct pt_watch_regs *regs, lldb::addr_t addr, | |
| uint32_t size, uint32_t irw, | |
| uint32_t num_valid) { | |
| lldb::addr_t last_byte = addr + size - 1; | |
| lldb::addr_t mask = GetRangeMask(addr ^ last_byte) | IRW; | |
| lldb::addr_t base_addr = addr & ~mask; | |
| // Check if this address is already watched by previous watch points. | |
| lldb::addr_t lo; | |
| uint16_t hi; | |
| uint32_t vacant_watches = 0; | |
| for (uint32_t index = 0; index < num_valid; index++) { | |
| lo = GetWatchLo(regs, index); | |
| if (lo != 0 && irw == ((uint32_t)lo & irw)) { | |
| hi = GetWatchHi(regs, index) | IRW; | |
| lo &= ~(lldb::addr_t)hi; | |
| if (addr >= lo && last_byte <= (lo + hi)) | |
| return index; | |
| } else | |
| vacant_watches++; | |
| } | |
| // Now try to find a vacant index | |
| if (vacant_watches > 0) { | |
| vacant_watches = 0; | |
| for (uint32_t index = 0; index < num_valid; index++) { | |
| lo = GetWatchLo(regs, index); | |
| if (lo == 0 && irw == (GetIRWMask(regs, index) & irw)) { | |
| if (mask <= (GetRegMask(regs, index) | IRW)) { | |
| // It fits, we can use it. | |
| SetWatchLo(regs, index, base_addr | irw); | |
| SetWatchHi(regs, index, mask & ~IRW); | |
| return index; | |
| } else { | |
| // It doesn't fit, but has the proper IRW capabilities | |
| vacant_watches++; | |
| } | |
| } | |
| } | |
| if (vacant_watches > 1) { | |
| // Split this watchpoint accross several registers | |
| struct pt_watch_regs regs_copy; | |
| regs_copy = *regs; | |
| lldb::addr_t break_addr; | |
| uint32_t segment_size; | |
| for (uint32_t index = 0; index < num_valid; index++) { | |
| lo = GetWatchLo(®s_copy, index); | |
| hi = GetRegMask(®s_copy, index) | IRW; | |
| if (lo == 0 && irw == (hi & irw)) { | |
| lo = addr & ~(lldb::addr_t)hi; | |
| break_addr = lo + hi + 1; | |
| if (break_addr >= addr + size) | |
| segment_size = size; | |
| else | |
| segment_size = break_addr - addr; | |
| mask = GetRangeMask(addr ^ (addr + segment_size - 1)); | |
| SetWatchLo(®s_copy, index, (addr & ~mask) | irw); | |
| SetWatchHi(®s_copy, index, mask & ~IRW); | |
| if (break_addr >= addr + size) { | |
| *regs = regs_copy; | |
| return index; | |
| } | |
| size = addr + size - break_addr; | |
| addr = break_addr; | |
| } | |
| } | |
| } | |
| } | |
| return LLDB_INVALID_INDEX32; | |
| } | |
| bool NativeRegisterContextLinux_mips64::IsMSA(uint32_t reg_index) const { | |
| return (m_reg_info.first_msa <= reg_index && | |
| reg_index <= m_reg_info.last_msa); | |
| } | |
| bool NativeRegisterContextLinux_mips64::IsMSAAvailable() { | |
| MSA_linux_mips msa_buf; | |
| unsigned int regset = NT_MIPS_MSA; | |
| Status error = NativeProcessLinux::PtraceWrapper( | |
| PTRACE_GETREGSET, Host::GetCurrentProcessID(), | |
| static_cast<void *>(®set), &msa_buf, sizeof(MSA_linux_mips)); | |
| if (error.Success() && msa_buf.mir) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| Status NativeRegisterContextLinux_mips64::IsWatchpointHit(uint32_t wp_index, | |
| bool &is_hit) { | |
| if (wp_index >= NumSupportedHardwareWatchpoints()) | |
| return Status("Watchpoint index out of range"); | |
| // reading the current state of watch regs | |
| struct pt_watch_regs watch_readback; | |
| Status error = DoReadWatchPointRegisterValue( | |
| m_thread.GetID(), static_cast<void *>(&watch_readback)); | |
| if (GetWatchHi(&watch_readback, wp_index) & (IRW)) { | |
| // clear hit flag in watchhi | |
| SetWatchHi(&watch_readback, wp_index, | |
| (GetWatchHi(&watch_readback, wp_index) & ~(IRW))); | |
| DoWriteWatchPointRegisterValue(m_thread.GetID(), | |
| static_cast<void *>(&watch_readback)); | |
| is_hit = true; | |
| return error; | |
| } | |
| is_hit = false; | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_mips64::GetWatchpointHitIndex( | |
| uint32_t &wp_index, lldb::addr_t trap_addr) { | |
| uint32_t num_hw_wps = NumSupportedHardwareWatchpoints(); | |
| for (wp_index = 0; wp_index < num_hw_wps; ++wp_index) { | |
| bool is_hit; | |
| Status error = IsWatchpointHit(wp_index, is_hit); | |
| if (error.Fail()) { | |
| wp_index = LLDB_INVALID_INDEX32; | |
| } else if (is_hit) { | |
| return error; | |
| } | |
| } | |
| wp_index = LLDB_INVALID_INDEX32; | |
| return Status(); | |
| } | |
| Status NativeRegisterContextLinux_mips64::IsWatchpointVacant(uint32_t wp_index, | |
| bool &is_vacant) { | |
| is_vacant = false; | |
| return Status("MIPS TODO: " | |
| "NativeRegisterContextLinux_mips64::IsWatchpointVacant not " | |
| "implemented"); | |
| } | |
| bool NativeRegisterContextLinux_mips64::ClearHardwareWatchpoint( | |
| uint32_t wp_index) { | |
| if (wp_index >= NumSupportedHardwareWatchpoints()) | |
| return false; | |
| struct pt_watch_regs regs; | |
| // First reading the current state of watch regs | |
| DoReadWatchPointRegisterValue(m_thread.GetID(), static_cast<void *>(®s)); | |
| if (regs.style == pt_watch_style_mips32) { | |
| regs.mips32.watchlo[wp_index] = default_watch_regs.mips32.watchlo[wp_index]; | |
| regs.mips32.watchhi[wp_index] = default_watch_regs.mips32.watchhi[wp_index]; | |
| regs.mips32.watch_masks[wp_index] = | |
| default_watch_regs.mips32.watch_masks[wp_index]; | |
| } else // pt_watch_style_mips64 | |
| { | |
| regs.mips64.watchlo[wp_index] = default_watch_regs.mips64.watchlo[wp_index]; | |
| regs.mips64.watchhi[wp_index] = default_watch_regs.mips64.watchhi[wp_index]; | |
| regs.mips64.watch_masks[wp_index] = | |
| default_watch_regs.mips64.watch_masks[wp_index]; | |
| } | |
| Status error = DoWriteWatchPointRegisterValue(m_thread.GetID(), | |
| static_cast<void *>(®s)); | |
| if (!error.Fail()) { | |
| hw_addr_map[wp_index] = LLDB_INVALID_ADDRESS; | |
| return true; | |
| } | |
| return false; | |
| } | |
| Status NativeRegisterContextLinux_mips64::ClearAllHardwareWatchpoints() { | |
| return DoWriteWatchPointRegisterValue( | |
| m_thread.GetID(), static_cast<void *>(&default_watch_regs)); | |
| } | |
| Status NativeRegisterContextLinux_mips64::SetHardwareWatchpointWithIndex( | |
| lldb::addr_t addr, size_t size, uint32_t watch_flags, uint32_t wp_index) { | |
| Status error; | |
| error.SetErrorString("MIPS TODO: " | |
| "NativeRegisterContextLinux_mips64::" | |
| "SetHardwareWatchpointWithIndex not implemented"); | |
| return error; | |
| } | |
| uint32_t NativeRegisterContextLinux_mips64::SetHardwareWatchpoint( | |
| lldb::addr_t addr, size_t size, uint32_t watch_flags) { | |
| struct pt_watch_regs regs; | |
| // First reading the current state of watch regs | |
| DoReadWatchPointRegisterValue(m_thread.GetID(), static_cast<void *>(®s)); | |
| // Try if a new watch point fits in this state | |
| int index = GetVacantWatchIndex(®s, addr, size, watch_flags, | |
| NumSupportedHardwareWatchpoints()); | |
| // New watchpoint doesn't fit | |
| if (index == LLDB_INVALID_INDEX32) | |
| return LLDB_INVALID_INDEX32; | |
| // It fits, so we go ahead with updating the state of watch regs | |
| DoWriteWatchPointRegisterValue(m_thread.GetID(), static_cast<void *>(®s)); | |
| // Storing exact address | |
| hw_addr_map[index] = addr; | |
| return index; | |
| } | |
| lldb::addr_t | |
| NativeRegisterContextLinux_mips64::GetWatchpointAddress(uint32_t wp_index) { | |
| if (wp_index >= NumSupportedHardwareWatchpoints()) | |
| return LLDB_INVALID_ADDRESS; | |
| return hw_addr_map[wp_index]; | |
| } | |
| struct EmulatorBaton { | |
| lldb::addr_t m_watch_hit_addr; | |
| NativeProcessLinux *m_process; | |
| NativeRegisterContext *m_reg_context; | |
| EmulatorBaton(NativeProcessLinux *process, NativeRegisterContext *reg_context) | |
| : m_watch_hit_addr(LLDB_INVALID_ADDRESS), m_process(process), | |
| m_reg_context(reg_context) {} | |
| }; | |
| static size_t ReadMemoryCallback(EmulateInstruction *instruction, void *baton, | |
| const EmulateInstruction::Context &context, | |
| lldb::addr_t addr, void *dst, size_t length) { | |
| size_t bytes_read; | |
| EmulatorBaton *emulator_baton = static_cast<EmulatorBaton *>(baton); | |
| emulator_baton->m_process->ReadMemory(addr, dst, length, bytes_read); | |
| return bytes_read; | |
| } | |
| static size_t WriteMemoryCallback(EmulateInstruction *instruction, void *baton, | |
| const EmulateInstruction::Context &context, | |
| lldb::addr_t addr, const void *dst, | |
| size_t length) { | |
| return length; | |
| } | |
| static bool ReadRegisterCallback(EmulateInstruction *instruction, void *baton, | |
| const RegisterInfo *reg_info, | |
| RegisterValue ®_value) { | |
| EmulatorBaton *emulator_baton = static_cast<EmulatorBaton *>(baton); | |
| const RegisterInfo *full_reg_info = | |
| emulator_baton->m_reg_context->GetRegisterInfo( | |
| lldb::eRegisterKindDWARF, reg_info->kinds[lldb::eRegisterKindDWARF]); | |
| Status error = | |
| emulator_baton->m_reg_context->ReadRegister(full_reg_info, reg_value); | |
| if (error.Success()) | |
| return true; | |
| return false; | |
| } | |
| static bool WriteRegisterCallback(EmulateInstruction *instruction, void *baton, | |
| const EmulateInstruction::Context &context, | |
| const RegisterInfo *reg_info, | |
| const RegisterValue ®_value) { | |
| if (reg_info->kinds[lldb::eRegisterKindDWARF] == dwarf_bad_mips64) { | |
| EmulatorBaton *emulator_baton = static_cast<EmulatorBaton *>(baton); | |
| emulator_baton->m_watch_hit_addr = reg_value.GetAsUInt64(); | |
| } | |
| return true; | |
| } | |
| /* | |
| * MIPS Linux kernel returns a masked address (last 3bits are masked) | |
| * when a HW watchpoint is hit. However user may not have set a watchpoint | |
| * on this address. Emulate instruction at PC and find the base address of | |
| * the load/store instruction. This will give the exact address used to | |
| * read/write the variable. Send this exact address to client so that | |
| * it can decide to stop or continue the thread. | |
| */ | |
| lldb::addr_t | |
| NativeRegisterContextLinux_mips64::GetWatchpointHitAddress(uint32_t wp_index) { | |
| if (wp_index >= NumSupportedHardwareWatchpoints()) | |
| return LLDB_INVALID_ADDRESS; | |
| lldb_private::ArchSpec arch; | |
| arch = GetRegisterInfoInterface().GetTargetArchitecture(); | |
| std::unique_ptr<EmulateInstruction> emulator_up( | |
| EmulateInstruction::FindPlugin(arch, lldb_private::eInstructionTypeAny, | |
| nullptr)); | |
| if (emulator_up == nullptr) | |
| return LLDB_INVALID_ADDRESS; | |
| EmulatorBaton baton( | |
| static_cast<NativeProcessLinux *>(&m_thread.GetProcess()), this); | |
| emulator_up->SetBaton(&baton); | |
| emulator_up->SetReadMemCallback(&ReadMemoryCallback); | |
| emulator_up->SetReadRegCallback(&ReadRegisterCallback); | |
| emulator_up->SetWriteMemCallback(&WriteMemoryCallback); | |
| emulator_up->SetWriteRegCallback(&WriteRegisterCallback); | |
| if (!emulator_up->ReadInstruction()) | |
| return LLDB_INVALID_ADDRESS; | |
| if (emulator_up->EvaluateInstruction(lldb::eEmulateInstructionOptionNone)) | |
| return baton.m_watch_hit_addr; | |
| return LLDB_INVALID_ADDRESS; | |
| } | |
| uint32_t NativeRegisterContextLinux_mips64::NumSupportedHardwareWatchpoints() { | |
| Log *log(ProcessPOSIXLog::GetLogIfAllCategoriesSet(POSIX_LOG_WATCHPOINTS)); | |
| struct pt_watch_regs regs; | |
| static int num_valid = 0; | |
| if (!num_valid) { | |
| DoReadWatchPointRegisterValue(m_thread.GetID(), static_cast<void *>(®s)); | |
| default_watch_regs = | |
| regs; // Keeping default watch regs values for future use | |
| switch (regs.style) { | |
| case pt_watch_style_mips32: | |
| num_valid = regs.mips32.num_valid; // Using num_valid as cache | |
| return num_valid; | |
| case pt_watch_style_mips64: | |
| num_valid = regs.mips64.num_valid; | |
| return num_valid; | |
| } | |
| LLDB_LOG(log, "Invalid watch register style"); | |
| return 0; | |
| } | |
| return num_valid; | |
| } | |
| Status | |
| NativeRegisterContextLinux_mips64::ReadRegisterRaw(uint32_t reg_index, | |
| RegisterValue &value) { | |
| const RegisterInfo *const reg_info = GetRegisterInfoAtIndex(reg_index); | |
| if (!reg_info) | |
| return Status("register %" PRIu32 " not found", reg_index); | |
| uint32_t offset = reg_info->kinds[lldb::eRegisterKindProcessPlugin]; | |
| if ((offset == ptrace_sr_mips) || (offset == ptrace_config5_mips)) | |
| return Read_SR_Config(reg_info->byte_offset, reg_info->name, | |
| reg_info->byte_size, value); | |
| return DoReadRegisterValue(offset, reg_info->name, reg_info->byte_size, | |
| value); | |
| } | |
| Status NativeRegisterContextLinux_mips64::WriteRegisterRaw( | |
| uint32_t reg_index, const RegisterValue &value) { | |
| const RegisterInfo *const reg_info = GetRegisterInfoAtIndex(reg_index); | |
| if (!reg_info) | |
| return Status("register %" PRIu32 " not found", reg_index); | |
| if (reg_info->invalidate_regs) | |
| lldbassert(false && "reg_info->invalidate_regs is unhandled"); | |
| uint32_t offset = reg_info->kinds[lldb::eRegisterKindProcessPlugin]; | |
| return DoWriteRegisterValue(offset, reg_info->name, value); | |
| } | |
| Status NativeRegisterContextLinux_mips64::Read_SR_Config(uint32_t offset, | |
| const char *reg_name, | |
| uint32_t size, | |
| RegisterValue &value) { | |
| GPR_linux_mips regs; | |
| ::memset(®s, 0, sizeof(GPR_linux_mips)); | |
| Status error = NativeProcessLinux::PtraceWrapper( | |
| PTRACE_GETREGS, m_thread.GetID(), NULL, ®s, sizeof regs); | |
| if (error.Success()) { | |
| const lldb_private::ArchSpec &arch = | |
| m_thread.GetProcess().GetArchitecture(); | |
| void *target_address = ((uint8_t *)®s) + offset + | |
| 4 * (arch.GetMachine() == llvm::Triple::mips); | |
| value.SetUInt(*(uint32_t *)target_address, size); | |
| } | |
| return error; | |
| } | |
| Status NativeRegisterContextLinux_mips64::DoReadWatchPointRegisterValue( | |
| lldb::tid_t tid, void *watch_readback) { | |
| return NativeProcessLinux::PtraceWrapper(PTRACE_GET_WATCH_REGS, | |
| m_thread.GetID(), watch_readback); | |
| } | |
| Status NativeRegisterContextLinux_mips64::DoWriteWatchPointRegisterValue( | |
| lldb::tid_t tid, void *watch_reg_value) { | |
| return NativeProcessLinux::PtraceWrapper(PTRACE_SET_WATCH_REGS, | |
| m_thread.GetID(), watch_reg_value); | |
| } | |
| #endif // defined (__mips__) |