[PATCH] gas: sframe: Represent .cfi_undefined RA as SFrame FDE without FREs

Jens Remus jremus@linux.ibm.com
Fri Jul 18 16:33:59 GMT 2025


In DWARF CFI an "undefined" register rule for the return address (RA)
register indicates that there is no return address and the stack trace
is complete.  Represent DW_CFA_undefined as SFrame FDE without any FREs,
so that a stack tracer implementation can use this as indication that
an outermost frame has been reached and the trace is complete.

This representation is backwards compatible, as existing stack tracers
should already deal with the case, that an SFrame FDE has a FRE count of
zero and stop the trace.

gas/
	* gen-sframe.h (enum sframe_xlate_err): Add
	SFRAME_XLATE_RA_UNDEFINED.
	* gen-sframe.c (sframe_xlate_do_cfi_undefined): Represent
	DW_CFA_undefined RA as SFrame FDE without and SFrame FREs and
	return SFRAME_XLATE_RA_UNDEFINED to stop any further processing
	of DWARF FREs.
	(sframe_do_fde): Add comment that RA undefined may stop
	processing of DWARF FREs.
	(create_sframe_all): Handle SFRAME_XLATE_RA_UNDEFINED with
	zero FREs as good case.

libsframe/
	* doc/sframe-spec.texi (Changes from Version 1 to Version 2):
	Mention that a SFrame FDE without any FREs indicates an
	outermost frame with an "undefined" RA register rule.
	(sfde_func_num_fres): Document that a FRE count of zero
	indicates an outermost frame with an "undefined" RA register
	rule.

gas/testsuite/
	* gas/cfi-sframe/cfi-sframe.exp: Run tests for .cfi_undefined RA
	on AArch64, s390x, and x86-64.
	* gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.d: Add test
	for .cfi_undefined RA on AArch64.
	* gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.s: Likewise.
	* as/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.d: Add test
	for .cfi_undefined RA on s390x.
	* gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.s: Likewise.
	* gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.d: Add test
	for .cfi_undefined RA on x86-64.
	* gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.s: Likewise.

Signed-off-by: Jens Remus <jremus@linux.ibm.com>
---

Notes (jremus):
    Stack tracer implementations using SFrame, such as Glibc backtrace(),
    benefit from the ability to detect whether an outermost frame has been
    reached, which indicates that the trace is complete.  In DWARF CFI this
    is indicated by an "undefined" register rule for the return address
    register.
    
    This would enable to implement s390 64-bit (s390x) support for
    backtracing using SFrame in Glibc [1], which requires the stack tracer
    implementation to reliably detect, that an outermost frame has been
    reached.
    
    Representing .cfi_undefined RA as SFrame FDE without any FREs is a
    simplification compared to DWARF CFI, as the latter can represent
    RA undefined for a range of PCs within a DWARF FDE.  An alternative
    would have been to represent .cfi_undefined RA as SFrame FRE without
    any offsets, which would have provided the same versatility.  Note
    that the current choice to represent this at a SFrame FDE level
    does not prevent to represent it at a SFrame FRE level in addition
    in the future.
    
    [1]: [RFC PATCH v2 0/2] s390x: Add support for SFrame stack
         backtracing,
         https://inbox.sourceware.org/libc-alpha/20250627133813.2198784-1-jremus@linux.ibm.com/

 gas/gen-sframe.c                              | 24 +++++++++++++++----
 gas/gen-sframe.h                              |  2 ++
 .../cfi-sframe-aarch64-ra-undefined-1.d       | 17 +++++++++++++
 .../cfi-sframe-aarch64-ra-undefined-1.s       | 13 ++++++++++
 .../cfi-sframe-s390x-ra-undefined-1.d         | 18 ++++++++++++++
 .../cfi-sframe-s390x-ra-undefined-1.s         | 11 +++++++++
 .../cfi-sframe-x86_64-ra-undefined-1.d        | 19 +++++++++++++++
 .../cfi-sframe-x86_64-ra-undefined-1.s        | 11 +++++++++
 gas/testsuite/gas/cfi-sframe/cfi-sframe.exp   |  3 +++
 libsframe/doc/sframe-spec.texi                |  8 ++++++-
 10 files changed, 120 insertions(+), 6 deletions(-)
 create mode 100644 gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.d
 create mode 100644 gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.s
 create mode 100644 gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.d
 create mode 100644 gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.s
 create mode 100644 gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.d
 create mode 100644 gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.s

