[RFC PATCH v3 2/3] sframe: Start stack trace from PC and CFA

Jens Remus jremus@linux.ibm.com
Wed Jul 23 14:23:46 GMT 2025


Stack tracing in backtrace using SFrame stack trace information starts
with a PC (program counter) in backtrace and the related SP (stack
pointer) and FP (frame pointer) values.  The following are then derived
using SFrame information:
- CFA (Canonical Frame Address): Using the tracked CFA base register
  (SP or FP) and tracked CFA offset from CFA base register
- SP: Using the CFA (the CFA is defined as SP at call site on most
  architectures)
- RA (return address): Using the CFA and the tracked RA stack slot
  offset from CFA
- FP: Using CFA and the tracked FP stack slot offset from CFA, otherwise
  it remains unchanged
If the CFA is based on the FP or if the FP is not saved then the initial
value of the FP is important.  Either because it is required to
determine the CFA (and from that the SP, FP, and RA) or because is not
restored from its stack slot and thus needs to be known.

On x86-64 and Aarch64 the initial FP (in backtrace) can be obtained
using __builtin_frame_address (0).  On s390-64 (s390x) this does not
work as __builtin_frame_address (0) does not return the FP register
value, even if a FP register is used (e.g. due to dynamic stack
allocation).  Instead __builtin_frame_address (0) always returns the
SP register value at function entry.  This is because GCC and Clang on
s390x setup a FP register only as late as possible, for instance after
static stack allocation, which makes the FP register value meaningless
for FP-based stack tracing purposes.

Stack tracing in backtrace always starts in backtrace itself.  Therefore
use __builtin_unwind_init to enforce saving of FP and RA, obtain the CFA
using __builtin_dwarf_cfa, and determine the caller's SP, FP, and RA
using that CFA and the SFrame FP and RA stack slot offsets, as those are
now guaranteed to be saved.  This avoids the need to obtain the initial
SP and FP values at all.

debug/
	* backtrace.c (do_sframe_backtrace): Call __builtin_unwind_init,
	obtain the CFA using __builtin_dwarf_cfa, and initialize SP and
	FP with NULL.

sysdeps/generic/
	* sframe.h (struct cframe): Add field cfa, used for startup of
	SFrame stack trace.
	(__getSP): Remove helper.
	* sframe.c (__stacktrace_sframe): Use CFA from frame in topmost
	frame to startup SFrame stack trace.  Treat FP not saved in
	topmost frame as error.  Update CFA in frame.
	(__getSP): Remove helper.

Suggested-by: Ulrich Weigand <ulrich.weigand@de.ibm.com>
Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---

Notes:
    Changes in v3:
    - Rebase on top of Adhemerval's series "[PATCH v2 0/2] sframe: Disable
      by default and support SFRAME_F_FDE_FUNC_START_PCREL":
      https://inbox.sourceware.org/libc-alpha/20250721234236.1434590-1-adhemerval.zanella@linaro.org/
    - Reword commit subject and GNU ChangeLog.
    
    Following are two options to resolve the SFrame strack trace startup
    issue on s390 64-bit (s390x):
    
    Option A:
    Introduce a helper macro that returns the FP value.  The generic
    implementation (used on e.g. x86-64 and AArch64) would resolve to
    __builtin_frame_address (0).  This is why is needs to be a macro.  The
    s390 implementation would resolve to a new inline helper function
    s390_getFP (), that uses inline assembly to return the preferred FP
    register r11 value.
    
    Option B:
    Use __builtin_unwind_init to enforce saving of SP and FP at function
    entry and use __builtin_dwarf_cfa instead of SFrame information to
    obtain the initial CFA in the topmost frame (i.e. backtrace).  This no
    longer requires the initial SP and FP values.  The SP, FP, and RA of the
    caller are then obtained using the CFA (SP) and SFrame information from
    the stack register save slots (FP and RA).
    
    This patch implements Option B.

 debug/backtrace.c        |  9 +++++---
 sysdeps/generic/sframe.c | 47 +++++++++++++++++++++-------------------
 sysdeps/generic/sframe.h |  5 +----
 3 files changed, 32 insertions(+), 29 deletions(-)

diff --git a/debug/backtrace.c b/debug/backtrace.c
index ea7ac4ddf0a4..d72f725d7da9 100644
--- a/debug/backtrace.c
+++ b/debug/backtrace.c
@@ -55,16 +55,19 @@ struct trace_arg
    backtracer can fall back to using the DWARF unwinder.
 
    This function must be always inline.  Otherwise the
-   __builtin_frame_address and the __getXX helper functions will not
+   __builtin_dwarf_cfa and the __getPC helper functions will not
    return the right addresses.  */
 
 static inline int __attribute__ ((always_inline))
 do_sframe_backtrace (void **array, int size)
 {
   frame frame;
+  /* Force saving of FP and RA on stack.  */
+  __builtin_unwind_init ();
   frame.pc = __getPC ();
-  frame.sp = __getSP ();
-  frame.fp = (_Unwind_Ptr) __builtin_frame_address (0);
+  frame.cfa = (_Unwind_Ptr) __builtin_dwarf_cfa ();
+  frame.sp = (_Unwind_Ptr) NULL;
+  frame.fp = (_Unwind_Ptr) NULL;
   return __stacktrace_sframe (array, size, &frame);
 }
 #endif
