AArch64 Linux and elf-core PAC stack unwinder support
authorMuhammad Omair Javaid <omair.javaid@linaro.org>
Tue, 15 Jun 2021 10:41:38 +0000 (15:41 +0500)
committerMuhammad Omair Javaid <omair.javaid@linaro.org>
Tue, 15 Jun 2021 21:09:46 +0000 (02:09 +0500)
This patch builds on D100521 and other related patches to add support
for unwinding stack on AArch64 systems with pointer authentication
feature enabled.

We override FixCodeAddress and FixDataAddress function in ABISysV_arm64
class. We now try to calculate and set code and data masks after reading
data_mask and code_mask registers exposed by AArch64 targets running Linux.

This patch utilizes core file linux-aarch64-pac.core for testing that
LLDB can successfully unwind stack frames in the presence of signed
return address after masking off ignored bits.

This patch also includes a AArch64 Linux native test case to demonstrate
successful back trace calculation in presence of pointer authentication
feature.

Differential Revision: https://reviews.llvm.org/D99944

lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.cpp
lldb/source/Plugins/ABI/AArch64/ABISysV_arm64.h
lldb/test/API/functionalities/postmortem/elf-core/TestLinuxCore.py
lldb/test/API/functionalities/postmortem/elf-core/linux-aarch64-pac.out [new file with mode: 0755]
lldb/test/API/functionalities/unwind/aarch64_unwind_pac/Makefile [new file with mode: 0644]
lldb/test/API/functionalities/unwind/aarch64_unwind_pac/TestAArch64UnwindPAC.py [new file with mode: 0644]
lldb/test/API/functionalities/unwind/aarch64_unwind_pac/main.c [new file with mode: 0644]

index 312b562..16fb38e 100644 (file)
@@ -787,6 +787,56 @@ lldb::addr_t ABISysV_arm64::FixAddress(addr_t pc, addr_t mask) {
   return (pc & pac_sign_extension) ? pc | mask : pc & (~mask);
 }
 