diff --git a/gas/gen-sframe.c b/gas/gen-sframe.c
index 806ef5f9b5be..72f84fcca16e 100644
--- a/gas/gen-sframe.c
+++ b/gas/gen-sframe.c
@@ -1597,17 +1597,29 @@ sframe_xlate_do_cfi_escape (const struct sframe_xlate_ctx *xlate_ctx,
    Return SFRAME_XLATE_OK if success.  */
 
 static int
-sframe_xlate_do_cfi_undefined (const struct sframe_xlate_ctx *xlate_ctx ATTRIBUTE_UNUSED,
+sframe_xlate_do_cfi_undefined (struct sframe_xlate_ctx *xlate_ctx,
 			       const struct cfi_insn_data *cfi_insn)
 {
   if (cfi_insn->u.r == SFRAME_CFA_FP_REG
-      || cfi_insn->u.r == SFRAME_CFA_RA_REG
       || cfi_insn->u.r == SFRAME_CFA_SP_REG)
     {
       as_warn (_("no SFrame FDE emitted; %s reg %u in .cfi_undefined"),
 	       sframe_register_name (cfi_insn->u.r), cfi_insn->u.r);
       return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented.  */
     }
+  else if (cfi_insn->u.r == SFRAME_CFA_RA_REG)
+    {
+      /* Represent RA undefined (i.e. outermost frame) as FDE without any
+	 FREs.  */
+      sframe_xlate_ctx_cleanup (xlate_ctx);
+      xlate_ctx->first_fre = NULL;
+      xlate_ctx->last_fre = NULL;
+      xlate_ctx->cur_fre = NULL;
+      xlate_ctx->remember_fre = NULL;
+      xlate_ctx->num_xlate_fres = 0;
+
+      return SFRAME_XLATE_RA_UNDEFINED;
+    }
 
   /* Safe to skip.  */
   return SFRAME_XLATE_OK;
@@ -1819,8 +1831,9 @@ sframe_do_fde (struct sframe_xlate_ctx *xlate_ctx,
       if (err != SFRAME_XLATE_OK)
 	{
 	  /* Skip generating SFrame stack trace info for the function if any
-	     offending CFI is encountered by sframe_do_cfi_insn ().  Warning
-	     message already printed by sframe_do_cfi_insn ().  */
+	     offending CFI is encountered by sframe_do_cfi_insn () or if
+	     RA is undefined (i.e. outermost frame).
+	     Warning message already printed by sframe_do_cfi_insn ().  */
 	  return err; /* Return the error code.  */
 	}
     }
@@ -1885,7 +1898,8 @@ create_sframe_all (void)
 	 SFrame FDE if there is no stack tracing information for the
 	 function.  */
       int err = sframe_do_fde (xlate_ctx, dw_fde);
-      if (err || xlate_ctx->num_xlate_fres == 0)
+      if ((err != SFRAME_XLATE_OK && err != SFRAME_XLATE_RA_UNDEFINED)
+	  || (err != SFRAME_XLATE_RA_UNDEFINED && xlate_ctx->num_xlate_fres == 0))
 	{
 	  sframe_xlate_ctx_cleanup (xlate_ctx);
 	  sframe_fde_free (sframe_fde);
diff --git a/gas/gen-sframe.h b/gas/gen-sframe.h
index e4a4c6ea0ceb..a46c9a0caa71 100644
--- a/gas/gen-sframe.h
+++ b/gas/gen-sframe.h
@@ -124,6 +124,8 @@ enum sframe_xlate_err
   SFRAME_XLATE_OK = 0,
   /* Error.  */
   SFRAME_XLATE_ERROR = 1,
+  /* RA undefined (i.e. outermost frame).  */
+  SFRAME_XLATE_RA_UNDEFINED = 2,
   /* Detailed error codes.  */
   SFRAME_XLATE_ERR_INVAL = -1,
   SFRAME_XLATE_ERR_NOTREPRESENTED = -2,
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.d
new file mode 100644
index 000000000000..a4d4885df58f
--- /dev/null
+++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.d
@@ -0,0 +1,17 @@
+#as: --gsframe
+#objdump: --sframe=.sframe
+#name: SFrame generation on aarch64 - .cfi_undefined RA
+#...
+Contents of the SFrame section .sframe:
+  Header :
+
+    Version: SFRAME_VERSION_2
+    Flags: SFRAME_F_FDE_FUNC_START_PCREL
+    Num FDEs: 1
+    Num FREs: 0
+
+  Function Index :
+
+    func idx \[0\]: pc = 0x0, size = 16 bytes
+    STARTPC +CFA +FP +RA +
+#pass
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.s b/gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.s
new file mode 100644
index 000000000000..7b28ab8f6528
--- /dev/null
+++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-aarch64-ra-undefined-1.s
@@ -0,0 +1,13 @@
+	.cfi_startproc
+	stp	fp, lr, [sp, #-16]!
+	.cfi_def_cfa_offset 16
+	.cfi_offset 29, -16
+	.cfi_offset 30, -8
+	nop
+	.cfi_undefined 30
+	ldp	fp, lr, [sp], #16
+	.cfi_restore 20
+	.cfi_restore 19
+	.cfi_def_cfa_offset 0
+	ret	lr
+	.cfi_endproc
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.d
new file mode 100644
index 000000000000..18e24ef207de
--- /dev/null
+++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.d
@@ -0,0 +1,18 @@
+#name: SFrame generation on s390x - .cfi_undefined RA
+#as: --gsframe
+#objdump: --sframe=.sframe
+#...
+Contents of the SFrame section .sframe:
+
+  Header :
+
+    Version: SFRAME_VERSION_2
+    Flags: SFRAME_F_FDE_FUNC_START_PCREL
+    Num FDEs: 1
+    Num FREs: 0
+
+  Function Index :
+
+    func idx \[0\]: pc = 0x0, size = 18 bytes
+    STARTPC +CFA +FP +RA +
+#pass
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.s b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.s
new file mode 100644
index 000000000000..dda7abcdf4d7
--- /dev/null
+++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-ra-undefined-1.s
@@ -0,0 +1,11 @@
+	.cfi_startproc
+	stmg	%r11,%r15,48(%r15)
+	.cfi_offset 14, -48
+	.cfi_offset 15, -40
+	nop
+	.cfi_undefined 14
+	lmg	%r14,%r15,160+48(%r11)
+	.cfi_restore 15
+	.cfi_restore 14
+	br	%r14
+	.cfi_endproc
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.d
new file mode 100644
index 000000000000..cd24ce667ed1
--- /dev/null
+++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.d
@@ -0,0 +1,19 @@
+#as: --gsframe -O0
+#objdump: --sframe=.sframe
+#name: SFrame generation on x86_64 - .cfi_undefined RA
+#...
+Contents of the SFrame section .sframe:
+
+  Header :
+
+    Version: SFRAME_VERSION_2
+    Flags: SFRAME_F_FDE_FUNC_START_PCREL
+    CFA fixed RA offset: \-8
+    Num FDEs: 1
+    Num FREs: 0
+
+  Function Index :
+
+    func idx \[0\]: pc = 0x0, size = 6 bytes
+    STARTPC +CFA +FP +RA +
+#pass
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.s b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.s
new file mode 100644
index 000000000000..073755b5716b
--- /dev/null
+++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-ra-undefined-1.s
@@ -0,0 +1,11 @@
+	.cfi_startproc
+	pushq	%rbp
+	.cfi_def_cfa_offset 16
+	.cfi_offset 6, -16
+	movq	%rsp, %rbp
+	.cfi_def_cfa_register 6
+	nop
+	.cfi_undefined 16
+	.cfi_def_cfa 7, 8
+	ret
+	.cfi_endproc
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp b/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp
index 00a3ecc067ce..42f77103d184 100644
--- a/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp
+++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp
@@ -62,6 +62,7 @@ if { [istarget "x86_64-*-*"] && [gas_sframe_check] } then {
 	run_dump_test "cfi-sframe-x86_64-empty-2"
 	run_dump_test "cfi-sframe-x86_64-empty-3"
 	run_dump_test "cfi-sframe-x86_64-empty-4"
+	run_dump_test "cfi-sframe-x86_64-ra-undefined-1"
 	set ASFLAGS "$old_ASFLAGS"
     }
 }
@@ -73,6 +74,7 @@ if { [istarget "aarch64*-*-*"] && [gas_sframe_check] } then {
     run_dump_test "cfi-sframe-aarch64-3"
     run_dump_test "cfi-sframe-aarch64-4"
     run_dump_test "cfi-sframe-aarch64-pac-ab-key-1"
+    run_dump_test "cfi-sframe-aarch64-ra-undefined-1"
 }
 
 # s390x specific tests
@@ -87,4 +89,5 @@ if { [istarget "s390x*-*-*"] && [gas_sframe_check] } then {
     run_dump_test "cfi-sframe-s390x-fpra-offset-2"
     run_dump_test "cfi-sframe-s390x-fpra-register-1"
     run_dump_test "cfi-sframe-s390x-fpra-register-2"
+    run_dump_test "cfi-sframe-s390x-ra-undefined-1"
 }
diff --git a/libsframe/doc/sframe-spec.texi b/libsframe/doc/sframe-spec.texi
index 7307789df264..29a822373928 100644
--- a/libsframe/doc/sframe-spec.texi
+++ b/libsframe/doc/sframe-spec.texi
@@ -165,6 +165,10 @@ CFA offset adjustment and then scale down by CFA offset alignment factor).
   @item SFRAME_V2_S390X_CFA_OFFSET_DECODE: Decode CFA offset (i.e., scale up
 by CFA offset alignment factor and then revert CFA offset adjustment).
  @end itemize
+@item
+Represent the "undefined" register rule for the RA register as an SFrame FDE
+without any FREs.  A stack tracer may use this as indication that an outermost
+frame has been reached and the trace is complete.
 @end itemize
 
 SFrame version 1 is now obsolete and should not be used.
@@ -531,7 +535,9 @@ function's first SFrame FRE in the SFrame section.
 @tab @code{uint32_t}
 @tab @code{sfde_func_num_fres}
 @tab Unsigned 32-bit integral field specifying the total number of SFrame FREs
-used for the function.
+used for the function.  A value of zero indicates that an outermost frame with
+an "undefined" register rule for the return address has been reached.  For a
+stack tracer this indicates the trace is complete.
 
 @item 0x10
 @tab @code{uint8_t}
-- 
2.48.1



More information about the Binutils mailing list