[PATCH 4/8] s390: Represent FP/RA saved in register in SFrame

Indu Bhagat indu.bhagat@oracle.com
Thu Apr 17 18:50:51 GMT 2025


On 4/2/25 9:11 AM, Jens Remus wrote:
> GCC on s390x, when in a leaf function, can be observed to save the
> frame pointer (FP) and/or return address (RA) register in a floating-
> point registers (FPR) instead of on the stack.  This is declared using
> the following CFI directive:
> 
>    .cfi_register <fp/ra-regnum>, <fpr-regnum>
> 
> SFrame cannot represent the FP and/or RA being saved in another
> register.  It does only track the CFA base register (SP/FP), CFA offset
> from CFA base register, and FP and RA save area offsets from CFA.
> 
> On s390x the FP and/or RA are only saved in another FPR when in a leaf
> function.  That is a function that does not call any other function.
> Therefore it can ever only be the topmost function in a call chain.
> An unwinder by default has access to all registers of the function that
> is the topmost on the call stack.  Therefore no further information
> is required to restore FP/RA from the FPR.
> 
> Represent FP/RA saved in another register on s390, by encoding the
> DWARF register number shifted by one to the left with the least-
> significant bit set in the offset as follows:
> 
>    offset = (regnum << 1) | 1
> 

I wonder if it makes sense to just save the three bits for any future 
use by doing this now:

     offset = (regnum << 3) | 1

I am still undecided.

> The use of the least-significant bit of the offset as indication is
> possible, as the stack pointer (SP), the CFA, and any register save
> area slots are 8-byte aligned according to the s390x ELF ABI:
> - The stack pointer (SP) "shall maintain an 8-byte alignment". [1]
> - The CFA is defined as SP at call site +160. [2]
> - Pointers and 8-byte integers, such as general register values, must
>    be 8-byte aligned. [3]
> SFrame FP and RA stack offsets must therefore always be a multiple of
> 8 on s390x.  Note that for the same reason the DWARF data alignment
> factor is -8 on s390x (see DWARF2_CIE_DATA_ALIGNMENT).
> 
> Add s390x-specific SFrame (error) tests for FP/RA saved in FPRs in leaf
> function.
> 
> [1]: s390x ELF ABI, sections "Register Roles" and "Stack Frame
>       Allocation", https://github.com/IBM/s390x-abi/releases
> [2]: s390x ELF ABI, commit 4e38ad9c8a88 ("Document the CFA"),
>       https://github.com/IBM/s390x-abi/commit/4e38ad9c8a88
> [3]: s390x ELF ABI, section "Fundamental Types", table "Scalar types",
>       https://github.com/IBM/s390x-abi/releases
> 
> include/
> 	* sframe.h (SFRAME_S390_OFFSET_IS_REGNUM): New S390-specific
> 	macro to test whether an SFrame FP/RA offset is a DWARF register
> 	number.
> 	(SFRAME_S390_OFFSET_ENCODE_REGNUM): New S390-specific macro to
> 	encode a DWARF register number into an SFrame FP/RA offset.
> 	(SFRAME_S390_OFFSET_DECODE_REGNUM): New S390-specific macro to
> 	decode an SFrame FP/RA offset into a DWARF register number.
> 
> gas/
> 	* gen-sframe.c (s390_sframe_xlate_do_register): New S390-
> 	specific function.  Uses SFRAME_S390_OFFSET_ENCODE_REGNUM to
> 	represent FP/RA saved in another register on S390.
> 	(sframe_xlate_do_register): Invoke s390_sframe_xlate_do_register
> 	on S390.
> 
> libsframe/
> 	* sframe-dump.c (is_sframe_abi_arch_s390): New helper to test
> 	whether ABI/arch is S390.
> 	(dump_sframe_func_with_fres): Use SFRAME_S390_OFFSET_IS_REGNUM
> 	and SFRAME_S390_OFFSET_DECODE_REGNUM to dump FP/RA saved in
> 	another register on S390.
> 	* doc/sframe-spec.texi (S390): Document S390-specific
> 	representation of FP/RA saved in another register.
> 

I did think for a bit: what sframe_fre_get_fp_offset () and 
sframe_fre_get_ra_offset () in libsframe/sframe.c ?

On more thought, I think its okay for these APIs to return the "raw 
data", and a stack tracer for s390x can do the right thing if the LSB is 
1 etc.  However, I have not yet looked at the stacktracer fixes (in the 
branch we shared sometime ago), to be able to comment if one way is 
clunker than other.  Thoughts ?

Perhaps we atleast add some comments, if you see appropriate, in the 
above APIs.  Something  along the lines that for s390x, for top most 
functions, this data may be for a save to register.  The API just 
returns the raw data in the offset.  Perhaps in the function-level 
comment if you like.  This is just to keep things clear as sometimes 
folks may refer to libsframe APIs when implementing their own SFrame 
based stack tracer.

