DavidSpickett created this revision.
Herald added a subscriber: kristof.beyls.
Herald added a project: All.
DavidSpickett requested review of this revision.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.

This register is used as the pointer to the current thread
local storage block and is read from NT_ARM_TLS on Linux.

Though tpidr will be present on all AArch64 Linux, I am soon
going to add a second register tpidr2 to this set.

tpidr is only present when SME is implemented, therefore the
NT_ARM_TLS set will change size. This is why I've added this
as a dynamic register set to save changes later.

As we can't predict exactly where the TLS data will be,
the tests modify the pointer and observe changes in the program's
behaviour based on that.

So if we read the wrong value in the first place, our modifications
wouldn't work as expected.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D152516

Files:
  lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
  lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
  lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
  lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
  lldb/test/API/linux/aarch64/tls_register/Makefile
  lldb/test/API/linux/aarch64/tls_register/TestAArch64LinuxTLSRegister.py
  lldb/test/API/linux/aarch64/tls_register/main.c

Index: lldb/test/API/linux/aarch64/tls_register/main.c
===================================================================
--- /dev/null
+++ lldb/test/API/linux/aarch64/tls_register/main.c
@@ -0,0 +1,25 @@
+#include <stdbool.h>
+#include <stdint.h>
+
+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.
+}
Index: lldb/test/API/linux/aarch64/tls_register/TestAArch64LinuxTLSRegister.py
===================================================================
--- /dev/null
+++ 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
Index: lldb/test/API/linux/aarch64/tls_register/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/linux/aarch64/tls_register/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
Index: lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
===================================================================
--- lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h
+++ 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<uint32_t, std::vector<lldb_private::RegisterInfo>>
@@ -155,6 +159,7 @@
 
   std::vector<uint32_t> pauth_regnum_collection;
   std::vector<uint32_t> m_mte_regnum_collection;
+  std::vector<uint32_t> m_tls_regnum_collection;
 };
 
 #endif
Index: lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
===================================================================
--- lldb/source/Plugins/Process/Utility/RegisterInfoPOSIX_arm64.cpp
+++ 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;
+}
Index: lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
===================================================================
--- lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h
+++ 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;
Index: lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
===================================================================
--- lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp
+++ 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, GetMTEControlSize(), 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.
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to