[Lldb-commits] [lldb] 9db2541 - [lldb][AArch64] Add UnwindPlan for Linux sigreturn

David Spickett via lldb-commits lldb-commits at lists.llvm.org
Thu Nov 11 03:32:10 PST 2021


Author: David Spickett
Date: 2021-11-11T11:32:06Z
New Revision: 9db2541d4c30100d7ccc6cc9db717df102b302d9

URL: https://github.com/llvm/llvm-project/commit/9db2541d4c30100d7ccc6cc9db717df102b302d9
DIFF: https://github.com/llvm/llvm-project/commit/9db2541d4c30100d7ccc6cc9db717df102b302d9.diff

LOG: [lldb][AArch64] Add UnwindPlan for Linux sigreturn

This adds a specific unwind plan for AArch64 Linux sigreturn frames.
Previously we assumed that the fp would be valid here but it is not.

https://github.com/torvalds/linux/blob/master/arch/arm64/kernel/vdso/sigreturn.S

On Ubuntu Bionic it happened to point to an old frame info which meant
you got what looked like a correct backtrace. On Focal, the info is
completely invalid. (probably due to some code shuffling in libc)

This adds an UnwindPlan that knows that the sp in a sigreturn frame
points to an rt_sigframe from which we can offset to get saved
sp and pc values to backtrace correctly.

Based on LibUnwind's change: https://reviews.llvm.org/D90898

A new test is added that sets all compares the frames from the initial
signal catch to the handler break. Ensuring that the stack/frame pointer,
function name and register values match.
(this test is AArch64 Linux specific because it's the only one
with a specific unwind plan for this situation)

Fixes https://bugs.llvm.org/show_bug.cgi?id=52165

Reviewed By: omjavaid, labath

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

Added: 
    lldb/test/API/linux/aarch64/unwind_signal/Makefile
    lldb/test/API/linux/aarch64/unwind_signal/TestUnwindSignal.py
    lldb/test/API/linux/aarch64/unwind_signal/main.c