> gas/testsuite/
> 	* gas/cfi-sframe/cfi-sframe.exp: Update s390x-specific SFrame
> 	(error) tests.
> 	* gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-2.s: Rename
> 	to ...
> 	* gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-2.d:
> 	Likewise.
> 	* gas/cfi-sframe/cfi-sframe-s390-fpra-register-1.s: This.  Test
> 	no longer triggers a warning, as SFrame can represent FP and RA
> 	saved in registers.
> 	* gas/cfi-sframe/cfi-sframe-s390-fpra-register-1.d: Likewise.
> 	* gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-1.d: Test now
> 	triggers a different warning, as SFrame can represent FP and RA
> 	saved in registers, but not FP without RA saved in register.
> 
> Signed-off-by: Jens Remus <jremus@linux.ibm.com>
> ---
>   gas/gen-sframe.c                              | 37 +++++++++++++++++++
>   .../cfi-sframe-s390-fpra-register-1.d         | 22 +++++++++++
>   ...-2.s => cfi-sframe-s390-fpra-register-1.s} |  0
>   .../cfi-sframe-s390-fpra-register-err-1.d     |  4 +-
>   .../cfi-sframe-s390-fpra-register-err-2.d     | 15 --------
>   gas/testsuite/gas/cfi-sframe/cfi-sframe.exp   |  2 +-
>   include/sframe.h                              | 13 +++++++
>   libsframe/doc/sframe-spec.texi                | 21 ++++++++---
>   libsframe/sframe-dump.c                       | 24 +++++++++++-
>   9 files changed, 113 insertions(+), 25 deletions(-)
>   create mode 100644 gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-1.d
>   rename gas/testsuite/gas/cfi-sframe/{cfi-sframe-s390-fpra-register-err-2.s => cfi-sframe-s390-fpra-register-1.s} (100%)
>   delete mode 100644 gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-2.d
> 
> diff --git a/gas/gen-sframe.c b/gas/gen-sframe.c
> index 4d00c4be0a80..fb3a0342b1ee 100644
> --- a/gas/gen-sframe.c
> +++ b/gas/gen-sframe.c
> @@ -1149,6 +1149,39 @@ sframe_xlate_do_val_offset (const struct sframe_xlate_ctx *xlate_ctx ATTRIBUTE_U
>     return SFRAME_XLATE_OK;
>   }
>   
> +/* S390-specific translate DW_CFA_register into SFrame context.
> +   Return SFRAME_XLATE_OK if success.  */
> +
> +static int
> +s390_sframe_xlate_do_register (struct sframe_xlate_ctx *xlate_ctx,
> +			       struct cfi_insn_data *cfi_insn)
> +{
> +  /* The scratchpad FRE currently being updated with each cfi_insn
> +     being interpreted.  This FRE eventually gets linked in into the
> +     list of FREs for the specific function.  */
> +  struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre;
> +
> +  gas_assert (cur_fre);
> +
> +  /* Change the rule for the register indicated by the register number to
> +     be the specified register.  Encode the register number as offset by
> +     shifting it to the left by one and setting the least-significant bit
> +     (LSB).  The LSB can be used to differentiate offsets from register
> +     numbers, as offsets from CFA are always a multiple of -8 on s390x.  */
> +  if (cfi_insn->u.rr.reg1 == SFRAME_CFA_FP_REG)
> +    sframe_fre_set_bp_track (cur_fre,
> +			     SFRAME_S390_OFFSET_ENCODE_REGNUM (cfi_insn->u.rr.reg2));
> +#ifdef SFRAME_FRE_RA_TRACKING
> +  else if (sframe_ra_tracking_p ()
> +	   && cfi_insn->u.rr.reg1 == SFRAME_CFA_RA_REG)
> +    sframe_fre_set_ra_track (cur_fre,
> +			     SFRAME_S390_OFFSET_ENCODE_REGNUM (cfi_insn->u.rr.reg2));
> +#endif
> +
> +  /* Safe to skip.  */
> +  return SFRAME_XLATE_OK;
> +}
> +
>   /* Translate DW_CFA_register into SFrame context.
>      Return SFRAME_XLATE_OK if success.  */
>   
> @@ -1156,6 +1189,10 @@ static int
>   sframe_xlate_do_register (struct sframe_xlate_ctx *xlate_ctx ATTRIBUTE_UNUSED,
>   			  struct cfi_insn_data *cfi_insn)
>   {
> +  /* Conditionally invoke S390-specific implementation.  */
> +  if (sframe_get_abi_arch () == SFRAME_ABI_S390_ENDIAN_BIG)
> +    return s390_sframe_xlate_do_register (xlate_ctx, cfi_insn);
> +
>     /* Previous value of register1 is register2.  However, if the specified
>        register1 is not interesting (FP or RA reg), the current DW_CFA_register
>        instruction can be safely skipped without sacrificing the asynchronicity of
> diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-1.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-1.d
> new file mode 100644
> index 000000000000..736869834d8b
> --- /dev/null
> +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-1.d
> @@ -0,0 +1,22 @@
> +#objdump: --sframe=.sframe
> +#name: SFrame generation on s390 - FP and RA registers saved in FPR registers
> +#...
> +Contents of the SFrame section .sframe:
> +
> +  Header :
> +
> +    Version: SFRAME_VERSION_2
> +    Flags: NONE
> +    Num FDEs: 1
> +    Num FREs: 5
> +
> +  Function Index :
> +
> +    func idx \[0\]: pc = 0x0, size = 26 bytes
> +    STARTPC +CFA +FP +RA +
> +    0+0000 +sp\+160 +u +u +
> +    0+0004 +sp\+160 +u +r16 +
> +    0+0008 +sp\+160 +r17 +r16 +
> +    0+0014 +sp\+160 +u +r16 +
> +    0+0018 +sp\+160 +u +u +
> +#pass
> diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-2.s b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-1.s
> similarity index 100%
> rename from gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-2.s
> rename to gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-1.s
> diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-1.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-1.d
> index ca6f9df729d1..6baaf59271b1 100644
> --- a/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-1.d
> +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-1.d
> @@ -1,6 +1,6 @@
> -#name: SFrame generation on s390 - FP and RA saved in register
> +#name: SFrame generation on s390 - FP without RA saved in register
>   #as: --gsframe
> -#warning: skipping SFrame FDE; FP register 11 in .cfi_register
> +#warning: skipping SFrame FDE; FP without RA on stack
>   #objdump: --sframe=.sframe
>   #...
>   Contents of the SFrame section .sframe:
> diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-2.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-2.d
> deleted file mode 100644
> index 670146c8eb14..000000000000
> --- a/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390-fpra-register-err-2.d
> +++ /dev/null
> @@ -1,15 +0,0 @@
> -#name: SFrame generation on s390 - FP and RA saved in register
> -#as: --gsframe
> -#warning: skipping SFrame FDE; RA register 14 in .cfi_register
> -#objdump: --sframe=.sframe
> -#...
> -Contents of the SFrame section .sframe:
> -
> -  Header :
> -
> -    Version: SFRAME_VERSION_2
> -    Flags: NONE
> -    Num FDEs: 0
> -    Num FREs: 0
> -
> -#pass
> diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp b/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp
> index 04ffbde260aa..13b2e236bb46 100644
> --- a/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp
> +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp
> @@ -116,6 +116,6 @@ if { [istarget "s390x*-*-*"] && [gas_sframe_check] } then {
>       run_dump_test "cfi-sframe-s390-err-3"
>       run_dump_test "cfi-sframe-s390-fpra-offset-1"
>       run_dump_test "cfi-sframe-s390-fpra-offset-err-1"
> +    run_dump_test "cfi-sframe-s390-fpra-register-1"
>       run_dump_test "cfi-sframe-s390-fpra-register-err-1"
> -    run_dump_test "cfi-sframe-s390-fpra-register-err-2"
>   }
> diff --git a/include/sframe.h b/include/sframe.h
> index 684aab811f8d..a790f4078121 100644
> --- a/include/sframe.h
> +++ b/include/sframe.h
> @@ -347,6 +347,19 @@ typedef struct sframe_frame_row_entry_addr4
>      SP value offset from CFA is -160.  */
>   #define SFRAME_S390_SP_VAL_OFFSET		-160
>   
> +/* On S390, the FP and RA registers can be saved either on the stack or,
> +   in case of leaf functions, in registers.  Store DWARF register numbers
> +   encoded as offset by using the least-significant bit (LSB) as indicator:
> +   - LSB=0: Stack offset.  The s390x ELF ABI mandates that stack register
> +     slots must be 8-byte aligned.
> +   - LSB=1: DWARF register number shifted to the left by one.  */
> +#define SFRAME_S390_OFFSET_IS_REGNUM(offset) \
> +  ((offset) & 1)
> +#define SFRAME_S390_OFFSET_ENCODE_REGNUM(regnum) \
> +  (((regnum) << 1) | 1)
> +#define SFRAME_S390_OFFSET_DECODE_REGNUM(offset) \
> +  ((offset) >> 1)
> +
>   #ifdef	__cplusplus
>   }
>   #endif
> diff --git a/libsframe/doc/sframe-spec.texi b/libsframe/doc/sframe-spec.texi
> index eba497b4d371..5bd027828e3f 100644
> --- a/libsframe/doc/sframe-spec.texi
> +++ b/libsframe/doc/sframe-spec.texi
> @@ -848,16 +848,27 @@ tracked in this ABI, the second stack offset is always used to locate the RA
>   stack slot, by interpreting it as: RA = CFA + offset2.  The third stack offset
>   is used to locate the FP stack slot, by interpreting it as: FP = CFA + offset3.
>   
> -Given the nature of things, the number of stack offsets seen on S390 per
> -SFrame FRE is either 1, 2, or 3.
> +In leaf functions the RA and FP may be saved in other registers, such as
> +floating-point registers (FPRs), instead of on the stack.  To represent this
> +in the SFrame stack trace format the DWARF register number is encoded as
> +RA/FP offset using the least-significant bit (LSB) as indication:
> +offset = (regnum << 1) | 1.  A LSB of zero indicates a stack slot offset.
> +A LSB of one indicates a DWARF register number, which is interpreted as:
> +regnum = offset >> 1.  Given the nature of leaf functions, this can only occur
> +in the topmost frame during stack tracing.
> +

