diff --git a/gas/gen-sframe.c b/gas/gen-sframe.c index 8da9de33ad9..c21ac9b63aa 100644 --- a/gas/gen-sframe.c +++ b/gas/gen-sframe.c @@ -24,6 +24,7 @@ #include "sframe-internal.h" #include "gen-sframe.h" #include "dw2gencfi.h" +#include "leb128.h" #ifdef support_sframe_p @@ -1250,30 +1251,46 @@ sframe_xlate_do_def_cfa (struct sframe_xlate_ctx *xlate_ctx, sframe_fre_set_begin_addr (cur_fre, get_dw_fde_start_addrS (xlate_ctx->dw_fde)); } - /* Define the current CFA rule to use the provided register and - offset. However, if the register is not FP/SP, skip creating - SFrame stack trace info for the function. */ - if (cfi_insn->u.ri.reg != SFRAME_CFA_SP_REG - && cfi_insn->u.ri.reg != SFRAME_CFA_FP_REG) - { - as_warn (_("no SFrame FDE emitted; " - "non-SP/FP register %u in .cfi_def_cfa"), - cfi_insn->u.ri.reg); - return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ - } - else if (sframe_fre_stack_offset_bound_p (cfi_insn->u.ri.offset, true)) - { - sframe_fre_set_cfa_base_reg (cur_fre, cfi_insn->u.ri.reg); - sframe_fre_set_cfa_offset (cur_fre, cfi_insn->u.ri.offset); - cur_fre->merge_candidate = false; - } - else + + offsetT offset = cfi_insn->u.ri.offset; + bool bound_p = sframe_fre_stack_offset_bound_p (offset, true); + if (!bound_p) { as_warn (_("no SFrame FDE emitted; " ".cfi_def_cfa with unsupported offset value")); return SFRAME_XLATE_ERR_NOTREPRESENTED; } + /* Define the current CFA rule to use the provided register and + offset. Typically, the CFA rule uses SP/FP based CFA. However, with + SFrame V3 specification, if the CFA register is not FP/SP, SFrame FDE type + SFRAME_FDE_TYPE_FLEX type may be used. + + GAS uses the hook sframe_support_flex_fde_p () to determine if SFrame FDE + of type SFRAME_FDE_TYPE_FLEX can be emitted for the specific target. + Non-SP/FP based CFA may be seen for: + - AMD64 (e.g., DRAP, stack alignment), or + - s390x, where this may be seen for (GCC) generated code for static stack + clash protection. */ + if (cfi_insn->u.ri.reg != SFRAME_CFA_SP_REG + && cfi_insn->u.ri.reg != SFRAME_CFA_FP_REG) + { + if (!sframe_support_flex_fde_p ()) + { + as_warn (_("no SFrame FDE emitted; " + "non-SP/FP register %u in .cfi_def_cfa"), + cfi_insn->u.ri.reg); + return SFRAME_XLATE_ERR_NOTREPRESENTED; + } + else + xlate_ctx->flex_p = true; + } + + sframe_fre_set_cfa_base_reg (cur_fre, cfi_insn->u.ri.reg); + sframe_fre_set_cfa_offset (cur_fre, cfi_insn->u.ri.offset); + cur_fre->merge_candidate = false; + cur_fre->cfa_deref_p = false; + return SFRAME_XLATE_OK; } @@ -1295,14 +1312,22 @@ sframe_xlate_do_def_cfa_register (struct sframe_xlate_ctx *xlate_ctx, if (cfi_insn->u.r != SFRAME_CFA_SP_REG && cfi_insn->u.r != SFRAME_CFA_FP_REG) { - as_warn (_("no SFrame FDE emitted; " - "non-SP/FP register %u in .cfi_def_cfa_register"), - cfi_insn->u.r); - return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ + if (!sframe_support_flex_fde_p ()) + { + as_warn (_("no SFrame FDE emitted; " + "non-SP/FP register %u in .cfi_def_cfa_register"), + cfi_insn->u.ri.reg); + return SFRAME_XLATE_ERR_NOTREPRESENTED; + } + else + /* Currently, SFRAME_FDE_TYPE_FLEX is generated for AMD64 only. */ + xlate_ctx->flex_p = true; } + sframe_fre_set_cfa_base_reg (cur_fre, cfi_insn->u.r); if (last_fre) sframe_fre_set_cfa_offset (cur_fre, sframe_fre_get_cfa_offset (last_fre)); + cur_fre->cfa_deref_p = false; cur_fre->merge_candidate = false; @@ -1638,6 +1663,134 @@ sframe_xlate_do_gnu_window_save (struct sframe_xlate_ctx *xlate_ctx, return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ } +/* Translate a DWARF sleb128 offset in the CFI escape data E to an offsetT. */ + +static offsetT +sframe_xlate_escape_sleb128_to_offsetT (const struct cfi_escape_data *e) +{ + offsetT offset; + + gas_assert (e->type == CFI_ESC_byte || e->type == CFI_ESC_sleb128); + /* Read the offset. */ + if (e->type == CFI_ESC_byte) + { + /* The user/compiler may provide an sleb128 encoded data of a single byte + length (DWARF offset of DW_OP_bregN is sleb128). On a big-endian + host, the endianness of data itself needs to be accommodated then. To + keep it simple, gather the LSB, and translate it to int64. */ + unsigned char sleb_data = e->exp.X_add_number & 0xff; + const unsigned char *buf_start = (const unsigned char *)&sleb_data; + const unsigned char *buf_end = buf_start + 1; + int64_t value = 0; + size_t read = read_sleb128_to_int64 (buf_start, buf_end, &value); + gas_assert (read); + offset = (offsetT) value; + } + else + /* offset must be CFI_ESC_sleb128. */ + offset = e->exp.X_add_number; + + return offset; +} + +/* Handle DW_CFA_def_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. All other CFA escape + expressions continue to be inadmissible (no SFrame FDE emitted). + + 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_cfa_expr (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; + const struct cfi_escape_data *e_offset = NULL; + int err = SFRAME_XLATE_OK; + unsigned int opcode1, opcode2; + offsetT offset; + unsigned int reg = SFRAME_FRE_REG_INVALID; + unsigned int i = 0; + bool x86_cfa_deref_p = false; + + /* Check roughly for an expression like so: + DW_CFA_def_cfa_expression (DW_OP_breg6 (rbp): -8; DW_OP_deref). */ +#define CFI_ESC_NUM_EXP 4 + offsetT items[CFI_ESC_NUM_EXP] = {0}; + while (e->next) + { + e = e->next; + /* Bounds check, must be constant, no relocs. */ + if (i >= CFI_ESC_NUM_EXP + || e->exp.X_op != O_constant + || e->reloc != TC_PARSE_CONS_RETURN_NONE) + goto warn_and_exit; + /* Other checks based on index i. + - For item[2], allow byte OR sleb128. + - items at index 0, 1, and 3: Must be byte. */ + if (i == 2 && (e->type != CFI_ESC_byte && e->type != CFI_ESC_sleb128)) + goto warn_and_exit; + else if (i != 2 && e->type != CFI_ESC_byte) + goto warn_and_exit; + /* Block length (items[0]) of 3 in DWARF expr. */ + if (i == 1 && items[0] != 3) + goto warn_and_exit; + + if (i == 2) + e_offset = e; + + items[i] = e->exp.X_add_number; + i++; + } + + if (i != CFI_ESC_NUM_EXP) + goto warn_and_exit; +#undef CFI_ESC_NUM_EXP + + opcode1 = items[1]; + opcode2 = items[3]; + /* DW_OP_breg6 is rbp. FIXME - this stub can be enhanced to handle more + regs. */ + if (sframe_get_abi_arch () == SFRAME_ABI_AMD64_ENDIAN_LITTLE + && sframe_support_flex_fde_p () + && opcode1 == DW_OP_breg6 && opcode2 == DW_OP_deref) + { + x86_cfa_deref_p = true; + reg = SFRAME_CFA_FP_REG; + } + + offset = sframe_xlate_escape_sleb128_to_offsetT (e_offset); + + struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; + gas_assert (cur_fre); + + /* Handle the specific CFA expression mentioned above. */ + if (x86_cfa_deref_p + && sframe_fre_stack_offset_bound_p (offset, false) + && reg != SFRAME_FRE_REG_INVALID) + { + xlate_ctx->flex_p = true; + sframe_fre_set_cfa_base_reg (cur_fre, reg); + sframe_fre_set_cfa_offset (cur_fre, offset); + cur_fre->cfa_deref_p = true; + cur_fre->merge_candidate = false; + /* Done handling here. */ + caller_warn_p = false; + + return err; + } + /* Any other CFA expression may not be safe to skip. Fall through to + warn_and_exit. */ + +warn_and_exit: + *caller_warn_p = true; + return err; +} + /* Handle DW_CFA_expression in .cfi_escape. As with sframe_xlate_do_cfi_escape, the intent of this function is to detect @@ -1649,13 +1802,14 @@ sframe_xlate_do_gnu_window_save (struct sframe_xlate_ctx *xlate_ctx, SFRAME_XLATE_ERR_NOTREPRESENTED for their callers. */ static int -sframe_xlate_do_escape_expr (const struct sframe_xlate_ctx *xlate_ctx, +sframe_xlate_do_escape_expr (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; + const struct cfi_escape_data *e_offset = NULL; int err = SFRAME_XLATE_OK; - unsigned int reg = 0; + offsetT offset; unsigned int i = 0; /* Check roughly for an expression @@ -1665,32 +1819,70 @@ sframe_xlate_do_escape_expr (const struct sframe_xlate_ctx *xlate_ctx, 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 - || e->type != CFI_ESC_byte - || e->reloc != TC_PARSE_CONS_RETURN_NONE))) + /* Bounds check, must be constant, no relocs. */ + if (i >= CFI_ESC_NUM_EXP + || e->exp.X_op != O_constant + || e->reloc != TC_PARSE_CONS_RETURN_NONE) goto warn_and_exit; + /* Other checks based on index i. + - For item[3], allow byte OR sleb128. + - items at index 0, 1, and 2: Must be byte. */ + if (i == 3 && (e->type != CFI_ESC_byte && e->type != CFI_ESC_sleb128)) + goto warn_and_exit; + else if (i != 3 && e->type != CFI_ESC_byte) + goto warn_and_exit; + /* Block length (items[1]) of 2 in DWARF expr. */ + if (i == 2 && items[1] != 2) + goto warn_and_exit; + + if (i == 3) + e_offset = e; + items[i] = e->exp.X_add_number; i++; } if (i <= CFI_ESC_NUM_EXP - 1) goto warn_and_exit; +#undef CFI_ESC_NUM_EXP /* 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 + unsigned int reg = items[0]; - if (reg == SFRAME_CFA_SP_REG || reg == SFRAME_CFA_FP_REG - || (sframe_ra_tracking_p () && reg == SFRAME_CFA_RA_REG) - || reg == sframe_xlate_ctx_get_cur_cfa_reg (xlate_ctx)) + unsigned opcode = items[2]; + unsigned int fp_base_reg = SFRAME_FRE_REG_INVALID; + bool x86_fp_deref_p = true; + + if (sframe_get_abi_arch () == SFRAME_ABI_AMD64_ENDIAN_LITTLE + && sframe_support_flex_fde_p () + && opcode == DW_OP_breg6) + { + x86_fp_deref_p = true; + fp_base_reg = SFRAME_CFA_FP_REG; + } + + offset = sframe_xlate_escape_sleb128_to_offsetT (e_offset); + + struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; + gas_assert (cur_fre); + + if (x86_fp_deref_p + && reg == SFRAME_CFA_FP_REG + && sframe_fre_stack_offset_bound_p (offset, false)) + { + xlate_ctx->flex_p = true; + sframe_fre_set_fp_track (cur_fre, offset); + cur_fre->fp_loc = SFRAME_FRE_ELEM_LOC_REG; + cur_fre->fp_reg = fp_base_reg; + cur_fre->fp_deref_p = true; + cur_fre->merge_candidate = false; + } + else if (reg == SFRAME_CFA_SP_REG || reg == SFRAME_CFA_FP_REG + || (sframe_ra_tracking_p () && reg == SFRAME_CFA_RA_REG) + || reg == sframe_xlate_ctx_get_cur_cfa_reg (xlate_ctx)) { as_warn (_("no SFrame FDE emitted; " ".cfi_escape DW_CFA_expression with %s reg %u"), @@ -1855,7 +2047,7 @@ warn_and_exit: SFRAME_XLATE_OK if OK to skip. */ static int -sframe_xlate_do_cfi_escape (const struct sframe_xlate_ctx *xlate_ctx, +sframe_xlate_do_cfi_escape (struct sframe_xlate_ctx *xlate_ctx, const struct cfi_insn_data *cfi_insn) { const struct cfi_escape_data *e; @@ -1891,6 +2083,10 @@ sframe_xlate_do_cfi_escape (const struct sframe_xlate_ctx *xlate_ctx, } break; + case DW_CFA_def_cfa_expression: + err = sframe_xlate_do_escape_cfa_expr (xlate_ctx, cfi_insn, &warn_p); + break; + case DW_CFA_expression: err = sframe_xlate_do_escape_expr (xlate_ctx, cfi_insn, &warn_p); break; @@ -2250,6 +2446,7 @@ create_sframe_all (void) if (err && get_dw_fde_signal_p (dw_fde)) { sframe_xlate_ctx_cleanup (xlate_ctx); + xlate_ctx->flex_p = false; err = SFRAME_XLATE_OK; }