[V2 20/36] [SFrame-V3] libsframe: textual dump of fde type SFRAME_FDE_TYPE_FLEX

Jens Remus jremus@linux.ibm.com
Thu Jan 8 16:02:02 GMT 2026


On 1/7/2026 9:42 AM, Indu Bhagat wrote:
> Refactor the SFrame textual dumper in sframe-dump.c to properly handle
> the new FDE type.
> 
> In SFrame V2, the textual dumper could afford to be oblivious to the
> exact DWARF register number for stack-pointer and frame-pointer
> registers in each ABI.  This is because a single bit was used to
> differentiate between the two (irrespective of the ABI), and the dumper
> could easily just use a:
>     const char *base_reg_str[] = {"fp", "sp"};
> to get the register name.
> 
> With the introduction of new SFrame FDE type SFRAME_FDE_TYPE_FLEX, which
> carry DWARF register numbers if applicable, this needs to change.  E.g.,
> for some patterns on AMD64, one may see CFA is the value at 'r10+0'; or
> FP is the value at 'rbp+8'.  This means that for textual dump, we now
> need a mapping from:
>   - the ABI-specific frame-pointer to string "fp"
>   - the ABI-specific stack-pointer to string "sp"
> This is done via the SFRAME_ABI_REG_MAP helper macros and the new
> sframe_get_reg_name () API.
> 
> For registers other than stack-pointer and frame-pointer, the SFrame
> textual dump does not print the register name (say, "rax"), but just the
> number (i.e., "r0").
> 
> Check the func_info2 byte and dispatch the stack frame row entry (FRE)
> dumping to the correct function: either dump_sframe_func_fre_simple or
> dump_sframe_func_fre_flex.
> 
> Ensure the display is consistent to previous semantics.  When flex FDE
> is in effect, there may not always be an RA offset (after the CFA
> offsets).  A padding offset for RA is present if FP offsets follow.  So
> if a padding offset for RA is seen, we will display "U".  If no RA
> offset is seen, however, we will display a "u" unless its an ABI where
> RA offset is fixed (in the latter case we display "f").
> 
> libsframe/
> 	* sframe-dump.c (SFRAME_SP): Define mapping from stack-pointer
> 	register number to "sp".
> 	(SFRAME_FP): Define mapping from frame-pointer register number
> 	to "fp".
> 	(SFRAME_ABI_REG_MAP): Helper macro to define per-ABI-arch
> 	mappings.
> 	(sframe_get_reg_name): Helper API to get register name.
> 	(dump_sframe_func_with_fres): Refactor a bit...
> 	(dump_sframe_func_fre_simple): ..into this.
> 	(sframe_format_fre_disp): New definition.
> 	(dump_sframe_func_fre_flex): Likewise.
> 	(dump_sframe): Allow both SFrame version 2 and version 3.
> 
> ---
> [Changes in V1]
>   - Display a "F" char for flexible FDE types.
>   - Fix dumping routines to account for one RA padding offset, when
>     present.  In RFC, we were forcefully always emitting two offsets for
>     RA in all flex FDEs [Jens].
>   - Ensure the display is consistent to previous semantics.  Added a
>     text in the commit log [Jens].
>   - Remove unnecessary inits of err variables with SFRAME_ERR [Jens].
>   - Rename out "flex_topmost" to "flex" in function name.  Remove
>     references to "topmost" [Jens].
>   - Fix uninitialized errors in sframe-dump.c sframe_get_reg_name.
>   - Use SFRAME_V3_FDE_TYPE for flex_p.
> [End of Changes in V1]
> 
> [Changes in V2]
>   - Bugfix flex and undefined_p in dumper.  Move stub to emit "RA
>     undefined" early on, and do not access offsets in case of
>     ra_undefined_p [Indu].
>   - Use unsigned int consistently for regnum [Indu].
>   - For clarity/intent, use explicit typecast uint32_t for
>     sframe_get_fre_offset based retrieval of CFA, FP and RA
>     Register/Control Data [Indu].
> [End of changes in V2]

> diff --git a/libsframe/sframe-dump.c b/libsframe/sframe-dump.c