Can we also weave in something along the lines of:

It is recommended that a stack tracer implementation performs the 
required sanity checks to ensure that restoring FP and RA from the said 
register locations is done only for top-most stack frames in the callchain.

> +Given the nature of things, the number of stack offsets and/or register numbers
> +seen on S390 per SFrame FRE is either 1, 2, or 3.
>   
>   Hence, in summary:
>   
> -@multitable {Offset ID} {Interpretation in S390 in X}
> +@multitable @columnfractions .15 .85
>   @headitem Offset ID @tab Interpretation in S390
>   @item 1 @tab CFA = @code{BASE_REG} + offset1
> -@item 2 @tab RA = CFA + offset2
> -@item 3 @tab FP = CFA + offset3
> +@item 2 @tab RA stack slot = CFA + offset2, if (offset2 & 1 == 0)
> +           @*RA register number = offset2 >> 1, if (offset2 & 1 == 1)
> +@item 3 @tab FP stack slot = CFA + offset3, if (offset3 & 1 == 0)
> +           @*FP register number = offset3 >> 1, if (offset3 & 1 == 1)
>   @end multitable
>   
>   On S390, specifically s390x, the CFA is defined as stack pointer (SP) at call
> diff --git a/libsframe/sframe-dump.c b/libsframe/sframe-dump.c
> index 1fa508d9bad2..f86b5c09ddc5 100644
> --- a/libsframe/sframe-dump.c
> +++ b/libsframe/sframe-dump.c
> @@ -40,6 +40,14 @@ is_sframe_abi_arch_aarch64 (sframe_decoder_ctx *sfd_ctx)
>     return aarch64_p;
>   }
>   
> +/* Return TRUE if the SFrame section is associated with the s390 ABIs.  */
> +
> +static bool
> +is_sframe_abi_arch_s390 (sframe_decoder_ctx *sfd_ctx)
> +{
> +  return sframe_decoder_get_abi_arch (sfd_ctx) == SFRAME_ABI_S390_ENDIAN_BIG;
> +}
> +
>   static void
>   dump_sframe_header (sframe_decoder_ctx *sfd_ctx)
>   {
> @@ -175,7 +183,13 @@ dump_sframe_func_with_fres (sframe_decoder_ctx *sfd_ctx,
>   
>         /* Dump SP/FP info.  */
>         if (err[1] == 0)
> -	sprintf (temp, "c%+d", fp_offset);
> +	{
> +	  if (is_sframe_abi_arch_s390 (sfd_ctx)
> +	      && SFRAME_S390_OFFSET_IS_REGNUM (fp_offset))
> +	    sprintf (temp, "r%d", SFRAME_S390_OFFSET_DECODE_REGNUM (fp_offset));
> +	  else
> +	    sprintf (temp, "c%+d", fp_offset);
> +	}
>         else
>   	strcpy (temp, "u");
>         printf ("%-10s", temp);
> @@ -187,7 +201,13 @@ dump_sframe_func_with_fres (sframe_decoder_ctx *sfd_ctx,
>   	  != SFRAME_CFA_FIXED_RA_INVALID)
>   	strcpy (temp, "f");
>         else if (err[2] == 0)
> -	sprintf (temp, "c%+d", ra_offset);
> +	{
> +	  if (is_sframe_abi_arch_s390 (sfd_ctx)
> +	      && SFRAME_S390_OFFSET_IS_REGNUM (ra_offset))
> +	    sprintf (temp, "r%d", SFRAME_S390_OFFSET_DECODE_REGNUM (ra_offset));
> +	  else
> +	    sprintf (temp, "c%+d", ra_offset);
> +	}
>         else
>   	strcpy (temp, "u");
>   



More information about the Binutils mailing list