| /* gen-sframe.c - Support for generating SFrame section. |
| Copyright (C) 2022-2026 Free Software Foundation, Inc. |
| |
| This file is part of GAS, the GNU Assembler. |
| |
| GAS is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GAS is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GAS; see the file COPYING. If not, write to the Free |
| Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA |
| 02110-1301, USA. */ |
| |
| #include "as.h" |
| #include "subsegs.h" |
| #include "sframe.h" |
| #include "sframe-internal.h" |
| #include "gen-sframe.h" |
| #include "dw2gencfi.h" |
| #include "leb128.h" |
| |
| #ifdef support_sframe_p |
| |
| #ifndef sizeof_member |
| # define sizeof_member(type, member) (sizeof (((type *)0)->member)) |
| #endif |
| |
| /* SFrame FRE type selection optimization is an optimization for size. |
| |
| There are three flavors of SFrame FRE representation in the binary format: |
| - sframe_frame_row_entry_addr1 where the FRE start address is 1 byte. |
| - sframe_frame_row_entry_addr2 where the FRE start address is 2 bytes. |
| - sframe_frame_row_entry_addr4 where the FRE start address is 4 bytes. |
| |
| Note that in the SFrame format, all SFrame FREs of a function use one |
| single representation. The SFrame FRE type itself is identified via the |
| information in the SFrame FDE function info. |
| |
| Now, to select the minimum required one from the list above, one needs to |
| make a decision based on the size (in bytes) of the function. |
| |
| As a result, for this optimization, some fragments (generated with a new |
| type rs_sframe) for the SFrame section are fixed up later. |
| |
| This optimization (for size) is enabled by default. */ |
| |
| #ifndef SFRAME_FRE_TYPE_SELECTION_OPT |
| # define SFRAME_FRE_TYPE_SELECTION_OPT 1 |
| #endif |
| |
| /* gas emits SFrame Version 3 only at this time. */ |
| typedef sframe_func_desc_idx_v3 sframe_func_desc_idx; |
| |
| /* List of SFrame FDE entries. */ |
| |
| static struct sframe_func_entry *all_sframe_fdes = NULL; |
| |
| /* Tail of the list to add to. */ |
| |
| static struct sframe_func_entry **last_sframe_fde = &all_sframe_fdes; |
| |
| /* Emit a single byte into the current segment. */ |
| |
| static inline void |
| out_one (int byte) |
| { |
| FRAG_APPEND_1_CHAR (byte); |
| } |
| |
| /* Emit a two-byte word into the current segment. */ |
| |
| static inline void |
| out_two (int data) |
| { |
| md_number_to_chars (frag_more (2), data, 2); |
| } |
| |
| /* Emit a four byte word into the current segment. */ |
| |
| static inline void |
| out_four (int data) |
| { |
| md_number_to_chars (frag_more (4), data, 4); |
| } |
| |
| /* Get the start address symbol from the DWARF FDE. */ |
| |
| static symbolS* |
| get_dw_fde_start_addrS (const struct fde_entry *dw_fde) |
| { |
| return dw_fde->start_address; |
| } |
| |
| /* Get the start address symbol from the DWARF FDE. */ |
| |
| static symbolS* |
| get_dw_fde_end_addrS (const struct fde_entry *dw_fde) |
| { |
| return dw_fde->end_address; |
| } |
| |
| /* Get whether PAUTH B key is used. */ |
| static bool |
| get_dw_fde_pauth_b_key_p (const struct fde_entry *dw_fde ATTRIBUTE_UNUSED) |
| { |
| #ifdef tc_fde_entry_extras |
| return (dw_fde->pauth_key == AARCH64_PAUTH_KEY_B); |
| #else |
| return false; |
| #endif |
| } |
| |
| /* Get whether signal frame. */ |
| static bool |
| get_dw_fde_signal_p (const struct fde_entry *dw_fde) |
| { |
| return (dw_fde->signal_frame == 1); |
| } |
| |
| /* SFrame Frame Row Entry (FRE) related functions. */ |
| |
| static void |
| sframe_fre_set_begin_addr (struct sframe_row_entry *fre, symbolS *beginS) |
| { |
| fre->pc_begin = beginS; |
| } |
| |
| static void |
| sframe_fre_set_end_addr (struct sframe_row_entry *fre, symbolS *endS) |
| { |
| fre->pc_end = endS; |
| } |
| |
| static void |
| sframe_fre_set_cfa_base_reg (struct sframe_row_entry *fre, |
| unsigned int cfa_base_reg) |
| { |
| fre->cfa_base_reg = cfa_base_reg; |
| fre->merge_candidate = false; |
| } |
| |
| static offsetT |
| sframe_fre_get_cfa_offset (const struct sframe_row_entry * fre) |
| { |
| offsetT offset = fre->cfa_offset; |
| |
| /* For s390x undo adjustment of CFA offset (to enable 8-bit offsets). */ |
| if (sframe_get_abi_arch () == SFRAME_ABI_S390X_ENDIAN_BIG) |
| offset = SFRAME_V2_S390X_CFA_OFFSET_DECODE (offset); |
| |
| return offset; |
| } |
| |
| /* All stack offsets in SFrame stack trace format must be representable as a |
| 1-byte (SFRAME_FRE_DATAWORD_1B), 2-byte (SFRAME_FRE_DATAWORD_2B) or 4-byte |
| (SFRAME_FRE_DATAWORD_4B) value. |
| |
| At the moment, sanity check on CFA offset (only) is performed to address PR |
| gas/33277. Arguably, such updates to ra_offset or fp_offset will only |
| follow after updates to cfa_offset in a real-world, useful program. */ |
| |
| static bool |
| sframe_fre_stack_offset_bound_p (offsetT offset, bool cfa_reg_p) |
| { |
| /* For s390x, CFA offset is adjusted to enable 8-bit offsets. */ |
| if (cfa_reg_p && sframe_get_abi_arch () == SFRAME_ABI_S390X_ENDIAN_BIG) |
| offset = SFRAME_V2_S390X_CFA_OFFSET_ENCODE (offset); |
| |
| return (offset >= INT32_MIN && offset <= INT32_MAX); |
| } |
| |
| static void |
| sframe_fre_set_cfa_offset (struct sframe_row_entry *fre, |
| offsetT cfa_offset) |
| { |
| /* For s390x adjust CFA offset to enable 8-bit offsets. */ |
| if (sframe_get_abi_arch () == SFRAME_ABI_S390X_ENDIAN_BIG) |
| cfa_offset = SFRAME_V2_S390X_CFA_OFFSET_ENCODE (cfa_offset); |
| |
| fre->cfa_offset = cfa_offset; |
| fre->merge_candidate = false; |
| } |
| |
| static void |
| sframe_fre_set_ra_track (struct sframe_row_entry *fre, offsetT ra_offset) |
| { |
| fre->ra_loc = SFRAME_FRE_ELEM_LOC_STACK; |
| fre->ra_offset = ra_offset; |
| fre->ra_undefined_p = false; |
| fre->merge_candidate = false; |
| } |
| |
| static void |
| sframe_fre_set_fp_track (struct sframe_row_entry *fre, offsetT fp_offset) |
| { |
| fre->fp_loc = SFRAME_FRE_ELEM_LOC_STACK; |
| fre->fp_offset = fp_offset; |
| fre->merge_candidate = false; |
| } |
| |
| /* Given a signed offset, return the size in bytes needed to represent it. */ |
| |
| static unsigned int |
| get_offset_size_in_bytes (offsetT value) |
| { |
| unsigned int size = 0; |
| |
| if (value == (int8_t)value) |
| size = 1; |
| else if (value == (int16_t)value) |
| size = 2; |
| else if (value == (int32_t)value) |
| size = 4; |
| else |
| return 8; |
| |
| return size; |
| } |
| |
| /* Given an unsigned item, return the size in bytes needed to represent it. */ |
| |
| static unsigned int |
| get_udata_size_in_bytes (unsigned int value) |
| { |
| unsigned int size = 0; |
| |
| if (value <= UINT8_MAX) |
| size = 1; |
| else if (value <= UINT16_MAX) |
| size = 2; |
| else |
| size = 4; |
| |
| return size; |
| } |
| #define SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_1B 0 /* SFRAME_FRE_DATAWORD_1B. */ |
| #define SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_2B 1 /* SFRAME_FRE_DATAWORD_2B. */ |
| #define SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_4B 2 /* SFRAME_FRE_DATAWORD_4B. */ |
| #define SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_8B 3 /* Not supported in SFrame. */ |
| #define SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_MAX \ |
| SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_8B |
| |
| /* Helper struct for mapping FRE data word size to output functions. */ |
| |
| struct sframe_fre_dataword_func_map |
| { |
| unsigned int dataword_size; |
| void (*out_func)(int); |
| }; |
| |
| /* Given an DATAWORD_SIZE, return the size in bytes needed to represent it. */ |
| |
| static unsigned int |
| sframe_fre_dataword_func_map_index (unsigned int dataword_size) |
| { |
| unsigned int idx = SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_MAX; |
| |
| switch (dataword_size) |
| { |
| case SFRAME_FRE_DATAWORD_1B: |
| idx = SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_1B; |
| break; |
| case SFRAME_FRE_DATAWORD_2B: |
| idx = SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_2B; |
| break; |
| case SFRAME_FRE_DATAWORD_4B: |
| idx = SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_4B; |
| break; |
| default: |
| /* Not supported in SFrame. */ |
| break; |
| } |
| |
| return idx; |
| } |
| |
| /* Mapping from data word size to the output function to emit the value. */ |
| |
| static const |
| struct sframe_fre_dataword_func_map |
| dataword_func_map[SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_MAX+1] = |
| { |
| { SFRAME_FRE_DATAWORD_1B, out_one }, |
| { SFRAME_FRE_DATAWORD_2B, out_two }, |
| { SFRAME_FRE_DATAWORD_4B, out_four }, |
| { -1, NULL } /* Not Supported in SFrame. */ |
| }; |
| |
| /* SFrame version specific operations access. */ |
| |
| static struct sframe_version_ops sframe_ver_ops; |
| |
| /* SFrame (SFRAME_VERSION_1) set FRE info. */ |
| |
| static unsigned char |
| sframe_v1_set_fre_info (unsigned int cfa_base_reg, unsigned int dataword_count, |
| unsigned int dataword_size, bool mangled_ra_p) |
| { |
| unsigned char fre_info; |
| fre_info = SFRAME_V1_FRE_INFO (cfa_base_reg, dataword_count, dataword_size); |
| fre_info = SFRAME_V1_FRE_INFO_UPDATE_MANGLED_RA_P (mangled_ra_p, fre_info); |
| return fre_info; |
| } |
| |
| /* SFrame (SFRAME_VERSION_3) set function info. */ |
| |
| static unsigned char |
| sframe_v3_set_func_info (unsigned int fde_pc_type, unsigned int fre_type, |
| unsigned int pauth_key, bool signal_p) |
| { |
| unsigned char func_info; |
| func_info = SFRAME_V3_FDE_FUNC_INFO (fde_pc_type, fre_type); |
| func_info = SFRAME_V3_FDE_UPDATE_PAUTH_KEY (pauth_key, func_info); |
| func_info = SFRAME_V3_FDE_UPDATE_SIGNAL_P (signal_p, func_info); |
| return func_info; |
| } |
| |
| /* SFrame version specific operations setup. */ |
| |
| static void |
| sframe_set_version (enum gen_sframe_version flag_ver) |
| { |
| if (flag_ver == GEN_SFRAME_VERSION_3) |
| { |
| sframe_ver_ops.format_version = SFRAME_VERSION_3; |
| /* These operations remain the same for SFRAME_VERSION_3 as fre_info and |
| func_info layout has not changed from SFRAME_VERSION_2 and |
| SFRAME_VERSION_1. */ |
| sframe_ver_ops.set_fre_info = sframe_v1_set_fre_info; |
| sframe_ver_ops.set_func_info = sframe_v3_set_func_info; |
| } |
| } |
| |
| /* SFrame set FRE info. */ |
| |
| static unsigned char |
| sframe_set_fre_info (unsigned int cfa_base_reg, unsigned int dataword_count, |
| unsigned int dataword_size, bool mangled_ra_p) |
| { |
| return sframe_ver_ops.set_fre_info (cfa_base_reg, dataword_count, |
| dataword_size, mangled_ra_p); |
| } |
| |
| /* SFrame set func info. */ |
| |
| static unsigned char |
| sframe_set_func_info (unsigned int fde_type, unsigned int fre_type, |
| unsigned int pauth_key, bool signal_p) |
| { |
| return sframe_ver_ops.set_func_info (fde_type, fre_type, pauth_key, |
| signal_p); |
| } |
| |
| /* Get the number of SFrame FDEs for the current file. */ |
| |
| static unsigned int |
| get_num_sframe_fdes (void); |
| |
| /* Get the number of SFrame frame row entries for the current file. */ |
| |
| static unsigned int |
| get_num_sframe_fres (void); |
| |
| /* Get CFA base register ID as represented in SFrame Frame Row Entry. */ |
| |
| static unsigned int |
| get_fre_base_reg_id (const struct sframe_row_entry *sframe_fre) |
| { |
| unsigned int cfi_insn_cfa_base_reg = sframe_fre->cfa_base_reg; |
| unsigned fre_base_reg = SFRAME_BASE_REG_SP; |
| |
| if (cfi_insn_cfa_base_reg == SFRAME_CFA_FP_REG) |
| fre_base_reg = SFRAME_BASE_REG_FP; |
| |
| /* Only one bit is reserved in SFRAME_VERSION_1. */ |
| gas_assert (fre_base_reg == SFRAME_BASE_REG_SP |
| || fre_base_reg == SFRAME_BASE_REG_FP); |
| |
| return fre_base_reg; |
| } |
| |
| /* Get number of data words necessary for the SFrame Frame Row Entry. */ |
| |
| static unsigned int |
| get_fre_dataword_count (const struct sframe_row_entry *sframe_fre, bool flex_p) |
| { |
| /* For SFRAME_FDE_TYPE_FLEX FDE type, each entity (CFA, FP, RA) may carry up |
| to two data words. */ |
| unsigned int count = flex_p ? 2 : 1; |
| |
| /* CFA data word (or data words when flex_p) must always be present. */ |
| unsigned int fre_dataword_count = count; |
| |
| /* For flexible FDE type, there will be two data words for RA (if RA |
| has a recovery rule applicable). 1 padding data word otherwise. */ |
| if (flex_p) |
| { |
| if (sframe_fre->ra_loc != SFRAME_FRE_ELEM_LOC_NONE) |
| fre_dataword_count += count; |
| else if (sframe_fre->fp_loc != SFRAME_FRE_ELEM_LOC_NONE) |
| fre_dataword_count += 1; |
| } |
| else if (sframe_ra_tracking_p () |
| && (sframe_fre->ra_loc != SFRAME_FRE_ELEM_LOC_NONE |
| /* For s390x account padding RA data word, if FP without RA |
| saved. */ |
| || (sframe_get_abi_arch () == SFRAME_ABI_S390X_ENDIAN_BIG |
| && sframe_fre->fp_loc != SFRAME_FRE_ELEM_LOC_NONE))) |
| fre_dataword_count++; |
| |
| if (sframe_fre->fp_loc != SFRAME_FRE_ELEM_LOC_NONE) |
| fre_dataword_count += count; |
| |
| return fre_dataword_count; |
| } |
| |
| /* Get the minimum necessary data word size (in bytes) for this |
| SFrame frame row entry. */ |
| |
| static unsigned int |
| sframe_get_fre_dataword_size (const struct sframe_row_entry *sframe_fre, |
| bool flex_p) |
| { |
| unsigned int max_dataword_size = 0; |
| unsigned int cfa_offset_size = 0; |
| unsigned int fp_offset_size = 0; |
| unsigned int ra_offset_size = 0; |
| |
| unsigned int fre_dataword_size = 0; |
| |
| /* What size of data words appear in this frame row entry. */ |
| cfa_offset_size = get_offset_size_in_bytes (sframe_fre->cfa_offset); |
| if (sframe_fre->fp_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| fp_offset_size = get_offset_size_in_bytes (sframe_fre->fp_offset); |
| if (sframe_ra_tracking_p ()) |
| { |
| if (sframe_fre->ra_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| ra_offset_size = get_offset_size_in_bytes (sframe_fre->ra_offset); |
| /* For s390x account padding RA offset, if FP without RA saved. */ |
| else if (sframe_get_abi_arch () == SFRAME_ABI_S390X_ENDIAN_BIG |
| && sframe_fre->fp_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| ra_offset_size = get_offset_size_in_bytes (SFRAME_FRE_RA_OFFSET_INVALID); |
| } |
| |
| /* Get the maximum size needed to represent the offsets. */ |
| max_dataword_size = cfa_offset_size; |
| if (fp_offset_size > max_dataword_size) |
| max_dataword_size = fp_offset_size; |
| if (ra_offset_size > max_dataword_size) |
| max_dataword_size = ra_offset_size; |
| |
| /* If flex FDE, account for reg data too. */ |
| if (flex_p) |
| { |
| bool reg_p = (sframe_fre->cfa_base_reg != SFRAME_FRE_REG_INVALID); |
| unsigned int data |
| = SFRAME_V3_FLEX_FDE_CTRLWORD_ENCODE (sframe_fre->cfa_base_reg, |
| sframe_fre->cfa_deref_p, reg_p); |
| unsigned int cfa_control_word_size = get_udata_size_in_bytes (data); |
| if (cfa_control_word_size > max_dataword_size) |
| max_dataword_size = cfa_control_word_size; |
| |
| if (sframe_fre->ra_loc == SFRAME_FRE_ELEM_LOC_REG) |
| { |
| data = SFRAME_V3_FLEX_FDE_CTRLWORD_ENCODE (sframe_fre->ra_reg, |
| sframe_fre->ra_deref_p, |
| 1 /* reg_p. */); |
| unsigned ra_control_word_size = get_udata_size_in_bytes (data); |
| if (ra_control_word_size > max_dataword_size) |
| max_dataword_size = ra_control_word_size; |
| } |
| |
| if (sframe_fre->fp_loc == SFRAME_FRE_ELEM_LOC_REG) |
| { |
| data = SFRAME_V3_FLEX_FDE_CTRLWORD_ENCODE (sframe_fre->fp_reg, |
| sframe_fre->fp_deref_p, |
| 1 /* reg_p. */); |
| unsigned fp_control_word_size = get_udata_size_in_bytes (data); |
| if (fp_control_word_size > max_dataword_size) |
| max_dataword_size = fp_control_word_size; |
| } |
| } |
| |
| gas_assert (max_dataword_size); |
| |
| switch (max_dataword_size) |
| { |
| case 1: |
| fre_dataword_size = SFRAME_FRE_DATAWORD_1B; |
| break; |
| case 2: |
| fre_dataword_size = SFRAME_FRE_DATAWORD_2B; |
| break; |
| case 4: |
| fre_dataword_size = SFRAME_FRE_DATAWORD_4B; |
| break; |
| default: |
| /* FRE data words of size 8 bytes is not supported in SFrame. */ |
| as_fatal (_("SFrame unsupported FRE data word size\n")); |
| break; |
| } |
| |
| return fre_dataword_size; |
| } |
| |
| /* Create a composite expression CEXP (for SFrame FRE start address) such that: |
| |
| exp = <val> OP_absent <width>, where, |
| |
| - <val> and <width> are themselves expressionS. |
| - <val> stores the expression which when evaluated gives the value of the |
| start address offset of the FRE. |
| - <width> stores the expression when evaluated gives the number of bytes |
| needed to encode the start address offset of the FRE. |
| |
| The use of OP_absent as the X_op_symbol helps identify this expression |
| later when fragments are fixed up. */ |
| |
| static void |
| create_fre_start_addr_exp (expressionS *cexp, symbolS *fre_pc_begin, |
| symbolS *fde_start_address, |
| symbolS *fde_end_address) |
| { |
| /* val expression stores the FDE start address offset from the start PC |
| of function. */ |
| expressionS val = { |
| .X_op = O_subtract, |
| .X_add_symbol = fre_pc_begin, |
| .X_op_symbol = fde_start_address, |
| }; |
| |
| /* width expressions stores the size of the function. This is used later |
| to determine the number of bytes to be used to encode the FRE start |
| address of each FRE of the function. */ |
| expressionS width = { |
| .X_op = O_subtract, |
| .X_add_symbol = fde_end_address, |
| .X_op_symbol = fde_start_address, |
| }; |
| |
| *cexp = (expressionS) { |
| .X_op = O_absent, |
| .X_add_symbol = make_expr_symbol (&val), |
| .X_op_symbol = make_expr_symbol (&width) |
| }; |
| } |
| |
| /* Create a composite expression CEXP (for SFrame FDE function info) such that: |
| |
| exp = <rest_of_func_info> OP_modulus <width>, where, |
| |
| - <rest_of_func_info> and <width> are themselves expressionS. |
| - <rest_of_func_info> stores a constant expression where X_add_number is |
| used to stash away the func_info. The upper 4-bits of the func_info are copied |
| back to the resulting byte by the fragment fixup logic. |
| - <width> stores the expression when evaluated gives the size of the |
| function in number of bytes. |
| |
| The use of OP_modulus as the X_op_symbol helps identify this expression |
| later when fragments are fixed up. */ |
| |
| static void |
| create_func_info_exp (expressionS *cexp, symbolS *dw_fde_end_addrS, |
| symbolS *dw_fde_start_addrS, uint8_t func_info) |
| { |
| expressionS width = { |
| .X_op = O_subtract, |
| .X_add_symbol = dw_fde_end_addrS, |
| .X_op_symbol = dw_fde_start_addrS |
| }; |
| |
| expressionS rest_of_func_info = { |
| .X_op = O_constant, |
| .X_add_number = func_info |
| }; |
| |
| *cexp = (expressionS) { |
| .X_op = O_modulus, |
| .X_add_symbol = make_expr_symbol (&rest_of_func_info), |
| .X_op_symbol = make_expr_symbol (&width) |
| }; |
| } |
| |
| static struct sframe_row_entry* |
| sframe_row_entry_new (void) |
| { |
| struct sframe_row_entry *fre = XCNEW (struct sframe_row_entry); |
| /* Reset all regs to SFRAME_FRE_REG_INVALID. A value of 0 may imply a |
| valid register for a supported arch. */ |
| fre->cfa_base_reg = SFRAME_FRE_REG_INVALID; |
| fre->fp_reg = SFRAME_FRE_REG_INVALID; |
| fre->ra_reg = SFRAME_FRE_REG_INVALID; |
| fre->merge_candidate = true; |
| /* Reset the mangled RA status bit to zero by default. We will |
| initialize it in sframe_row_entry_initialize () with the sticky |
| bit if set. */ |
| fre->mangled_ra_p = false; |
| /* Reset the RA undefined status by to zero by default. */ |
| fre->ra_undefined_p = false; |
| |
| return fre; |
| } |
| |
| static void |
| sframe_row_entry_free (struct sframe_row_entry *fre) |
| { |
| while (fre) |
| { |
| struct sframe_row_entry *fre_next = fre->next; |
| XDELETE (fre); |
| fre = fre_next; |
| } |
| } |
| |
| /* Allocate an SFrame FDE. */ |
| |
| static struct sframe_func_entry* |
| sframe_fde_alloc (void) |
| { |
| return XCNEW (struct sframe_func_entry); |
| } |
| |
| /* Free up the SFrame FDE. */ |
| |
| static void |
| sframe_fde_free (struct sframe_func_entry *sframe_fde) |
| { |
| if (sframe_fde == NULL) |
| return; |
| |
| if (sframe_fde->sframe_fres) |
| sframe_row_entry_free (sframe_fde->sframe_fres); |
| |
| XDELETE (sframe_fde); |
| } |
| |
| /* Output the varlen data (SFrame FRE data words) for SFrame FRE object |
| SFRAME_FRE of the SFrame FDE object SFRAME_FDE. Each emitted entry is of |
| size FRE_DATAWORD_SIZE. Write out the data words in order - CFA, RA, FP. */ |
| |
| static unsigned int |
| output_sframe_row_entry_datawords (const struct sframe_func_entry *sframe_fde, |
| const struct sframe_row_entry *sframe_fre, |
| unsigned int fre_dataword_size) |
| { |
| unsigned int fre_write_datawords = 0; |
| |
| unsigned int idx = sframe_fre_dataword_func_map_index (fre_dataword_size); |
| gas_assert (idx < SFRAME_FRE_DATAWORD_FUNC_MAP_INDEX_MAX); |
| |
| if (sframe_fde->fde_flex_p) |
| { |
| /* SFrame FDE of type SFRAME_FDE_TYPE_FLEX. */ |
| /* Output CFA related FRE data words. */ |
| uint32_t reg = sframe_fre->cfa_base_reg; |
| bool deref_p = sframe_fre->cfa_deref_p; |
| uint32_t reg_data |
| = SFRAME_V3_FLEX_FDE_CTRLWORD_ENCODE (reg, deref_p, 1 /* reg_p. */); |
| offsetT offset_data = sframe_fre->cfa_offset; |
| dataword_func_map[idx].out_func (reg_data); |
| dataword_func_map[idx].out_func (offset_data); |
| fre_write_datawords += 2; |
| |
| bool reg_p = false; |
| if (sframe_fre->ra_loc != SFRAME_FRE_ELEM_LOC_NONE) |
| { |
| /* Output RA related FRE data words. */ |
| reg_p = sframe_fre->ra_loc == SFRAME_FRE_ELEM_LOC_REG; |
| reg = reg_p ? sframe_fre->ra_reg : 0; |
| deref_p = sframe_fre->ra_deref_p; |
| reg_data = SFRAME_V3_FLEX_FDE_CTRLWORD_ENCODE (reg, deref_p, reg_p); |
| |
| offset_data = sframe_fre->ra_offset; |
| dataword_func_map[idx].out_func (reg_data); |
| dataword_func_map[idx].out_func (offset_data); |
| fre_write_datawords += 2; |
| } |
| else if (sframe_fre->fp_loc != SFRAME_FRE_ELEM_LOC_NONE) |
| { |
| /* If RA is not in REG/STACK, emit RA padding if there are more |
| data words to follow. Note that, emitting |
| SFRAME_FRE_RA_OFFSET_INVALID is equivalent to emitting |
| SFRAME_V3_FLEX_FDE_CTRLWORD_ENCODE (0, 0, 0). */ |
| dataword_func_map[idx].out_func (SFRAME_FRE_RA_OFFSET_INVALID); |
| fre_write_datawords += 1; |
| } |
| |
| if (sframe_fre->fp_loc != SFRAME_FRE_ELEM_LOC_NONE) |
| { |
| /* Output FP related FRE data words. */ |
| reg_p = sframe_fre->fp_loc == SFRAME_FRE_ELEM_LOC_REG; |
| reg = reg_p ? sframe_fre->fp_reg : 0; |
| deref_p = sframe_fre->fp_deref_p; |
| reg_data = SFRAME_V3_FLEX_FDE_CTRLWORD_ENCODE (reg, deref_p, reg_p); |
| |
| offset_data = sframe_fre->fp_offset; |
| dataword_func_map[idx].out_func (reg_data); |
| dataword_func_map[idx].out_func (offset_data); |
| fre_write_datawords += 2; |
| } |
| } |
| else |
| { |
| /* SFrame FDE of type SFRAME_FDE_TYPE_DEFAULT. */ |
| /* Output CFA related FRE data words. */ |
| dataword_func_map[idx].out_func (sframe_fre->cfa_offset); |
| fre_write_datawords++; |
| |
| if (sframe_ra_tracking_p ()) |
| { |
| if (sframe_fre->ra_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| { |
| dataword_func_map[idx].out_func (sframe_fre->ra_offset); |
| fre_write_datawords++; |
| } |
| /* For s390x write padding RA offset, if FP without RA saved. */ |
| else if (sframe_get_abi_arch () == SFRAME_ABI_S390X_ENDIAN_BIG |
| && sframe_fre->fp_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| { |
| dataword_func_map[idx].out_func (SFRAME_FRE_RA_OFFSET_INVALID); |
| fre_write_datawords++; |
| } |
| } |
| if (sframe_fre->fp_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| { |
| dataword_func_map[idx].out_func (sframe_fre->fp_offset); |
| fre_write_datawords++; |
| } |
| } |
| |
| return fre_write_datawords; |
| } |
| |
| static void |
| output_sframe_row_entry (const struct sframe_func_entry *sframe_fde, |
| const struct sframe_row_entry *sframe_fre) |
| { |
| unsigned char fre_info; |
| unsigned int fre_dataword_count; |
| unsigned int fre_dataword_size; |
| unsigned int fre_base_reg; |
| bool fre_mangled_ra_p; |
| expressionS exp; |
| unsigned int fre_addr_size; |
| |
| unsigned int fre_write_datawords = 0; |
| symbolS *fde_start_addr = get_dw_fde_start_addrS (sframe_fde->dw_fde); |
| symbolS *fde_end_addr = get_dw_fde_end_addrS (sframe_fde->dw_fde); |
| bool flex_p = sframe_fde->fde_flex_p; |
| |
| fre_addr_size = 4; /* 4 bytes by default. FIXME tie it to fre_type? */ |
| |
| /* SFrame FRE Start Address. */ |
| if (SFRAME_FRE_TYPE_SELECTION_OPT) |
| { |
| create_fre_start_addr_exp (&exp, sframe_fre->pc_begin, fde_start_addr, |
| fde_end_addr); |
| frag_grow (fre_addr_size); |
| frag_var (rs_sframe, fre_addr_size, 0, 0, |
| make_expr_symbol (&exp), 0, (char *) frag_now); |
| } |
| else |
| { |
| gas_assert (fde_end_addr); |
| exp = (expressionS) { |
| .X_op = O_subtract, |
| .X_add_symbol = sframe_fre->pc_begin, /* to. */ |
| .X_op_symbol = fde_start_addr /* from. */ |
| }; |
| emit_expr (&exp, fre_addr_size); |
| } |
| |
| /* Create the fre_info using the CFA base register, number of data words and |
| max size of a data word in this FRE. Represent RA undefined as FRE |
| without any data words and all FRE info word fields zeroed. */ |
| if (sframe_fre->ra_undefined_p) |
| { |
| fre_base_reg = 0; |
| fre_dataword_count = 0; |
| fre_dataword_size = 0; |
| fre_mangled_ra_p = 0; |
| } |
| else |
| { |
| fre_base_reg = get_fre_base_reg_id (sframe_fre); |
| fre_dataword_count = get_fre_dataword_count (sframe_fre, flex_p); |
| fre_dataword_size = sframe_get_fre_dataword_size (sframe_fre, flex_p); |
| fre_mangled_ra_p = sframe_fre->mangled_ra_p; |
| } |
| |
| /* Unused for flex FDE. Set to zero. */ |
| if (flex_p) |
| fre_base_reg = 0; |
| |
| fre_info = sframe_set_fre_info (fre_base_reg, fre_dataword_count, |
| fre_dataword_size, fre_mangled_ra_p); |
| out_one (fre_info); |
| |
| /* Represent RA undefined as FRE without any data words. */ |
| if (sframe_fre->ra_undefined_p) |
| return; |
| |
| fre_write_datawords = output_sframe_row_entry_datawords (sframe_fde, |
| sframe_fre, |
| fre_dataword_size); |
| |
| /* Check if the expected number data words have been written out |
| in this FRE. */ |
| gas_assert (fre_write_datawords == fre_dataword_count); |
| } |
| |
| static void |
| output_sframe_funcdesc_idx (symbolS *start_of_fre_section, |
| symbolS *fre_symbol, |
| const struct sframe_func_entry *sframe_fde) |
| { |
| expressionS exp; |
| symbolS *dw_fde_start_addrS, *dw_fde_end_addrS; |
| |
| dw_fde_start_addrS = get_dw_fde_start_addrS (sframe_fde->dw_fde); |
| dw_fde_end_addrS = get_dw_fde_end_addrS (sframe_fde->dw_fde); |
| |
| /* Start address of the function. gas always emits this value with encoding |
| SFRAME_F_FDE_FUNC_START_PCREL. See PR ld/32666. */ |
| exp.X_op = O_subtract; |
| exp.X_add_symbol = dw_fde_start_addrS; /* to location. */ |
| exp.X_op_symbol = symbol_temp_new_now (); /* from location. */ |
| exp.X_add_number = 0; |
| emit_expr (&exp, sizeof_member (sframe_func_desc_idx, |
| sfdi_func_start_offset)); |
| |
| /* Size of the function in bytes. */ |
| exp.X_op = O_subtract; |
| exp.X_add_symbol = dw_fde_end_addrS; |
| exp.X_op_symbol = dw_fde_start_addrS; |
| exp.X_add_number = 0; |
| emit_expr (&exp, sizeof_member (sframe_func_desc_idx, |
| sfdi_func_size)); |
| |
| /* Offset to the function data (attribtues, FREs) in the FRE subsection. */ |
| exp.X_op = O_subtract; |
| exp.X_add_symbol = fre_symbol; /* Minuend. */ |
| exp.X_op_symbol = start_of_fre_section; /* Subtrahend. */ |
| exp.X_add_number = 0; |
| emit_expr (&exp, sizeof_member (sframe_func_desc_idx, |
| sfdi_func_start_fre_off)); |
| } |
| |
| static void |
| output_sframe_func_desc_attr (const struct sframe_func_entry *sframe_fde) |
| { |
| symbolS *dw_fde_start_addrS, *dw_fde_end_addrS; |
| unsigned int pauth_key; |
| bool signal_p; |
| |
| dw_fde_start_addrS = get_dw_fde_start_addrS (sframe_fde->dw_fde); |
| dw_fde_end_addrS = get_dw_fde_end_addrS (sframe_fde->dw_fde); |
| |
| /* Number of FREs must fit uint16_t. */ |
| gas_assert (sframe_fde->num_fres <= UINT16_MAX); |
| out_two (sframe_fde->num_fres); |
| |
| /* SFrame FDE function info. */ |
| unsigned char func_info; |
| pauth_key = (get_dw_fde_pauth_b_key_p (sframe_fde->dw_fde) |
| ? SFRAME_AARCH64_PAUTH_KEY_B : SFRAME_AARCH64_PAUTH_KEY_A); |
| signal_p = get_dw_fde_signal_p (sframe_fde->dw_fde); |
| func_info = sframe_set_func_info (SFRAME_V3_FDE_PCTYPE_INC, |
| SFRAME_FRE_TYPE_ADDR4, |
| pauth_key, signal_p); |
| if (SFRAME_FRE_TYPE_SELECTION_OPT) |
| { |
| expressionS cexp; |
| create_func_info_exp (&cexp, dw_fde_end_addrS, dw_fde_start_addrS, |
| func_info); |
| frag_grow (1); /* Size of func info is unsigned char. */ |
| frag_var (rs_sframe, 1, 0, 0, make_expr_symbol (&cexp), 0, |
| (char *) frag_now); |
| } |
| else |
| out_one (func_info); |
| |
| uint8_t finfo2 = 0; |
| if (sframe_fde->fde_flex_p) |
| finfo2 = SFRAME_V3_SET_FDE_TYPE (finfo2, SFRAME_FDE_TYPE_FLEX); |
| out_one (finfo2); |
| |
| /* Currently, GAS only emits SFrame FDE with PC Type |
| SFRAME_V3_FDE_PCTYPE_INC. Emit repetitive block size of 0. */ |
| out_one (0); |
| } |
| |
| static void |
| output_sframe_internal (void) |
| { |
| expressionS exp; |
| unsigned int i = 0; |
| |
| symbolS *end_of_frame_hdr; |
| symbolS *end_of_frame_section; |
| symbolS *start_of_func_desc_section; |
| symbolS *start_of_fre_section; |
| struct sframe_func_entry *sframe_fde, *sframe_fde_next; |
| struct sframe_row_entry *sframe_fre; |
| unsigned char abi_arch = 0; |
| int fixed_fp_offset = SFRAME_CFA_FIXED_FP_INVALID; |
| int fixed_ra_offset = SFRAME_CFA_FIXED_RA_INVALID; |
| |
| /* The function descriptor entries as dumped by the assembler are not |
| sorted on PCs. Fix for PR ld/32666 requires setting of an additional |
| flag in SFrame Version 2. */ |
| unsigned char sframe_flags = SFRAME_F_FDE_FUNC_START_PCREL; |
| |
| unsigned int num_fdes = get_num_sframe_fdes (); |
| unsigned int num_fres = get_num_sframe_fres (); |
| symbolS **fde_fre_symbols = XNEWVEC (symbolS *, num_fdes); |
| for (i = 0; i < num_fdes; i++) |
| fde_fre_symbols[i] = symbol_temp_make (); |
| |
| end_of_frame_hdr = symbol_temp_make (); |
| start_of_fre_section = symbol_temp_make (); |
| start_of_func_desc_section = symbol_temp_make (); |
| end_of_frame_section = symbol_temp_make (); |
| |
| /* Output the preamble of SFrame section. */ |
| out_two (SFRAME_MAGIC); |
| out_one (SFRAME_VERSION); |
| /* gas must ensure emitted SFrame sections have at least the required flags |
| set. */ |
| gas_assert ((sframe_flags & SFRAME_V2_GNU_AS_LD_ENCODING_FLAGS) |
| == SFRAME_V2_GNU_AS_LD_ENCODING_FLAGS); |
| out_one (sframe_flags); |
| /* abi/arch. */ |
| #ifdef sframe_get_abi_arch |
| abi_arch = sframe_get_abi_arch (); |
| #endif |
| gas_assert (abi_arch); |
| out_one (abi_arch); |
| |
| /* Offset for the FP register from CFA. Neither of the AMD64 or AAPCS64 |
| ABIs have a fixed offset for the FP register from the CFA. This may be |
| useful in future (but not without additional support in the toolchain) |
| for specialized handling/encoding for cases where, for example, |
| -fno-omit-frame-pointer is used. */ |
| out_one (fixed_fp_offset); |
| |
| /* All ABIs participating in SFrame generation must define |
| sframe_ra_tracking_p. |
| When RA tracking (in FREs) is not needed (e.g., AMD64), SFrame assumes |
| the RA is going to be at a fixed offset from CFA. Check that the fixed RA |
| offset is appropriately defined in all cases. */ |
| if (!sframe_ra_tracking_p ()) |
| { |
| fixed_ra_offset = sframe_cfa_ra_offset (); |
| gas_assert (fixed_ra_offset != SFRAME_CFA_FIXED_RA_INVALID); |
| } |
| out_one (fixed_ra_offset); |
| |
| /* None of the AMD64, AARCH64, or s390x ABIs need the auxiliary header. |
| When the need does arise to use this field, the appropriate backend |
| must provide this information. */ |
| out_one (0); /* Auxiliary SFrame header length. */ |
| |
| out_four (num_fdes); /* Number of FDEs. */ |
| out_four (num_fres); /* Number of FREs. */ |
| |
| /* Size of FRE sub-section. */ |
| exp.X_op = O_subtract; |
| exp.X_add_symbol = end_of_frame_section; |
| exp.X_op_symbol = start_of_fre_section; |
| exp.X_add_number = 0; |
| emit_expr (&exp, sizeof_member (sframe_header, sfh_fre_len)); |
| |
| /* Offset of FDE sub-section. */ |
| exp.X_op = O_subtract; |
| exp.X_add_symbol = end_of_frame_hdr; |
| exp.X_op_symbol = start_of_func_desc_section; |
| exp.X_add_number = 0; |
| emit_expr (&exp, sizeof_member (sframe_header, sfh_fdeoff)); |
| |
| /* Offset of FRE sub-section. */ |
| exp.X_op = O_subtract; |
| exp.X_add_symbol = start_of_fre_section; |
| exp.X_op_symbol = end_of_frame_hdr; |
| exp.X_add_number = 0; |
| emit_expr (&exp, sizeof_member (sframe_header, sfh_freoff)); |
| |
| symbol_set_value_now (end_of_frame_hdr); |
| symbol_set_value_now (start_of_func_desc_section); |
| |
| /* Output the SFrame function descriptor entries. */ |
| i = 0; |
| for (sframe_fde = all_sframe_fdes; sframe_fde; sframe_fde = sframe_fde->next) |
| { |
| output_sframe_funcdesc_idx (start_of_fre_section, fde_fre_symbols[i], |
| sframe_fde); |
| i++; |
| } |
| |
| symbol_set_value_now (start_of_fre_section); |
| |
| /* Output the SFrame FREs. */ |
| i = 0; |
| sframe_fde = all_sframe_fdes; |
| |
| for (sframe_fde = all_sframe_fdes; sframe_fde; sframe_fde = sframe_fde_next) |
| { |
| symbol_set_value_now (fde_fre_symbols[i]); |
| |
| output_sframe_func_desc_attr (sframe_fde); |
| |
| for (sframe_fre = sframe_fde->sframe_fres; |
| sframe_fre; |
| sframe_fre = sframe_fre->next) |
| { |
| output_sframe_row_entry (sframe_fde, sframe_fre); |
| } |
| i++; |
| sframe_fde_next = sframe_fde->next; |
| sframe_fde_free (sframe_fde); |
| } |
| all_sframe_fdes = NULL; |
| last_sframe_fde = &all_sframe_fdes; |
| |
| symbol_set_value_now (end_of_frame_section); |
| |
| gas_assert (i == num_fdes); |
| |
| free (fde_fre_symbols); |
| fde_fre_symbols = NULL; |
| } |
| |
| static unsigned int |
| get_num_sframe_fdes (void) |
| { |
| struct sframe_func_entry *sframe_fde; |
| unsigned int total_fdes = 0; |
| |
| for (sframe_fde = all_sframe_fdes; sframe_fde ; sframe_fde = sframe_fde->next) |
| total_fdes++; |
| |
| return total_fdes; |
| } |
| |
| /* Get the total number of SFrame row entries across the FDEs. */ |
| |
| static unsigned int |
| get_num_sframe_fres (void) |
| { |
| struct sframe_func_entry *sframe_fde; |
| unsigned int total_fres = 0; |
| |
| for (sframe_fde = all_sframe_fdes; sframe_fde ; sframe_fde = sframe_fde->next) |
| total_fres += sframe_fde->num_fres; |
| |
| return total_fres; |
| } |
| |
| /* SFrame translation context functions. */ |
| |
| /* Allocate a new SFrame translation context. */ |
| |
| static struct sframe_xlate_ctx* |
| sframe_xlate_ctx_alloc (void) |
| { |
| struct sframe_xlate_ctx* xlate_ctx = XCNEW (struct sframe_xlate_ctx); |
| return xlate_ctx; |
| } |
| |
| /* Initialize the given SFrame translation context. */ |
| |
| static void |
| sframe_xlate_ctx_init (struct sframe_xlate_ctx *xlate_ctx) |
| { |
| xlate_ctx->dw_fde = NULL; |
| xlate_ctx->flex_p = false; |
| xlate_ctx->first_fre = NULL; |
| xlate_ctx->last_fre = NULL; |
| xlate_ctx->cur_fre = NULL; |
| xlate_ctx->remember_fre = NULL; |
| xlate_ctx->num_xlate_fres = 0; |
| } |
| |
| /* Cleanup the given SFrame translation context. */ |
| |
| static void |
| sframe_xlate_ctx_cleanup (struct sframe_xlate_ctx *xlate_ctx) |
| { |
| sframe_row_entry_free (xlate_ctx->first_fre); |
| XDELETE (xlate_ctx->remember_fre); |
| xlate_ctx->remember_fre = NULL; |
| XDELETE (xlate_ctx->cur_fre); |
| xlate_ctx->cur_fre = NULL; |
| } |
| |
| /* Transfer the state from the SFrame translation context to the SFrame FDE. */ |
| |
| static void |
| sframe_xlate_ctx_finalize (struct sframe_xlate_ctx *xlate_ctx, |
| struct sframe_func_entry *sframe_fde) |
| { |
| sframe_fde->dw_fde = xlate_ctx->dw_fde; |
| sframe_fde->fde_flex_p = xlate_ctx->flex_p; |
| sframe_fde->sframe_fres = xlate_ctx->first_fre; |
| sframe_fde->num_fres = xlate_ctx->num_xlate_fres; |
| /* remember_fre is cloned copy of the applicable fre (where necessary). |
| Since this is not included in the list of sframe_fres, free it. */ |
| XDELETE (xlate_ctx->remember_fre); |
| xlate_ctx->remember_fre = NULL; |
| } |
| |
| /* Get the current CFA base register from the scratchpad FRE (cur_fre). |
| NB: this may return a value of SFRAME_FRE_REG_INVALID. */ |
| |
| static unsigned int |
| sframe_xlate_ctx_get_cur_cfa_reg (const struct sframe_xlate_ctx *xlate_ctx) |
| { |
| gas_assert (xlate_ctx && xlate_ctx->cur_fre); |
| |
| return xlate_ctx->cur_fre->cfa_base_reg; |
| } |
| |
| /* Add the given FRE in the list of frame row entries in the given FDE |
| translation context. */ |
| |
| static void |
| sframe_xlate_ctx_add_fre (struct sframe_xlate_ctx *xlate_ctx, |
| struct sframe_row_entry *fre) |
| { |
| gas_assert (xlate_ctx && fre); |
| |
| /* Add the frame row entry. */ |
| if (!xlate_ctx->first_fre) |
| xlate_ctx->first_fre = fre; |
| else if (xlate_ctx->last_fre) |
| xlate_ctx->last_fre->next = fre; |
| |
| xlate_ctx->last_fre = fre; |
| |
| /* Keep track of the total number of SFrame frame row entries. */ |
| xlate_ctx->num_xlate_fres++; |
| } |
| |
| /* A SFrame Frame Row Entry is self-sufficient in terms of stack tracing info |
| for a given PC. It contains information assimilated from multiple CFI |
| instructions, and hence, a new SFrame FRE is initialized with the data from |
| the previous known FRE, if any. |
| |
| Understandably, not all information (especially the instruction begin |
| and end boundaries) needs to be relayed. Hence, the caller of this API |
| must set the pc_begin and pc_end as applicable. */ |
| |
| static void |
| sframe_row_entry_initialize (struct sframe_row_entry *cur_fre, |
| const struct sframe_row_entry *prev_fre) |
| { |
| gas_assert (prev_fre); |
| cur_fre->cfa_base_reg = prev_fre->cfa_base_reg; |
| cur_fre->cfa_offset = prev_fre->cfa_offset; |
| cur_fre->cfa_deref_p = prev_fre->cfa_deref_p; |
| cur_fre->fp_loc = prev_fre->fp_loc; |
| cur_fre->fp_reg = prev_fre->fp_reg; |
| cur_fre->fp_offset = prev_fre->fp_offset; |
| cur_fre->fp_deref_p = prev_fre->fp_deref_p; |
| cur_fre->ra_loc = prev_fre->ra_loc; |
| cur_fre->ra_reg = prev_fre->ra_reg; |
| cur_fre->ra_offset = prev_fre->ra_offset; |
| cur_fre->ra_deref_p = prev_fre->ra_deref_p; |
| /* Treat RA mangling as a sticky bit. It retains its value until another |
| .cfi_negate_ra_state is seen. */ |
| cur_fre->mangled_ra_p = prev_fre->mangled_ra_p; |
| /* Treat RA undefined as a sticky bit. It retains its value until a |
| .cfi_offset RA, .cfi_register RA, .cfi_restore RA, or .cfi_same_value RA |
| is seen. */ |
| cur_fre->ra_undefined_p = prev_fre->ra_undefined_p; |
| } |
| |
| /* Return SFrame register name for SP, FP, and RA, or NULL if other. */ |
| |
| static const char * |
| sframe_register_name (unsigned int reg) |
| { |
| if (reg == SFRAME_CFA_SP_REG) |
| return "SP"; |
| else if (reg == SFRAME_CFA_FP_REG) |
| return "FP"; |
| else if (reg == SFRAME_CFA_RA_REG) |
| return "RA"; |
| else |
| return NULL; |
| } |
| |
| /* Translate DW_CFA_advance_loc into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_advance_loc (struct sframe_xlate_ctx *xlate_ctx, |
| const struct cfi_insn_data *cfi_insn) |
| { |
| struct sframe_row_entry *last_fre = xlate_ctx->last_fre; |
| /* Get the scratchpad FRE currently being updated as the cfi_insn's |
| get interpreted. This FRE eventually gets linked in into the |
| list of FREs for the specific function. */ |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| |
| if (cur_fre) |
| { |
| if (!cur_fre->merge_candidate) |
| { |
| sframe_fre_set_end_addr (cur_fre, cfi_insn->u.ll.lab2); |
| |
| sframe_xlate_ctx_add_fre (xlate_ctx, cur_fre); |
| last_fre = xlate_ctx->last_fre; |
| |
| xlate_ctx->cur_fre = sframe_row_entry_new (); |
| cur_fre = xlate_ctx->cur_fre; |
| |
| if (last_fre) |
| sframe_row_entry_initialize (cur_fre, last_fre); |
| } |
| else |
| { |
| sframe_fre_set_end_addr (last_fre, cfi_insn->u.ll.lab2); |
| gas_assert (last_fre->merge_candidate == false); |
| } |
| } |
| else |
| { |
| xlate_ctx->cur_fre = sframe_row_entry_new (); |
| cur_fre = xlate_ctx->cur_fre; |
| } |
| |
| gas_assert (cur_fre); |
| sframe_fre_set_begin_addr (cur_fre, cfi_insn->u.ll.lab2); |
| |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_def_cfa into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_def_cfa (struct sframe_xlate_ctx *xlate_ctx, |
| const struct cfi_insn_data *cfi_insn) |
| |
| { |
| /* Get the scratchpad FRE. This FRE will eventually get linked in. */ |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| if (!cur_fre) |
| { |
| xlate_ctx->cur_fre = sframe_row_entry_new (); |
| cur_fre = xlate_ctx->cur_fre; |
| sframe_fre_set_begin_addr (cur_fre, |
| get_dw_fde_start_addrS (xlate_ctx->dw_fde)); |
| } |
| |
| 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; |
| } |
| |
| /* Translate DW_CFA_def_cfa_register into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_def_cfa_register (struct sframe_xlate_ctx *xlate_ctx, |
| const struct cfi_insn_data *cfi_insn) |
| { |
| const struct sframe_row_entry *last_fre = xlate_ctx->last_fre; |
| /* Get the scratchpad FRE. This FRE will eventually get linked in. */ |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| |
| gas_assert (cur_fre); |
| /* Define the current CFA rule to use the provided register (but to |
| keep the old offset). However, if the register is not FP/SP, |
| skip creating SFrame stack trace info for the function. */ |
| if (cfi_insn->u.r != SFRAME_CFA_SP_REG |
| && cfi_insn->u.r != 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_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; |
| |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_def_cfa_offset into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_def_cfa_offset (struct sframe_xlate_ctx *xlate_ctx, |
| const struct cfi_insn_data *cfi_insn) |
| { |
| /* The scratchpad FRE currently being updated with each cfi_insn |
| being interpreted. This FRE eventually gets linked in into the |
| list of FREs for the specific function. */ |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| unsigned int cur_cfa_reg = sframe_xlate_ctx_get_cur_cfa_reg (xlate_ctx); |
| |
| gas_assert (cur_fre); |
| /* Define the current CFA rule to use the provided offset (but to keep |
| the old register). However, if the old register is not FP/SP, |
| skip creating SFrame stack trace info for the function. */ |
| if (cur_cfa_reg == SFRAME_CFA_FP_REG || cur_cfa_reg == SFRAME_CFA_SP_REG) |
| { |
| if (sframe_fre_stack_offset_bound_p (cfi_insn->u.i, true)) |
| { |
| sframe_fre_set_cfa_offset (cur_fre, cfi_insn->u.i); |
| cur_fre->merge_candidate = false; |
| } |
| else |
| { |
| as_warn (_("no SFrame FDE emitted; " |
| ".cfi_def_cfa_offset with unsupported offset value")); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; |
| } |
| } |
| else |
| { |
| /* No CFA base register in effect. Non-SP/FP CFA base register should |
| not occur, as sframe_xlate_do_def_cfa[_register] would detect this. */ |
| as_warn (_("no SFrame FDE emitted; " |
| ".cfi_def_cfa_offset without CFA base register in effect")); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; |
| } |
| |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_offset into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_offset (struct sframe_xlate_ctx *xlate_ctx, |
| const struct cfi_insn_data *cfi_insn) |
| { |
| /* The scratchpad FRE currently being updated with each cfi_insn |
| being interpreted. This FRE eventually gets linked in into the |
| list of FREs for the specific function. */ |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| gas_assert (cur_fre); |
| |
| /* For ABIs not tracking RA, the return address is expected to be in a |
| specific location. Explicit manourvering to a different offset (than the |
| default offset) is non-representable in SFrame, unless flex FDE generation |
| is supported for the ABI. */ |
| if (!sframe_support_flex_fde_p () && !sframe_ra_tracking_p () |
| && cfi_insn->u.ri.reg == SFRAME_CFA_RA_REG |
| && cfi_insn->u.ri.offset != sframe_cfa_ra_offset ()) |
| { |
| as_warn (_("no SFrame FDE emitted; %s register %u in .cfi_offset"), |
| sframe_register_name (cfi_insn->u.ri.reg), cfi_insn->u.ri.reg); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ |
| } |
| |
| /* Change the rule for the register indicated by the register number to |
| be the specified offset. */ |
| /* Ignore SP reg, as it can be recovered from the CFA tracking info. */ |
| if (cfi_insn->u.ri.reg == SFRAME_CFA_FP_REG) |
| { |
| sframe_fre_set_fp_track (cur_fre, cfi_insn->u.ri.offset); |
| cur_fre->fp_reg = SFRAME_FRE_REG_INVALID; |
| cur_fre->fp_deref_p = true; |
| cur_fre->merge_candidate = false; |
| } |
| /* Either the ABI has enabled RA tracking, in which case we must process the |
| DW_CFA_offset opcode for REG_RA like usual. Or if the ABI has not enabled |
| RA tracking, but flex FDE generation is supported, distinguish between |
| whether its time to reset the RA tracking state or not. */ |
| else if (cfi_insn->u.ri.reg == SFRAME_CFA_RA_REG) |
| { |
| if (!sframe_ra_tracking_p () |
| && cfi_insn->u.ri.offset == sframe_cfa_ra_offset ()) |
| { |
| /* Reset RA tracking info, if fixed offset. */ |
| cur_fre->ra_reg = SFRAME_FRE_REG_INVALID; |
| cur_fre->ra_loc = SFRAME_FRE_ELEM_LOC_NONE; |
| cur_fre->ra_deref_p = false; |
| cur_fre->merge_candidate = false; |
| } |
| else |
| { |
| sframe_fre_set_ra_track (cur_fre, cfi_insn->u.ri.offset); |
| cur_fre->ra_reg = SFRAME_FRE_REG_INVALID; |
| cur_fre->ra_loc = SFRAME_FRE_ELEM_LOC_STACK; |
| cur_fre->ra_deref_p = true; |
| cur_fre->merge_candidate = false; |
| |
| if (!sframe_ra_tracking_p () && sframe_support_flex_fde_p ()) |
| xlate_ctx->flex_p = true; |
| } |
| } |
| |
| /* Skip all other registers. */ |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_val_offset into SFrame context. |
| 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 (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 |
| DW_CFA_val_offset instruction can be safely skipped without sacrificing |
| the asynchronicity of stack trace information. */ |
| if (cfi_insn->u.ri.reg == SFRAME_CFA_FP_REG |
| || (sframe_ra_tracking_p () && cfi_insn->u.ri.reg == SFRAME_CFA_RA_REG) |
| /* Ignore SP reg, if offset matches assumed default rule. */ |
| || (cfi_insn->u.ri.reg == SFRAME_CFA_SP_REG |
| && ((sframe_get_abi_arch () != SFRAME_ABI_S390X_ENDIAN_BIG |
| && cfi_insn->u.ri.offset != 0) |
| || (sframe_get_abi_arch () == SFRAME_ABI_S390X_ENDIAN_BIG |
| && cfi_insn->u.ri.offset != SFRAME_S390X_SP_VAL_OFFSET)))) |
| { |
| as_warn (_("no SFrame FDE emitted; %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. */ |
| } |
| |
| /* Safe to skip. */ |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_register into SFrame context. |
| |
| This opcode indicates: Previous value of register1 is register2. This is |
| not representable using FDE type SFRAME_FDE_TYPE_DEFAULT. Hence, if |
| flexible FDE is not enabled for the ABI/arch, detect the use of registers |
| interesting to SFrame (FP, RA for this opcode), and skip FDE generation |
| while warning the user. Same applies for SP, except that it needs special |
| handling for s390. |
| |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_register (struct sframe_xlate_ctx *xlate_ctx, |
| const struct cfi_insn_data *cfi_insn) |
| { |
| if (sframe_support_flex_fde_p ()) |
| { |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| |
| if (cfi_insn->u.rr.reg1 == SFRAME_CFA_FP_REG) |
| { |
| sframe_fre_set_fp_track (cur_fre, 0); |
| cur_fre->fp_loc = SFRAME_FRE_ELEM_LOC_REG; |
| cur_fre->fp_reg = cfi_insn->u.rr.reg2; |
| cur_fre->fp_deref_p = false; |
| cur_fre->merge_candidate = false; |
| xlate_ctx->flex_p = true; |
| return SFRAME_XLATE_OK; |
| } |
| else if (cfi_insn->u.rr.reg1 == SFRAME_CFA_RA_REG) |
| { |
| sframe_fre_set_ra_track (cur_fre, 0); |
| cur_fre->ra_loc = SFRAME_FRE_ELEM_LOC_REG; |
| cur_fre->ra_reg = cfi_insn->u.rr.reg2; |
| cur_fre->ra_deref_p = false; |
| cur_fre->merge_candidate = false; |
| xlate_ctx->flex_p = true; |
| return SFRAME_XLATE_OK; |
| } |
| /* Recovering REG_SP from an alternate register is not represented in |
| SFrame. Fallthrough if SFRAME_CFA_SP_REG and error out. */ |
| } |
| |
| if (cfi_insn->u.rr.reg1 == SFRAME_CFA_RA_REG |
| /* SFrame does not track SP explicitly. */ |
| || (cfi_insn->u.rr.reg1 == SFRAME_CFA_SP_REG |
| && sframe_get_abi_arch () != SFRAME_ABI_S390X_ENDIAN_BIG) |
| || cfi_insn->u.rr.reg1 == SFRAME_CFA_FP_REG) |
| { |
| as_warn (_("no SFrame FDE emitted; %s register %u in .cfi_register"), |
| sframe_register_name (cfi_insn->u.rr.reg1), cfi_insn->u.rr.reg1); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ |
| } |
| |
| /* Safe to skip. */ |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_remember_state into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_remember_state (struct sframe_xlate_ctx *xlate_ctx) |
| { |
| const struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| |
| /* If there is no FRE state to remember, nothing to do here. Return |
| early with non-zero error code, this will cause no SFrame stack trace |
| info for the function involved. */ |
| if (!cur_fre) |
| { |
| as_warn (_("no SFrame FDE emitted; " |
| ".cfi_remember_state without prior SFrame FRE state")); |
| return SFRAME_XLATE_ERR_INVAL; |
| } |
| |
| if (!xlate_ctx->remember_fre) |
| xlate_ctx->remember_fre = sframe_row_entry_new (); |
| sframe_row_entry_initialize (xlate_ctx->remember_fre, cur_fre); |
| |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_restore_state into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_restore_state (struct sframe_xlate_ctx *xlate_ctx) |
| { |
| /* The scratchpad FRE currently being updated with each cfi_insn |
| being interpreted. This FRE eventually gets linked in into the |
| list of FREs for the specific function. */ |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| |
| gas_assert (xlate_ctx->remember_fre); |
| gas_assert (cur_fre && cur_fre->merge_candidate); |
| |
| /* Get the CFA state from the DW_CFA_remember_state insn. */ |
| sframe_row_entry_initialize (cur_fre, xlate_ctx->remember_fre); |
| /* The PC boundaries of the current SFrame FRE are updated |
| via other machinery. */ |
| cur_fre->merge_candidate = false; |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_restore into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_restore (struct sframe_xlate_ctx *xlate_ctx, |
| const struct cfi_insn_data *cfi_insn) |
| { |
| struct sframe_row_entry *cie_fre = xlate_ctx->first_fre; |
| /* The scratchpad FRE currently being updated with each cfi_insn |
| being interpreted. This FRE eventually gets linked in into the |
| list of FREs for the specific function. */ |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| |
| /* PR gas/33170. It is valid to have a: |
| .cfi_restore N |
| even at the entry of a function; in which case cie_fre is not yet setup. |
| Point cie_fre to cur_fre, and let the machinery proceed to update |
| merge_candidate as usual. */ |
| if (cie_fre == NULL) |
| cie_fre = cur_fre; |
| |
| /* Change the rule for the indicated register to the rule assigned to |
| it by the initial_instructions in the CIE. SFrame FREs track only CFA |
| and FP / RA for backtracing purposes; skip the other .cfi_restore |
| directives. */ |
| if (cfi_insn->u.r == SFRAME_CFA_FP_REG) |
| { |
| gas_assert (cur_fre); |
| cur_fre->fp_loc = cie_fre->fp_loc; |
| cur_fre->fp_offset = cie_fre->fp_offset; |
| cur_fre->merge_candidate = false; |
| } |
| else if (sframe_ra_tracking_p () |
| && cfi_insn->u.r == SFRAME_CFA_RA_REG) |
| { |
| gas_assert (cur_fre); |
| cur_fre->ra_loc = cie_fre->ra_loc; |
| cur_fre->ra_offset = cie_fre->ra_offset; |
| cur_fre->ra_undefined_p = cie_fre->ra_undefined_p; |
| cur_fre->merge_candidate = false; |
| } |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_AARCH64_negate_ra_state into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_aarch64_negate_ra_state (struct sframe_xlate_ctx *xlate_ctx, |
| const struct cfi_insn_data *cfi_insn ATTRIBUTE_UNUSED) |
| { |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| |
| gas_assert (cur_fre); |
| /* Toggle the mangled RA status bit. */ |
| cur_fre->mangled_ra_p = !cur_fre->mangled_ra_p; |
| cur_fre->merge_candidate = false; |
| |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_AARCH64_negate_ra_state_with_pc into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_aarch64_negate_ra_state_with_pc (struct sframe_xlate_ctx *xlate_ctx ATTRIBUTE_UNUSED, |
| const struct cfi_insn_data *cfi_insn ATTRIBUTE_UNUSED) |
| { |
| as_warn (_("no SFrame FDE emitted; .cfi_negate_ra_state_with_pc")); |
| /* The used signing method should be encoded inside the FDE in SFrame v3. |
| For now, PAuth_LR extension is not supported with SFrame. */ |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ |
| } |
| |
| /* Translate DW_CFA_GNU_window_save into SFrame context. |
| DW_CFA_GNU_window_save is a DWARF Sparc extension, but is multiplexed with a |
| directive of DWARF AArch64 extension: DW_CFA_AARCH64_negate_ra_state. |
| The AArch64 backend of GCC 14 and older versions was emitting mistakenly the |
| Sparc CFI directive (.cfi_window_save). From GCC 15, the AArch64 backend |
| only emits .cfi_negate_ra_state. For backward compatibility, the handler for |
| .cfi_window_save needs to check whether the directive was used in a AArch64 |
| ABI context or not. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_gnu_window_save (struct sframe_xlate_ctx *xlate_ctx, |
| const struct cfi_insn_data *cfi_insn) |
| { |
| unsigned char abi_arch = sframe_get_abi_arch (); |
| |
| /* Translate DW_CFA_AARCH64_negate_ra_state into SFrame context. */ |
| if (abi_arch == SFRAME_ABI_AARCH64_ENDIAN_BIG |
| || abi_arch == SFRAME_ABI_AARCH64_ENDIAN_LITTLE) |
| return sframe_xlate_do_aarch64_negate_ra_state (xlate_ctx, cfi_insn); |
| |
| as_warn (_("no SFrame FDE emitted; .cfi_window_save")); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ |
| } |
| |
| /* Translate a DWARF sleb128 offset in the CFI escape data E to an offsetT. |
| Update the value in OFFSET if success (and return SFRAME_XLATE_OK). |
| Return SFRAME_XLATE_ERR_INVAL if error. */ |
| |
| static int |
| 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); |
| /* In case of bogus input (highest bit erroneously set, e.g., 0x80), |
| gracefully exit. */ |
| if (!read) |
| return SFRAME_XLATE_ERR_INVAL; |
| *offset = (offsetT) value; |
| } |
| else |
| /* offset must be CFI_ESC_sleb128. */ |
| *offset = e->exp.X_add_number; |
| |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* 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 = 0; |
| 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 |
| |
| err = sframe_xlate_escape_sleb128_to_offsetT (e_offset, &offset); |
| if (err == SFRAME_XLATE_ERR_INVAL) |
| goto warn_and_exit; |
| |
| 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; |
| } |
| |
| 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 |
| 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 (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; |
| offsetT offset = 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; |
| /* 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 |
| |
| err = sframe_xlate_escape_sleb128_to_offsetT (e_offset, &offset); |
| if (err == SFRAME_XLATE_ERR_INVAL) |
| 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. */ |
| unsigned int reg = items[0]; |
| |
| 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; |
| } |
| |
| 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"), |
| 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 |
| || e->type != CFI_ESC_byte |
| || e->reloc != TC_PARSE_CONS_RETURN_NONE) |
| 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 DW_CFA_GNU_args_size in .cfi_escape. |
| |
| The purpose of DW_CFA_GNU_args_size is to adjust SP when performing stack |
| unwinding for exception handling. For stack tracing needs, |
| DW_CFA_GNU_args_size can be ignored, when CFA is FP-based. This is because |
| if the topmost frame is that of the catch block, the SP has been restored to |
| correct value by exception handling logic. From this point of interest in |
| the catch block now, stack tracing intends to go backwards to the caller |
| frame. If CFA restoration does not need SP, DW_CFA_GNU_args_size can be |
| ignored for stack tracing. |
| |
| Continue to warn and not emit SFrame FDE if CFA is SP based. The pattern |
| where the CFA is SP based and there is a DW_CFA_GNU_args_size for |
| SP-adjustment is not entirely clear. |
| |
| 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_gnu_args_size (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; |
| unsigned int i = 0; |
| |
| /* Check for (DW_CFA_GNU_args_size offset) sequence. */ |
| #define CFI_ESC_NUM_EXP 1 |
| 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 |
| || e->type != CFI_ESC_byte |
| || e->reloc != TC_PARSE_CONS_RETURN_NONE) |
| goto warn_and_exit; |
| items[i] = e->exp.X_add_number; |
| i++; |
| } |
| if (i == 0) |
| goto warn_and_exit; |
| |
| #undef CFI_ESC_NUM_EXP |
| |
| offsetT offset = items[0]; |
| |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| gas_assert (cur_fre); |
| /* If CFA is FP based, safe to skip. */ |
| if (offset == 0 |
| || sframe_xlate_ctx_get_cur_cfa_reg (xlate_ctx) == SFRAME_CFA_FP_REG) |
| return SFRAME_XLATE_OK; |
| |
| warn_and_exit: |
| *caller_warn_p = true; |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* 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 (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 |
| || e->type != CFI_ESC_byte |
| || e->reloc != TC_PARSE_CONS_RETURN_NONE) |
| 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 |
| || e->type != CFI_ESC_byte |
| || e->reloc != TC_PARSE_CONS_RETURN_NONE) |
| { |
| warn_p = true; |
| break; |
| } |
| } |
| 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; |
| |
| case DW_CFA_val_offset: |
| err = sframe_xlate_do_escape_val_offset (xlate_ctx, cfi_insn, &warn_p); |
| break; |
| |
| case DW_CFA_GNU_args_size: |
| err = sframe_xlate_do_escape_gnu_args_size (xlate_ctx, cfi_insn, &warn_p); |
| break; |
| |
| 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 (_("no SFrame FDE emitted; .cfi_escape with op (%#lx)"), |
| (unsigned long)firstop); |
| err = SFRAME_XLATE_ERR_NOTREPRESENTED; |
| } |
| |
| return err; |
| } |
| |
| /* Translate DW_CFA_undefined into SFrame context. |
| |
| DW_CFA_undefined op indicates that from now on, the previous value of |
| register can’t be restored anymore. In DWARF, for the return address (RA) |
| register, this indicates to an unwinder that there is no return address |
| and the unwind is complete. |
| |
| In SFrame, represent the use of the RA register with DW_CFA_undefined as |
| SFrame FRE without any trailing FRE data words. Stack tracers can use this |
| as indication that an outermost frame has been reached and the stack trace |
| is complete. The use of other registers of interest with DW_CFA_undefined |
| cannot be represented in SFrame. Therefore skip generating an SFrame FDE. |
| |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_cfi_undefined (const struct sframe_xlate_ctx *xlate_ctx ATTRIBUTE_UNUSED, |
| const struct cfi_insn_data *cfi_insn) |
| { |
| if (cfi_insn->u.r == SFRAME_CFA_FP_REG |
| || cfi_insn->u.r == SFRAME_CFA_SP_REG) |
| { |
| as_warn (_("no SFrame FDE emitted; %s reg %u in .cfi_undefined"), |
| sframe_register_name (cfi_insn->u.r), cfi_insn->u.r); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ |
| } |
| else if (cfi_insn->u.r == SFRAME_CFA_RA_REG) |
| { |
| /* Represent RA undefined (i.e. outermost frame) as FRE without any |
| data words. */ |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| |
| gas_assert (cur_fre); |
| /* Set RA undefined status bit. */ |
| cur_fre->ra_undefined_p = true; |
| cur_fre->merge_candidate = false; |
| } |
| |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_same_value into SFrame context. |
| |
| DW_CFA_same_value op indicates that current value of register is the same as |
| in the previous frame, i.e. no restoration needed. In SFrame stack trace |
| format, the handling is done similar to DW_CFA_restore. |
| |
| For SFRAME_CFA_RA_REG, if RA-tracking is enabled, reset the SFrame FRE state |
| for REG_RA to indicate that register does not need restoration. P.S.: Even |
| though resetting just REG_RA may be contradicting the AArch64 ABI (as Frame |
| Record contains for FP and LR), sframe_xlate_do_same_value () does not |
| detect the case and assumes the users' DW_CFA_same_value SFRAME_CFA_RA_REG |
| has a sound reason. For ABIs, where RA-tracking is disabled, handle it |
| similar to DW_CFA_restore: ignore the directive, it is safe to skip. The |
| reasoning is similar to that for DW_CFA_restore: if such a restoration was |
| meant to be of any consequence, there must have been the necessary CFI |
| directives for updating the CFA rule too such that the recovered RA from |
| stack is valid. |
| |
| SFrame based stacktracers will implement CFA-based SP recovery for all ABIs: |
| SP for previous frame is based on the applicable CFA-rule. There is no |
| representation in SFrame to indicate "no restoration needed" for REG_SP, |
| when going to the previous frame. That said, if DW_CFA_same_value is seen |
| for SFRAME_CFA_SP_REG, handle it similar to DW_CFA_restore: ignore the |
| directive, it is safe to skip. The reasoning is similar to that for |
| DW_CFA_restore: if such a restoration was meant to be of any consequence, |
| there must have been the necessary CFI directives for updating the CFA rule |
| too. The latter will be duly processed by the SFrame generation code, as |
| expected. |
| |
| For SFRAME_CFA_FP_REG, reset the state of the current FRE to indicate that |
| the value is the same as previous frame. |
| |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_same_value (const struct sframe_xlate_ctx *xlate_ctx, |
| const struct cfi_insn_data *cfi_insn) |
| { |
| struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre; |
| |
| if (sframe_ra_tracking_p () && cfi_insn->u.r == SFRAME_CFA_RA_REG) |
| { |
| cur_fre->ra_loc = SFRAME_FRE_ELEM_LOC_NONE; |
| cur_fre->ra_offset = 0; |
| cur_fre->ra_undefined_p = false; |
| cur_fre->merge_candidate = false; |
| } |
| else if (cfi_insn->u.r == SFRAME_CFA_FP_REG) |
| { |
| cur_fre->fp_loc = SFRAME_FRE_ELEM_LOC_NONE; |
| cur_fre->fp_offset = 0; |
| cur_fre->merge_candidate = false; |
| } |
| |
| /* Safe to skip. */ |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Returns the DWARF call frame instruction name or fake CFI name for the |
| specified CFI opcode, or NULL if the value is not recognized. */ |
| |
| static const char * |
| sframe_get_cfi_name (int cfi_opc) |
| { |
| const char *cfi_name; |
| |
| switch (cfi_opc) |
| { |
| /* Fake CFI type; outside the byte range of any real CFI insn. */ |
| /* See gas/dw2gencfi.h. */ |
| case CFI_adjust_cfa_offset: |
| cfi_name = "CFI_adjust_cfa_offset"; |
| break; |
| case CFI_return_column: |
| cfi_name = "CFI_return_column"; |
| break; |
| case CFI_rel_offset: |
| cfi_name = "CFI_rel_offset"; |
| break; |
| case CFI_escape: |
| cfi_name = "CFI_escape"; |
| break; |
| case CFI_signal_frame: |
| cfi_name = "CFI_signal_frame"; |
| break; |
| case CFI_val_encoded_addr: |
| cfi_name = "CFI_val_encoded_addr"; |
| break; |
| case CFI_label: |
| cfi_name = "CFI_label"; |
| break; |
| default: |
| cfi_name = get_DW_CFA_name (cfi_opc); |
| } |
| |
| return cfi_name; |
| } |
| |
| /* Process CFI_INSN and update the translation context with the FRE |
| information. |
| |
| Returns an error code (sframe_xlate_err) if CFI_INSN is not successfully |
| processed. */ |
| |
| static int |
| sframe_do_cfi_insn (struct sframe_xlate_ctx *xlate_ctx, |
| const struct cfi_insn_data *cfi_insn) |
| { |
| int err = 0; |
| |
| /* Atleast one cfi_insn per FDE is expected. */ |
| gas_assert (cfi_insn); |
| int op = cfi_insn->insn; |
| |
| switch (op) |
| { |
| case DW_CFA_advance_loc: |
| err = sframe_xlate_do_advance_loc (xlate_ctx, cfi_insn); |
| break; |
| case DW_CFA_def_cfa: |
| err = sframe_xlate_do_def_cfa (xlate_ctx, cfi_insn); |
| break; |
| case DW_CFA_def_cfa_register: |
| err = sframe_xlate_do_def_cfa_register (xlate_ctx, cfi_insn); |
| break; |
| case DW_CFA_def_cfa_offset: |
| err = sframe_xlate_do_def_cfa_offset (xlate_ctx, cfi_insn); |
| break; |
| case DW_CFA_offset: |
| 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, false); |
| break; |
| case DW_CFA_remember_state: |
| err = sframe_xlate_do_remember_state (xlate_ctx); |
| break; |
| case DW_CFA_restore_state: |
| err = sframe_xlate_do_restore_state (xlate_ctx); |
| break; |
| case DW_CFA_restore: |
| err = sframe_xlate_do_restore (xlate_ctx, cfi_insn); |
| break; |
| /* DW_CFA_AARCH64_negate_ra_state is multiplexed with |
| DW_CFA_GNU_window_save. */ |
| case DW_CFA_GNU_window_save: |
| err = sframe_xlate_do_gnu_window_save (xlate_ctx, cfi_insn); |
| break; |
| case DW_CFA_AARCH64_negate_ra_state_with_pc: |
| err = sframe_xlate_do_aarch64_negate_ra_state_with_pc (xlate_ctx, cfi_insn); |
| break; |
| 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; |
| case DW_CFA_undefined: |
| err = sframe_xlate_do_cfi_undefined (xlate_ctx, cfi_insn); |
| break; |
| case DW_CFA_same_value: |
| err = sframe_xlate_do_same_value (xlate_ctx, cfi_insn); |
| break; |
| default: |
| /* Other skipped operations may, however, impact the asynchronicity. */ |
| { |
| const char *cfi_name = sframe_get_cfi_name (op); |
| |
| if (!cfi_name) |
| cfi_name = _("(unknown)"); |
| as_warn (_("no SFrame FDE emitted; CFI insn %s (%#x)"), |
| cfi_name, op); |
| err = SFRAME_XLATE_ERR_NOTREPRESENTED; |
| } |
| } |
| |
| /* Any error will cause no SFrame FDE later. The user has already been |
| warned. */ |
| return err; |
| } |
| |
| |
| static int |
| sframe_do_fde (struct sframe_xlate_ctx *xlate_ctx, |
| const struct fde_entry *dw_fde) |
| { |
| const struct cfi_insn_data *cfi_insn; |
| int err = SFRAME_XLATE_OK; |
| |
| xlate_ctx->dw_fde = dw_fde; |
| |
| /* SFrame format cannot represent a non-default DWARF return column reg. */ |
| if (xlate_ctx->dw_fde->return_column != DWARF2_DEFAULT_RETURN_COLUMN) |
| { |
| as_warn (_("no SFrame FDE emitted; non-default RA register %u"), |
| xlate_ctx->dw_fde->return_column); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; |
| } |
| |
| /* Iterate over the CFIs and create SFrame FREs. */ |
| for (cfi_insn = dw_fde->data; cfi_insn; cfi_insn = cfi_insn->next) |
| { |
| /* Translate each CFI, and buffer the state in translation context. */ |
| err = sframe_do_cfi_insn (xlate_ctx, cfi_insn); |
| if (err != SFRAME_XLATE_OK) |
| { |
| /* Skip generating SFrame stack trace info for the function if any |
| offending CFI is encountered by sframe_do_cfi_insn (). Warning |
| message already printed by sframe_do_cfi_insn (). */ |
| return err; /* Return the error code. */ |
| } |
| } |
| |
| /* Link in the scratchpad FRE that the last few CFI insns helped create. */ |
| if (xlate_ctx->cur_fre) |
| { |
| sframe_xlate_ctx_add_fre (xlate_ctx, xlate_ctx->cur_fre); |
| xlate_ctx->cur_fre = NULL; |
| } |
| /* Designate the end of the last SFrame FRE. */ |
| if (xlate_ctx->last_fre) |
| { |
| xlate_ctx->last_fre->pc_end |
| = get_dw_fde_end_addrS (xlate_ctx->dw_fde); |
| } |
| |
| /* Number of FREs must fit uint16_t. Check now, and do not emit the SFrame |
| FDE if it doesnt fit (although, it is not expected to happen for |
| real-world, useful programs). The approach of truncating the FDE and |
| emitting multiple SFrame FDEs instead, is not a clearly preferable |
| handling either. Its a divergence from the model where an SFrame FDE |
| encodes stack trace data between a .cfi_startproc and .cfi_endproc pair. |
| Further, some components (linkers, stack tracers) want to associate the |
| Start PC of a function to a known symbol in the file? */ |
| if (xlate_ctx->num_xlate_fres > UINT16_MAX) |
| { |
| as_warn (_("no SFrame FDE emitted; Number of FREs exceeds UINT16_MAX")); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; |
| } |
| |
| /* ABI/arch except s390x cannot represent FP without RA saved. */ |
| if (sframe_ra_tracking_p () |
| && sframe_get_abi_arch () != SFRAME_ABI_S390X_ENDIAN_BIG) |
| { |
| struct sframe_row_entry *fre; |
| |
| /* Iterate over the scratchpad FREs and validate them. */ |
| for (fre = xlate_ctx->first_fre; fre; fre = fre->next) |
| { |
| /* SFrame format cannot represent FP on stack without RA on stack. */ |
| if (fre->ra_loc != SFRAME_FRE_ELEM_LOC_STACK |
| && fre->fp_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| { |
| as_warn (_("no SFrame FDE emitted; FP without RA on stack")); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; |
| } |
| } |
| } |
| |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Create SFrame stack trace info for all functions. |
| |
| This function consumes the already generated DWARF FDEs (by dw2gencfi) and |
| generates data which is later emitted as stack trace information encoded in |
| the SFrame format. */ |
| |
| static void |
| create_sframe_all (void) |
| { |
| struct fde_entry *dw_fde = NULL; |
| struct sframe_func_entry *sframe_fde = NULL; |
| |
| struct sframe_xlate_ctx *xlate_ctx = sframe_xlate_ctx_alloc (); |
| |
| for (dw_fde = all_fde_data; dw_fde ; dw_fde = dw_fde->next) |
| { |
| sframe_fde = sframe_fde_alloc (); |
| /* Initialize the translation context with information anew. */ |
| sframe_xlate_ctx_init (xlate_ctx); |
| |
| /* Process and link SFrame FDEs if no error. */ |
| int err = sframe_do_fde (xlate_ctx, dw_fde); |
| if (err && get_dw_fde_signal_p (dw_fde)) |
| { |
| sframe_xlate_ctx_cleanup (xlate_ctx); |
| xlate_ctx->flex_p = false; |
| err = SFRAME_XLATE_OK; |
| } |
| |
| if (err) |
| { |
| sframe_xlate_ctx_cleanup (xlate_ctx); |
| sframe_fde_free (sframe_fde); |
| } |
| else |
| { |
| /* All done. Transfer the state from the SFrame translation |
| context to the SFrame FDE. */ |
| sframe_xlate_ctx_finalize (xlate_ctx, sframe_fde); |
| *last_sframe_fde = sframe_fde; |
| last_sframe_fde = &sframe_fde->next; |
| } |
| } |
| |
| XDELETE (xlate_ctx); |
| } |
| |
| void |
| output_sframe (segT sframe_seg) |
| { |
| (void) sframe_seg; |
| |
| /* Currently only SFRAME_VERSION_3 can be emitted. */ |
| gas_assert (flag_gen_sframe_version == GEN_SFRAME_VERSION_3); |
| /* Setup the version specific access functions. */ |
| sframe_set_version (flag_gen_sframe_version); |
| |
| /* Process all fdes and create SFrame stack trace information. */ |
| create_sframe_all (); |
| |
| output_sframe_internal (); |
| } |
| |
| #else /* support_sframe_p */ |
| |
| void |
| output_sframe (segT sframe_seg ATTRIBUTE_UNUSED) |
| { |
| } |
| |
| #endif /* support_sframe_p */ |