Modified: 
    lldb/include/lldb/Target/Platform.h
    lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp
    lldb/source/Plugins/Platform/Linux/PlatformLinux.h
    lldb/source/Target/RegisterContextUnwind.cpp
    lldb/test/API/functionalities/signal/handle-abrt/TestHandleAbort.py

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Target/Platform.h b/lldb/include/lldb/Target/Platform.h
index 1106ce868761c..86c62140ba1ff 100644
--- a/lldb/include/lldb/Target/Platform.h
+++ b/lldb/include/lldb/Target/Platform.h
@@ -719,6 +719,24 @@ class Platform : public PluginInterface {
   ///     A list of symbol names.  The list may be empty.
   virtual const std::vector<ConstString> &GetTrapHandlerSymbolNames();
 
+  /// Try to get a specific unwind plan for a named trap handler.
+  /// The default is not to have specific unwind plans for trap handlers.
+  ///
+  /// \param[in] triple
+  ///     Triple of the current target.
+  ///
+  /// \param[in] name
+  ///     Name of the trap handler function.
+  ///
+  /// \return
+  ///     A specific unwind plan for that trap handler, or an empty
+  ///     shared pointer. The latter means there is no specific plan,
+  ///     unwind as normal.
+  virtual lldb::UnwindPlanSP
+  GetTrapHandlerUnwindPlan(const llvm::Triple &triple, ConstString name) {
+    return {};
+  }
+
   /// Find a support executable that may not live within in the standard
   /// locations related to LLDB.
   ///

diff  --git a/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp b/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp
index 52c86d7a1328b..940ecc5c5e831 100644
--- a/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp
+++ b/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp
@@ -14,9 +14,11 @@
 #include <sys/utsname.h>
 #endif
 
+#include "Utility/ARM64_DWARF_Registers.h"
 #include "lldb/Core/Debugger.h"
 #include "lldb/Core/PluginManager.h"
 #include "lldb/Host/HostInfo.h"
+#include "lldb/Symbol/UnwindPlan.h"
 #include "lldb/Target/Process.h"
 #include "lldb/Target/Target.h"
 #include "lldb/Utility/FileSpec.h"
@@ -251,6 +253,96 @@ void PlatformLinux::CalculateTrapHandlerSymbolNames() {
   m_trap_handlers.push_back(ConstString("__restore_rt"));
 }
 
+static lldb::UnwindPlanSP GetAArch64TrapHanlderUnwindPlan(ConstString name) {
+  UnwindPlanSP unwind_plan_sp;
+  if (name != "__kernel_rt_sigreturn")
+    return unwind_plan_sp;
+
+  UnwindPlan::RowSP row = std::make_shared<UnwindPlan::Row>();
+  row->SetOffset(0);
+
+  // In the signal trampoline frame, sp points to an rt_sigframe[1], which is:
+  //  - 128-byte siginfo struct
+  //  - ucontext struct:
+  //     - 8-byte long (uc_flags)
+  //     - 8-byte pointer (uc_link)
+  //     - 24-byte stack_t
+  //     - 128-byte signal set
+  //     - 8 bytes of padding because sigcontext has 16-byte alignment
+  //     - sigcontext/mcontext_t
+  // [1]
+  // https://github.com/torvalds/linux/blob/master/arch/arm64/kernel/signal.c
+  int32_t offset = 128 + 8 + 8 + 24 + 128 + 8;
+  // Then sigcontext[2] is:
+  // - 8 byte fault address
+  // - 31 8 byte registers
+  // - 8 byte sp
+  // - 8 byte pc
+  // [2]
+  // https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/sigcontext.h
+
+  // Skip fault address
+  offset += 8;
+  row->GetCFAValue().SetIsRegisterPlusOffset(arm64_dwarf::sp, offset);
+
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x0, 0 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x1, 1 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x2, 2 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x3, 3 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x4, 4 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x5, 5 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x6, 6 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x7, 7 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x8, 8 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x9, 9 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x10, 10 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x11, 11 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x12, 12 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x13, 13 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x14, 14 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x15, 15 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x16, 16 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x17, 17 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x18, 18 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x19, 19 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x20, 20 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x21, 21 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x22, 22 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x23, 23 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x24, 24 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x25, 25 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x26, 26 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x27, 27 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x28, 28 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::fp, 29 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::x30, 30 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::sp, 31 * 8, false);
+  row->SetRegisterLocationToAtCFAPlusOffset(arm64_dwarf::pc, 32 * 8, false);
+
+  // The sigcontext may also contain floating point and SVE registers.
+  // However this would require a dynamic unwind plan so they are not included
+  // here.
+
+  unwind_plan_sp = std::make_shared<UnwindPlan>(eRegisterKindDWARF);
+  unwind_plan_sp->AppendRow(row);
+  unwind_plan_sp->SetSourceName("AArch64 Linux sigcontext");
+  unwind_plan_sp->SetSourcedFromCompiler(eLazyBoolYes);
+  // Because sp is the same throughout the function
+  unwind_plan_sp->SetUnwindPlanValidAtAllInstructions(eLazyBoolYes);
+  unwind_plan_sp->SetUnwindPlanForSignalTrap(eLazyBoolYes);
+
+  return unwind_plan_sp;
+}
+
+lldb::UnwindPlanSP
+PlatformLinux::GetTrapHandlerUnwindPlan(const llvm::Triple &triple,
+                                        ConstString name) {
+  if (triple.isAArch64())
+    return GetAArch64TrapHanlderUnwindPlan(name);
+
+  return {};
+}
+
 MmapArgList PlatformLinux::GetMmapArgumentList(const ArchSpec &arch,
                                                addr_t addr, addr_t length,
                                                unsigned prot, unsigned flags,

diff  --git a/lldb/source/Plugins/Platform/Linux/PlatformLinux.h b/lldb/source/Plugins/Platform/Linux/PlatformLinux.h
index 0dca31273fde0..516089cd783b1 100644
--- a/lldb/source/Plugins/Platform/Linux/PlatformLinux.h
+++ b/lldb/source/Plugins/Platform/Linux/PlatformLinux.h
@@ -50,6 +50,9 @@ class PlatformLinux : public PlatformPOSIX {
 
   void CalculateTrapHandlerSymbolNames() override;
 
+  lldb::UnwindPlanSP GetTrapHandlerUnwindPlan(const llvm::Triple &triple,
+                                              ConstString name) override;
+
   MmapArgList GetMmapArgumentList(const ArchSpec &arch, lldb::addr_t addr,
                                   lldb::addr_t length, unsigned prot,
                                   unsigned flags, lldb::addr_t fd,

diff  --git a/lldb/source/Target/RegisterContextUnwind.cpp b/lldb/source/Target/RegisterContextUnwind.cpp
index 1ce21e6306e09..96b69640a3a3a 100644
--- a/lldb/source/Target/RegisterContextUnwind.cpp
+++ b/lldb/source/Target/RegisterContextUnwind.cpp
@@ -893,13 +893,22 @@ UnwindPlanSP RegisterContextUnwind::GetFullUnwindPlanForFrame() {
     return arch_default_unwind_plan_sp;
   }
 
-  // If we're in _sigtramp(), unwinding past this frame requires special
-  // knowledge.  On Mac OS X this knowledge is properly encoded in the eh_frame
-  // section, so prefer that if available. On other platforms we may need to
-  // provide a platform-specific UnwindPlan which encodes the details of how to
-  // unwind out of sigtramp.
   if (m_frame_type == eTrapHandlerFrame && process) {
     m_fast_unwind_plan_sp.reset();
+
+    // On some platforms the unwind information for signal handlers is not
+    // present or correct. Give the platform plugins a chance to provide
+    // substitute plan. Otherwise, use eh_frame.
+    if (m_sym_ctx_valid) {
+      lldb::PlatformSP platform = process->GetTarget().GetPlatform();
+      unwind_plan_sp = platform->GetTrapHandlerUnwindPlan(
+          process->GetTarget().GetArchitecture().GetTriple(),
+          GetSymbolOrFunctionName(m_sym_ctx));
+
+      if (unwind_plan_sp)
+        return unwind_plan_sp;
+    }
+
     unwind_plan_sp =
         func_unwinders_sp->GetEHFrameUnwindPlan(process->GetTarget());
     if (!unwind_plan_sp)

diff  --git a/lldb/test/API/functionalities/signal/handle-abrt/TestHandleAbort.py b/lldb/test/API/functionalities/signal/handle-abrt/TestHandleAbort.py
index 013ea8859b45a..5f3eb31c83aee 100644
--- a/lldb/test/API/functionalities/signal/handle-abrt/TestHandleAbort.py
+++ b/lldb/test/API/functionalities/signal/handle-abrt/TestHandleAbort.py
@@ -16,8 +16,6 @@ class HandleAbortTestCase(TestBase):
     NO_DEBUG_INFO_TESTCASE = True
 
     @skipIfWindows  # signals do not exist on Windows
-    # Fails on Ubuntu Focal
-    @skipIf(archs=["aarch64"], oslist=["linux"])
     @expectedFailureNetBSD
     def test_inferior_handle_sigabrt(self):
         """Inferior calls abort() and handles the resultant SIGABRT.

diff  --git a/lldb/test/API/linux/aarch64/unwind_signal/Makefile b/lldb/test/API/linux/aarch64/unwind_signal/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/unwind_signal/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules

diff  --git a/lldb/test/API/linux/aarch64/unwind_signal/TestUnwindSignal.py b/lldb/test/API/linux/aarch64/unwind_signal/TestUnwindSignal.py
new file mode 100644
index 0000000000000..7f7b079543629
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/unwind_signal/TestUnwindSignal.py
@@ -0,0 +1,80 @@
+"""Test that we can unwind out of a signal handler.
+   Which for AArch64 Linux requires a specific unwind plan."""
+
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class UnwindSignalTestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipUnlessArch("aarch64")
+    @skipUnlessPlatform(["linux"])
+    def test_unwind_signal(self):
+        """Inferior calls sigill() and handles the resultant SIGILL.
+           Stopped at a breakpoint in the handler, check that we can unwind
+           back to sigill() and get the expected register contents there."""
+        self.build()
+        exe = self.getBuildArtifact("a.out")
+
+        target = self.dbg.CreateTarget(exe)
+        self.assertTrue(target, VALID_TARGET)
+
+        process = target.LaunchSimple(
+            None, None, self.get_process_working_directory())
+        self.assertTrue(process, PROCESS_IS_VALID)
+        self.assertEqual(process.GetState(), lldb.eStateStopped)
+        signo = process.GetUnixSignals().GetSignalNumberFromName("SIGILL")
+
+        thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonSignal)
+        self.assertTrue(
+            thread and thread.IsValid(),
+            "Thread should be stopped due to a signal")
+        self.assertTrue(
+            thread.GetStopReasonDataCount() >= 1,
+            "There should be data in the event.")
+        self.assertEqual(thread.GetStopReasonDataAtIndex(0),
+                         signo, "The stop signal should be SIGILL")
+
+        # Continue to breakpoint in sigill handler
+        bkpt = target.FindBreakpointByID(
+            lldbutil.run_break_set_by_source_regexp(self, "Set a breakpoint here"))
+        threads = lldbutil.continue_to_breakpoint(process, bkpt)
+        self.assertEqual(len(threads), 1, "Expected single thread")
+        thread = threads[0]
+
+        # Expect breakpoint in 'handler'
+        frame = thread.GetFrameAtIndex(0)
+        self.assertEqual(frame.GetDisplayFunctionName(), "handler", "Unexpected break?")
+
+        # Expect that unwinding should find 'sigill'
+        found_caller = False
+        for frame in thread.get_thread_frames():
+            if frame.GetDisplayFunctionName() == "sigill":
+                # We should have ascending values in the x registers
+                regs = frame.GetRegisters().GetValueAtIndex(0)
+                err = lldb.SBError()
+
+                for i in range(31):
+                  name = 'x{}'.format(i)
+                  value = regs.GetChildMemberWithName(name).GetValueAsUnsigned(err)
+                  self.assertTrue(err.Success(), "Failed to get register {}: {}".format(
+                                      name, err))
+                  self.assertEqual(value, i, "Unexpected value for register {}".format(
+                                      name))
+
+                found_caller = True
+                break
+
+        self.assertTrue(found_caller, "Unwinding did not find func that caused the SIGILL")
+
+        # Continue until we exit.
+        process.Continue()
+        self.assertEqual(process.GetState(), lldb.eStateExited)
+        self.assertEqual(process.GetExitStatus(), 0)

diff  --git a/lldb/test/API/linux/aarch64/unwind_signal/main.c b/lldb/test/API/linux/aarch64/unwind_signal/main.c
new file mode 100644
index 0000000000000..ba4346545bd9a
--- /dev/null
+++ b/lldb/test/API/linux/aarch64/unwind_signal/main.c
@@ -0,0 +1,64 @@
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+void handler(int sig) {
+  // The kernel only changes a few registers so set them all to something other
+  // than the values in sigill() so that we can't fall back to real registers
+  // and still pass the test.
+#define SETREG(N) "mov x" N ", #" N "+1\n\t"
+  asm volatile(
+      /* clang-format off */
+      /* x0 is used for a parameter */
+                   SETREG("1")  SETREG("2")  SETREG("3")
+      SETREG("4")  SETREG("5")  SETREG("6")  SETREG("7")
+      SETREG("8")  SETREG("9")  SETREG("10") SETREG("11")
+      SETREG("12") SETREG("13") SETREG("14") SETREG("15")
+      SETREG("16") SETREG("17") SETREG("18") SETREG("19")
+      SETREG("20") SETREG("21") SETREG("22") SETREG("23")
+      SETREG("24") SETREG("25") SETREG("26") SETREG("27")
+      SETREG("28") // fp/x29 needed for unwiding
+      SETREG("30") // 31 is xzr/sp
+      /* clang-format on */
+      ::
+          : /* skipped x0 */ "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8",
+            "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18",
+            "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27",
+            "x28",
+            /* skipped fp/x29 */ "x30");
+  printf("Set a breakpoint here.\n");
+  exit(0);
+}
+
+static void sigill() {
+  // Set all general registers to known values to check
+  // that the signal unwind plan sets their locations correctly.
+#define SETREG(N) "mov x" N ", #" N "\n\t"
+  asm volatile(
+      /* clang-format off */
+      SETREG("0")  SETREG("1")  SETREG("2")  SETREG("3")
+      SETREG("4")  SETREG("5")  SETREG("6")  SETREG("7")
+      SETREG("8")  SETREG("9")  SETREG("10") SETREG("11")
+      SETREG("12") SETREG("13") SETREG("14") SETREG("15")
+      SETREG("16") SETREG("17") SETREG("18") SETREG("19")
+      SETREG("20") SETREG("21") SETREG("22") SETREG("23")
+      SETREG("24") SETREG("25") SETREG("26") SETREG("27")
+      SETREG("28") SETREG("29") SETREG("30") /* 31 is xzr/sp */
+      /* clang-format on */
+      ".inst   0x00000000\n\t" // udf #0 (old binutils don't support udf)
+      ::
+          : "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10",
+            "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18", "x19",
+            "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28",
+            "x29", "x30");
+}
+
+int main() {
+  if (signal(SIGILL, handler) == SIG_ERR) {
+    perror("signal");
+    return 1;
+  }
+
+  sigill();
+  return 2;
+}


        


More information about the lldb-commits mailing list