diff --git a/sysdeps/generic/sframe.c b/sysdeps/generic/sframe.c
index 0dff78a9cacc..67e019e3e773 100644
--- a/sysdeps/generic/sframe.c
+++ b/sysdeps/generic/sframe.c
@@ -124,16 +124,24 @@ __stacktrace_sframe (void **ra_lst, int count, frame *frame)
 	  return i;
 	}
 
-      /* Get the CFA offset from the FRE.  If offset is unavailable,
-	 sets err.  */
-      cfa_offset = __sframe_fre_get_cfa_offset (dctx, frep, &err);
-      if (err != _URC_NO_REASON)
-	/* Force fallback to DWARF stacktracer.  */
-	return 0;
-
-      /* Get CFA using base reg id from the FRE info.  */
-      cfa = ((__sframe_fre_get_base_reg_id (frep)
-	      == SFRAME_BASE_REG_SP) ? frame->sp : frame->fp) + cfa_offset;
+      if (i == 0)
+	/* The caller (i.e. backtrace) provided its CFA value and ensured
+	   to save its FP and RA at entry, so that the caller's SP, FP, and
+	   RA at entry can be determined using the CFA.  */
+	cfa = frame->cfa;
+      else
+	{
+	  /* Get the CFA offset from the FRE.  If offset is unavailable,
+	     sets err.  */
+	  cfa_offset = __sframe_fre_get_cfa_offset (dctx, frep, &err);
+	  if (err != _URC_NO_REASON)
+	    /* Force fallback to DWARF stacktracer.  */
+	    return 0;
+
+	  /* Get CFA using base reg id from the FRE info.  */
+	  cfa = ((__sframe_fre_get_base_reg_id (frep) == SFRAME_BASE_REG_SP)
+		  ? frame->sp : frame->fp) + cfa_offset;
+	}
 
       /* Get the RA offset from the FRE.  If the offset is
 	 unavailable, sets err.  */
@@ -153,7 +161,12 @@ __stacktrace_sframe (void **ra_lst, int count, frame *frame)
 	 unavailable, sets err.  */
       fp_offset = __sframe_fre_get_fp_offset (dctx, frep, &err);
       frame_ptr = frame->fp;
-      if (err == _URC_NO_REASON)
+      /* The FP must be saved in the topmost frame, as backtrace called
+	 __builtin_unwind_init, which guarantees this.  */
+      if (i == 0 && err != _URC_NO_REASON)
+	/* Force fallback to DWARF stacktracer.  */
+	return 0;
+      else if (err == _URC_NO_REASON)
 	{
 	  /* FP offset is available, get the value stored in the stack
 	     location.  */
@@ -162,6 +175,7 @@ __stacktrace_sframe (void **ra_lst, int count, frame *frame)
 	}
 
       /* Set up for the next frame.  */
+      frame->cfa = cfa;
       frame->fp = frame_ptr;
       frame->sp = cfa + SFRAME_SP_VAL_OFFSET;
       frame->pc = return_addr;
@@ -181,14 +195,3 @@ __getPC (void)
 }
 
 libc_hidden_def (__getPC);
-
-/* A noinline helper used to obtain the caller's current SP.  It
-   mimics gcc14's __builtin_stack_address() functionality.  */
-
-_Unwind_Ptr  __attribute__ ((noinline))
-__getSP (void)
-{
-  return (_Unwind_Ptr) __builtin_dwarf_cfa() + SFRAME_SP_VAL_OFFSET;
-}
-
-libc_hidden_def (__getSP);
diff --git a/sysdeps/generic/sframe.h b/sysdeps/generic/sframe.h
index 6225eda5d6dc..9cedcad257a2 100644
--- a/sysdeps/generic/sframe.h
+++ b/sysdeps/generic/sframe.h
@@ -362,6 +362,7 @@ typedef struct sframe_frame_row_entry_addr4
 typedef struct cframe
 {
   _Unwind_Ptr pc;
+  _Unwind_Ptr cfa;
   _Unwind_Ptr sp;
   _Unwind_Ptr fp;
 } frame;
@@ -374,10 +375,6 @@ libc_hidden_proto (__stacktrace_sframe);
 _Unwind_Ptr __getPC (void);
 libc_hidden_proto (__getPC);
 
-/* Helper used by SFrame tracing algorithm.  */
-_Unwind_Ptr __getSP (void);
-libc_hidden_proto (__getSP);
-
 #ifdef	__cplusplus
 }
 #endif
-- 
2.48.1



More information about the Libc-alpha mailing list