+// Reads code or data address mask for the current Linux process.
+static lldb::addr_t ReadLinuxProcessAddressMask(lldb::ProcessSP process_sp,
+                                                llvm::StringRef reg_name) {
+  // Linux configures user-space virtual addresses with top byte ignored.
+  // We set default value of mask such that top byte is masked out.
+  uint64_t address_mask = ~((1ULL << 56) - 1);
+  // If Pointer Authentication feature is enabled then Linux exposes
+  // PAC data and code mask register. Try reading relevant register
+  // below and merge it with default address mask calculated above.
+  lldb::ThreadSP thread_sp = process_sp->GetThreadList().GetSelectedThread();
+  if (thread_sp) {
+    lldb::RegisterContextSP reg_ctx_sp = thread_sp->GetRegisterContext();
+    if (reg_ctx_sp) {
+      const RegisterInfo *reg_info =
+          reg_ctx_sp->GetRegisterInfoByName(reg_name, 0);
+      if (reg_info) {
+        lldb::addr_t mask_reg_val = reg_ctx_sp->ReadRegisterAsUnsigned(
+            reg_info->kinds[eRegisterKindLLDB], LLDB_INVALID_ADDRESS);
+        if (mask_reg_val != LLDB_INVALID_ADDRESS)
+          address_mask |= mask_reg_val;
+      }
+    }
+  }
+  return address_mask;
+}
+
+lldb::addr_t ABISysV_arm64::FixCodeAddress(lldb::addr_t pc) {
+  if (lldb::ProcessSP process_sp = GetProcessSP()) {
+    if (process_sp->GetTarget().GetArchitecture().GetTriple().isOSLinux() &&
+        !process_sp->GetCodeAddressMask())
+      process_sp->SetCodeAddressMask(
+          ReadLinuxProcessAddressMask(process_sp, "code_mask"));
+
+    return FixAddress(pc, process_sp->GetCodeAddressMask());
+  }
+  return pc;
+}
+
+lldb::addr_t ABISysV_arm64::FixDataAddress(lldb::addr_t pc) {
+  if (lldb::ProcessSP process_sp = GetProcessSP()) {
+    if (process_sp->GetTarget().GetArchitecture().GetTriple().isOSLinux() &&
+        !process_sp->GetDataAddressMask())
+      process_sp->SetDataAddressMask(
+          ReadLinuxProcessAddressMask(process_sp, "data_mask"));
+
+    return FixAddress(pc, process_sp->GetDataAddressMask());
+  }
+  return pc;
+}
+
 void ABISysV_arm64::Initialize() {
   PluginManager::RegisterPlugin(GetPluginNameStatic(),
                                 "SysV ABI for AArch64 targets", CreateInstance);
index 4c88ee2..3428a7a 100644 (file)
@@ -85,6 +85,9 @@ public:
 
   uint32_t GetPluginVersion() override;
 
+  lldb::addr_t FixCodeAddress(lldb::addr_t pc) override;
+  lldb::addr_t FixDataAddress(lldb::addr_t pc) override;
+
 protected:
   lldb::ValueObjectSP
   GetReturnValueObjectImpl(lldb_private::Thread &thread,
index 6f8d051..3dfc7a0 100644 (file)
@@ -20,6 +20,7 @@ class LinuxCoreTestCase(TestBase):
     mydir = TestBase.compute_mydir(__file__)
 
     _aarch64_pid = 37688
+    _aarch64_pac_pid = 387
     _i386_pid = 32306
     _x86_64_pid = 32259
     _s390x_pid = 1045
@@ -258,6 +259,18 @@ class LinuxCoreTestCase(TestBase):
         self.dbg.DeleteTarget(target)
 
     @skipIfLLVMTargetMissing("AArch64")
+    def test_aarch64_pac(self):
+        """Test that lldb can unwind stack for AArch64 elf core file with PAC enabled."""
+
+        target = self.dbg.CreateTarget("linux-aarch64-pac.out")
+        self.assertTrue(target, VALID_TARGET)
+        process = target.LoadCore("linux-aarch64-pac.core")
+
+        self.check_all(process, self._aarch64_pac_pid, self._aarch64_regions, "a.out")
+
+        self.dbg.DeleteTarget(target)
+
+    @skipIfLLVMTargetMissing("AArch64")
     @expectedFailureAll(archs=["aarch64"], oslist=["freebsd"],
                         bugnumber="llvm.org/pr49415")
     def test_aarch64_regs(self):
diff --git a/lldb/test/API/functionalities/postmortem/elf-core/linux-aarch64-pac.out b/lldb/test/API/functionalities/postmortem/elf-core/linux-aarch64-pac.out
new file mode 100755 (executable)
index 0000000..6df8e1c
Binary files /dev/null and b/lldb/test/API/functionalities/postmortem/elf-core/linux-aarch64-pac.out differ
diff --git a/lldb/test/API/functionalities/unwind/aarch64_unwind_pac/Makefile b/lldb/test/API/functionalities/unwind/aarch64_unwind_pac/Makefile
new file mode 100644 (file)
index 0000000..18bb607
--- /dev/null
@@ -0,0 +1,5 @@
+C_SOURCES := main.c
+
+CFLAGS := -g -Os -march=armv8.3-a -mbranch-protection=pac-ret+leaf
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/unwind/aarch64_unwind_pac/TestAArch64UnwindPAC.py b/lldb/test/API/functionalities/unwind/aarch64_unwind_pac/TestAArch64UnwindPAC.py
new file mode 100644 (file)
index 0000000..8f88e64
--- /dev/null
@@ -0,0 +1,44 @@
+"""
+Test that we can backtrace correctly when AArch64 PAC is enabled
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class AArch64UnwindPAC(TestBase):
+    mydir = TestBase.compute_mydir(__file__)
+
+    @skipIf(archs=no_match(["aarch64"]))
+    @skipIf(oslist=no_match(['linux']))
+    def test(self):
+        """Test that we can backtrace correctly when AArch64 PAC is enabled"""
+        self.build()
+
+        self.line = line_number('main.c', '// Frame func_c')
+
+        exe = self.getBuildArtifact("a.out")
+        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
+
+        lldbutil.run_break_set_by_file_and_line(
+            self, "main.c", self.line, num_expected_locations=1)
+        self.runCmd("run", RUN_SUCCEEDED)
+        self.expect("thread backtrace", STOPPED_DUE_TO_BREAKPOINT,
+                    substrs=["stop reason = breakpoint 1."])
+
+        target = self.dbg.GetSelectedTarget()
+        process = target.GetProcess()
+        thread = process.GetThreadAtIndex(0)
+
+        backtrace = ["func_c", "func_b", "func_a", "main", "__libc_start_main", "_start"]
+        self.assertEqual(thread.GetNumFrames(), len(backtrace))
+        for frame_idx, frame in enumerate(thread.frames):
+            frame = thread.GetFrameAtIndex(frame_idx)
+            self.assertTrue(frame)
+            self.assertEqual(frame.GetFunctionName(), backtrace[frame_idx])
+                       # Check line number for functions in main.c
+            if (frame_idx < 4):
+                self.assertEqual(frame.GetLineEntry().GetLine(),
+                                 line_number("main.c", "Frame " + backtrace[frame_idx]))
diff --git a/lldb/test/API/functionalities/unwind/aarch64_unwind_pac/main.c b/lldb/test/API/functionalities/unwind/aarch64_unwind_pac/main.c
new file mode 100644 (file)
index 0000000..7de8fc0
--- /dev/null
@@ -0,0 +1,24 @@
+// This program makes a multi tier nested function call to test AArch64
+// Pointer Authentication feature.
+
+// To enable PAC return address signing compile with following clang arguments:
+// -march=armv8.3-a -mbranch-protection=pac-ret+leaf
+
+#include <stdlib.h>
+
+static void __attribute__((noinline)) func_c(void) {
+  exit(0); // Frame func_c
+}
+
+static void __attribute__((noinline)) func_b(void) {
+  func_c(); // Frame func_b
+}
+
+static void __attribute__((noinline)) func_a(void) {
+  func_b(); // Frame func_a
+}
+
+int main(int argc, char *argv[]) {
+  func_a(); // Frame main
+  return 0;
+}