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 @@ -116,6 +116,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 +130,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; @@ -281,6 +283,14 @@ offset = reg_info->byte_offset - GetRegisterInfo().GetMTEOffset(); assert(offset < GetMTEControlSize()); src = (uint8_t *)GetMTEControl() + offset; + } 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 return Status("failed - register wasn't recognized to be a GPR or an FPR, " "write strategy unknown"); @@ -450,6 +460,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 +511,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 +534,8 @@ if (GetRegisterInfo().IsMTEEnabled()) ::memcpy(dst, GetMTEControl(), GetMTEControlSize()); + ::memcpy(dst, GetTLSTPIDR(), GetTLSTPIDRSize()); + return error; } @@ -641,6 +670,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 +817,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 +948,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 @@ -102,6 +102,8 @@ void AddRegSetMTE(); + void AddRegSetTLS(); + uint32_t ConfigureVectorLength(uint32_t sve_vq); bool VectorSizeIsValid(uint32_t vq) { @@ -121,6 +123,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 +132,7 @@ uint32_t GetRegNumSVEVG() const; uint32_t GetPAuthOffset() const; uint32_t GetMTEOffset() const; + uint32_t GetTLSOffset() const; private: typedef std::map> @@ -155,6 +159,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,77 @@ +""" +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"], + ) + + # We can't know what the value should be since it moves about between + # runs. So we'll check that we can read it, and by modifying it, see + # changes in the program. + + 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.") + tpidr = tpidr.GetValueAsUnsigned() + + # We should be able to find a known value in the TLS area it points to. + # test_data should be very soon after the header, 64 bytes seems to work fine. + self.expect("memory find -e 0xABABABABCDCDCDCD $tpidr $tpidr+64", + substrs=["data found at location: 0x"]) + + # Now modify the TLS pointer so that anyone reading test_data.one actually + # reads test_data.two. + self.expect("register write tpidr 0x{:x}".format(tpidr+8)) + + # Let the program read test_data.one. + self.expect("continue") + + self.expect( + "thread list", + STOPPED_DUE_TO_BREAKPOINT, + substrs=["stopped", "stop reason = breakpoint"], + ) + + self.expect("p tls_has_moved", substrs=["true"]) + + # Reset TLS so we don't potentially confuse the libc. + self.expect("register write tpidr 0x{:x}".format(tpidr)) \ 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,25 @@ +#include +#include + +struct __attribute__((__packed__)) TestStruct { + uint64_t one; + uint64_t two; +}; + +int main() { + static __thread struct TestStruct test_data; + test_data.one = 0xABABABABCDCDCDCD; +#define TWO_VALUE 0xEFEFEFEF01010101 + test_data.two = TWO_VALUE; + + // Barrier to ensure the above writes are done first. + __asm__ volatile("" ::: "memory"); // Set break point at this line. + + // Here lldb moves the TLS pointer 8 bytes forward. + // So this actually reads test_data.two instead of test_data.one. + volatile bool tls_has_moved = test_data.one == TWO_VALUE; + + return 0; // Set break point 2 at this line. + // lldb will reset the thread pointer here so we don't potentially confuse + // libc. +}