https://github.com/jasonmolenda updated 
https://github.com/llvm/llvm-project/pull/138805

>From 4fc9acd03a58a3617b113352c48e5dd2fdb58eda Mon Sep 17 00:00:00 2001
From: Jason Molenda <jmole...@apple.com>
Date: Tue, 6 May 2025 22:37:17 -0700
Subject: [PATCH 1/4] [lldb] Provide lr value in faulting frame on arm64

When a frameless function faults or is interrupted asynchronously,
the UnwindPlan MAY have no register location rule for the return
address register (lr on arm64); the value is simply live in the
lr register when it was interrupted, and the frame below this on
the stack -- e.g. sigtramp on a Unix system -- has the full
register context, including that register.

RegisterContextUnwind::SavedLocationForRegister, when asked to find
the caller's pc value, will first see if there is a pc register
location.  If there isn't, on a Return Address Register architecture
like arm/mips/riscv, we rewrite the register request from "pc" to
"RA register", and search for a location.

On frame 0 (the live frame) and an interrupted frame, the UnwindPlan
may have no register location rule for the RA Reg, that is valid.
A frameless function that never calls another may simply keep the
return address in the live register the whole way.  Our instruction
emulation unwind plans explicitly add a rule (see Pavel's May 2024
change https://github.com/llvm/llvm-project/pull/91321 ), but an
UnwindPlan sourced from debug_frame may not.

I've got a case where this exactly happens - clang debug_frame for
arm64 where there is no register location for the lr in a frameless
function.  There is a fault in the middle of this frameless function
and we only get the lr value from the fault handler below this frame
if lr has a register location of `IsSame`, in line with Pavel's
2024 change.

Similar to how we see a request of the RA Reg from frame 0 after
failing to find an unwind location for the pc register, the same
style of special casing is needed when this is a function that
was interrupted.

Without this change, we can find the pc of the frame that was
executing when it was interrupted, but we need $lr to find its
caller, and we don't descend down to the trap handler to get that
value, truncating the stack.

rdar://145614545
---
 lldb/source/Target/RegisterContextUnwind.cpp | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/lldb/source/Target/RegisterContextUnwind.cpp 
b/lldb/source/Target/RegisterContextUnwind.cpp
index 3ed49e12476dd..23a86bee2518b 100644
--- a/lldb/source/Target/RegisterContextUnwind.cpp
+++ b/lldb/source/Target/RegisterContextUnwind.cpp
@@ -1377,6 +1377,7 @@ RegisterContextUnwind::SavedLocationForRegister(
         }
       }
 
