[PATCH] gas: sframe: partially process DWARF expressions in CFI_escape
Jan Beulich
jbeulich@suse.com
Thu Jan 30 07:40:13 GMT 2025
On 29.01.2025 21:23, Indu Bhagat wrote:
> On 1/28/25 11:22 PM, Jan Beulich wrote:
>> On 28.01.2025 01:57, Indu Bhagat wrote:
>>> --- a/gas/gen-sframe.c
>>> +++ b/gas/gen-sframe.c
>>> @@ -1310,6 +1310,77 @@ sframe_xlate_do_gnu_window_save (struct sframe_xlate_ctx *xlate_ctx,
>>> return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */
>>> }
>>>
>>> +/* Translate CFI_escape into SFrame context.
>>> +
>>> + .cfi_escape CFI directive allows the user to add arbitrary bytes to the
>>> + unwind info. DWARF expressions commonly follow after CFI_escape (fake CFI)
>>> + DWARF opcode. One might also use CFI_escape to add OS-specific CFI opcodes
>>> + even.
>>> +
>>> + In SFrame stack trace format, complex unwind info cannot be represented. In
>>> + such cases, SFrame FDE generation is skipped and the user is warned. Recall,
>>> + however, that SFrame stack trace information is meant to convey information
>>> + about SP, FP and RA only. Hence, some DWARF expressions, are indeed safe to
>>> + skip.
>>> +
>>> + This function partially processes some DWARF expresssions and returns
>>> + SFRAME_XLATE_OK if OK to skip. */
>>> +
>>> +static int
>>> +sframe_xlate_do_expr (struct sframe_xlate_ctx *xlate_ctx,
>>> + struct cfi_insn_data *cfi_insn)
>>> +{
>>> + int op;
>>> + struct cfi_escape_data *e;
>>> + unsigned int reg = 0;
>>> + int err = SFRAME_XLATE_OK;
>>> + struct sframe_row_entry *cur_fre = NULL;
>>> +
>>> + e = cfi_insn->u.esc;
>>> +
>>> + if (e)
>>> + {
>>> + op = e->exp.X_add_number;
>>> + switch (op)
>>> + {
>>> + /* Of all the possible opcodes expected here, it is safe to
>>> + ignore DW_CFA_expression and DW_CFA_val_expression, provided they
>>> + do not impact the SP / FP register. */
>>> + case DW_CFA_expression:
>>> + case DW_CFA_val_expression:
>>> + /* Both DW_CFA_expression and DW_CFA_val_expression instructions take
>>> + two operands: an unsigned LEB128 value representing a register
>>> + number, and a DW_FORM_block value representing a DWARF expression.
>>> + For the current purpose, we simply need to know the register
>>> + number. */
>>> + e = e->next;
>>
>> Another thing (quite the opposite of my concern regarding the chain being
>> quite long): How do you know e is non-NULL at this point, i.e. that it is
>> safe to de-reference ...
>>
>
> We can know that e is non-NULL because we are doing this only for
> DW_CFA_expression and DW_CFA_val_expression.
>
> The DWARF standard says:
>
> "The DW_CFA_expression instruction takes two operands: an unsigned
> LEB128 value representing a register number, and a DW_FORM_block value
> representing a DWARF expression."
>
> Similar specification for DW_CFA_val_expression.
>
> So the code is assuming that after the op (DW_CFA_expression or
> DW_CFA_val_expression), the next one up is the register. I think
> assuming next one is the register is also OK to do because the DWARF
> expression evaluation is based on a stack machine model.
I'm sorry, but no - the Dwarf standard doesn't matter here. What you
say needs to hold for the eventual section contents. Yet the piecing
together may be done by multiple .cfi_escape directives, each with a
single operand. In that case each one's cfi_insn->u.esc->next will be
NULL afaict.
>>> + /* Keep in sync with the behaviour of cfi_parse_reg (). */
>>> + gas_assert (e->exp.X_op == O_register || e->exp.X_op == O_constant);
>>> + reg = e->exp.X_add_number;
>>
>> ... without checking? Much like a single .cfi_escape can comprise many
>> DW_CFA_*, a single DW_CFA_* can also be split across multiple
>> .cfi_escape, aiui.
>>
>
> We have checked that the op is DW_CFA_expression or
> DW_CFA_val_expression at this point...
>
> For all other DW_CFA_* in the .cfi_escape, we do not decipher them, and
> simply warn and error out (and not generate any SFrame FDE for that
> function).
No, you don't. You only do so if every individual .cfi_escape encodes
one single DW_CFA_*. If it encodes multiple, as in ...
>> Consider this example covering both of the named cases (without involving
>> any DW_CFA_*expression, just to demonstrate the possible uses of the
>> directive):
>>
>> .text
>> func:
>> .cfi_startproc
>> .cfi_escape 0x0a
>> nop
>> .cfi_escape 0x02, 0x00, 0x02, 0x00
... this example, to entirely ignore the subsequent one(s).
>> nop
>> .cfi_escape 0x03
>> .cfi_escape 0x00
>> .cfi_escape 0x00
>> nop
>> .cfi_escape 0x0b
>> ret
>> .cfi_endproc
>>
>> Also what about in particular operations that don't affect any registers
>> (other than perhaps PC)? DW_CFA_nop being the most prominent example, but
>> also any purely advance-loc ones (and others, like remember/restore state).
>> Wouldn't you better skip those before deciding whether to warn?
>
> advance_loc, remember/restore state in .cfi_escape are important for
> SFrame stack trace data generation. GAS will continue to warn and error
> out with:
> skipping SFrame FDE; .cfi_escape with op (XXX)
Well, then please consider DW_CFA_nop as the most basic example of something
surely not affecting any state.
> But to your point, surely, there may be more "harmless" cases when it is
> better to scan the .cfi_escape data and if it doesnt affect SFrame stack
> trace data, we do not warn. My intention is to take them on a
> case-by-case basis: target the easy-to-parse/process and commonly
> occurring ones first.
See Jens' similar request.
Jan
More information about the Binutils
mailing list