> @@ -301,6 +312,240 @@ dump_sframe_func_with_fres (const sframe_decoder_ctx *sfd_ctx,
>      }
>  }
>  
> +/* Helper to safely format "reg+offset" or "(reg+offset)". */
> +
> +static void
> +sframe_format_fre_disp (char *buf, size_t size, uint8_t abi_arch,
> +			unsigned int reg_num, bool reg_p, int32_t offset,
> +			bool deref_p)
> +{
> +  /* Initialize to string for CFA-based.  */
> +  const char *reg_name = "c";
> +
> +  /* Allocate space for the potential fallback name (e.g., "r12") */
> +  char temp_reg_name[32] = {0};
> +  if (reg_p)
> +    reg_name = sframe_get_reg_name (abi_arch, reg_num, temp_reg_name,
> +				    sizeof (temp_reg_name));
> +
> +  if (deref_p)
> +    snprintf (buf, size, "(%s%+d)", reg_name, offset);
> +  else
> +    snprintf (buf, size, "%s%+d", reg_name, offset);
> +}
> +
> +static void
> +dump_sframe_func_fres_flex (const sframe_decoder_ctx *sfd_ctx,
> +			    unsigned int funcidx,
> +			    uint32_t num_fres,
> +			    int64_t func_start_pc_vma,
> +			    bool pc_mask_p)
> +{
> +  uint32_t j = 0;
> +  bool ra_undefined_p = false;
> +  int64_t fre_start_pc_vma = 0;
> +  uint32_t fde_type = SFRAME_FDE_TYPE_FLEX;
> +
> +  sframe_frame_row_entry fre;
> +  char temp[100] = {0};
> +
> +  for (j = 0; j < num_fres; j++)
> +    {
> +      sframe_decoder_get_fre (sfd_ctx, funcidx, j, &fre);
> +
> +      fre_start_pc_vma = (pc_mask_p
> +			  ? fre.fre_start_addr
> +			  : func_start_pc_vma + fre.fre_start_addr);
> +
> +      /* Dump VMA.  */
> +      printf ("\n");
> +      printf ("    %016"PRIx64, fre_start_pc_vma);
> +
> +      int err_ra_offset = 0;
> +      /* Dump RA undefined (FRE without any offsets).  */
> +      ra_undefined_p = sframe_fre_get_ra_undefined_p (sfd_ctx, &fre,
> +						      &err_ra_offset);
> +      sframe_assert (!err_ra_offset);
> +      if (ra_undefined_p)
> +	{
> +	  printf ("  RA undefined");
> +	  continue;
> +	}
> +
> +      unsigned int cfa_reg = 0, ra_reg = 0, fp_reg = 0;
> +      bool cfa_deref_p = 0, ra_deref_p = 0, fp_deref_p = 0;
> +
> +      int err_cfa_reg = 0;
> +      int err_cfa_offset = 0;
> +      /* Read the Register/Control Data as unsigned.  */
> +      uint32_t cfa_reg_data
> +	= (uint32_t) sframe_get_fre_offset (&fre, SFRAME_FRE_CFA_OFFSET_IDX,
> +					    &err_cfa_reg);

I doubt that a simple cast to uint32_t does the right thing:
sframe_get_fre_offset() reads an int8_t, int16_t, or int32_t and returns
it as int32_t, thus performing sign extension.  Casting a int8_t/int16_t
that got sign-extended to int32_t to uint32_t does not undo the sign
extension.

I would expect that a register number value of 31 << 3, if stored as
uint8_t "offset", would get erroneously extended to MAX_UINT32 >> 3.

This does not surface, as the Register/Control Data write logic performs
wrong sizing.  See my feedback to the respective patch.


> +      int32_t cfa_offset = sframe_fre_get_cfa_offset (sfd_ctx, &fre, fde_type,
> +						      &err_cfa_offset);
> +      sframe_assert (!err_cfa_reg && !err_cfa_offset);
> +      bool cfa_reg_p = SFRAME_V3_FLEX_FDE_OFFSET_REG_P (cfa_reg_data);
> +      if (cfa_reg_p)
> +	{
> +	  cfa_reg = SFRAME_V3_FLEX_FDE_OFFSET_REG_NUM (cfa_reg_data);
> +	  cfa_deref_p = SFRAME_V3_FLEX_FDE_OFFSET_REG_DEREF_P (cfa_reg_data);
> +	}
> +
> +      int err_ra_reg = 0;
> +      /* Read the Register/Control Data as unsigned.  */
> +      uint32_t ra_reg_data
> +	= (uint32_t) sframe_get_fre_offset (&fre, SFRAME_FRE_RA_OFFSET_IDX * 2,
> +					    &err_ra_reg);

Dito.

> +      int32_t ra_offset = sframe_fre_get_ra_offset (sfd_ctx, &fre, fde_type,
> +						    &err_ra_offset);
> +      bool ra_reg_p = SFRAME_V3_FLEX_FDE_OFFSET_REG_P (ra_reg_data);
> +      if (ra_reg_p)
> +	{
> +	  ra_reg = SFRAME_V3_FLEX_FDE_OFFSET_REG_NUM (ra_reg_data);
> +	  ra_deref_p = SFRAME_V3_FLEX_FDE_OFFSET_REG_DEREF_P (ra_reg_data);
> +	}
> +
> +      int err_fp_reg = 0;
> +      int err_fp_offset = 0;
> +      int fp_idx = SFRAME_FRE_FP_OFFSET_IDX * 2;
> +      if (!err_ra_reg && ra_reg_data == SFRAME_FRE_RA_OFFSET_INVALID)
> +       fp_idx -= 1;
> +
> +      /* Read the Register/Control Data as unsigned.  */
> +      uint32_t fp_reg_data
> +	= (uint32_t) sframe_get_fre_offset (&fre, fp_idx, &err_fp_reg);

Dito.

> +      int32_t fp_offset = sframe_fre_get_fp_offset (sfd_ctx, &fre, fde_type,
> +						    &err_fp_offset);
> +      bool fp_reg_p = SFRAME_V3_FLEX_FDE_OFFSET_REG_P (fp_reg_data);
> +      if (fp_reg_p)
> +	{
> +	  fp_reg = SFRAME_V3_FLEX_FDE_OFFSET_REG_NUM (fp_reg_data);
> +	  fp_deref_p = SFRAME_V3_FLEX_FDE_OFFSET_REG_DEREF_P (fp_reg_data);
> +	}
> +
> +      /* Dump CFA info.  */
> +      uint8_t abi_arch = sframe_decoder_get_abi_arch (sfd_ctx);
> +      sframe_format_fre_disp (temp, sizeof (temp), abi_arch, cfa_reg,
> +			      cfa_reg_p, cfa_offset, cfa_deref_p);
> +      printf ("  %-10s", temp);
> +
> +      /* Dump FP info.  */
> +      if (!err_fp_reg && !err_fp_offset)
> +	sframe_format_fre_disp (temp, sizeof (temp), abi_arch, fp_reg,
> +				fp_reg_p, fp_offset, fp_deref_p);
> +      else
> +	strcpy (temp, "u");
> +      printf ("%-10s", temp);
> +
> +      /* Dump RA info.
> +	 Even if an ABI does not track RA offset, e.g., AMD64, for flex
> +	 frame, it may have RA recovery from register.  Else, display 'f'.  */
> +      if (err_ra_reg)
> +	{
> +	  if (sframe_decoder_get_fixed_ra_offset (sfd_ctx)
> +	      != SFRAME_CFA_FIXED_RA_INVALID)
> +	    strcpy (temp, "f");
> +	  else
> +	    strcpy (temp, "u");
> +	}
> +      else if (ra_reg_data == SFRAME_FRE_RA_OFFSET_INVALID)
> +	strcpy (temp, "U");
> +      else
> +	sframe_format_fre_disp (temp, sizeof (temp), abi_arch, ra_reg,
> +				ra_reg_p, ra_offset, ra_deref_p);
> +
> +      /* Mark SFrame FRE's RA information with "[s]" if the RA is mangled
> +	 with signature bits.  */
> +      err_ra_offset = 0;
> +      const char *ra_mangled_p_str
> +	= ((sframe_fre_get_ra_mangled_p (sfd_ctx, &fre, &err_ra_offset))
> +	   ? "[s]" : "   ");
> +      sframe_assert (!err_ra_offset);
> +      strcat (temp, ra_mangled_p_str);
> +      printf ("%-13s", temp);
> +    }
> +}
Regards,
Jens
-- 
Jens Remus
Linux on Z Development (D3303)
jremus@de.ibm.com / jremus@linux.ibm.com

IBM Deutschland Research & Development GmbH; Vorsitzender des Aufsichtsrats: Wolfgang Wendt; Geschäftsführung: David Faller; Sitz der Gesellschaft: Ehningen; Registergericht: Amtsgericht Stuttgart, HRB 243294
IBM Data Privacy Statement: https://www.ibm.com/privacy/



More information about the Binutils mailing list