]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
[SFrame-V3] sframe: gas: translate specific CFI directives for SFRAME_FDE_TYPE_FLEX
authorIndu Bhagat <indu.bhagat@oracle.com>
Fri, 16 Jan 2026 00:42:50 +0000 (16:42 -0800)
committerIndu Bhagat <indu.bhagat@oracle.com>
Fri, 16 Jan 2026 01:02:26 +0000 (17:02 -0800)
This patch updates the SFrame generation in GAS to translate specific
.cfi_escape directives into the new SFrame V3 FDE type
SFRAME_FDE_TYPE_FLEX.

The primary goal is to support code patterns where:
  - the Canonical Frame Address (CFA) is not defined by a simple offset
    from the Stack Pointer (SP) or Frame Pointer (FP), or where the CFA
    rule involves a dereference. Such patterns are generated by compilers
    for stack realignment (e.g., DRAP on x86_64, or when mixing legacy
    codes that keep 4-byte stack alignment with modern codes that keep
    16-byte stack alignment for SSE compatibility).
  - the Frame Pointer is not defined by a simple offset from the CFA,
    but may even involve another register and/or dereferencing.
  - the Return Address is not defined by a simple offset from the CFA,
    but may even involve another register and/or dereferencing.

Support for non-SP/FP based CFA: Update sframe_xlate_do_def_cfa () and
sframe_xlate_do_def_cfa_register () to detect when a non-SP/FP register
is used for the CFA.

Support for CFA expressions: A vital part of supporting the
above-mentioned cases on AMD64 is support for CFA expressions.  Add
sframe_xlate_do_escape_cfa_expr () to parse simple
DW_CFA_def_cfa_expression sequence in .cfi_escape.

Support for FP expressions: Update sframe_xlate_do_escape_expr () to
handle DW_CFA_expression involving DW_OP_breg6 (rbp) on AMD64,
allowing for tracking of the Frame Pointer when it is saved with a
dereference rule in the DRAP pattern.

The "support" for both CFA expressions and FP expressions is quite
minimal, and is tailored to the most commonly seen occurrences generated
by GCC for AMD64.

gas/
* gas/gen-sframe.c (output_sframe_row_entry_offsets):
(sframe_xlate_do_def_cfa): Handle non-SP/FP CFA registers by setting
flex_p for AMD64.
(sframe_xlate_do_def_cfa_register): Likewise.
(sframe_xlate_escape_sleb128_to_int64): New definition.
(sframe_xlate_do_escape_cfa_expr): New function to handle
DW_CFA_def_cfa_expression of specific shapes.
(sframe_xlate_do_escape_expr): Update to handle dereferenced FP rules.
(sframe_xlate_do_cfi_escape): Invoke sframe_xlate_do_escape_cfa_expr ().
(create_sframe_all): In case of error, but when signal frame is
also true, there cannot be a flex FDE.

gas/gen-sframe.c

index 8da9de33ad91e5c90a9517bef2db7b48387c3e55..c21ac9b63aaa96fcde5a10cd64f64a6a743b1573 100644 (file)
@@ -24,6 +24,7 @@
 #include "sframe-internal.h"
 #include "gen-sframe.h"
 #include "dw2gencfi.h"
+#include "leb128.h"
 
 #ifdef support_sframe_p
 
@@ -1250,30 +1251,46 @@ sframe_xlate_do_def_cfa (struct sframe_xlate_ctx *xlate_ctx,
     sframe_fre_set_begin_addr (cur_fre,
                               get_dw_fde_start_addrS (xlate_ctx->dw_fde));
   }
