diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h @@ -83,6 +83,7 @@ bool m_fpu_is_valid; bool m_sve_buffer_is_valid; bool m_mte_ctrl_is_valid; + bool m_tls_tpidr_is_valid; bool m_sve_header_is_valid; bool m_pac_mask_is_valid; @@ -107,6 +108,8 @@ uint64_t m_mte_ctrl_reg; + uint64_t m_tls_tpidr_reg; + bool IsGPR(unsigned reg) const; bool IsFPR(unsigned reg) const; @@ -125,9 +128,14 @@ Status WriteMTEControl(); + Status ReadTLSTPIDR(); + + Status WriteTLSTPIDR(); + bool IsSVE(unsigned reg) const; bool IsPAuth(unsigned reg) const; bool IsMTE(unsigned reg) const; + bool IsTLS(unsigned reg) const; uint64_t GetSVERegVG() { return m_sve_header.vl / 8; } @@ -139,6 +147,8 @@ void *GetMTEControl() { return &m_mte_ctrl_reg; } + void *GetTLSTPIDR() { return &m_tls_tpidr_reg; } + void *GetSVEBuffer() { return m_sve_ptrace_payload.data(); }; size_t GetSVEHeaderSize() { return sizeof(m_sve_header); } @@ -149,6 +159,8 @@ size_t GetMTEControlSize() { return sizeof(m_mte_ctrl_reg); } + size_t GetTLSTPIDRSize() { return sizeof(m_tls_tpidr_reg); } + llvm::Error ReadHardwareDebugInfo() override; llvm::Error WriteHardwareDebugRegs(DREGType hwbType) override; diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp @@ -86,6 +86,8 @@ if (auxv_at_hwcap2 && (*auxv_at_hwcap2 & HWCAP2_MTE)) opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskMTE); + opt_regsets.Set(RegisterInfoPOSIX_arm64::eRegsetMaskTLS); + auto register_info_up = std::make_unique(target_arch, opt_regsets); return std::make_unique( @@ -116,6 +118,7 @@ ::memset(&m_pac_mask, 0, sizeof(m_pac_mask)); m_mte_ctrl_reg = 0; + m_tls_tpidr_reg = 0; // 16 is just a maximum value, query hardware for actual watchpoint count m_max_hwp_supported = 16; @@ -129,6 +132,7 @@ m_sve_header_is_valid = false; m_pac_mask_is_valid = false; m_mte_ctrl_is_valid = false; + m_tls_tpidr_is_valid = false; if (GetRegisterInfo().IsSVEEnabled()) m_sve_state = SVEState::Unknown; @@ -232,8 +236,15 @@ assert(offset < GetSVEBufferSize()); src = (uint8_t *)GetSVEBuffer() + offset; } - } else if (IsSVE(reg)) { + } else if (IsTLS(reg)) { + error = ReadTLSTPIDR(); + if (error.Fail()) + return error; + offset = reg_info->byte_offset - GetRegisterInfo().GetTLSOffset(); + assert(offset < GetTLSTPIDRSize()); + src = (uint8_t *)GetTLSTPIDR() + offset; + } else if (IsSVE(reg)) { if (m_sve_state == SVEState::Disabled || m_sve_state == SVEState::Unknown) return Status("SVE disabled or not supported"); @@ -450,6 +461,17 @@ ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); return WriteMTEControl(); + } else if (IsTLS(reg)) { + error = ReadTLSTPIDR(); + if (error.Fail()) + return error; + + offset = reg_info->byte_offset - GetRegisterInfo().GetTLSOffset(); + assert(offset < GetTLSTPIDRSize()); + dst = (uint8_t *)GetTLSTPIDR() + offset; + ::memcpy(dst, reg_value.GetBytes(), reg_info->byte_size); + + return WriteTLSTPIDR(); } return Status("Failed to write register value"); @@ -490,6 +512,12 @@ return error; } + // tpidr is always present but there will be more in future. + reg_data_byte_size += GetTLSTPIDRSize(); + error = ReadTLSTPIDR(); + if (error.Fail()) + return error; + data_sp.reset(new DataBufferHeap(reg_data_byte_size, 0)); uint8_t *dst = data_sp->GetBytes(); @@ -507,6 +535,8 @@ if (GetRegisterInfo().IsMTEEnabled()) ::memcpy(dst, GetMTEControl(), GetMTEControlSize()); + ::memcpy(dst, GetTLSTPIDR(), GetTLSTPIDRSize()); + return error; } @@ -641,6 +671,10 @@ return GetRegisterInfo().IsMTEReg(reg); } +bool NativeRegisterContextLinux_arm64::IsTLS(unsigned reg) const { + return GetRegisterInfo().IsTLSReg(reg); +} + llvm::Error NativeRegisterContextLinux_arm64::ReadHardwareDebugInfo() { if (!m_refresh_hwdebug_info) { return llvm::Error::success(); @@ -784,6 +818,7 @@ m_sve_header_is_valid = false; m_pac_mask_is_valid = false; m_mte_ctrl_is_valid = false; + m_tls_tpidr_is_valid = false; // Update SVE registers in case there is change in configuration. ConfigureRegisterContext(); @@ -914,6 +949,40 @@ return WriteRegisterSet(&ioVec, GetMTEControlSize(), NT_ARM_TAGGED_ADDR_CTRL); } +Status NativeRegisterContextLinux_arm64::ReadTLSTPIDR() { + Status error; + + if (m_tls_tpidr_is_valid) + return error; + + struct iovec ioVec; + ioVec.iov_base = GetTLSTPIDR(); + ioVec.iov_len = GetTLSTPIDRSize(); + + error = ReadRegisterSet(&ioVec, GetTLSTPIDRSize(), NT_ARM_TLS); + + if (error.Success()) + m_tls_tpidr_is_valid = true; + + return error; +} + +Status NativeRegisterContextLinux_arm64::WriteTLSTPIDR() { + Status error; + + error = ReadTLSTPIDR(); + if (error.Fail()) + return error; + + struct iovec ioVec; + ioVec.iov_base = GetTLSTPIDR(); + ioVec.iov_len = GetTLSTPIDRSize(); + + m_tls_tpidr_is_valid = false; + + return WriteRegisterSet(&ioVec, GetTLSTPIDRSize(), NT_ARM_TLS); +} + void NativeRegisterContextLinux_arm64::ConfigureRegisterContext() { // ConfigureRegisterContext gets called from InvalidateAllRegisters // on every stop and configures SVE vector length. diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h --- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h +++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h @@ -28,6 +28,7 @@ eRegsetMaskSVE = 1, eRegsetMaskPAuth = 2, eRegsetMaskMTE = 4, + eRegsetMaskTLS = 8, eRegsetMaskDynamic = ~1, }; @@ -102,6 +103,8 @@ void AddRegSetMTE(); + void AddRegSetTLS(); + uint32_t ConfigureVectorLength(uint32_t sve_vq); bool VectorSizeIsValid(uint32_t vq) { @@ -121,6 +124,7 @@ bool IsSVERegVG(unsigned reg) const; bool IsPAuthReg(unsigned reg) const; bool IsMTEReg(unsigned reg) const; + bool IsTLSReg(unsigned reg) const; uint32_t GetRegNumSVEZ0() const; uint32_t GetRegNumSVEFFR() const; @@ -129,6 +133,7 @@ uint32_t GetRegNumSVEVG() const; uint32_t GetPAuthOffset() const; uint32_t GetMTEOffset() const; + uint32_t GetTLSOffset() const; private: typedef std::map> @@ -155,6 +160,7 @@ std::vector pauth_regnum_collection; std::vector m_mte_regnum_collection; + std::vector m_tls_regnum_collection; }; #endif diff --git a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp --- a/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp +++ b/lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp @@ -78,12 +78,16 @@ static lldb_private::RegisterInfo g_register_infos_mte[] = { DEFINE_EXTENSION_REG(mte_ctrl)}; +static lldb_private::RegisterInfo g_register_infos_tls[] = { + DEFINE_EXTENSION_REG(tpidr)}; + // Number of register sets provided by this context. enum { k_num_gpr_registers = gpr_w28 - gpr_x0 + 1, k_num_fpr_registers = fpu_fpcr - fpu_v0 + 1, k_num_sve_registers = sve_ffr - sve_vg + 1, k_num_mte_register = 1, + k_num_tls_register = 1, k_num_pauth_register = 2, k_num_register_sets_default = 2, k_num_register_sets = 3 @@ -189,6 +193,9 @@ static const lldb_private::RegisterSet g_reg_set_mte_arm64 = { "MTE Control Register", "mte", k_num_mte_register, nullptr}; +static const lldb_private::RegisterSet g_reg_set_tls_arm64 = { + "Thread Local Storage Registers", "tls", k_num_tls_register, nullptr}; + RegisterInfoPOSIX_arm64::RegisterInfoPOSIX_arm64( const lldb_private::ArchSpec &target_arch, lldb_private::Flags opt_regsets) : lldb_private::RegisterInfoAndSetInterface(target_arch), @@ -229,6 +236,10 @@ if (m_opt_regsets.AllSet(eRegsetMaskMTE)) AddRegSetMTE(); + // tpidr is always present, but in future there will be others so this is + // done as a dynamic set. + AddRegSetTLS(); + m_register_info_count = m_dynamic_reg_infos.size(); m_register_info_p = m_dynamic_reg_infos.data(); m_register_set_p = m_dynamic_reg_sets.data(); @@ -312,6 +323,21 @@ m_dynamic_reg_sets.back().registers = m_mte_regnum_collection.data(); } +void RegisterInfoPOSIX_arm64::AddRegSetTLS() { + uint32_t tls_regnum = m_dynamic_reg_infos.size(); + m_tls_regnum_collection.push_back(tls_regnum); + m_dynamic_reg_infos.push_back(g_register_infos_tls[0]); + m_dynamic_reg_infos[tls_regnum].byte_offset = + m_dynamic_reg_infos[tls_regnum - 1].byte_offset + + m_dynamic_reg_infos[tls_regnum - 1].byte_size; + m_dynamic_reg_infos[tls_regnum].kinds[lldb::eRegisterKindLLDB] = tls_regnum; + + m_per_regset_regnum_range[m_register_set_count] = + std::make_pair(tls_regnum, tls_regnum + 1); + m_dynamic_reg_sets.push_back(g_reg_set_tls_arm64); + m_dynamic_reg_sets.back().registers = m_tls_regnum_collection.data(); +} + uint32_t RegisterInfoPOSIX_arm64::ConfigureVectorLength(uint32_t sve_vq) { // sve_vq contains SVE Quad vector length in context of AArch64 SVE. // SVE register infos if enabled cannot be disabled by selecting sve_vq = 0. @@ -403,6 +429,10 @@ return llvm::is_contained(m_mte_regnum_collection, reg); } +bool RegisterInfoPOSIX_arm64::IsTLSReg(unsigned reg) const { + return llvm::is_contained(m_tls_regnum_collection, reg); +} + uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEZ0() const { return sve_z0; } uint32_t RegisterInfoPOSIX_arm64::GetRegNumSVEFFR() const { return sve_ffr; } @@ -420,3 +450,7 @@ uint32_t RegisterInfoPOSIX_arm64::GetMTEOffset() const { return m_register_info_p[m_mte_regnum_collection[0]].byte_offset; } + +uint32_t RegisterInfoPOSIX_arm64::GetTLSOffset() const { + return m_register_info_p[m_tls_regnum_collection[0]].byte_offset; +} diff --git a/lldb/test/API/linux/aarch64/tls_register/Makefile b/lldb/test/API/linux/aarch64/tls_register/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/tls_register/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/linux/aarch64/tls_register/TestAArch64LinuxTLSRegister.py b/lldb/test/API/linux/aarch64/tls_register/TestAArch64LinuxTLSRegister.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/tls_register/TestAArch64LinuxTLSRegister.py @@ -0,0 +1,66 @@ +""" +Test lldb's ability to read and write the AArch64 TLS register tpidr. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class AArch64LinuxTLSRegister(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + @skipUnlessArch("aarch64") + @skipUnlessPlatform(["linux"]) + def test_tls(self): + self.build() + self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) + + lldbutil.run_break_set_by_file_and_line( + self, + "main.c", + line_number("main.c", "// Set break point at this line."), + num_expected_locations=1, + ) + + lldbutil.run_break_set_by_file_and_line( + self, + "main.c", + line_number("main.c", "// Set break point 2 at this line."), + num_expected_locations=1, + ) + + self.runCmd("run", RUN_SUCCEEDED) + + if self.process().GetState() == lldb.eStateExited: + self.fail("Test program failed to run.") + + self.expect( + "thread list", + STOPPED_DUE_TO_BREAKPOINT, + substrs=["stopped", "stop reason = breakpoint"], + ) + + # Since we can't predict what the value will be, the program has set + # a target value for us to find. + + regs = self.thread().GetSelectedFrame().GetRegisters() + tls_regs = regs.GetFirstValueByName("Thread Local Storage Registers") + self.assertTrue(tls_regs.IsValid(), "No TLS registers found.") + tpidr = tls_regs.GetChildMemberWithName("tpidr") + self.assertTrue(tpidr.IsValid(), "No tpidr register found.") + + self.assertEqual(tpidr.GetValueAsUnsigned(), 0x1122334455667788) + + # Set our own value for the program to find. + self.expect("register write tpidr 0x{:x}".format(0x8877665544332211)) + self.expect("continue") + + self.expect( + "thread list", + STOPPED_DUE_TO_BREAKPOINT, + substrs=["stopped", "stop reason = breakpoint"], + ) + + self.expect("p tpidr_was_set", substrs=["true"]) \ No newline at end of file diff --git a/lldb/test/API/linux/aarch64/tls_register/main.c b/lldb/test/API/linux/aarch64/tls_register/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/tls_register/main.c @@ -0,0 +1,24 @@ +#include +#include + +int main() { + // Save tpidr to restore later. + uint64_t tpidr = 0; + __asm__ volatile("mrs %0, tpidr_el0" : "=r"(tpidr)); + + // Set a pattern for lldb to find. + uint64_t pattern = 0x1122334455667788; + __asm__ volatile("msr tpidr_el0, %0" ::"r"(pattern)); + + // Set break point at this line. + // lldb will now set its own pattern for us to find. + + uint64_t new_tpidr = pattern; + __asm__ volatile("mrs %0, tpidr_el0" : "=r"(new_tpidr)); + volatile bool tpidr_was_set = new_tpidr == 0x8877665544332211; + + // Restore the original. + __asm__ volatile("msr tpidr_el0, %0" ::"r"(tpidr)); + + return 0; // Set break point 2 at this line. +}