[RFC 15/28] [SFrame-V3] include: sframe: reserve 4 bits for new FDE types

Jens Remus jremus@linux.ibm.com
Thu Dec 11 17:36:43 GMT 2025
Hello Indu,

I am trying to wrap my head around the new FLEX_TOPMOST_FRAME encoding.

On 12/9/2025 10:07 AM, Indu Bhagat wrote:
> In SFrame V2, the FDE representation caters to the most common cases of
> stack trace metadata:
>   - CFA is SP/FP based,
>   - FP/RA offsets, if present are CFA based (except some cases in s390x
>     where reg can be encoded).
> 
> Introduce an additional FDE type SFRAME_FDE_TYPE_FLEX_TOPMOST_FRAME,
> which can encode a more flexible set of CFA, FP and RA recovery rules.
> Some of the patterns supported include:
>   - CFA may be non-SP/FP based.
>   - CFA, FP may encode dereferencing of register after offset adjustment
>   - RA may be in a non-default register.
> 
> The important bit here is that since SFrame does not track all
> callee-saved registers, the above-mentioned recovery rules must only be
> done for top-most frames (by the stack tracer).
> 
> Adding a new FDE type does have implications for a stack tracer in that
> it needs to:
>   - Check the FDE type before interpreting the Stack offsets data.
>   - If the FDE type is SFRAME_FDE_TYPE_FLEX_TOPMOST_FRAME, and the
>     recovery rules employ the use of any non-SP/FP based register, the
>     stack tracer must proceed only if it is the topmost frame on stack.

Can this be summarize to:  Recovery rules that employ arbitrary register
contents (other than SP/FP) are only allowed in the topmost frame?

> 
> For each of CFA and RA two offsets are always used.  The two offsets
> represent the information as follows:
>   - (minimum 8-bit) offset1 to encode register like:
>     (regnum << 3) | unused << 2 | deref << 1 | reg_p (=1)
>   - offset2 to encode offset: offset
> 
> reg_p = 1 indicates a register, reg_p = 0 indicates CFA.
> 
> The offsets are in the usual order:  CFA, RA, FP if present.

Is it required to have this flexibility for both CFA and RA/FP?  Would
it be an alternative to use the common encoding but leave some features
as "reserved" in each?

> For example, for FP/RA tracking,
> 
> a) If the reg is REG1 for FP/RA tracking,
>    - Encoding:
>      + offset1 to encode register: (REG1 << 3) | unused << 2 | deref << 1 | reg_p (=1)
>      + offset2 to encode offset: offset
>    - Action:
>      + if deref, FP/RA = \*(REG1 + offset) (e.g., seen for FP recovery
>        with certain DRAP patterns on x86_64)

How can this be expressed by CFI directives?  Could you please provide
an example?

>      + if no deref, FP/RA = REG1 + offset

Should s390x use that to encode FP/RA saved in register?  Since that is
only supported for topmost frames FLEX_TOPMOST_FRAME seems a good fit.
It would remove the need for a s390x-specific encoding and would also
make your patch "[RFC 13/28] [SFrame-V3] include: sframe: s390x: keep
unused bits when possible" obsolete.

It would be interesting to see the .sframe section size penalty though.
I would need to implement a prototype to compare based on your patch
"[RFC 21/28] [SFrame-V3] gas: sframe: add handling for .cfi_register
for FP and RA".  Have you had considered this?  If yes why didn't you
decide for that approach?

> b) If the reg is CFA for FP/RA tracking,
>    - Encoding:
>      + [=Effectively Padding] offset1 to encode register:
>         (( 0 << 3 ) | unused << 2 | 0 << 1 | reg_p (=0))

Nit:
        (( 0 << 3 ) | unused << 2 | deref << 1 | reg_p (=0))

>      + offset2 to encode offset: offset
>    - Action:
>      + if deref, FP/RA = *(CFA + offset)

Makes sense.

>      + if no deref, FP/RA = CFA + offset (pattern shouldnt be seen for
>        RA)

This could be used to represent .cfi_val_offset with FP/RA register,
correct?  Although I have only ever seen .cfi_val_offset with
SP register so far.


> Next for CFA tracking,
>    - Action:
>      + if deref, CFA = *(reg + offset) (e.g., seen for CFA recovery in
>        some stack realignment patterns on AMD64)

How can this be expressed by CFI directives?  Could you please provide
an example?

>      + if no deref, CFA = reg + offset (e.g., for .cfi_def_cfa 2, 8, or
>        .cfi_def_cfa 10, 0)