-  /* Define the current CFA rule to use the provided register and
-     offset.  However, if the register is not FP/SP, skip creating
-     SFrame stack trace info for the function.  */
-  if (cfi_insn->u.ri.reg != SFRAME_CFA_SP_REG
-      && cfi_insn->u.ri.reg != SFRAME_CFA_FP_REG)
-    {
-      as_warn (_("no SFrame FDE emitted; "
-                "non-SP/FP register %u in .cfi_def_cfa"),
-              cfi_insn->u.ri.reg);
-      return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented.  */
-    }
-  else if (sframe_fre_stack_offset_bound_p (cfi_insn->u.ri.offset, true))
-    {
-      sframe_fre_set_cfa_base_reg (cur_fre, cfi_insn->u.ri.reg);
-      sframe_fre_set_cfa_offset (cur_fre, cfi_insn->u.ri.offset);
-      cur_fre->merge_candidate = false;
-    }
-  else
+
+  offsetT offset = cfi_insn->u.ri.offset;
+  bool bound_p = sframe_fre_stack_offset_bound_p (offset, true);
+  if (!bound_p)
     {
       as_warn (_("no SFrame FDE emitted; "
                 ".cfi_def_cfa with unsupported offset value"));
       return SFRAME_XLATE_ERR_NOTREPRESENTED;
     }
 
+  /* Define the current CFA rule to use the provided register and
+     offset.  Typically, the CFA rule uses SP/FP based CFA.  However, with
+     SFrame V3 specification, if the CFA register is not FP/SP, SFrame FDE type
+     SFRAME_FDE_TYPE_FLEX type may be used.
+
+     GAS uses the hook sframe_support_flex_fde_p () to determine if SFrame FDE
+     of type SFRAME_FDE_TYPE_FLEX can be emitted for the specific target.
+     Non-SP/FP based CFA may be seen for:
+       - AMD64 (e.g., DRAP, stack alignment), or
+       - s390x, where this may be seen for (GCC) generated code for static stack
+         clash protection.  */
+  if (cfi_insn->u.ri.reg != SFRAME_CFA_SP_REG
+      && cfi_insn->u.ri.reg != SFRAME_CFA_FP_REG)
+    {
+      if (!sframe_support_flex_fde_p ())
+       {
+         as_warn (_("no SFrame FDE emitted; "
+                    "non-SP/FP register %u in .cfi_def_cfa"),
+                  cfi_insn->u.ri.reg);
+         return SFRAME_XLATE_ERR_NOTREPRESENTED;
+       }
+      else
+       xlate_ctx->flex_p = true;
+    }
+
+  sframe_fre_set_cfa_base_reg (cur_fre, cfi_insn->u.ri.reg);
+  sframe_fre_set_cfa_offset (cur_fre, cfi_insn->u.ri.offset);
+  cur_fre->merge_candidate = false;
+  cur_fre->cfa_deref_p = false;
+
   return SFRAME_XLATE_OK;
 }
 
