[PATCH v2] gas: sframe: Represent .cfi_undefined RA as SFrame FDE without FREs
Indu Bhagat
indu.bhagat@oracle.com
Tue Jul 22 03:00:28 GMT 2025
On 7/21/25 9:23 AM, Jens Remus wrote:
> 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.
>
Hi Jens,
Thanks for your patch.
While representing the construct "DW_CFA_undefined RA" with an SFrame
FDE with 0 FREs is OK, I think using this as an unambiguous marker for
outermost frame may be not be
I think of case when, say, we may need to carry some function-specific
information in the SFrame FDE, but cannot represent the stack trace
informtion in SFrame format (for various reasons). In such a case, we
may want an SFrame FDE with 0 FREs (with markers on FDE as necessary).
Yes that implcitly indicates that one cannot stack trace beyond this
function (as there is no information for stacktracing), but is that
necessarily the outermost frame? May be not.
Which is why I think for a stack tracer, using "SFrame FDE with 0 FREs"
_together_ with a marker like FP is zero on AMD64 and FP/LR are zero on
AArch64 is still reasonable. If a similar check is not possible for
s390x as you point out (although I have a question on that, posted on
the thread on libc-alpha), an option is to use the spare bit in FDE func
info to unambiguously mark outermost frame along with an SFrame FDE with
0 FREs.
Meanwhile this patch below is generally OK. The text in sframe-spec will
need adjustment, depending on your inputs to above. Also a nit: some
test asm files have tab between opcode and operand.
Thanks
> 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 stops processing
> of further 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):
> Changes in V2:
> - Use sframe_xlate_ctx_init to re-initialize translation context.
> - Reword comment on sframe_xlate_do_cfi_escape.
> - Reword comment in sframe_do_fde for SFRAME_XLATE_RA_UNDEFINED.
> - Reword comment in create_sframe_all for SFRAME_XLATE_RA_UNDEFINED.
>
> 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 | 45 ++++++++++++++-----
> 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, 135 insertions(+), 12 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..54f7fefc1f89 100644
> --- a/gas/gen-sframe.c
> +++ b/gas/gen-sframe.c
> @@ -1590,24 +1590,43 @@ sframe_xlate_do_cfi_escape (const struct sframe_xlate_ctx *xlate_ctx,
> /* Translate DW_CFA_undefined into SFrame context.
>
> DW_CFA_undefined op indicates that from now on, the previous value of
> - register can’t be restored anymore. In SFrame stack trace, we cannot
> - represent such a semantic. So, we skip generating an SFrame FDE for this,
> - when a register of interest is used with DW_CFA_undefined.
> + register can’t be restored anymore. In DWARF, for the return address (RA)
> + register, this indicates to an unwinder that there is no return address
> + and the unwind is complete.
> +
> + In SFrame, represent the use of the RA register with DW_CFA_undefined as
> + SFrame FDE without any FREs. Stack tracers can use this as indication
> + that an outermost frame has been reached and the stack trace is complete.
> + The use of other registers of interest with DW_CFA_undefined cannot be
> + represented in SFrame. Therefore skip generating an SFrame FDE.
>
> 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. */
> + const struct fde_entry *dw_fde = xlate_ctx->dw_fde;
> +
> + /* Re-initialize translation context while preserving the associated
> + DWARF FDE. */
> + sframe_xlate_ctx_cleanup (xlate_ctx);
> + sframe_xlate_ctx_init (xlate_ctx);
> + xlate_ctx->dw_fde = dw_fde;
> +
> + return SFRAME_XLATE_RA_UNDEFINED;
> + }
> > /* Safe to skip. */
> return SFRAME_XLATE_OK;
> @@ -1819,8 +1838,10 @@ 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 (). Likewise
> + stop translating any further CFI, if RA undefined is encountered
> + (SFRAME_XLATE_RA_UNDEFINED).
> + Warning message already printed by sframe_do_cfi_insn (). */
> return err; /* Return the error code. */
> }
> }
> @@ -1881,11 +1902,13 @@ create_sframe_all (void)
> sframe_xlate_ctx_init (xlate_ctx);
>
> /* Process and link SFrame FDEs if no error. Also skip adding an SFrame
> - FDE if it does not contain any SFrame FREs. There is little use of an
> - SFrame FDE if there is no stack tracing information for the
> - function. */
> + FDE if it does not contain any SFrame FREs, except if it is to
> + represent RA undefined (SFRAME_XLATE_RA_UNDEFINED). Otherwise there
> + is little use of an 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}
More information about the Binutils
mailing list