This is used to represent .cfi_def_cfa[_offset|_register] with non-SP/FP
register, as in the following example, correct?

	.cfi_def_cfa <non-SP/FP-reg>, <offset>

Is encoded as:

	offset1 = (regnum=<non-SP/FP-reg>) << 3 | (deref=0) << 1 | (reg_p=1)
	offset2 = <offset>

> 
> Expected usage of this FDE type is quite low (DRAP on x86_64).
So a FLEX_TOPMOST_FRAME would encode SP/FP-based CFA using the register
number instead of the FRE use_fp flag?  This then requires knowledge of
the SP and FP DWARF register numbers in the stacktracer, so that it can
decide whether a rule is allowed in a non-topmost frame.  In the Kernel
I would hide those in unwind user sframe, so that the common unwind user
framework does not need to have this knowledge (see SP_OFFSET and
FP_OFFSET CFA rules below).


To support a most of the new FLEX_TOPMOST_FRAME encoding in the Kernel
common unwind user framework, I roughly assume the following high-level
changes:

For the CFA:

+enum unwind_user_cfa_rule_type {
+	UNWIND_USER_CFA_RT_SP_OFFSET,		// CFA = SP + offset	(formerly .use_fp=false)
+	UNWIND_USER_CFA_RT_FP_OFFSET,		// CFA = FP + offset	(formerly .use_fp=true)
+	UNWIND_USER_CFA_RT_REG_OFFSET,		// CFA = <non-SP/FP register> + offset
+	UNWIND_USER_CFA_RT_REG_OFFSET_DEREF,	// CFA = *(<non-SP/FP register> + offset)
+};
+
+struct unwind_user_cfa_rule {
+	enum unwind_user_cfa_rule_type type;
+	s32 offset;
+	int regnum;
+};

Note that I omitted the following exotic cases.  Not sure whether they
need to be supported?

	UNWIND_USER_CFA_RT_SP_OFFSET_DEREF,	// CFA = *(SP + offset)
	UNWIND_USER_CFA_RT_FP_OFFSET_DEREF,	// CFA = *(FP + offset)

The SP_OFFSET and FP_OFFSET cases hide the need for unwind user to know
their architecture-specific DWARF register numbers.  unwind user sframe
would need to know them, depending on whether SFrame V3 still uses the
FDE use_fp flag.


For FP/RA:

 enum unwind_user_rule_type {
 	UNWIND_USER_RT_UNKNOWN,			// FP/RA = 0 (used with unwind user fp on s390 using back chain)
 	UNWIND_USER_RT_RETAIN,			// FP/RA = FP/RA
-	UNWIND_USER_RT_STACK,
-	UNWIND_USER_RT_REG,
+	UNWIND_USER_RT_CFA_OFFSET_DEREF,	// FP/RA = *(CFA + offset)	(formerly UNWIND_USER_LOC_STACK)
+	UNWIND_USER_RT_REG_OFFSET,		// FP/RA = register + offset	(formerly UNWIND_USER_LOC_REG)
+	UNWIND_USER_RT_REG_OFFSET_DEREF,	// FP/RA = *(register + offset)
 };

 struct unwind_user_rule {
 	enum unwind_user_rule_type type;
-	union {
-		s32 offset;
-		int regnum;
-	};
+	s32 offset;
+	int regnum;
 };

Note that the following omitted case would be required for
.cfi_val_offset <FP/RA> and could easily be added:

	UNWIND_USER_RT_CFA_OFFSET,		// FP/RA = CFA + offset


 struct unwind_user_frame {
-	s32 cfa_off;				// now .cfa.offset
-	s32 sp_off;				// moved
+	struct unwind_user_cfa_rule cfa;
 	struct unwind_user_rule ra;
 	struct unwind_user_rule fp;
-	bool use_fp;				// now .cfa.type = SP_OFFSET/FP_OFFSET (formerly .use_fp=false/true)
+	s32 sp_off;
 	bool outermost;
 };


Note that rule types that contain "_REG_" are only allowed in the
topmost frame.

Maybe the _DEREF cases could better be represented as follows, but that
would allow all kinds of dereference variations that are not sensible,
e.g. UNKNOWN or RETAIN with deref=true.

struct unwind_user_[cfa_]rule {
...
	bool deref;
};

Thoughts?

Thanks and regards,
Jens
-- 
Jens Remus
Linux on Z Development (D3303)
+49-7031-16-1128 Office
jremus@de.ibm.com

IBM

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



More information about the Binutils mailing list