]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
s390: Represent FP/RA saved in register in SFrame
authorJens Remus <jremus@linux.ibm.com>
Fri, 11 Jul 2025 08:29:40 +0000 (10:29 +0200)
committerJens Remus <jremus@linux.ibm.com>
Fri, 11 Jul 2025 08:29:40 +0000 (10:29 +0200)
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 s390x, 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

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_V2_S390X_OFFSET_IS_REGNUM): New s390x-
specific macro to test whether an SFrame FP/RA offset is a DWARF
register number.
(SFRAME_V2_S390X_OFFSET_ENCODE_REGNUM): New s390x-specific macro
to encode a DWARF register number into an SFrame FP/RA offset.
(SFRAME_V2_S390X_OFFSET_DECODE_REGNUM): New s390x-specific macro
to decode an SFrame FP/RA offset into a DWARF register number.
* sframe-api.h (sframe_fre_get_fp_offset,
sframe_fre_get_fp_offset): Add comment that for s390x the offset
may be an encoded register number.

gas/
* gen-sframe.c (s390_sframe_xlate_do_register): New S390-
specific function.  Uses SFRAME_V2_S390X_OFFSET_ENCODE_REGNUM to
represent FP/RA saved in another register on s390x.
(sframe_xlate_do_register): Invoke s390_sframe_xlate_do_register
on s390x.

libsframe/
* sframe.c (sframe_fre_get_fp_offset, sframe_fre_get_fp_offset):
Add comment that for s390x the offset may be an encoded register
number.
* sframe-dump.c (is_sframe_abi_arch_s390x): New helper to test
whether ABI/arch is s390x.
(dump_sframe_func_with_fres): Use
SFRAME_V2_S390X_OFFSET_IS_REGNUM and
SFRAME_V2_S390X_OFFSET_DECODE_REGNUM to dump FP/RA saved in
another register on s390x.
* doc/sframe-spec.texi (s390x): Document s390x-specific
representation of FP/RA saved in another register.

gas/testsuite/
* gas/cfi-sframe/cfi-sframe.exp: Update s390x-specific SFrame
(error) tests.
* gas/cfi-sframe/cfi-sframe-s390x-fpra-register-err-2.s: Rename
to ...
* gas/cfi-sframe/cfi-sframe-s390x-fpra-register-err-2.d:
Likewise.
* gas/cfi-sframe/cfi-sframe-s390x-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-s390x-fpra-register-1.d: Likewise.
* gas/cfi-sframe/cfi-sframe-s390x-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
gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-fpra-register-1.d [new file with mode: 0644]
gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-fpra-register-1.s [moved from gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-fpra-register-err-2.s with 100% similarity]
gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-fpra-register-err-1.d
gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-fpra-register-err-2.d [deleted file]
gas/testsuite/gas/cfi-sframe/cfi-sframe.exp
include/sframe-api.h
include/sframe.h
libsframe/doc/sframe-spec.texi
libsframe/sframe-dump.c
libsframe/sframe.c

index 3d727b6475d88e64924e7a69fff80d5a5bd8b7d6..2741a8f93affc53b77202cebff1175f66c678c2e 100644 (file)
@@ -1133,6 +1133,36 @@ 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_V2_S390X_OFFSET_ENCODE_REGNUM (cfi_insn->u.rr.reg2));
+  else if (sframe_ra_tracking_p ()
+          && cfi_insn->u.rr.reg1 == SFRAME_CFA_RA_REG)
+    sframe_fre_set_ra_track (cur_fre,
+                            SFRAME_V2_S390X_OFFSET_ENCODE_REGNUM (cfi_insn->u.rr.reg2));
+
+  return SFRAME_XLATE_OK;
+}
+
 /* Translate DW_CFA_register into SFrame context.
    Return SFRAME_XLATE_OK if success.  */
 
@@ -1140,6 +1170,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_S390X_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-s390x-fpra-register-1.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-fpra-register-1.d
new file mode 100644 (file)
index 0000000..aa1b195
--- /dev/null
@@ -0,0 +1,22 @@
+#name: SFrame generation on s390x - RA and then FP saved in registers
+#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: 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
index ad83f650cd68af5e2ebc35fddf5cff41554d943b..f6854c26814f8d97cfe56c785d2abd2f9a394ed9 100644 (file)
@@ -1,6 +1,6 @@
-#name: SFrame generation on s390x - FP and then RA saved in register
+#name: SFrame generation on s390x - FP without RA saved in registers
 #as: --gsframe
-#warning: FP register 11 in .cfi_register
+#warning: FP without RA on stack
 #objdump: --sframe=.sframe
 #...
 Contents of the SFrame section .sframe:
diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-fpra-register-err-2.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-s390x-fpra-register-err-2.d
deleted file mode 100644 (file)
index 7dde59c..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-#name: SFrame generation on s390x - RA and then FP saved in register
-#as: --gsframe
-#warning: RA register 14 in .cfi_register
-#objdump: --sframe=.sframe
-#...
-Contents of the SFrame section .sframe:
-
-  Header :
-
-    Version: SFRAME_VERSION_2
-    Flags: SFRAME_F_FDE_FUNC_START_PCREL
-    Num FDEs: 0
-    Num FREs: 0
-
-#pass
index d17cd767cc6e00790e75e85314b89f053c41633a..1e7fc9e03cad735617d9867e4bd3c307598cc943 100644 (file)
@@ -120,6 +120,6 @@ if { [istarget "s390x*-*-*"] && [gas_sframe_check] } then {
     run_dump_test "cfi-sframe-s390x-err-3"
     run_dump_test "cfi-sframe-s390x-fpra-offset-1"
     run_dump_test "cfi-sframe-s390x-fpra-offset-err-1"
+    run_dump_test "cfi-sframe-s390x-fpra-register-1"
     run_dump_test "cfi-sframe-s390x-fpra-register-err-1"
-    run_dump_test "cfi-sframe-s390x-fpra-register-err-2"
 }
index 3dc18b6ad394977f97f595c38390210ba2974941..cb94f358b3d71de52dc391a35ae0229cc8f27753 100644 (file)
@@ -214,12 +214,18 @@ extern int32_t
 sframe_fre_get_cfa_offset (sframe_decoder_ctx *dtcx,
                           sframe_frame_row_entry *fre, int *errp);
 
-/* Get the FP offset from the FRE.  If the offset is invalid, sets errp.  */
+/* Get the FP offset from the FRE.  If the offset is invalid, sets errp.
+
+   For s390x the offset may be an encoded register number, indicated by
+   LSB set to one, which is only valid in the topmost frame.  */
 extern int32_t
 sframe_fre_get_fp_offset (sframe_decoder_ctx *dctx,
                          sframe_frame_row_entry *fre, int *errp);
 
-/* Get the RA offset from the FRE.  If the offset is invalid, sets errp.  */
+/* Get the RA offset from the FRE.  If the offset is invalid, sets errp.
+
+   For s390x the offset may be an encoded register number, indicated by
+   LSB set to one, which is only valid in the topmost frame.  */
 extern int32_t
 sframe_fre_get_ra_offset (sframe_decoder_ctx *dctx,
                          sframe_frame_row_entry *fre, int *errp);
index 0278a8dc47c8a9c85b4c483879ce0d7bc90e085c..7bbdf04db171134623870fc46ee68d3b09f0fcfe 100644 (file)
@@ -358,6 +358,19 @@ typedef struct sframe_frame_row_entry_addr4
    SP value offset from CFA is -160.  */
 #define SFRAME_S390X_SP_VAL_OFFSET                     (-160)
 