+      // Check if the active_row has a register location listed.
       if (regnum.IsValid() &&
           
active_row->GetRegisterInfo(regnum.GetAsKind(unwindplan_registerkind),
                                       unwindplan_regloc)) {
@@ -1390,11 +1391,10 @@ RegisterContextUnwind::SavedLocationForRegister(
       // This is frame 0 and we're retrieving the PC and it's saved in a Return
       // Address register and it hasn't been saved anywhere yet -- that is,
       // it's still live in the actual register. Handle this specially.
-
       if (!have_unwindplan_regloc && return_address_reg.IsValid() &&
-          IsFrameZero()) {
-        if (return_address_reg.GetAsKind(eRegisterKindLLDB) !=
-            LLDB_INVALID_REGNUM) {
+          return_address_reg.GetAsKind(eRegisterKindLLDB) !=
+              LLDB_INVALID_REGNUM) {
+        if (IsFrameZero()) {
           lldb_private::UnwindLLDB::ConcreteRegisterLocation new_regloc;
           new_regloc.type = UnwindLLDB::ConcreteRegisterLocation::
               eRegisterInLiveRegisterContext;
@@ -1408,6 +1408,17 @@ RegisterContextUnwind::SavedLocationForRegister(
                        return_address_reg.GetAsKind(eRegisterKindLLDB),
                        return_address_reg.GetAsKind(eRegisterKindLLDB));
           return UnwindLLDB::RegisterSearchResult::eRegisterFound;
+        } else if (BehavesLikeZerothFrame()) {
+          // This function was interrupted asynchronously -- it faulted,
+          // an async interrupt, a timer fired, a debugger expression etc.
+          // The caller's pc is in the Return Address register, but the
+          // UnwindPlan for this function may have no location rule for
+          // the RA reg.
+          // This means that the caller's return address is in the RA reg
+          // when the function was interrupted--descend down one stack frame
+          // to retrieve it from the trap handler's saved context.
+          unwindplan_regloc.SetSame();
+          have_unwindplan_regloc = true;
         }
       }
 

>From b10162deb49ecddca6439665c2b8ea1995fdd81f Mon Sep 17 00:00:00 2001
From: Jason Molenda <jmole...@apple.com>
Date: Wed, 7 May 2025 23:24:17 -0700
Subject: [PATCH 2/4] Add the sources for an API test case to be written

---
 .../interrupt-and-trap-funcs.s                | 92 +++++++++++++++++++
 .../macosx/unwind-frameless-faulted/main.c    |  6 ++
 2 files changed, 98 insertions(+)
 create mode 100644 
lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s
 create mode 100644 lldb/test/API/macosx/unwind-frameless-faulted/main.c

diff --git 
a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s 
b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s
new file mode 100644
index 0000000000000..23eb35c2b31ca
--- /dev/null
+++ b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s
@@ -0,0 +1,92 @@
+#define DW_CFA_register 0x9
+#define ehframe_x22  22
+#define ehframe_x23  23
+#define ehframe_pc 32
+
+  .section  __TEXT,__text,regular,pure_instructions
+
+//--------------------------------------
+// to_be_interrupted() a frameless function that does a non-ABI
+// function call ("is interrupted/traps" simulated) to trap().
+// Before it branches to trap(), it puts its return address in
+// x23.  trap() knows to branch back to $x23 when it has finished.
+//--------------------------------------
+  .globl  _to_be_interrupted
+  .p2align  2
+_to_be_interrupted:
+  .cfi_startproc
+
+  // This is a garbage entry to ensure that eh_frame is emitted,
+  // it isn't used for anything.  If there's no eh_frame, lldb
+  // can do an assembly emulation scan and add a rule for $lr
+  // which won't expose the issue at hand.
+  .cfi_escape DW_CFA_register, ehframe_x22, ehframe_x23
+  mov x24, x0
+  add x24, x24, #1
+
+  adrp x23, L_.return@PAGE        ; put return address in x4
+  add x23, x23, L_.return@PAGEOFF
+
+  b _trap                     ; branch to trap handler, fake async interrupt
+
+L_.return:
+  mov x0, x24
+  ret
+  .cfi_endproc
+  
+
+
+//--------------------------------------
+// trap() trap handler function, sets up stack frame
+// with special unwind rule for the pc value of the
+// "interrupted" stack frame (it's in x23), then calls
+// break_to_debugger().
+//--------------------------------------
+  .globl  _trap
+  .p2align  2
+_trap:                                  
+  .cfi_startproc
+  .cfi_signal_frame
+
+  // The pc value when we were interrupted is in x23
+  .cfi_escape DW_CFA_register, ehframe_pc, ehframe_x23
+
+  // standard prologue save of fp & lr so we can call puts()
+  sub sp, sp, #32
+  stp x29, x30, [sp, #16]
+  add x29, sp, #16
+  .cfi_def_cfa w29, 16
+  .cfi_offset w30, -8
+  .cfi_offset w29, -16
+
+  bl _break_to_debugger
+
+  ldp x29, x30, [sp, #16]
+  add sp, sp, #32
+
+  // jump back to $x23 to resume execution of to_be_interrupted
+  br x23
+  .cfi_endproc
+
+//--------------------------------------
+// break_to_debugger() executes a BRK instruction
+//--------------------------------------
+  .globl _break_to_debugger
+  .p2align  2
+_break_to_debugger:                                  
+  .cfi_startproc
+
+  // standard prologue save of fp & lr so we can call puts()
+  sub sp, sp, #32
+  stp x29, x30, [sp, #16]
+  add x29, sp, #16
+  .cfi_def_cfa w29, 16
+  .cfi_offset w30, -8
+  .cfi_offset w29, -16
+
+  brk #0xf000   ;; __builtin_debugtrap aarch64 instruction
+
+  ldp x29, x30, [sp, #16]
+  add sp, sp, #32
+  ret
+  .cfi_endproc
diff --git a/lldb/test/API/macosx/unwind-frameless-faulted/main.c 
b/lldb/test/API/macosx/unwind-frameless-faulted/main.c
new file mode 100644
index 0000000000000..e121809f25478
--- /dev/null
+++ b/lldb/test/API/macosx/unwind-frameless-faulted/main.c
@@ -0,0 +1,6 @@
+int to_be_interrupted(int);
+int main() {
+  int c = 10;
+  c = to_be_interrupted(c);
+  return c;
+}

>From 508b9cb404072263a6ed52a75b3b7aad5d949510 Mon Sep 17 00:00:00 2001
From: Jason Molenda <jmole...@apple.com>
Date: Wed, 7 May 2025 23:24:58 -0700
Subject: [PATCH 3/4] Add a log msg when a frame is marked as a trap handler
 via the UnwindPlan.  And use the trap handler flag for zeroth frame so we can
 properly unwind a frameless function that was interrupted while in the trap
 handler function.

---
 lldb/source/Target/RegisterContextUnwind.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lldb/source/Target/RegisterContextUnwind.cpp 
b/lldb/source/Target/RegisterContextUnwind.cpp
index 51c0f56e9bc1e..cf4b96c6eda9f 100644
--- a/lldb/source/Target/RegisterContextUnwind.cpp
+++ b/lldb/source/Target/RegisterContextUnwind.cpp
@@ -248,6 +248,7 @@ void RegisterContextUnwind::InitializeZerothFrame() {
     active_row =
         m_full_unwind_plan_sp->GetRowForFunctionOffset(m_current_offset);
     row_register_kind = m_full_unwind_plan_sp->GetRegisterKind();
+    PropagateTrapHandlerFlagFromUnwindPlan(m_full_unwind_plan_sp);
     if (active_row && log) {
       StreamString active_row_strm;
       active_row->Dump(active_row_strm, m_full_unwind_plan_sp.get(), &m_thread,
@@ -1933,6 +1934,7 @@ void 
RegisterContextUnwind::PropagateTrapHandlerFlagFromUnwindPlan(
   }
 
   m_frame_type = eTrapHandlerFrame;
+  UnwindLogMsg("This frame is marked as a trap handler via its UnwindPlan");
 
   if (m_current_offset_backed_up_one != m_current_offset) {
     // We backed up the pc by 1 to compute the symbol context, but

>From d8843fe86c0eaaa1483bed0317f832b7630d8548 Mon Sep 17 00:00:00 2001
From: Jason Molenda <jmole...@apple.com>
Date: Wed, 7 May 2025 23:28:40 -0700
Subject: [PATCH 4/4] Update assembly a bit.

---
 .../interrupt-and-trap-funcs.s                      | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git 
a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s 
b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s
index 23eb35c2b31ca..708ad9ed56a1c 100644
--- a/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s
+++ b/lldb/test/API/macosx/unwind-frameless-faulted/interrupt-and-trap-funcs.s
@@ -51,7 +51,8 @@ _trap:
   // The pc value when we were interrupted is in x23
   .cfi_escape DW_CFA_register, ehframe_pc, ehframe_x23
 
-  // standard prologue save of fp & lr so we can call puts()
+  // standard prologue save of fp & lr so we can call 
+  // break_to_debugger()
   sub sp, sp, #32
   stp x29, x30, [sp, #16]
   add x29, sp, #16
@@ -76,17 +77,7 @@ _trap:
 _break_to_debugger:                                  
   .cfi_startproc
 
-  // standard prologue save of fp & lr so we can call puts()
-  sub sp, sp, #32
-  stp x29, x30, [sp, #16]
-  add x29, sp, #16
-  .cfi_def_cfa w29, 16
-  .cfi_offset w30, -8
-  .cfi_offset w29, -16
-
   brk #0xf000   ;; __builtin_debugtrap aarch64 instruction
 
-  ldp x29, x30, [sp, #16]
-  add sp, sp, #32
   ret
   .cfi_endproc

_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to