| /* gen-sframe.c - Support for generating SFrame section. |
| Copyright (C) 2022-2024 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 "gen-sframe.h" |
| #include "dw2gencfi.h" |
| |
| #ifdef support_sframe_p |
| |
| /* By default, use 32-bit relocations from .sframe into .text. */ |
| #ifndef SFRAME_RELOC_SIZE |
| # define SFRAME_RELOC_SIZE 4 |
| #endif |
| |
| /* Whether frame row entries track RA. |
| |
| A target may not need return address tracking for stack tracing. If it |
| does need the same, SFRAME_CFA_RA_REG must be defined with the return |
| address register number. */ |
| |
| #if defined (sframe_ra_tracking_p) && defined (SFRAME_CFA_RA_REG) |
| # ifndef SFRAME_FRE_RA_TRACKING |
| # define SFRAME_FRE_RA_TRACKING 1 |
| # endif |
| #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 |
| |
| /* 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 |
| } |
| |
| /* 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 void |
| sframe_fre_set_cfa_offset (struct sframe_row_entry *fre, |
| offsetT cfa_offset) |
| { |
| fre->cfa_offset = cfa_offset; |
| fre->merge_candidate = false; |
| } |
| |
| #ifdef SFRAME_FRE_RA_TRACKING |
| 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->merge_candidate = false; |
| } |
| #endif |
| |
| static void |
| sframe_fre_set_bp_track (struct sframe_row_entry *fre, offsetT bp_offset) |
| { |
| fre->bp_loc = SFRAME_FRE_ELEM_LOC_STACK; |
| fre->bp_offset = bp_offset; |
| fre->merge_candidate = false; |
| } |
| |
| /* All stack offset values within an FRE are uniformly encoded in the same |
| number of bytes. The size of the stack offset values will, however, vary |
| across FREs. */ |
| |
| #define VALUE_8BIT 0x7f |
| #define VALUE_16BIT 0x7fff |
| #define VALUE_32BIT 0x7fffffff |
| #define VALUE_64BIT 0x7fffffffffffffff |
| |
| /* 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 <= VALUE_8BIT && value >= (offsetT) -VALUE_8BIT) |
| size = 1; |
| else if (value <= VALUE_16BIT && value >= (offsetT) -VALUE_16BIT) |
| size = 2; |
| else if (value <= VALUE_32BIT && value >= (offsetT) -VALUE_32BIT) |
| size = 4; |
| else if ((sizeof (offsetT) > 4) && (value <= (offsetT) VALUE_64BIT |
| && value >= (offsetT) -VALUE_64BIT)) |
| size = 8; |
| |
| return size; |
| } |
| |
| #define SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_1B 0 /* SFRAME_FRE_OFFSET_1B. */ |
| #define SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_2B 1 /* SFRAME_FRE_OFFSET_2B. */ |
| #define SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_4B 2 /* SFRAME_FRE_OFFSET_4B. */ |
| #define SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_8B 3 /* Not supported in SFrame. */ |
| #define SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_MAX SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_8B |
| |
| /* Helper struct for mapping offset size to output functions. */ |
| |
| struct sframe_fre_offset_func_map |
| { |
| unsigned int offset_size; |
| void (*out_func)(int); |
| }; |
| |
| /* Given an OFFSET_SIZE, return the size in bytes needed to represent it. */ |
| |
| static unsigned int |
| sframe_fre_offset_func_map_index (unsigned int offset_size) |
| { |
| unsigned int idx = SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_MAX; |
| |
| switch (offset_size) |
| { |
| case SFRAME_FRE_OFFSET_1B: |
| idx = SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_1B; |
| break; |
| case SFRAME_FRE_OFFSET_2B: |
| idx = SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_2B; |
| break; |
| case SFRAME_FRE_OFFSET_4B: |
| idx = SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_4B; |
| break; |
| default: |
| /* Not supported in SFrame. */ |
| break; |
| } |
| |
| return idx; |
| } |
| |
| /* Mapping from offset size to the output function to emit the value. */ |
| |
| static const |
| struct sframe_fre_offset_func_map |
| fre_offset_func_map[SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_MAX+1] = |
| { |
| { SFRAME_FRE_OFFSET_1B, out_one }, |
| { SFRAME_FRE_OFFSET_2B, out_two }, |
| { SFRAME_FRE_OFFSET_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 base_reg, unsigned int num_offsets, |
| unsigned int offset_size, bool mangled_ra_p) |
| { |
| unsigned char fre_info; |
| fre_info = SFRAME_V1_FRE_INFO (base_reg, num_offsets, offset_size); |
| fre_info = SFRAME_V1_FRE_INFO_UPDATE_MANGLED_RA_P (mangled_ra_p, fre_info); |
| return fre_info; |
| } |
| |
| /* SFrame (SFRAME_VERSION_1) set function info. */ |
| static unsigned char |
| sframe_v1_set_func_info (unsigned int fde_type, unsigned int fre_type, |
| unsigned int pauth_key) |
| { |
| unsigned char func_info; |
| func_info = SFRAME_V1_FUNC_INFO (fde_type, fre_type); |
| func_info = SFRAME_V1_FUNC_INFO_UPDATE_PAUTH_KEY (pauth_key, func_info); |
| return func_info; |
| } |
| |
| /* SFrame version specific operations setup. */ |
| |
| static void |
| sframe_set_version (uint32_t sframe_version ATTRIBUTE_UNUSED) |
| { |
| sframe_ver_ops.format_version = SFRAME_VERSION_2; |
| |
| /* These operations remain the same for SFRAME_VERSION_2 as fre_info and |
| func_info have not changed from SFRAME_VERSION_1. */ |
| |
| sframe_ver_ops.set_fre_info = sframe_v1_set_fre_info; |
| |
| sframe_ver_ops.set_func_info = sframe_v1_set_func_info; |
| } |
| |
| /* SFrame set FRE info. */ |
| |
| static unsigned char |
| sframe_set_fre_info (unsigned int base_reg, unsigned int num_offsets, |
| unsigned int offset_size, bool mangled_ra_p) |
| { |
| return sframe_ver_ops.set_fre_info (base_reg, num_offsets, |
| offset_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) |
| { |
| return sframe_ver_ops.set_func_info (fde_type, fre_type, pauth_key); |
| } |
| |
| /* 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 (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 offsets necessary for the SFrame Frame Row Entry. */ |
| |
| static unsigned int |
| get_fre_num_offsets (struct sframe_row_entry *sframe_fre) |
| { |
| /* Atleast 1 must always be present (to recover CFA). */ |
| unsigned int fre_num_offsets = 1; |
| |
| if (sframe_fre->bp_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| fre_num_offsets++; |
| #ifdef SFRAME_FRE_RA_TRACKING |
| if (sframe_ra_tracking_p () |
| && sframe_fre->ra_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| fre_num_offsets++; |
| #endif |
| return fre_num_offsets; |
| } |
| |
| /* Get the minimum necessary offset size (in bytes) for this |
| SFrame frame row entry. */ |
| |
| static unsigned int |
| sframe_get_fre_offset_size (struct sframe_row_entry *sframe_fre) |
| { |
| unsigned int max_offset_size = 0; |
| unsigned int cfa_offset_size = 0; |
| unsigned int bp_offset_size = 0; |
| unsigned int ra_offset_size = 0; |
| |
| unsigned int fre_offset_size = 0; |
| |
| /* What size of offsets appear in this frame row entry. */ |
| cfa_offset_size = get_offset_size_in_bytes (sframe_fre->cfa_offset); |
| if (sframe_fre->bp_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| bp_offset_size = get_offset_size_in_bytes (sframe_fre->bp_offset); |
| #ifdef SFRAME_FRE_RA_TRACKING |
| if (sframe_ra_tracking_p () |
| && sframe_fre->ra_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| ra_offset_size = get_offset_size_in_bytes (sframe_fre->ra_offset); |
| #endif |
| |
| /* Get the maximum size needed to represent the offsets. */ |
| max_offset_size = cfa_offset_size; |
| if (bp_offset_size > max_offset_size) |
| max_offset_size = bp_offset_size; |
| if (ra_offset_size > max_offset_size) |
| max_offset_size = ra_offset_size; |
| |
| gas_assert (max_offset_size); |
| |
| switch (max_offset_size) |
| { |
| case 1: |
| fre_offset_size = SFRAME_FRE_OFFSET_1B; |
| break; |
| case 2: |
| fre_offset_size = SFRAME_FRE_OFFSET_2B; |
| break; |
| case 4: |
| fre_offset_size = SFRAME_FRE_OFFSET_4B; |
| break; |
| default: |
| /* Offset of size 8 bytes is not supported in SFrame format |
| version 1. */ |
| as_fatal (_("SFrame unsupported offset value\n")); |
| break; |
| } |
| |
| return fre_offset_size; |
| } |
| |
| #if SFRAME_FRE_TYPE_SELECTION_OPT |
| |
| /* 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) |
| { |
| expressionS val; |
| expressionS width; |
| |
| /* val expression stores the FDE start address offset from the start PC |
| of function. */ |
| val.X_op = O_subtract; |
| val.X_add_symbol = fre_pc_begin; |
| val.X_op_symbol = fde_start_address; |
| val.X_add_number = 0; |
| |
| /* 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. */ |
| width.X_op = O_subtract; |
| width.X_add_symbol = fde_end_address; |
| width.X_op_symbol = fde_start_address; |
| width.X_add_number = 0; |
| |
| cexp->X_op = O_absent; |
| cexp->X_add_symbol = make_expr_symbol (&val); |
| cexp->X_op_symbol = make_expr_symbol (&width); |
| cexp->X_add_number = 0; |
| } |
| |
| /* 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; |
| expressionS rest_of_func_info; |
| |
| width.X_op = O_subtract; |
| width.X_add_symbol = dw_fde_end_addrS; |
| width.X_op_symbol = dw_fde_start_addrS; |
| width.X_add_number = 0; |
| |
| rest_of_func_info.X_op = O_constant; |
| rest_of_func_info.X_add_number = func_info; |
| |
| cexp->X_op = O_modulus; |
| cexp->X_add_symbol = make_expr_symbol (&rest_of_func_info); |
| cexp->X_op_symbol = make_expr_symbol (&width); |
| cexp->X_add_number = 0; |
| } |
| |
| #endif |
| |
| static void |
| output_sframe_row_entry (symbolS *fde_start_addr, |
| symbolS *fde_end_addr, |
| struct sframe_row_entry *sframe_fre) |
| { |
| unsigned char fre_info; |
| unsigned int fre_num_offsets; |
| unsigned int fre_offset_size; |
| unsigned int fre_base_reg; |
| expressionS exp; |
| unsigned int fre_addr_size; |
| |
| unsigned int idx = 0; |
| unsigned int fre_write_offsets = 0; |
| |
| 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, (relax_substateT) 0, |
| make_expr_symbol (&exp), 0, (char *) frag_now); |
| #else |
| gas_assert (fde_end_addr); |
| exp.X_op = O_subtract; |
| exp.X_add_symbol = sframe_fre->pc_begin; /* to. */ |
| exp.X_op_symbol = fde_start_addr; /* from. */ |
| exp.X_add_number = 0; |
| emit_expr (&exp, fre_addr_size); |
| #endif |
| |
| /* Create the fre_info using the CFA base register, number of offsets and max |
| size of offset in this frame row entry. */ |
| fre_base_reg = get_fre_base_reg_id (sframe_fre); |
| fre_num_offsets = get_fre_num_offsets (sframe_fre); |
| fre_offset_size = sframe_get_fre_offset_size (sframe_fre); |
| fre_info = sframe_set_fre_info (fre_base_reg, fre_num_offsets, |
| fre_offset_size, sframe_fre->mangled_ra_p); |
| out_one (fre_info); |
| |
| idx = sframe_fre_offset_func_map_index (fre_offset_size); |
| gas_assert (idx < SFRAME_FRE_OFFSET_FUNC_MAP_INDEX_MAX); |
| |
| /* Write out the offsets in order - cfa, bp, ra. */ |
| fre_offset_func_map[idx].out_func (sframe_fre->cfa_offset); |
| fre_write_offsets++; |
| |
| #ifdef SFRAME_FRE_RA_TRACKING |
| if (sframe_ra_tracking_p () |
| && sframe_fre->ra_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| { |
| fre_offset_func_map[idx].out_func (sframe_fre->ra_offset); |
| fre_write_offsets++; |
| } |
| #endif |
| if (sframe_fre->bp_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| { |
| fre_offset_func_map[idx].out_func (sframe_fre->bp_offset); |
| fre_write_offsets++; |
| } |
| |
| /* Check if the expected number offsets have been written out |
| in this FRE. */ |
| gas_assert (fre_write_offsets == fre_num_offsets); |
| } |
| |
| static void |
| output_sframe_funcdesc (symbolS *start_of_fre_section, |
| symbolS *fre_symbol, |
| struct sframe_func_entry *sframe_fde) |
| { |
| expressionS exp; |
| unsigned int addr_size; |
| symbolS *dw_fde_start_addrS, *dw_fde_end_addrS; |
| unsigned int pauth_key; |
| |
| addr_size = SFRAME_RELOC_SIZE; |
| 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. */ |
| 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, addr_size); |
| |
| /* 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, addr_size); |
| |
| /* Offset to the first frame row entry. */ |
| 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, addr_size); |
| |
| /* Number of FREs. */ |
| out_four (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); |
| func_info = sframe_set_func_info (SFRAME_FDE_TYPE_PCINC, |
| SFRAME_FRE_TYPE_ADDR4, |
| pauth_key); |
| #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, (relax_substateT) 0, |
| make_expr_symbol (&cexp), 0, (char *) frag_now); |
| #else |
| out_one (func_info); |
| #endif |
| out_one (0); |
| out_two (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; |
| 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; |
| unsigned int addr_size; |
| |
| addr_size = SFRAME_RELOC_SIZE; |
| |
| /* The function descriptor entries as dumped by the assembler are not |
| sorted on PCs. */ |
| unsigned char sframe_flags = 0; |
| sframe_flags |= !SFRAME_F_FDE_SORTED; |
| |
| unsigned int num_fdes = get_num_sframe_fdes (); |
| unsigned int num_fres = get_num_sframe_fres (); |
| symbolS **fre_symbols = XNEWVEC (symbolS *, num_fres); |
| for (i = 0; i < num_fres; i++) |
| 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); |
| 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, or AARCH64 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. */ |
| |
| /* FRE sub-section len. */ |
| 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, addr_size); |
| |
| /* Offset of Function Index 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, addr_size); |
| |
| /* 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, addr_size); |
| |
| 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 (start_of_fre_section, |
| fre_symbols[i], sframe_fde); |
| i += sframe_fde->num_fres; |
| } |
| |
| 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) |
| { |
| for (sframe_fre = sframe_fde->sframe_fres; |
| sframe_fre; |
| sframe_fre = sframe_fre->next) |
| { |
| symbol_set_value_now (fre_symbols[i]); |
| output_sframe_row_entry (get_dw_fde_start_addrS (sframe_fde->dw_fde), |
| get_dw_fde_end_addrS (sframe_fde->dw_fde), |
| sframe_fre); |
| i++; |
| } |
| } |
| |
| symbol_set_value_now (end_of_frame_section); |
| |
| gas_assert (i == num_fres); |
| |
| free (fre_symbols); |
| fre_symbols = NULL; |
| } |
| |
| /* List of SFrame FDE entries. */ |
| |
| struct sframe_func_entry *all_sframe_fdes; |
| |
| /* Tail of the list to add to. */ |
| |
| static struct sframe_func_entry **last_sframe_fde = &all_sframe_fdes; |
| |
| 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; |
| } |
| |
| /* Allocate an SFrame FDE. */ |
| |
| static struct sframe_func_entry* |
| sframe_fde_alloc (void) |
| { |
| struct sframe_func_entry *sframe_fde = XCNEW (struct sframe_func_entry); |
| return sframe_fde; |
| } |
| |
| /* Link the SFrame FDE in. */ |
| |
| static int |
| sframe_fde_link (struct sframe_func_entry *sframe_fde) |
| { |
| *last_sframe_fde = sframe_fde; |
| last_sframe_fde = &sframe_fde->next; |
| |
| return 0; |
| } |
| |
| /* Free up the SFrame FDE. */ |
| |
| static void |
| sframe_fde_free (struct sframe_func_entry *sframe_fde) |
| { |
| XDELETE (sframe_fde); |
| sframe_fde = NULL; |
| } |
| |
| /* 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->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) |
| { |
| struct sframe_row_entry *fre, *fre_next; |
| |
| if (xlate_ctx->num_xlate_fres) |
| { |
| fre = xlate_ctx->first_fre; |
| while (fre) |
| { |
| fre_next = fre->next; |
| XDELETE (fre); |
| fre = fre_next; |
| } |
| } |
| |
| XDELETE (xlate_ctx->cur_fre); |
| |
| sframe_xlate_ctx_init (xlate_ctx); |
| } |
| |
| /* 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->sframe_fres = xlate_ctx->first_fre; |
| sframe_fde->num_fres = xlate_ctx->num_xlate_fres; |
| } |
| |
| static struct sframe_row_entry* |
| sframe_row_entry_new (void) |
| { |
| struct sframe_row_entry *fre = XCNEW (struct sframe_row_entry); |
| /* Reset cfa_base_reg to -1. A value of 0 will imply some valid register |
| for the supported arches. */ |
| fre->cfa_base_reg = SFRAME_FRE_BASE_REG_INVAL; |
| 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; |
| |
| return fre; |
| } |
| |
| /* 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, |
| 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->bp_loc = prev_fre->bp_loc; |
| cur_fre->bp_offset = prev_fre->bp_offset; |
| cur_fre->ra_loc = prev_fre->ra_loc; |
| cur_fre->ra_offset = prev_fre->ra_offset; |
| /* 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; |
| } |
| |
| /* 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"; |
| #ifdef SFRAME_FRE_RA_TRACKING |
| else if (reg == SFRAME_CFA_RA_REG) |
| return "RA"; |
| #endif |
| 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, |
| 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, |
| 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)); |
| } |
| /* 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.r != SFRAME_CFA_SP_REG |
| && cfi_insn->u.r != SFRAME_CFA_FP_REG) |
| { |
| as_warn (_("skipping SFrame FDE; non-SP/FP register %u in .cfi_def_cfa"), |
| cfi_insn->u.r); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ |
| } |
| 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; |
| |
| 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, |
| struct cfi_insn_data *cfi_insn) |
| { |
| 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) |
| { |
| as_warn (_("skipping SFrame FDE; " |
| "non-SP/FP register %u in .cfi_def_cfa_register"), |
| cfi_insn->u.r); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ |
| } |
| sframe_fre_set_cfa_base_reg (cur_fre, cfi_insn->u.ri.reg); |
| sframe_fre_set_cfa_offset (cur_fre, last_fre->cfa_offset); |
| 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, |
| 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); |
| /* 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_fre->cfa_base_reg == SFRAME_CFA_FP_REG) |
| || (cur_fre->cfa_base_reg == SFRAME_CFA_SP_REG)) |
| { |
| sframe_fre_set_cfa_offset (cur_fre, cfi_insn->u.i); |
| cur_fre->merge_candidate = false; |
| } |
| 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 (_("skipping SFrame FDE; " |
| ".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, |
| 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); |
| /* 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.r == SFRAME_CFA_FP_REG) |
| { |
| gas_assert (!cur_fre->base_reg); |
| sframe_fre_set_bp_track (cur_fre, cfi_insn->u.ri.offset); |
| cur_fre->merge_candidate = false; |
| } |
| #ifdef SFRAME_FRE_RA_TRACKING |
| else if (sframe_ra_tracking_p () |
| && cfi_insn->u.r == SFRAME_CFA_RA_REG) |
| { |
| sframe_fre_set_ra_track (cur_fre, cfi_insn->u.ri.offset); |
| cur_fre->merge_candidate = false; |
| } |
| #endif |
| /* This is used to track changes to non-rsp registers, skip all others |
| except FP / RA for now. */ |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_val_offset into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_val_offset (struct sframe_xlate_ctx *xlate_ctx ATTRIBUTE_UNUSED, |
| struct cfi_insn_data *cfi_insn) |
| { |
| /* Previous value of register is CFA + offset. However, if the specified |
| register is not interesting (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.r == SFRAME_CFA_FP_REG |
| #ifdef SFRAME_FRE_RA_TRACKING |
| || (sframe_ra_tracking_p () && cfi_insn->u.r == SFRAME_CFA_RA_REG) |
| #endif |
| /* Ignore SP reg, as it can be recovered from the CFA tracking info. */ |
| ) |
| { |
| as_warn (_("skipping SFrame FDE; %s register %u in .cfi_val_offset"), |
| sframe_register_name (cfi_insn->u.r), cfi_insn->u.r); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ |
| } |
| |
| /* Safe to skip. */ |
| return SFRAME_XLATE_OK; |
| } |
| |
| /* Translate DW_CFA_register into SFrame context. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_register (struct sframe_xlate_ctx *xlate_ctx ATTRIBUTE_UNUSED, |
| struct cfi_insn_data *cfi_insn) |
| { |
| /* Previous value of register1 is register2. However, if the specified |
| register1 is not interesting (FP or RA reg), the current DW_CFA_register |
| instruction can be safely skipped without sacrificing the asynchronicity of |
| stack trace information. */ |
| if (cfi_insn->u.rr.reg1 == SFRAME_CFA_FP_REG |
| #ifdef SFRAME_FRE_RA_TRACKING |
| || (sframe_ra_tracking_p () && cfi_insn->u.rr.reg1 == SFRAME_CFA_RA_REG) |
| #endif |
| /* Ignore SP reg, as it can be recovered from the CFA tracking info. */ |
| ) |
| { |
| as_warn (_("skipping SFrame FDE; %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) |
| { |
| struct sframe_row_entry *last_fre = xlate_ctx->last_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 (!last_fre) |
| { |
| as_warn (_("skipping SFrame FDE; " |
| ".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, last_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, |
| 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; |
| |
| /* Change the rule for the indicated register to the rule assigned to |
| it by the initial_instructions in the CIE. */ |
| gas_assert (cie_fre); |
| /* 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->bp_loc = cie_fre->bp_loc; |
| cur_fre->bp_offset = cie_fre->bp_offset; |
| cur_fre->merge_candidate = false; |
| } |
| #ifdef SFRAME_FRE_RA_TRACKING |
| 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->merge_candidate = false; |
| } |
| #endif |
| 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, |
| 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_GNU_window_save into SFrame context. |
| DW_CFA_AARCH64_negate_ra_state is multiplexed with DW_CFA_GNU_window_save. |
| Return SFRAME_XLATE_OK if success. */ |
| |
| static int |
| sframe_xlate_do_gnu_window_save (struct sframe_xlate_ctx *xlate_ctx, |
| 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 (_("skipping SFrame FDE; .cfi_window_save")); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */ |
| } |
| |
| /* 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, |
| 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); |
| 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_register: |
| err = sframe_xlate_do_register (xlate_ctx, cfi_insn); |
| break; |
| /* Following CFI opcodes are not processed at this time. |
| These do not impact the coverage of the basic stack tracing |
| information as conveyed in the SFrame format. */ |
| case DW_CFA_undefined: |
| case DW_CFA_same_value: |
| break; |
| default: |
| /* Following skipped operations do, however, impact the asynchronicity: |
| - CFI_escape. */ |
| { |
| const char *cfi_name = sframe_get_cfi_name (op); |
| |
| if (!cfi_name) |
| cfi_name = _("(unknown)"); |
| as_warn (_("skipping SFrame FDE; 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) |
| { |
| 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 (_("skipping SFrame FDE; 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); |
| } |
| |
| #ifdef SFRAME_FRE_RA_TRACKING |
| if (sframe_ra_tracking_p ()) |
| { |
| 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->bp_loc == SFRAME_FRE_ELEM_LOC_STACK) |
| { |
| as_warn (_("skipping SFrame FDE; FP without RA on stack")); |
| return SFRAME_XLATE_ERR_NOTREPRESENTED; |
| } |
| } |
| } |
| #endif /* SFRAME_FRE_RA_TRACKING */ |
| |
| 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. Also skip adding an SFrame |
| FDE if it does not contain any SFrame FREs. There is little use of an |
| SFrame FDE if there is no stack tracing information for the |
| function. */ |
| int err = sframe_do_fde (xlate_ctx, dw_fde); |
| if (err || xlate_ctx->num_xlate_fres == 0) |
| { |
| 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); |
| sframe_fde_link (sframe_fde); |
| } |
| } |
| |
| XDELETE (xlate_ctx); |
| } |
| |
| void |
| output_sframe (segT sframe_seg) |
| { |
| (void) sframe_seg; |
| |
| /* Setup the version specific access functions. */ |
| sframe_set_version (SFRAME_VERSION_2); |
| |
| /* 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 */ |