+/* On s390x, 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_V2_S390X_OFFSET_IS_REGNUM(offset) \
+  ((offset) & 1)
+#define SFRAME_V2_S390X_OFFSET_ENCODE_REGNUM(regnum) \
+  (((regnum) << 1) | 1)
+#define SFRAME_V2_S390X_OFFSET_DECODE_REGNUM(offset) \
+  ((offset) >> 1)
+
 #ifdef __cplusplus
 }
 #endif
index 57e116356a18a0b2e84126b9b37ea96da3bb3398..1938207233cdf97fa0abd616ec8996c786caa235 100644 (file)
@@ -146,6 +146,12 @@ changes including the following helper definitions have been incrementally
 added to SFrame version 2 only:
  @itemize @minus
   @item SFRAME_S390X_SP_VAL_OFFSET: SP value offset from CFA.
+  @item SFRAME_V2_S390X_OFFSET_IS_REGNUM: Test whether FP/RA offset is an encoded
+DWARF register number.
+  @item SFRAME_V2_S390X_OFFSET_ENCODE_REGNUM: Encode a DWARF register number as an
+FP/RA offset.
+  @item SFRAME_V2_S390X_OFFSET_DECODE_REGNUM: Decode a DWARF register number from
+an FP/RA offset.
  @end itemize
 @end itemize
 
@@ -792,10 +798,11 @@ This section covers the ABI/arch-specific definition of the SFrame file format.
 
 Currently, the only part of the SFrame file format definition that is
 ABI/arch-specific is the interpretation of the variable number of bytes at the
-tail end of each SFrame FRE.  Currently, these bytes are only used for
-representing stack offsets (for all the currently supported ABIs).  It is
-recommended to peruse this section along with @xref{SFrame Frame Row Entries}
-for clarity of context.
+tail end of each SFrame FRE.  Currently, these bytes are used for representing
+stack offsets (for AMD64 and AARCH64 ABIs).  For s390x ABI, the interpretation
+of these bytes may be stack offsets or even register numbers.  It is recommended
+to peruse this section along with @xref{SFrame Frame Row Entries} for clarity of
+context.
 
 Future ABIs must specify the algorithm for identifying the appropriate SFrame
 FRE stack offsets in this chapter.  This should inevitably include the
@@ -890,16 +897,30 @@ frame.  The third stack offset is used to locate the FP stack slot, by
 interpreting it as: FP = CFA + offset3.  FP remains unchanged, if the offset is
 not available.
 
-Given the nature of things, the number of stack offsets seen on s390x 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.  It is recommended that a stack
+tracer implementation performs the required checks to ensure that restoring
+FP and RA from the said register locations is done only for topmost stack
+frame in the callchain.
+
+Given the nature of things, the number of stack offsets and/or register numbers
+seen on s390x per SFrame FRE is either 1, 2, or 3.
 
 Hence, in summary:
 
-@multitable {Offset ID} {Interpretation in s390x in X}
+@multitable @columnfractions .15 .85
 @headitem Offset ID @tab Interpretation in s390x
 @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
 
 The s390x ELF ABI defines the CFA as stack pointer (SP) at call site +160.  The
index 47ac00e31597e6d64e80814f9ed6969be70d50b6..f17ff64e1224fdc403871affc67e02c4f1dcd7ea 100644 (file)
@@ -38,6 +38,14 @@ is_sframe_abi_arch_aarch64 (sframe_decoder_ctx *sfd_ctx)
   return aarch64_p;
 }
 
+/* Return TRUE if the SFrame section is associated with the s390x ABI.  */
+
+static bool
+is_sframe_abi_arch_s390x (sframe_decoder_ctx *sfd_ctx)
+{
+  return sframe_decoder_get_abi_arch (sfd_ctx) == SFRAME_ABI_S390X_ENDIAN_BIG;
+}
+
 static void
 dump_sframe_header_flags (sframe_decoder_ctx *sfd_ctx)
 {
@@ -186,7 +194,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_s390x (sfd_ctx)
+             && SFRAME_V2_S390X_OFFSET_IS_REGNUM (fp_offset))
+           sprintf (temp, "r%d", SFRAME_V2_S390X_OFFSET_DECODE_REGNUM (fp_offset));
+         else
+           sprintf (temp, "c%+d", fp_offset);
+       }
       else
        strcpy (temp, "u");
       printf ("%-10s", temp);
@@ -198,7 +212,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_s390x (sfd_ctx)
+             && SFRAME_V2_S390X_OFFSET_IS_REGNUM (ra_offset))
+           sprintf (temp, "r%d", SFRAME_V2_S390X_OFFSET_DECODE_REGNUM (ra_offset));
+         else
+           sprintf (temp, "c%+d", ra_offset);
+       }
       else
        strcpy (temp, "u");
 
index ba01f06a09b4a3876812170f3d0fd11b753e1730..af40e5b4fadfae79dd9a0a27b4506f43da9982ee 100644 (file)
@@ -701,7 +701,10 @@ sframe_fre_get_cfa_offset (sframe_decoder_ctx *dctx ATTRIBUTE_UNUSED,
   return sframe_get_fre_offset (fre, SFRAME_FRE_CFA_OFFSET_IDX, errp);
 }
 
-/* Get the FP offset from the FRE.  If the offset is invalid, sets errp.  */
+/* Get the FP offset from the FRE.  If the offset is invalid, sets errp.
+
+   For s390x the offset may be an encoded register number, indicated by
+   LSB set to one, which is only valid in the topmost frame.  */
 
 int32_t
 sframe_fre_get_fp_offset (sframe_decoder_ctx *dctx,
@@ -728,7 +731,10 @@ sframe_fre_get_fp_offset (sframe_decoder_ctx *dctx,
   return sframe_get_fre_offset (fre, fp_offset_idx, errp);
 }
 
-/* Get the RA offset from the FRE.  If the offset is invalid, sets errp.  */
+/* Get the RA offset from the FRE.  If the offset is invalid, sets errp.
+
+   For s390x the offset may be an encoded register number, indicated by
+   LSB set to one, which is only valid in the topmost frame.  */
 
 int32_t
 sframe_fre_get_ra_offset (sframe_decoder_ctx *dctx,