From: Indu Bhagat Date: Wed, 26 Feb 2025 21:50:49 +0000 (-0800) Subject: gas: sframe: partially process DWARF unwind info in CFI_escape X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=887373a45f475ef74f91a0e4e7940c8566784c50;p=thirdparty%2Fbinutils-gdb.git gas: sframe: partially process DWARF unwind info in CFI_escape CFI_escape is most commonly used to include DWARF expressions in the unwind information. One may also use CFI_escape to add OS-specific CFI opcodes. Up until now, SFrame generation process would skip generating SFrame FDE at the mere sight of a CFI_escape opcode. Fine tune the handling of CFI_escape for SFrame generation by explicitly checking for few "harmless" (in context of SFrame generation) CFI_escape DWARF info: - DW_CFA_expression affecting registers of no significance to SFrame stack trace info - DW_CFA_value_offset affecting registers of no significance to SFrame stack trace info Expose the current cfi_escape_data structure in dw2gencfi.c to the relevant header file to allow SFrame generation APIs to use it too. Valid unwind info may be split across multiple .cfi_escape directives. Conversely, it is also allowed to simply put multiple DWARF expressions and/or operations in a single .cfi_escape directive. Handling all of these cases correctly will need parsing/processing that is not deemed worth the effort in context of SFrame generation; We continue to skip generating SFrame FDE for these cases and warn the user. In future, SFrame stack trace format may support non-SP/FP as base register (albeit in limited form). Add an explicit check in sframe_xlate_do_escape_expr (to test against the current CFA register) to ensure the functionality continues to work. Use differentiated warning text in sframe_xlate_do_val_offset to avoid confusion to the user as the same function is used for handling .cfi_val_offset and .cfi_escape DW_CFA_val_offset,... Also, add a common test with DWARF reg 12 which is non SP / FP on x86_64 and aarch64 (and s390x too). gas/ * gas/dw2gencfi.c (struct cfi_escape_data): Move from ... * gas/dw2gencfi.h (struct cfi_escape_data): ... to. * gas/gen-sframe.c (sframe_xlate_do_val_offset): Include string for .cfi_escape conditionally. (sframe_xlate_do_escape_expr): New definition. (sframe_xlate_do_escape_val_offset): Likewise. (sframe_xlate_do_cfi_escape): Likewise. (sframe_do_cfi_insn): Handle CFI_escape explicitly. gas/testsuite/ * gas/cfi-sframe/cfi-sframe.exp: Add new tests. * gas/cfi-sframe/cfi-sframe-common-9.d: New test. * gas/cfi-sframe/cfi-sframe-common-9.s: New test. * gas/cfi-sframe/cfi-sframe-x86_64-empty-1.d: New test. * gas/cfi-sframe/cfi-sframe-x86_64-empty-1.s: New test. * gas/cfi-sframe/cfi-sframe-x86_64-empty-2.d: New test. * gas/cfi-sframe/cfi-sframe-x86_64-empty-2.s: New test. * gas/cfi-sframe/cfi-sframe-x86_64-empty-3.d: New test. * gas/cfi-sframe/cfi-sframe-x86_64-empty-3.s: New test. --- diff --git a/gas/dw2gencfi.c b/gas/dw2gencfi.c index 012af0ff33e..41e15cd1463 100644 --- a/gas/dw2gencfi.c +++ b/gas/dw2gencfi.c @@ -368,12 +368,6 @@ generic_dwarf2_emit_offset (symbolS *symbol, unsigned int size) } #endif -struct cfi_escape_data -{ - struct cfi_escape_data *next; - expressionS exp; -}; - struct cie_entry { struct cie_entry *next; diff --git a/gas/dw2gencfi.h b/gas/dw2gencfi.h index 42eb3863a0a..9b0e5979c0d 100644 --- a/gas/dw2gencfi.h +++ b/gas/dw2gencfi.h @@ -93,6 +93,12 @@ extern void cfi_add_CFA_restore_state (void); #define MULTIPLE_FRAME_SECTIONS (SUPPORT_FRAME_LINKONCE || SUPPORT_COMPACT_EH \ || TARGET_MULTIPLE_EH_FRAME_SECTIONS) +struct cfi_escape_data +{ + struct cfi_escape_data *next; + expressionS exp; +}; + struct cfi_insn_data { struct cfi_insn_data *next; diff --git a/gas/gen-sframe.c b/gas/gen-sframe.c index 13478efab6b..947faf366d3 100644 --- a/gas/gen-sframe.c +++ b/gas/gen-sframe.c @@ -1117,11 +1117,15 @@ sframe_xlate_do_offset (struct sframe_xlate_ctx *xlate_ctx, } /* Translate DW_CFA_val_offset into SFrame context. - Return SFRAME_XLATE_OK if success. */ + Return SFRAME_XLATE_OK if success. + + When CFI_ESC_P is true, the CFI_INSN is hand-crafted using CFI_escape + data. See sframe_xlate_do_escape_val_offset. */ static int -sframe_xlate_do_val_offset (struct sframe_xlate_ctx *xlate_ctx ATTRIBUTE_UNUSED, - struct cfi_insn_data *cfi_insn) +sframe_xlate_do_val_offset (const struct sframe_xlate_ctx *xlate_ctx ATTRIBUTE_UNUSED, + const struct cfi_insn_data *cfi_insn, + bool cfi_esc_p) { /* Previous value of register is CFA + offset. However, if the specified register is not interesting (SP, FP, or RA reg), the current @@ -1134,7 +1138,8 @@ sframe_xlate_do_val_offset (struct sframe_xlate_ctx *xlate_ctx ATTRIBUTE_UNUSED, /* Ignore SP reg, if offset matches assumed default rule. */ || (cfi_insn->u.ri.reg == SFRAME_CFA_SP_REG && cfi_insn->u.ri.offset != 0)) { - as_warn (_("skipping SFrame FDE; %s register %u in .cfi_val_offset"), + as_warn (_("skipping SFrame FDE; %s with %s reg %u"), + cfi_esc_p ? ".cfi_escape DW_CFA_val_offset" : ".cfi_val_offset", sframe_register_name (cfi_insn->u.ri.reg), cfi_insn->u.ri.reg); return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ } @@ -1310,6 +1315,221 @@ sframe_xlate_do_gnu_window_save (struct sframe_xlate_ctx *xlate_ctx, return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ } +/* Handle DW_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, where skipping over the escape + expr data does not affect correctness of the SFrame stack trace data. + + 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_expr (const 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; + int err = SFRAME_XLATE_OK; + unsigned int reg = 0; + unsigned int i = 0; + + /* Check roughly for an expression + DW_CFA_expression: r1 (rdx) (DW_OP_bregN (reg): OFFSET). */ +#define CFI_ESC_NUM_EXP 4 + offsetT items[CFI_ESC_NUM_EXP] = {0}; + 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)) + goto warn_and_exit; + items[i] = e->exp.X_add_number; + i++; + } + + if (i <= CFI_ESC_NUM_EXP - 1) + goto warn_and_exit; + + /* 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 + + if (reg == SFRAME_CFA_SP_REG || reg == SFRAME_CFA_FP_REG +#ifdef SFRAME_FRE_RA_TRACKING + || (sframe_ra_tracking_p () && reg == SFRAME_CFA_RA_REG) +#endif + || reg == xlate_ctx->cur_fre->cfa_base_reg) + { + as_warn (_("skipping SFrame FDE; " + ".cfi_escape DW_CFA_expression with %s reg %u"), + sframe_register_name (reg), reg); + err = SFRAME_XLATE_ERR_NOTREPRESENTED; + } + /* else safe to skip, so continue to return SFRAME_XLATE_OK. */ + + return err; + +warn_and_exit: + *caller_warn_p = true; + return err; +} + +/* Handle DW_CFA_val_offset 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, where skipping over the escape + expr data does not affect correctness of the SFrame stack trace data. + + 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_val_offset (const 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; + int err = SFRAME_XLATE_OK; + unsigned int i = 0; + unsigned int reg; + offsetT offset; + + /* Check for (DW_CFA_val_offset reg scaled_offset) sequence. */ +#define CFI_ESC_NUM_EXP 2 + offsetT items[CFI_ESC_NUM_EXP] = {0}; + while (e->next) + { + e = e->next; + if (i >= CFI_ESC_NUM_EXP || e->exp.X_op != O_constant) + goto warn_and_exit; + items[i] = e->exp.X_add_number; + i++; + } + if (i <= CFI_ESC_NUM_EXP - 1) + goto warn_and_exit; + + /* Both arguments to DW_CFA_val_offset are ULEB128. Especially with APX (on + x86) we're going to see DWARF register numbers above 127, for the extended + GPRs. And large enough stack frames would also require multi-byte offset + representation. However, since we limit our focus on cases when + CFI_ESC_NUM_EXP is 2, reading ULEB can be skipped. IOW, although not + ideal, SFrame FDE generation in case of an APX register in + DW_CFA_val_offset is being skipped (PS: this does _not_ mean incorrect + SFrame stack trace data). + + Recall that the intent here is to check for simple and prevalent cases, + when feasible. */ + + reg = items[0]; + offset = items[1]; +#undef CFI_ESC_NUM_EXP + + /* Invoke sframe_xlate_do_val_offset itself for checking. */ + struct cfi_insn_data temp = { + .insn = DW_CFA_val_offset, + .u = { + .ri = { + .reg = reg, + .offset = offset * DWARF2_CIE_DATA_ALIGNMENT + } + } + }; + err = sframe_xlate_do_val_offset (xlate_ctx, &temp, true); + return err; + +warn_and_exit: + *caller_warn_p = true; + return err; +} + +/* Handle CFI_escape in SFrame context. + + .cfi_escape CFI directive allows the user to add arbitrary data 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. + + Complex unwind info added using .cfi_escape directive _may_ be of no + consequence for SFrame when the affected registers are not SP, FP, RA or + CFA. The challenge in confirming the afore-mentioned is that it needs full + parsing (and validation) of the data presented after .cfi_escape. Here we + take a case-by-case approach towards skipping _some_ instances of + .cfi_escape: skip those that can be *easily* determined to be harmless in + the context of SFrame stack trace information. + + This function partially processes data following .cfi_escape and returns + SFRAME_XLATE_OK if OK to skip. */ + +static int +sframe_xlate_do_cfi_escape (const struct sframe_xlate_ctx *xlate_ctx, + const struct cfi_insn_data *cfi_insn) +{ + const struct cfi_escape_data *e; + bool warn_p = false; + int err = SFRAME_XLATE_OK; + offsetT firstop; + + e = cfi_insn->u.esc; + + if (!e) + return SFRAME_XLATE_ERR_INVAL; + + if (e->exp.X_op != O_constant) + return SFRAME_XLATE_ERR_NOTREPRESENTED; + + firstop = e->exp.X_add_number; + switch (firstop) + { + case DW_CFA_nop: + /* One or more nops together are harmless for SFrame. */ + while (e->next) + { + e = e->next; + if (e->exp.X_op != O_constant || e->exp.X_add_number != DW_CFA_nop) + { + warn_p = true; + break; + } + } + break; + + case DW_CFA_expression: + err = sframe_xlate_do_escape_expr (xlate_ctx, cfi_insn, &warn_p); + break; + + case DW_CFA_val_offset: + err = sframe_xlate_do_escape_val_offset (xlate_ctx, cfi_insn, &warn_p); + break; + + /* FIXME - Also add processing for DW_CFA_GNU_args_size in future? */ + + default: + warn_p = true; + break; + } + + if (warn_p) + { + /* In all other cases (e.g., DW_CFA_def_cfa_expression or other + OS-specific CFI opcodes), skip inspecting the DWARF expression. + This may impact the asynchronicity due to loss of coverage. + Continue to warn the user and bail out. */ + as_warn (_("skipping SFrame FDE; .cfi_escape with op (%#lx)"), + (unsigned long)firstop); + err = SFRAME_XLATE_ERR_NOTREPRESENTED; + } + + return err; +} + /* Returns the DWARF call frame instruction name or fake CFI name for the specified CFI opcode, or NULL if the value is not recognized. */ @@ -1384,7 +1604,7 @@ sframe_do_cfi_insn (struct sframe_xlate_ctx *xlate_ctx, err = sframe_xlate_do_offset (xlate_ctx, cfi_insn); break; case DW_CFA_val_offset: - err = sframe_xlate_do_val_offset (xlate_ctx, cfi_insn); + err = sframe_xlate_do_val_offset (xlate_ctx, cfi_insn, false); break; case DW_CFA_remember_state: err = sframe_xlate_do_remember_state (xlate_ctx); @@ -1406,6 +1626,9 @@ sframe_do_cfi_insn (struct sframe_xlate_ctx *xlate_ctx, case DW_CFA_register: err = sframe_xlate_do_register (xlate_ctx, cfi_insn); break; + case CFI_escape: + err = sframe_xlate_do_cfi_escape (xlate_ctx, cfi_insn); + break; /* Following CFI opcodes are not processed at this time. These do not impact the coverage of the basic stack tracing information as conveyed in the SFrame format. */ @@ -1413,8 +1636,7 @@ sframe_do_cfi_insn (struct sframe_xlate_ctx *xlate_ctx, case DW_CFA_same_value: break; default: - /* Following skipped operations do, however, impact the asynchronicity: - - CFI_escape. */ + /* Other skipped operations may, however, impact the asynchronicity. */ { const char *cfi_name = sframe_get_cfi_name (op); diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-common-9.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-common-9.d new file mode 100644 index 00000000000..80c92357073 --- /dev/null +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-common-9.d @@ -0,0 +1,22 @@ +#as: --gsframe +#objdump: --sframe=.sframe +#name: SFrame cfi_escape test +#... +Contents of the SFrame section .sframe: + + Header : + + Version: SFRAME_VERSION_2 + Flags: NONE +#? CFA fixed FP offset: \-?\d+ +#? CFA fixed RA offset: \-?\d+ + Num FDEs: 1 + Num FREs: 2 + + Function Index : + func idx \[0\]: pc = 0x0, size = 8 bytes + STARTPC + CFA + FP + RA + +#... + 0+0004 +sp\+16 +u +[uf] + + +#pass diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-common-9.s b/gas/testsuite/gas/cfi-sframe/cfi-sframe-common-9.s new file mode 100644 index 00000000000..3ecde7e4cc9 --- /dev/null +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-common-9.s @@ -0,0 +1,18 @@ +## CFI_escape may be used to encode DWARF expressions among other things. +## Depending on the register applicable for the DWARF expression, skipping +## SFrame FDE may be OK: SFrame stack trace information is relevant for SP, FP +## and RA only. In this test, CFI_escape is safe to skip (does not affect +## correctness of SFrame data). The register 0xc is non SP / FP on both +## aarch64 and x86_64. + .cfi_startproc + .long 0 + .cfi_def_cfa_offset 16 +# DW_CFA_expression,reg 0xc,length 2,DW_OP_breg6,SLEB(-8) + .cfi_escape 0x10,0xc,0x2,0x76,0x78 +# DW_CFA_nop + .cfi_escape 0x0 + .cfi_escape 0x0,0x0,0x0,0x0 +# DW_CFA_val_offset,reg 0xc,ULEB scaled offset + .cfi_escape 0x14,0xc,0x4 + .long 0 + .cfi_endproc diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-1.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-1.d new file mode 100644 index 00000000000..01239944863 --- /dev/null +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-1.d @@ -0,0 +1,17 @@ +#as: --gsframe +#warning: skipping SFrame FDE; \.cfi_escape DW\_CFA\_expression with SP reg 7 +#objdump: --sframe=.sframe +#name: CFI_escape with register of significance to SFrame +#... +Contents of the SFrame section .sframe: + + Header : + + Version: SFRAME_VERSION_2 + Flags: NONE +#? CFA fixed FP offset: \-?\d+ +#? CFA fixed RA offset: \-?\d+ + Num FDEs: 0 + Num FREs: 0 + +#pass diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-1.s b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-1.s new file mode 100644 index 00000000000..cfb1a95d7c5 --- /dev/null +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-1.s @@ -0,0 +1,11 @@ +## CFI_escape may be used to encode DWARF expressions among other things. +## In this test, a DWARF expression involving a register of interest (REG_SP on +## x86_64, which is also the CFA in the current FRE) is seen. SFrame +## generation, is skipped and a warning issued to the user. + .cfi_startproc + .long 0 + .cfi_def_cfa_offset 16 +# DW_CFA_expression,reg 0x7,length 2,DW_OP_breg6,SLEB(-8) + .cfi_escape 0x10,0x7,0x2,0x76,0x78 + .long 0 + .cfi_endproc diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-2.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-2.d new file mode 100644 index 00000000000..482803b65a2 --- /dev/null +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-2.d @@ -0,0 +1,17 @@ +#as: --gsframe +#warning: skipping SFrame FDE; \.cfi\_escape DW\_CFA\_val\_offset with FP reg 6 +#objdump: --sframe=.sframe +#name: CFI_escape with register of significance to SFrame II +#... +Contents of the SFrame section .sframe: + + Header : + + Version: SFRAME_VERSION_2 + Flags: NONE +#? CFA fixed FP offset: \-?\d+ +#? CFA fixed RA offset: \-?\d+ + Num FDEs: 0 + Num FREs: 0 + +#pass diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-2.s b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-2.s new file mode 100644 index 00000000000..90e108e96ad --- /dev/null +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-2.s @@ -0,0 +1,11 @@ +## CFI_escape may be used to encode DWARF expressions among other things. +## In this test, a DWARF expression involving a register of interest (REG_SP on +## x86_64, which is also the CFA in the current FRE) is seen. SFrame +## generation, is skipped and a warning issued to the user. + .cfi_startproc + .long 0 + .cfi_def_cfa_offset 16 +# DW_CFA_val_offset,rbp,ULEB scaled offset(16) + .cfi_escape 0x14,0x6,0x2 + .long 0 + .cfi_endproc diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-3.d b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-3.d new file mode 100644 index 00000000000..177fb21fc8a --- /dev/null +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-3.d @@ -0,0 +1,17 @@ +#as: --gsframe +#warning: skipping SFrame FDE; \.cfi\_escape with op \(0x14\) +#objdump: --sframe=.sframe +#name: CFI_escape with multiple DWARF expr +#... +Contents of the SFrame section .sframe: + + Header : + + Version: SFRAME_VERSION_2 + Flags: NONE +#? CFA fixed FP offset: \-?\d+ +#? CFA fixed RA offset: \-?\d+ + Num FDEs: 0 + Num FREs: 0 + +#pass diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-3.s b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-3.s new file mode 100644 index 00000000000..b6af0c3588d --- /dev/null +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe-x86_64-empty-3.s @@ -0,0 +1,12 @@ +## CFI_escape may be used to encode DWARF expressions among other things. +## In this test, a stream of valid DWARF information is presented in a +## single CFI_escape. Parsing such cases is currently not deemed worth the +## effort. So, such cases are skipped for SFrame FDE generation and a +## warning issued to the user. + .cfi_startproc + .long 0 + .cfi_def_cfa_offset 16 +# DW_CFA_val_offset,rcx,ULEB scaled offset(16), DW_CFA_expr,r10,length,DW_OP_deref,SLEB(-8) + .cfi_escape 0x14,0x2,0x2,0x10,0xa,0x2,0x76,0x78 + .long 0 + .cfi_endproc diff --git a/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp b/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp index b119b9da73d..a09946635c2 100644 --- a/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp +++ b/gas/testsuite/gas/cfi-sframe/cfi-sframe.exp @@ -78,6 +78,7 @@ if { ([istarget "x86_64-*-*"] || [istarget "aarch64*-*-*"]) \ run_dump_test "cfi-sframe-common-6" run_dump_test "cfi-sframe-common-7" run_dump_test "cfi-sframe-common-8" + run_dump_test "cfi-sframe-common-9" run_dump_test "common-empty-1" run_dump_test "common-empty-2" @@ -89,6 +90,9 @@ if { [istarget "x86_64-*-*"] && [gas_sframe_check] } then { if { [gas_x86_64_check] } then { set ASFLAGS "$ASFLAGS --64" run_dump_test "cfi-sframe-x86_64-1" + run_dump_test "cfi-sframe-x86_64-empty-1" + run_dump_test "cfi-sframe-x86_64-empty-2" + run_dump_test "cfi-sframe-x86_64-empty-3" set ASFLAGS "$old_ASFLAGS" } }