@@ -1295,14 +1312,22 @@ sframe_xlate_do_def_cfa_register (struct sframe_xlate_ctx *xlate_ctx,
   if (cfi_insn->u.r != SFRAME_CFA_SP_REG
       && cfi_insn->u.r != SFRAME_CFA_FP_REG)
     {
-      as_warn (_("no SFrame FDE emitted; "
-                "non-SP/FP register %u in .cfi_def_cfa_register"),
-              cfi_insn->u.r);
-      return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented.  */
+      if (!sframe_support_flex_fde_p ())
+       {
+         as_warn (_("no SFrame FDE emitted; "
+                    "non-SP/FP register %u in .cfi_def_cfa_register"),
+                  cfi_insn->u.ri.reg);
+         return SFRAME_XLATE_ERR_NOTREPRESENTED;
+       }
+      else
+       /* Currently, SFRAME_FDE_TYPE_FLEX is generated for AMD64 only.  */
+       xlate_ctx->flex_p = true;
     }
+
   sframe_fre_set_cfa_base_reg (cur_fre, cfi_insn->u.r);
   if (last_fre)
     sframe_fre_set_cfa_offset (cur_fre, sframe_fre_get_cfa_offset (last_fre));
+  cur_fre->cfa_deref_p = false;
 
   cur_fre->merge_candidate = false;
 
@@ -1638,6 +1663,134 @@ sframe_xlate_do_gnu_window_save (struct sframe_xlate_ctx *xlate_ctx,
   return SFRAME_XLATE_ERR_NOTREPRESENTED;  /* Not represented.  */
 }
 
+/* Translate a DWARF sleb128 offset in the CFI escape data E to an offsetT.  */
+
+static offsetT
+sframe_xlate_escape_sleb128_to_offsetT (const struct cfi_escape_data *e)
+{
+  offsetT offset;
+
+  gas_assert (e->type == CFI_ESC_byte || e->type == CFI_ESC_sleb128);
+  /* Read the offset.  */
+  if (e->type == CFI_ESC_byte)
+    {
+      /* The user/compiler may provide an sleb128 encoded data of a single byte
+        length (DWARF offset of DW_OP_bregN is sleb128).  On a big-endian
+        host, the endianness of data itself needs to be accommodated then.  To
+        keep it simple, gather the LSB, and translate it to int64.  */
+      unsigned char sleb_data = e->exp.X_add_number & 0xff;
+      const unsigned char *buf_start = (const unsigned char *)&sleb_data;
+      const unsigned char *buf_end = buf_start + 1;
+      int64_t value = 0;
+      size_t read = read_sleb128_to_int64 (buf_start, buf_end, &value);
+      gas_assert (read);
+      offset = (offsetT) value;
+    }
+  else
+    /* offset must be CFI_ESC_sleb128.  */
+    offset = e->exp.X_add_number;
+
+  return offset;
+}
+
+/* Handle DW_CFA_def_cfa_expression in .cfi_escape.
+
+   As with sframe_xlate_do_cfi_escape, the intent of this function is to detect
+   only the simple-to-process but common cases.  All other CFA escape
+   expressions continue to be inadmissible (no SFrame FDE emitted).
+
+   Sets CALLER_WARN_P for skipped cases (and returns SFRAME_XLATE_OK) where the
+   caller must warn.  The caller then must also set
+   SFRAME_XLATE_ERR_NOTREPRESENTED for their callers.  */
+
+static int
+sframe_xlate_do_escape_cfa_expr (struct sframe_xlate_ctx *xlate_ctx,
+                                const struct cfi_insn_data *cfi_insn,
+                                bool *caller_warn_p)
+{
+  const struct cfi_escape_data *e = cfi_insn->u.esc;
+  const struct cfi_escape_data *e_offset = NULL;
+  int err = SFRAME_XLATE_OK;
+  unsigned int opcode1, opcode2;
+  offsetT offset;
+  unsigned int reg = SFRAME_FRE_REG_INVALID;
+  unsigned int i = 0;
+  bool x86_cfa_deref_p = false;
+
+  /* Check roughly for an expression like so:
+     DW_CFA_def_cfa_expression (DW_OP_breg6 (rbp): -8; DW_OP_deref).  */
+#define CFI_ESC_NUM_EXP 4
+  offsetT items[CFI_ESC_NUM_EXP] = {0};
+  while (e->next)
+    {
+      e = e->next;
+      /* Bounds check, must be constant, no relocs.  */
+      if (i >= CFI_ESC_NUM_EXP
+         || e->exp.X_op != O_constant
+         || e->reloc != TC_PARSE_CONS_RETURN_NONE)
+       goto warn_and_exit;
+      /* Other checks based on index i.
+          - For item[2], allow byte OR sleb128.
+          - items at index 0, 1, and 3: Must be byte.  */
+      if (i == 2 && (e->type != CFI_ESC_byte && e->type != CFI_ESC_sleb128))
+       goto warn_and_exit;
+      else if (i != 2 && e->type != CFI_ESC_byte)
+       goto warn_and_exit;
+      /* Block length (items[0]) of 3 in DWARF expr.  */
+      if (i == 1 && items[0] != 3)
+       goto warn_and_exit;
+
+      if (i == 2)
+       e_offset = e;
+
+      items[i] = e->exp.X_add_number;
+      i++;
+    }
+
+  if (i != CFI_ESC_NUM_EXP)
+    goto warn_and_exit;
+#undef CFI_ESC_NUM_EXP
+
+  opcode1 = items[1];
+  opcode2 = items[3];
+  /* DW_OP_breg6 is rbp.  FIXME - this stub can be enhanced to handle more
+     regs.  */
+  if (sframe_get_abi_arch () == SFRAME_ABI_AMD64_ENDIAN_LITTLE
+      && sframe_support_flex_fde_p ()
+      && opcode1 == DW_OP_breg6 && opcode2 == DW_OP_deref)
+    {
+      x86_cfa_deref_p = true;
+      reg = SFRAME_CFA_FP_REG;
+    }
+
+  offset = sframe_xlate_escape_sleb128_to_offsetT (e_offset);
+
+  struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre;
+  gas_assert (cur_fre);
+
+  /* Handle the specific CFA expression mentioned above.  */
+  if (x86_cfa_deref_p
+      && sframe_fre_stack_offset_bound_p (offset, false)
+      && reg != SFRAME_FRE_REG_INVALID)
+    {
+      xlate_ctx->flex_p = true;
+      sframe_fre_set_cfa_base_reg (cur_fre, reg);
+      sframe_fre_set_cfa_offset (cur_fre, offset);
+      cur_fre->cfa_deref_p = true;
+      cur_fre->merge_candidate = false;
+      /* Done handling here.  */
+      caller_warn_p = false;
+
+      return err;
+    }
+  /* Any other CFA expression may not be safe to skip.  Fall through to
+     warn_and_exit.  */
+
+warn_and_exit:
+  *caller_warn_p = true;
+  return err;
+}
+
 /* Handle DW_CFA_expression in .cfi_escape.
 
    As with sframe_xlate_do_cfi_escape, the intent of this function is to detect
@@ -1649,13 +1802,14 @@ sframe_xlate_do_gnu_window_save (struct sframe_xlate_ctx *xlate_ctx,
    SFRAME_XLATE_ERR_NOTREPRESENTED for their callers.  */
 
 static int
-sframe_xlate_do_escape_expr (const struct sframe_xlate_ctx *xlate_ctx,
+sframe_xlate_do_escape_expr (struct sframe_xlate_ctx *xlate_ctx,
                             const struct cfi_insn_data *cfi_insn,
                             bool *caller_warn_p)
 {
   const struct cfi_escape_data *e = cfi_insn->u.esc;
+  const struct cfi_escape_data *e_offset = NULL;
   int err = SFRAME_XLATE_OK;
-  unsigned int reg = 0;
+  offsetT offset;
   unsigned int i = 0;
 
   /* Check roughly for an expression
@@ -1665,32 +1819,70 @@ sframe_xlate_do_escape_expr (const struct sframe_xlate_ctx *xlate_ctx,
   while (e->next)
     {
       e = e->next;
-      if ((i == 2 && (items[1] != 2)) /* Expected len of 2 in DWARF expr.  */
-         /* We do not care for the exact values of items[2] and items[3],
-            so an explicit check for O_constant isnt necessary either.  */
-         || i >= CFI_ESC_NUM_EXP
-         || (i < 2
-             && (e->exp.X_op != O_constant
-                 || e->type != CFI_ESC_byte
-                 || e->reloc != TC_PARSE_CONS_RETURN_NONE)))
+      /* Bounds check, must be constant, no relocs.  */
+      if (i >= CFI_ESC_NUM_EXP
+         || e->exp.X_op != O_constant
+         || e->reloc != TC_PARSE_CONS_RETURN_NONE)
+       goto warn_and_exit;
+      /* Other checks based on index i.
+          - For item[3], allow byte OR sleb128.
+          - items at index 0, 1, and 2: Must be byte.  */
+      if (i == 3 && (e->type != CFI_ESC_byte && e->type != CFI_ESC_sleb128))
        goto warn_and_exit;
+      else if (i != 3 && e->type != CFI_ESC_byte)
+       goto warn_and_exit;
+      /* Block length (items[1]) of 2 in DWARF expr.  */
+      if (i == 2 && items[1] != 2)
+       goto warn_and_exit;
+
+      if (i == 3)
+       e_offset = e;
+
       items[i] = e->exp.X_add_number;
       i++;
     }
 
   if (i <= CFI_ESC_NUM_EXP - 1)
     goto warn_and_exit;
+#undef CFI_ESC_NUM_EXP
 
   /* reg operand to DW_CFA_expression is ULEB128.  For the purpose at hand,
      however, the register value will be less than 128 (CFI_ESC_NUM_EXP set
      to 4).  See an extended comment in sframe_xlate_do_escape_expr for why
      reading ULEB is okay to skip without sacrificing correctness.  */
-  reg = items[0];
-#undef CFI_ESC_NUM_EXP
+  unsigned int reg = items[0];
 
-  if (reg == SFRAME_CFA_SP_REG || reg == SFRAME_CFA_FP_REG
-      || (sframe_ra_tracking_p () && reg == SFRAME_CFA_RA_REG)
-      || reg == sframe_xlate_ctx_get_cur_cfa_reg (xlate_ctx))
+  unsigned opcode = items[2];
+  unsigned int fp_base_reg = SFRAME_FRE_REG_INVALID;
+  bool x86_fp_deref_p = true;
+
+  if (sframe_get_abi_arch () == SFRAME_ABI_AMD64_ENDIAN_LITTLE
+      && sframe_support_flex_fde_p ()
+      && opcode == DW_OP_breg6)
+    {
+      x86_fp_deref_p = true;
+      fp_base_reg = SFRAME_CFA_FP_REG;
+    }
+
+  offset = sframe_xlate_escape_sleb128_to_offsetT (e_offset);
+
+  struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre;
+  gas_assert (cur_fre);
+
+  if (x86_fp_deref_p
+      && reg == SFRAME_CFA_FP_REG
+      && sframe_fre_stack_offset_bound_p (offset, false))
+    {
+      xlate_ctx->flex_p = true;
+      sframe_fre_set_fp_track (cur_fre, offset);
+      cur_fre->fp_loc = SFRAME_FRE_ELEM_LOC_REG;
+      cur_fre->fp_reg = fp_base_reg;
+      cur_fre->fp_deref_p = true;
+      cur_fre->merge_candidate = false;
+    }
+  else if (reg == SFRAME_CFA_SP_REG || reg == SFRAME_CFA_FP_REG
+          || (sframe_ra_tracking_p () && reg == SFRAME_CFA_RA_REG)
+          || reg == sframe_xlate_ctx_get_cur_cfa_reg (xlate_ctx))
     {
       as_warn (_("no SFrame FDE emitted; "
                 ".cfi_escape DW_CFA_expression with %s reg %u"),
@@ -1855,7 +2047,7 @@ warn_and_exit:
    SFRAME_XLATE_OK if OK to skip.  */
 
 static int
-sframe_xlate_do_cfi_escape (const struct sframe_xlate_ctx *xlate_ctx,
+sframe_xlate_do_cfi_escape (struct sframe_xlate_ctx *xlate_ctx,
                            const struct cfi_insn_data *cfi_insn)
 {
   const struct cfi_escape_data *e;
@@ -1891,6 +2083,10 @@ sframe_xlate_do_cfi_escape (const struct sframe_xlate_ctx *xlate_ctx,
        }
       break;
 
+    case DW_CFA_def_cfa_expression:
+      err = sframe_xlate_do_escape_cfa_expr (xlate_ctx, cfi_insn, &warn_p);
+      break;
+
     case DW_CFA_expression:
       err = sframe_xlate_do_escape_expr (xlate_ctx, cfi_insn, &warn_p);
       break;
@@ -2250,6 +2446,7 @@ create_sframe_all (void)
       if (err && get_dw_fde_signal_p (dw_fde))
        {
          sframe_xlate_ctx_cleanup (xlate_ctx);
+         xlate_ctx->flex_p = false;
          err = SFRAME_XLATE_OK;
        }