[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