| /* scfi.c - Support for synthesizing DWARF CFI for hand-written asm. |
| Copyright (C) 2023 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 "scfi.h" |
| #include "subsegs.h" |
| #include "scfidw2gen.h" |
| #include "dw2gencfi.h" |
| |
| #if defined (TARGET_USE_SCFI) && defined (TARGET_USE_GINSN) |
| |
| /* Beyond the target defined number of registers to be tracked |
| (SCFI_MAX_REG_ID), keep the next register ID, in sequence, for REG_CFA. */ |
| #define REG_CFA (SCFI_MAX_REG_ID+1) |
| /* Define the total number of registers being tracked. |
| Used as index into an array of cfi_reglocS. Note that a ginsn may carry a |
| register number greater than MAX_NUM_SCFI_REGS, e.g., for the ginsns |
| corresponding to push fs/gs in AMD64. */ |
| #define MAX_NUM_SCFI_REGS (REG_CFA+1) |
| |
| #define REG_INVALID ((unsigned int)-1) |
| |
| enum cfi_reglocstate |
| { |
| CFI_UNDEFINED, |
| CFI_IN_REG, |
| CFI_ON_STACK |
| }; |
| |
| /* Location at which CFI register is saved. |
| |
| A CFI register (callee-saved registers, RA/LR) are always an offset from |
| the CFA. REG_CFA itself, however, may have REG_SP or REG_FP as base |
| register. Hence, keep the base reg ID and offset per tracked register. */ |
| |
| struct cfi_regloc |
| { |
| /* Base reg ID (DWARF register number). */ |
| unsigned int base; |
| /* Location as offset from the CFA. */ |
| offsetT offset; |
| /* Current state of the CFI register. */ |
| enum cfi_reglocstate state; |
| }; |
| |
| typedef struct cfi_regloc cfi_reglocS; |
| |
| struct scfi_op_data |
| { |
| const char *name; |
| }; |
| |
| typedef struct scfi_op_data scfi_op_dataS; |
| |
| /* SCFI operation. |
| |
| An SCFI operation represents a single atomic change to the SCFI state. |
| This can also be understood as an abstraction for what eventually gets |
| emitted as a DWARF CFI operation. */ |
| |
| struct scfi_op |
| { |
| /* An SCFI op updates the state of either the CFA or other tracked |
| (callee-saved, REG_SP etc) registers. 'reg' is in the DWARF register |
| number space and must be strictly less than MAX_NUM_SCFI_REGS. */ |
| unsigned int reg; |
| /* Location of the reg. */ |
| cfi_reglocS loc; |
| /* DWARF CFI opcode. */ |
| uint32_t dw2cfi_op; |
| /* Some SCFI ops, e.g., for CFI_label, may need to carry additional data. */ |
| scfi_op_dataS *op_data; |
| /* A linked list. */ |
| struct scfi_op *next; |
| }; |
| |
| /* SCFI State - accumulated unwind information at a PC. |
| |
| SCFI state is the accumulated unwind information encompassing: |
| - REG_SP, REG_FP, |
| - RA, and |
| - all callee-saved registers. |
| |
| Note that SCFI_MAX_REG_ID is target/ABI dependent and is provided by the |
| backends. The backend must also identify the DWARF register numbers for |
| the REG_SP, and REG_FP registers. */ |
| |
| struct scfi_state |
| { |
| cfi_reglocS regs[MAX_NUM_SCFI_REGS]; |
| cfi_reglocS scratch[MAX_NUM_SCFI_REGS]; |
| /* Current stack size. */ |
| offsetT stack_size; |
| /* Whether the stack size is known. |
| Stack size may become untraceable depending on the specific stack |
| manipulation machine instruction, e.g., rsp = rsp op reg instruction |
| makes the stack size untraceable. */ |
| bool traceable_p; |
| }; |
| |
| /* Initialize a new SCFI op. */ |
| |
| static scfi_opS * |
| init_scfi_op (void) |
| { |
| scfi_opS *op = XCNEW (scfi_opS); |
| |
| return op; |
| } |
| |
| /* Free the SCFI ops, given the HEAD of the list. */ |
| |
| void |
| scfi_ops_cleanup (scfi_opS **head) |
| { |
| scfi_opS *op; |
| scfi_opS *next; |
| |
| if (!head || !*head) |
| return; |
| |
| op = *head; |
| next = op->next; |
| |
| while (op) |
| { |
| free (op->op_data); |
| free (op); |
| op = next; |
| next = op ? op->next : NULL; |
| } |
| |
| free (head); |
| } |
| |
| /* Compare two SCFI states. */ |
| |
| static int |
| cmp_scfi_state (scfi_stateS *state1, scfi_stateS *state2) |
| { |
| int ret; |
| |
| if (!state1 || !state2) |
| return 1; |
| |
| /* Skip comparing the scratch[] value of registers. The user visible |
| unwind information is derived from the regs[] from the SCFI state. */ |
| ret = memcmp (state1->regs, state2->regs, |
| sizeof (cfi_reglocS) * MAX_NUM_SCFI_REGS); |
| |
| /* For user functions which perform dynamic stack allocation, after switching |
| t REG_FP based CFA tracking, it is perfectly possible to have stack usage |
| in some control flows. Further, the different control flows may even not |
| have the same idea of CFA tracking (likely later necessitating generation |
| of .cfi_remember_state / .cfi_restore_state pair). */ |
| ret |= state1->regs[REG_CFA].base != state2->regs[REG_CFA].base; |
| |
| if (!ret && state1->regs[REG_CFA].base == REG_SP) |
| ret |= state1->stack_size != state2->stack_size; |
| |
| ret |= state1->traceable_p != state2->traceable_p; |
| |
| return ret; |
| } |
| |
| #if 0 |
| static void |
| scfi_state_update_reg (scfi_stateS *state, uint32_t dst, uint32_t base, |
| int32_t offset) |
| { |
| if (dst >= MAX_NUM_SCFI_REGS) |
| return; |
| |
| state->regs[dst].base = base; |
| state->regs[dst].offset = offset; |
| } |
| #endif |
| |
| /* Update the SCFI state of REG as available on execution stack at OFFSET |
| from REG_CFA (BASE). |
| |
| Note that BASE must be REG_CFA, because any other base (REG_SP, REG_FP) |
| is by definition transitory in the function. */ |
| |
| static void |
| scfi_state_save_reg (scfi_stateS *state, unsigned int reg, unsigned int base, |
| offsetT offset) |
| { |
| if (reg >= MAX_NUM_SCFI_REGS) |
| return; |
| |
| gas_assert (base == REG_CFA); |
| |
| state->regs[reg].base = base; |
| state->regs[reg].offset = offset; |
| state->regs[reg].state = CFI_ON_STACK; |
| } |
| |
| static void |
| scfi_state_restore_reg (scfi_stateS *state, unsigned int reg) |
| { |
| if (reg >= MAX_NUM_SCFI_REGS) |
| return; |
| |
| /* Sanity check. See Rule 4. */ |
| gas_assert (state->regs[reg].state == CFI_ON_STACK); |
| gas_assert (state->regs[reg].base == REG_CFA); |
| |
| /* PS: the register may still be on stack much after the restore. Reset the |
| SCFI state to CFI_UNDEFINED, however, to indicate that the most updated |
| source of value is register itself from here onwards. */ |
| state->regs[reg].base = 0; |
| state->regs[reg].offset = 0; |
| state->regs[reg].state = CFI_UNDEFINED; |
| } |
| |
| /* Identify if the given GAS instruction GINSN saves a register |
| (of interest) on stack. */ |
| |
| static bool |
| ginsn_scfi_save_reg_p (ginsnS *ginsn, scfi_stateS *state) |
| { |
| bool save_reg_p = false; |
| struct ginsn_src *src; |
| struct ginsn_dst *dst; |
| |
| src = ginsn_get_src1 (ginsn); |
| dst = ginsn_get_dst (ginsn); |
| |
| /* The first save to stack of callee-saved register is deemed as |
| register save. */ |
| if (!ginsn_track_reg_p (ginsn_get_src_reg (src), GINSN_GEN_SCFI) |
| || state->regs[ginsn_get_src_reg (src)].state == CFI_ON_STACK) |
| return save_reg_p; |
| |
| /* A register save insn may be an indirect mov. */ |
| if (ginsn->type == GINSN_TYPE_MOV |
| && ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT |
| && (ginsn_get_dst_reg (dst) == REG_SP |
| || (ginsn_get_dst_reg (dst) == REG_FP |
| && state->regs[REG_CFA].base == REG_FP))) |
| save_reg_p = true; |
| /* or an explicit store to stack. */ |
| else if (ginsn->type == GINSN_TYPE_STORE |
| && ginsn_get_dst_type (dst) == GINSN_DST_INDIRECT |
| && ginsn_get_dst_reg (dst) == REG_SP) |
| save_reg_p = true; |
| |
| return save_reg_p; |
| } |
| |
| /* Identify if the given GAS instruction GINSN restores a register |
| (of interest) on stack. */ |
| |
| static bool |
| ginsn_scfi_restore_reg_p (ginsnS *ginsn, scfi_stateS *state) |
| { |
| bool restore_reg_p = false; |
| struct ginsn_dst *dst; |
| struct ginsn_src *src1; |
| |
| dst = ginsn_get_dst (ginsn); |
| src1 = ginsn_get_src1 (ginsn); |
| |
| if (!ginsn_track_reg_p (ginsn_get_dst_reg (dst), GINSN_GEN_SCFI)) |
| return restore_reg_p; |
| |
| /* A register restore insn may be an indirect mov... */ |
| if (ginsn->type == GINSN_TYPE_MOV |
| && ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT |
| && (ginsn_get_src_reg (src1) == REG_SP |
| || (ginsn_get_src_reg (src1) == REG_FP |
| && state->regs[REG_CFA].base == REG_FP))) |
| restore_reg_p = true; |
| /* ...or an explicit load from stack. */ |
| else if (ginsn->type == GINSN_TYPE_LOAD |
| && ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT |
| && ginsn_get_src_reg (src1) == REG_SP) |
| restore_reg_p = true; |
| |
| return restore_reg_p; |
| } |
| |
| /* Append the SCFI operation OP to the list of SCFI operations in the |
| given GINSN. */ |
| |
| static int |
| ginsn_append_scfi_op (ginsnS *ginsn, scfi_opS *op) |
| { |
| scfi_opS *sop; |
| |
| if (!ginsn || !op) |
| return 1; |
| |
| if (!ginsn->scfi_ops) |
| { |
| ginsn->scfi_ops = XCNEW (scfi_opS *); |
| *ginsn->scfi_ops = op; |
| } |
| else |
| { |
| /* Add to tail. Most ginsns have a single SCFI operation, |
| so this traversal for every insertion is acceptable for now. */ |
| sop = *ginsn->scfi_ops; |
| while (sop->next) |
| sop = sop->next; |
| |
| sop->next = op; |
| } |
| ginsn->num_scfi_ops++; |
| |
| return 0; |
| } |
| |
| static void |
| scfi_op_add_def_cfa_reg (scfi_stateS *state, ginsnS *ginsn, unsigned int reg) |
| { |
| scfi_opS *op = NULL; |
| |
| state->regs[REG_CFA].base = reg; |
| |
| op = init_scfi_op (); |
| |
| op->dw2cfi_op = DW_CFA_def_cfa_register; |
| op->reg = REG_CFA; |
| op->loc = state->regs[REG_CFA]; |
| |
| ginsn_append_scfi_op (ginsn, op); |
| } |
| |
| static void |
| scfi_op_add_cfa_offset_inc (scfi_stateS *state, ginsnS *ginsn, offsetT num) |
| { |
| scfi_opS *op = NULL; |
| |
| state->regs[REG_CFA].offset -= num; |
| |
| op = init_scfi_op (); |
| |
| op->dw2cfi_op = DW_CFA_def_cfa_offset; |
| op->reg = REG_CFA; |
| op->loc = state->regs[REG_CFA]; |
| |
| ginsn_append_scfi_op (ginsn, op); |
| } |
| |
| static void |
| scfi_op_add_cfa_offset_dec (scfi_stateS *state, ginsnS *ginsn, offsetT num) |
| { |
| scfi_opS *op = NULL; |
| |
| state->regs[REG_CFA].offset += num; |
| |
| op = init_scfi_op (); |
| |
| op->dw2cfi_op = DW_CFA_def_cfa_offset; |
| op->reg = REG_CFA; |
| op->loc = state->regs[REG_CFA]; |
| |
| ginsn_append_scfi_op (ginsn, op); |
| } |
| |
| static void |
| scfi_op_add_def_cfa (scfi_stateS *state, ginsnS *ginsn, unsigned int reg, |
| offsetT num) |
| { |
| scfi_opS *op = NULL; |
| |
| state->regs[REG_CFA].base = reg; |
| state->regs[REG_CFA].offset = num; |
| |
| op = init_scfi_op (); |
| |
| op->dw2cfi_op = DW_CFA_def_cfa; |
| op->reg = REG_CFA; |
| op->loc = state->regs[REG_CFA]; |
| |
| ginsn_append_scfi_op (ginsn, op); |
| } |
| |
| static void |
| scfi_op_add_cfi_offset (scfi_stateS *state, ginsnS *ginsn, unsigned int reg) |
| { |
| scfi_opS *op = NULL; |
| |
| op = init_scfi_op (); |
| |
| op->dw2cfi_op = DW_CFA_offset; |
| op->reg = reg; |
| op->loc = state->regs[reg]; |
| |
| ginsn_append_scfi_op (ginsn, op); |
| } |
| |
| static void |
| scfi_op_add_cfa_restore (ginsnS *ginsn, unsigned int reg) |
| { |
| scfi_opS *op = NULL; |
| |
| op = init_scfi_op (); |
| |
| op->dw2cfi_op = DW_CFA_restore; |
| op->reg = reg; |
| op->loc.base = REG_INVALID; |
| op->loc.offset = 0; |
| |
| ginsn_append_scfi_op (ginsn, op); |
| } |
| |
| static void |
| scfi_op_add_cfi_remember_state (ginsnS *ginsn) |
| { |
| scfi_opS *op = NULL; |
| |
| op = init_scfi_op (); |
| |
| op->dw2cfi_op = DW_CFA_remember_state; |
| |
| ginsn_append_scfi_op (ginsn, op); |
| } |
| |
| static void |
| scfi_op_add_cfi_restore_state (ginsnS *ginsn) |
| { |
| scfi_opS *op = NULL; |
| |
| op = init_scfi_op (); |
| |
| op->dw2cfi_op = DW_CFA_restore_state; |
| |
| /* FIXME - add to the beginning of the scfi_ops. */ |
| ginsn_append_scfi_op (ginsn, op); |
| } |
| |
| void |
| scfi_op_add_cfi_label (ginsnS *ginsn, const char *name) |
| { |
| scfi_opS *op = NULL; |
| |
| op = init_scfi_op (); |
| op->dw2cfi_op = CFI_label; |
| op->op_data = XCNEW (scfi_op_dataS); |
| op->op_data->name = name; |
| |
| ginsn_append_scfi_op (ginsn, op); |
| } |
| |
| void |
| scfi_op_add_signal_frame (ginsnS *ginsn) |
| { |
| scfi_opS *op = NULL; |
| |
| op = init_scfi_op (); |
| op->dw2cfi_op = CFI_signal_frame; |
| |
| ginsn_append_scfi_op (ginsn, op); |
| } |
| |
| static int |
| verify_heuristic_traceable_reg_fp (ginsnS *ginsn, scfi_stateS *state) |
| { |
| /* The function uses this variable to issue error to user right away. */ |
| int fp_traceable_p = 0; |
| enum ginsn_type gtype; |
| struct ginsn_src *src1; |
| struct ginsn_src *src2; |
| struct ginsn_dst *dst; |
| unsigned int src1_reg; |
| unsigned int dst_reg; |
| enum ginsn_src_type src1_type; |
| enum ginsn_src_type src2_type; |
| enum ginsn_dst_type dst_type; |
| |
| gtype = ginsn->type; |
| |
| src1 = ginsn_get_src1 (ginsn); |
| src2 = ginsn_get_src2 (ginsn); |
| dst = ginsn_get_dst (ginsn); |
| |
| src1_reg = ginsn_get_src_reg (src1); |
| dst_reg = ginsn_get_dst_reg (dst); |
| |
| src1_type = ginsn_get_src_type (src1); |
| src2_type = ginsn_get_src_type (src2); |
| dst_type = ginsn_get_dst_type (dst); |
| |
| /* Stack manipulation can be done in a variety of ways. A program may |
| allocate stack statically or may perform dynamic stack allocation in |
| the prologue. |
| |
| The SCFI machinery in GAS is based on some heuristics: |
| |
| - Rule 3 If the base register for CFA tracking is REG_FP, the program |
| must not clobber REG_FP, unless it is for switch to REG_SP based CFA |
| tracking (via say, a pop %rbp in X86). */ |
| |
| /* Check all applicable instructions with dest REG_FP, when the CFA base |
| register is REG_FP. */ |
| if (state->regs[REG_CFA].base == REG_FP |
| && (dst_type == GINSN_DST_REG || dst_type == GINSN_DST_INDIRECT) |
| && dst_reg == REG_FP) |
| { |
| /* Excuse the add/sub with imm usage: They are OK. */ |
| if ((gtype == GINSN_TYPE_ADD || gtype == GINSN_TYPE_SUB) |
| && src1_type == GINSN_SRC_REG && src1_reg == REG_FP |
| && src2_type == GINSN_SRC_IMM) |
| fp_traceable_p = 0; |
| /* REG_FP restore is OK too. */ |
| else if (ginsn->type == GINSN_TYPE_LOAD) |
| fp_traceable_p = 0; |
| /* mov's to memory with REG_FP base do not make REG_FP untraceable. */ |
| else if (dst_type == GINSN_DST_INDIRECT |
| && (gtype == GINSN_TYPE_MOV || gtype == GINSN_TYPE_STORE)) |
| fp_traceable_p = 0; |
| /* Manipulations of the values possibly on stack are OK too. */ |
| else if ((gtype == GINSN_TYPE_ADD || gtype == GINSN_TYPE_SUB |
| || gtype == GINSN_TYPE_AND) |
| && dst_type == GINSN_DST_INDIRECT) |
| fp_traceable_p = 0; |
| /* All other ginsns with REG_FP as destination make REG_FP not |
| traceable. */ |
| else |
| fp_traceable_p = 1; |
| } |
| |
| if (fp_traceable_p) |
| as_bad_where (ginsn->file, ginsn->line, |
| _("SCFI: usage of REG_FP as scratch not supported")); |
| |
| return fp_traceable_p; |
| } |
| |
| static int |
| verify_heuristic_traceable_stack_manipulation (ginsnS *ginsn, |
| scfi_stateS *state) |
| { |
| /* The function uses this variable to issue error to user right away. */ |
| int sp_untraceable_p = 0; |
| bool possibly_untraceable = false; |
| enum ginsn_type gtype; |
| struct ginsn_dst *dst; |
| struct ginsn_src *src1; |
| struct ginsn_src *src2; |
| unsigned int src1_reg; |
| unsigned int dst_reg; |
| enum ginsn_src_type src1_type; |
| enum ginsn_src_type src2_type; |
| enum ginsn_dst_type dst_type; |
| |
| gtype = ginsn->type; |
| |
| src1 = ginsn_get_src1 (ginsn); |
| src2 = ginsn_get_src2 (ginsn); |
| dst = ginsn_get_dst (ginsn); |
| |
| src1_reg = ginsn_get_src_reg (src1); |
| dst_reg = ginsn_get_dst_reg (dst); |
| |
| src1_type = ginsn_get_src_type (src1); |
| src2_type = ginsn_get_src_type (src2); |
| dst_type = ginsn_get_dst_type (dst); |
| |
| /* Stack manipulation can be done in a variety of ways. A program may |
| allocate stack statically in prologue or may need to do dynamic stack |
| allocation. |
| |
| The SCFI machinery in GAS is based on some heuristics: |
| |
| - Rule 1 The base register for CFA tracking may be either REG_SP or |
| REG_FP. |
| |
| - Rule 2 If the base register for CFA tracking is REG_SP, the precise |
| amount of stack usage (and hence, the value of rsp) must be known at |
| all times. */ |
| |
| if (gtype == GINSN_TYPE_MOV |
| && dst_type == GINSN_DST_REG && dst_reg == REG_SP |
| /* Exclude mov %rbp, %rsp from this check. */ |
| && src1_type == GINSN_SRC_REG && src1_reg != REG_FP) |
| { |
| /* A previous mov %rsp, %reg must have been seen earlier for this to be |
| an OK for stack manipulation. */ |
| if (state->scratch[src1_reg].base != REG_CFA |
| || state->scratch[src1_reg].state != CFI_IN_REG) |
| possibly_untraceable = true; |
| } |
| /* Check add/sub/and insn usage when CFA base register is REG_SP. |
| Any stack size manipulation, including stack realignment is not allowed |
| if CFA base register is REG_SP. */ |
| else if (dst_type == GINSN_DST_REG && dst_reg == REG_SP |
| && (((gtype == GINSN_TYPE_ADD || gtype == GINSN_TYPE_SUB) |
| && src2_type != GINSN_SRC_IMM) |
| || gtype == GINSN_TYPE_AND || gtype == GINSN_TYPE_OTHER)) |
| possibly_untraceable = true; |
| /* If a register save operation is seen when REG_SP is untraceable, |
| CFI cannot be synthesized for register saves, hence bail out. */ |
| else if (ginsn_scfi_save_reg_p (ginsn, state) && !state->traceable_p) |
| { |
| sp_untraceable_p = 1; |
| /* If, however, the register save is an REG_FP-based, indirect mov |
| like: mov reg, disp(%rbp) and CFA base register is REG_BP, |
| untraceable REG_SP is not a problem. */ |
| if (gtype == GINSN_TYPE_MOV && state->regs[REG_CFA].base == REG_FP |
| && dst_type == GINSN_DST_INDIRECT && dst_reg == REG_FP) |
| sp_untraceable_p = 0; |
| } |
| else if (ginsn_scfi_restore_reg_p (ginsn, state) && !state->traceable_p) |
| { |
| if (gtype == GINSN_TYPE_MOV && dst_type == GINSN_DST_INDIRECT |
| && (src1_reg == REG_SP |
| || (src1_reg == REG_FP && state->regs[REG_CFA].base != REG_FP))) |
| sp_untraceable_p = 1; |
| } |
| |
| if (possibly_untraceable) |
| { |
| /* See Rule 2. For SP-based CFA, this makes CFA tracking not possible. |
| Propagate now to caller. */ |
| if (state->regs[REG_CFA].base == REG_SP) |
| sp_untraceable_p = 1; |
| else if (state->traceable_p) |
| { |
| /* An extension of Rule 2. |
| For FP-based CFA, this may be a problem *if* certain specific |
| changes to the SCFI state are seen beyond this point, e.g., |
| register save / restore from stack. */ |
| gas_assert (state->regs[REG_CFA].base == REG_FP); |
| /* Simply make a note in the SCFI state object for now and |
| continue. Indicate an error when register save / restore |
| for callee-saved registers is seen. */ |
| sp_untraceable_p = 0; |
| state->traceable_p = false; |
| } |
| } |
| |
| if (sp_untraceable_p) |
| as_bad_where (ginsn->file, ginsn->line, |
| _("SCFI: unsupported stack manipulation pattern")); |
| |
| return sp_untraceable_p; |
| } |
| |
| static int |
| verify_heuristic_symmetrical_restore_reg (scfi_stateS *state, ginsnS* ginsn) |
| { |
| int sym_restore = true; |
| offsetT expected_offset = 0; |
| struct ginsn_src *src1; |
| struct ginsn_dst *dst; |
| unsigned int reg; |
| |
| /* Rule 4: Save and Restore of callee-saved registers must be symmetrical. |
| It is expected that value of the saved register is restored correctly. |
| E.g., |
| push reg1 |
| push reg2 |
| ... |
| body of func which uses reg1 , reg2 as scratch, |
| and may be even spills them to stack. |
| ... |
| pop reg2 |
| pop reg1 |
| It is difficult to verify the Rule 4 in all cases. For the SCFI machinery, |
| it is difficult to separate prologue-epilogue from the body of the function |
| |
| Hence, the SCFI machinery at this time, should only warn on an asymetrical |
| restore. */ |
| src1 = ginsn_get_src1 (ginsn); |
| dst = ginsn_get_dst (ginsn); |
| reg = ginsn_get_dst_reg (dst); |
| |
| /* For non callee-saved registers, calling the API is meaningless. */ |
| if (!ginsn_track_reg_p (ginsn_get_dst_reg (dst), GINSN_GEN_SCFI)) |
| return sym_restore; |
| |
| /* The register must have been saved on stack, for sure. */ |
| gas_assert (state->regs[reg].state == CFI_ON_STACK); |
| gas_assert (state->regs[reg].base == REG_CFA); |
| |
| if ((ginsn->type == GINSN_TYPE_MOV |
| || ginsn->type == GINSN_TYPE_LOAD) |
| && ginsn_get_src_type (src1) == GINSN_SRC_INDIRECT |
| && (ginsn_get_src_reg (src1) == REG_SP |
| || (ginsn_get_src_reg (src1) == REG_FP |
| && state->regs[REG_CFA].base == REG_FP))) |
| { |
| /* mov disp(%rsp), reg. */ |
| /* mov disp(%rbp), reg. */ |
| expected_offset = (((ginsn_get_src_reg (src1) == REG_SP) |
| ? -state->stack_size |
| : state->regs[REG_FP].offset) |
| + ginsn_get_src_disp (src1)); |
| } |
| |
| sym_restore = (expected_offset == state->regs[reg].offset); |
| |
| return sym_restore; |
| } |
| |
| /* Perform symbolic execution of the GINSN and update its list of scfi_ops. |
| scfi_ops are later used to directly generate the DWARF CFI directives. |
| Also update the SCFI state object STATE for the caller. */ |
| |
| static int |
| gen_scfi_ops (ginsnS *ginsn, scfi_stateS *state) |
| { |
| int ret = 0; |
| offsetT offset; |
| struct ginsn_src *src1; |
| struct ginsn_src *src2; |
| struct ginsn_dst *dst; |
| unsigned int src1_reg; |
| unsigned int dst_reg; |
| enum ginsn_src_type src1_type; |
| enum ginsn_src_type src2_type; |
| enum ginsn_dst_type dst_type; |
| |
| if (!ginsn || !state) |
| ret = 1; |
| |
| /* For the first ginsn (of type GINSN_TYPE_SYMBOL) in the gbb, generate |
| the SCFI op with DW_CFA_def_cfa. Note that the register and offset are |
| target-specific. */ |
| if (GINSN_F_FUNC_BEGIN_P (ginsn)) |
| { |
| scfi_op_add_def_cfa (state, ginsn, REG_SP, SCFI_INIT_CFA_OFFSET); |
| state->stack_size += SCFI_INIT_CFA_OFFSET; |
| return ret; |
| } |
| |
| src1 = ginsn_get_src1 (ginsn); |
| src2 = ginsn_get_src2 (ginsn); |
| dst = ginsn_get_dst (ginsn); |
| |
| src1_reg = ginsn_get_src_reg (src1); |
| dst_reg = ginsn_get_dst_reg (dst); |
| |
| src1_type = ginsn_get_src_type (src1); |
| src2_type = ginsn_get_src_type (src2); |
| dst_type = ginsn_get_dst_type (dst); |
| |
| ret = verify_heuristic_traceable_stack_manipulation (ginsn, state); |
| if (ret) |
| return ret; |
| |
| ret = verify_heuristic_traceable_reg_fp (ginsn, state); |
| if (ret) |
| return ret; |
| |
| switch (dst_type) |
| { |
| case GINSN_DST_REG: |
| switch (ginsn->type) |
| { |
| case GINSN_TYPE_MOV: |
| if (src1_type == GINSN_SRC_REG && src1_reg == REG_SP |
| && dst_type == GINSN_DST_REG && dst_reg == REG_FP |
| && state->regs[REG_CFA].base == REG_SP) |
| { |
| /* mov %rsp, %rbp. */ |
| scfi_op_add_def_cfa_reg (state, ginsn, dst_reg); |
| } |
| else if (src1_type == GINSN_SRC_REG && src1_reg == REG_FP |
| && dst_type == GINSN_DST_REG && dst_reg == REG_SP |
| && state->regs[REG_CFA].base == REG_FP) |
| { |
| /* mov %rbp, %rsp. */ |
| state->stack_size = -state->regs[REG_FP].offset; |
| scfi_op_add_def_cfa_reg (state, ginsn, dst_reg); |
| state->traceable_p = true; |
| } |
| else if (src1_type == GINSN_SRC_INDIRECT |
| && (src1_reg == REG_SP || src1_reg == REG_FP) |
| && ginsn_track_reg_p (dst_reg, GINSN_GEN_SCFI)) |
| { |
| /* mov disp(%rsp), reg. */ |
| /* mov disp(%rbp), reg. */ |
| if (verify_heuristic_symmetrical_restore_reg (state, ginsn)) |
| { |
| scfi_state_restore_reg (state, dst_reg); |
| scfi_op_add_cfa_restore (ginsn, dst_reg); |
| } |
| else |
| as_warn_where (ginsn->file, ginsn->line, |
| _("SCFI: asymetrical register restore")); |
| } |
| else if (src1_type == GINSN_SRC_REG && src1_reg == REG_SP |
| && dst_type == GINSN_DST_REG) |
| { |
| /* mov %rsp, %reg. */ |
| /* The value of rsp is taken directly from state->stack_size. |
| IMP: The workflow in gen_scfi_ops must keep it updated. |
| PS: Not taking the value from state->scratch[REG_SP] is |
| intentional. */ |
| state->scratch[dst_reg].base = REG_CFA; |
| state->scratch[dst_reg].offset = -state->stack_size; |
| state->scratch[dst_reg].state = CFI_IN_REG; |
| } |
| else if (src1_type == GINSN_SRC_REG |
| && dst_type == GINSN_DST_REG && dst_reg == REG_SP) |
| { |
| /* mov %reg, %rsp. */ |
| /* Keep the value of REG_SP updated. */ |
| if (state->scratch[src1_reg].state == CFI_IN_REG) |
| { |
| state->stack_size = -state->scratch[src1_reg].offset; |
| state->traceable_p = true; |
| } |
| # if 0 |
| scfi_state_update_reg (state, ginsn_get_dst_reg (dst), |
| state->scratch[ginsn_get_src_reg (src1)].base, |
| state->scratch[ginsn_get_src_reg (src1)].offset); |
| #endif |
| |
| } |
| break; |
| case GINSN_TYPE_SUB: |
| if (src1_type == GINSN_SRC_REG && src1_reg == REG_SP |
| && dst_type == GINSN_DST_REG && dst_reg == REG_SP |
| && src2_type == GINSN_SRC_IMM) |
| { |
| /* Stack inc/dec offset, when generated due to stack push and pop is |
| target-specific. Use the value encoded in the ginsn. */ |
| state->stack_size += ginsn_get_src_imm (src2); |
| if (state->regs[REG_CFA].base == REG_SP) |
| { |
| /* push reg. */ |
| scfi_op_add_cfa_offset_dec (state, ginsn, ginsn_get_src_imm (src2)); |
| } |
| } |
| break; |
| case GINSN_TYPE_ADD: |
| if (src1_type == GINSN_SRC_REG && src1_reg == REG_SP |
| && dst_type == GINSN_DST_REG && dst_reg == REG_SP |
| && src2_type == GINSN_SRC_IMM) |
| { |
| /* Stack inc/dec offset is target-specific. Use the value |
| encoded in the ginsn. */ |
| state->stack_size -= ginsn_get_src_imm (src2); |
| /* pop %reg affects CFA offset only if CFA is currently |
| stack-pointer based. */ |
| if (state->regs[REG_CFA].base == REG_SP) |
| { |
| scfi_op_add_cfa_offset_inc (state, ginsn, ginsn_get_src_imm (src2)); |
| } |
| } |
| else if (src1_type == GINSN_SRC_REG && src1_reg == REG_FP |
| && dst_type == GINSN_DST_REG && dst_reg == REG_SP |
| && state->regs[REG_CFA].base == REG_FP) |
| { |
| /* FIXME - what is this for ? */ |
| state->stack_size = 0 - (state->regs[REG_FP].offset + ginsn_get_src_imm (src2)); |
| } |
| break; |
| case GINSN_TYPE_LOAD: |
| /* If this is a load from stack. */ |
| if (src1_type == GINSN_SRC_INDIRECT |
| && ((src1_reg == REG_FP && state->regs[REG_CFA].base == REG_FP) |
| || src1_reg == REG_SP)) |
| |
| { |
| /* pop %rbp when CFA tracking is REG_FP based. */ |
| if (dst_reg == REG_FP && state->regs[REG_CFA].base == REG_FP) |
| { |
| scfi_op_add_def_cfa_reg (state, ginsn, REG_SP); |
| if (state->regs[REG_CFA].offset != state->stack_size) |
| scfi_op_add_cfa_offset_inc (state, ginsn, |
| (state->regs[REG_CFA].offset - state->stack_size)); |
| } |
| if (ginsn_track_reg_p (dst_reg, GINSN_GEN_SCFI)) |
| { |
| if (verify_heuristic_symmetrical_restore_reg (state, ginsn)) |
| { |
| scfi_state_restore_reg (state, dst_reg); |
| scfi_op_add_cfa_restore (ginsn, dst_reg); |
| } |
| else |
| as_warn_where (ginsn->file, ginsn->line, |
| _("SCFI: asymetrical register restore")); |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| break; |
| |
| case GINSN_DST_INDIRECT: |
| /* Some operations with an indirect access to memory (or even to stack) |
| may still be uninteresting for SCFI purpose (e.g, addl %edx, -32(%rsp) |
| in x86). In case of x86_64, these can neither be a register |
| save / unsave, nor can alter the stack size. |
| PS: This condition may need to be revisited for other arches. */ |
| if (ginsn->type == GINSN_TYPE_ADD || ginsn->type == GINSN_TYPE_SUB |
| || ginsn->type == GINSN_TYPE_AND) |
| break; |
| gas_assert (ginsn->type == GINSN_TYPE_MOV |
| || ginsn->type == GINSN_TYPE_STORE |
| || ginsn->type == GINSN_TYPE_LOAD); |
| /* mov reg, disp(%rbp) */ |
| /* mov reg, disp(%rsp) */ |
| if (ginsn_scfi_save_reg_p (ginsn, state)) |
| { |
| if (dst_reg == REG_SP) |
| { |
| /* mov reg, disp(%rsp) */ |
| offset = 0 - state->stack_size + ginsn_get_dst_disp (dst); |
| scfi_state_save_reg (state, src1_reg, REG_CFA, offset); |
| scfi_op_add_cfi_offset (state, ginsn, src1_reg); |
| } |
| else if (dst_reg == REG_FP) |
| { |
| gas_assert (state->regs[REG_CFA].base == REG_FP); |
| /* mov reg, disp(%rbp) */ |
| offset = 0 - state->regs[REG_CFA].offset + ginsn_get_dst_disp (dst); |
| scfi_state_save_reg (state, src1_reg, REG_CFA, offset); |
| scfi_op_add_cfi_offset (state, ginsn, src1_reg); |
| } |
| } |
| break; |
| |
| default: |
| /* Skip GINSN_DST_UNKNOWN and GINSN_DST_MEM as they are uninteresting |
| currently for SCFI. */ |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /* Recursively perform forward flow of the (unwind information) SCFI STATE |
| starting at basic block GBB. |
| |
| The core of forward flow process takes the SCFI state at the entry of a bb |
| and updates it incrementally as per the semantics of each ginsn in the bb. |
| |
| Returns error code, if any. */ |
| |
| static int |
| forward_flow_scfi_state (gcfgS *gcfg, gbbS *gbb, scfi_stateS *state) |
| { |
| ginsnS *ginsn; |
| gbbS *prev_bb; |
| gedgeS *gedge = NULL; |
| int ret = 0; |
| |
| if (gbb->visited) |
| { |
| /* Check that the SCFI state is the same as previous. */ |
| ret = cmp_scfi_state (state, gbb->entry_state); |
| if (ret) |
| as_bad (_("SCFI: Bad CFI propagation perhaps")); |
| return ret; |
| } |
| |
| gbb->visited = true; |
| |
| gbb->entry_state = XCNEW (scfi_stateS); |
| memcpy (gbb->entry_state, state, sizeof (scfi_stateS)); |
| |
| /* Perform symbolic execution of each ginsn in the gbb and update the |
| scfi_ops list of each ginsn (and also update the STATE object). */ |
| bb_for_each_insn(gbb, ginsn) |
| { |
| ret = gen_scfi_ops (ginsn, state); |
| if (ret) |
| goto fail; |
| } |
| |
| gbb->exit_state = XCNEW (scfi_stateS); |
| memcpy (gbb->exit_state, state, sizeof (scfi_stateS)); |
| |
| /* Forward flow the SCFI state. Currently, we process the next basic block |
| in DFS order. But any forward traversal order should be fine. */ |
| prev_bb = gbb; |
| if (gbb->num_out_gedges) |
| { |
| bb_for_each_edge(gbb, gedge) |
| { |
| gbb = gedge->dst_bb; |
| /* Ensure that the state is the one from the exit of the prev bb. */ |
| memcpy (state, prev_bb->exit_state, sizeof (scfi_stateS)); |
| if (gbb->visited) |
| { |
| ret = cmp_scfi_state (gbb->entry_state, state); |
| if (ret) |
| goto fail; |
| } |
| |
| if (!gedge->visited) |
| { |
| gedge->visited = true; |
| |
| /* Entry SCFI state for the destination bb of the edge is the |
| same as the exit SCFI state of the source bb of the edge. */ |
| memcpy (state, prev_bb->exit_state, sizeof (scfi_stateS)); |
| ret = forward_flow_scfi_state (gcfg, gbb, state); |
| if (ret) |
| goto fail; |
| } |
| } |
| } |
| |
| return 0; |
| |
| fail: |
| |
| if (gedge) |
| gedge->visited = true; |
| return 1; |
| } |
| |
| static int |
| backward_flow_scfi_state (const symbolS *func ATTRIBUTE_UNUSED, gcfgS *gcfg) |
| { |
| gbbS **prog_order_bbs; |
| gbbS **restore_bbs; |
| gbbS *current_bb; |
| gbbS *prev_bb; |
| gbbS *dst_bb; |
| ginsnS *ginsn; |
| gedgeS *gedge = NULL; |
| |
| int ret = 0; |
| uint64_t i, j; |
| |
| /* Basic blocks in reverse program order. */ |
| prog_order_bbs = XCNEWVEC (gbbS *, gcfg->num_gbbs); |
| /* Basic blocks for which CFI remember op needs to be generated. */ |
| restore_bbs = XCNEWVEC (gbbS *, gcfg->num_gbbs); |
| |
| gcfg_get_bbs_in_prog_order (gcfg, prog_order_bbs); |
| |
| i = gcfg->num_gbbs - 1; |
| /* Traverse in reverse program order. */ |
| while (i > 0) |
| { |
| current_bb = prog_order_bbs[i]; |
| prev_bb = prog_order_bbs[i-1]; |
| if (cmp_scfi_state (prev_bb->exit_state, current_bb->entry_state)) |
| { |
| /* Candidate for .cfi_restore_state found. */ |
| ginsn = bb_get_first_ginsn (current_bb); |
| scfi_op_add_cfi_restore_state (ginsn); |
| /* Memorize current_bb now to find location for its remember state |
| later. */ |
| restore_bbs[i] = current_bb; |
| } |
| else |
| { |
| bb_for_each_edge (current_bb, gedge) |
| { |
| dst_bb = gedge->dst_bb; |
| for (j = 0; j < gcfg->num_gbbs; j++) |
| if (restore_bbs[j] == dst_bb) |
| { |
| ginsn = bb_get_last_ginsn (current_bb); |
| scfi_op_add_cfi_remember_state (ginsn); |
| /* Remove the memorised restore_bb from the list. */ |
| restore_bbs[j] = NULL; |
| break; |
| } |
| } |
| } |
| i--; |
| } |
| |
| /* All .cfi_restore_state pseudo-ops must have a corresponding |
| .cfi_remember_state by now. */ |
| for (j = 0; j < gcfg->num_gbbs; j++) |
| if (restore_bbs[j] != NULL) |
| { |
| ret = 1; |
| break; |
| } |
| |
| free (restore_bbs); |
| free (prog_order_bbs); |
| |
| return ret; |
| } |
| |
| /* Synthesize DWARF CFI for a function. */ |
| |
| int |
| scfi_synthesize_dw2cfi (const symbolS *func, gcfgS *gcfg, gbbS *root_bb) |
| { |
| int ret; |
| scfi_stateS *init_state; |
| |
| init_state = XCNEW (scfi_stateS); |
| init_state->traceable_p = true; |
| |
| /* Traverse the input GCFG and perform forward flow of information. |
| Update the scfi_op(s) per ginsn. */ |
| ret = forward_flow_scfi_state (gcfg, root_bb, init_state); |
| if (ret) |
| { |
| as_bad (_("SCFI: forward pass failed for func '%s'"), S_GET_NAME (func)); |
| goto end; |
| } |
| |
| ret = backward_flow_scfi_state (func, gcfg); |
| if (ret) |
| { |
| as_bad (_("SCFI: backward pass failed for func '%s'"), S_GET_NAME (func)); |
| goto end; |
| } |
| |
| end: |
| free (init_state); |
| return ret; |
| } |
| |
| static int |
| handle_scfi_dot_cfi (ginsnS *ginsn) |
| { |
| scfi_opS *op; |
| |
| /* Nothing to do. */ |
| if (!ginsn->scfi_ops) |
| return 0; |
| |
| op = *ginsn->scfi_ops; |
| if (!op) |
| goto bad; |
| |
| while (op) |
| { |
| switch (op->dw2cfi_op) |
| { |
| case DW_CFA_def_cfa_register: |
| scfi_dot_cfi (DW_CFA_def_cfa_register, op->loc.base, 0, 0, NULL, |
| ginsn->sym); |
| break; |
| case DW_CFA_def_cfa_offset: |
| scfi_dot_cfi (DW_CFA_def_cfa_offset, op->loc.base, 0, |
| op->loc.offset, NULL, ginsn->sym); |
| break; |
| case DW_CFA_def_cfa: |
| scfi_dot_cfi (DW_CFA_def_cfa, op->loc.base, 0, op->loc.offset, |
| NULL, ginsn->sym); |
| break; |
| case DW_CFA_offset: |
| scfi_dot_cfi (DW_CFA_offset, op->reg, 0, op->loc.offset, NULL, |
| ginsn->sym); |
| break; |
| case DW_CFA_restore: |
| scfi_dot_cfi (DW_CFA_restore, op->reg, 0, 0, NULL, ginsn->sym); |
| break; |
| case DW_CFA_remember_state: |
| scfi_dot_cfi (DW_CFA_remember_state, 0, 0, 0, NULL, ginsn->sym); |
| break; |
| case DW_CFA_restore_state: |
| scfi_dot_cfi (DW_CFA_restore_state, 0, 0, 0, NULL, ginsn->sym); |
| break; |
| case CFI_label: |
| scfi_dot_cfi (CFI_label, 0, 0, 0, op->op_data->name, ginsn->sym); |
| free ((char *) op->op_data->name); |
| break; |
| case CFI_signal_frame: |
| scfi_dot_cfi (CFI_signal_frame, 0, 0, 0, NULL, ginsn->sym); |
| break; |
| default: |
| goto bad; |
| break; |
| } |
| op = op->next; |
| } |
| |
| return 0; |
| bad: |
| as_bad (_("SCFI: Invalid DWARF CFI opcode data")); |
| return 1; |
| } |
| |
| /* Emit Synthesized DWARF CFI. */ |
| |
| int |
| scfi_emit_dw2cfi (const symbolS *func) |
| { |
| struct frch_ginsn_data *frch_gdata; |
| ginsnS* ginsn = NULL; |
| |
| frch_gdata = frchain_now->frch_ginsn_data; |
| ginsn = frch_gdata->gins_rootP; |
| |
| while (ginsn) |
| { |
| switch (ginsn->type) |
| { |
| case GINSN_TYPE_SYMBOL: |
| /* .cfi_startproc and .cfi_endproc pseudo-ops. */ |
| if (GINSN_F_FUNC_BEGIN_P (ginsn)) |
| { |
| scfi_dot_cfi_startproc (frch_gdata->start_addr); |
| break; |
| } |
| else if (GINSN_F_FUNC_END_P (ginsn)) |
| { |
| scfi_dot_cfi_endproc (ginsn->sym); |
| break; |
| } |
| /* Fall through. */ |
| case GINSN_TYPE_ADD: |
| case GINSN_TYPE_AND: |
| case GINSN_TYPE_CALL: |
| case GINSN_TYPE_JUMP: |
| case GINSN_TYPE_JUMP_COND: |
| case GINSN_TYPE_MOV: |
| case GINSN_TYPE_LOAD: |
| case GINSN_TYPE_PHANTOM: |
| case GINSN_TYPE_STORE: |
| case GINSN_TYPE_SUB: |
| case GINSN_TYPE_OTHER: |
| case GINSN_TYPE_RETURN: |
| |
| /* For all other SCFI ops, invoke the handler. */ |
| if (ginsn->scfi_ops) |
| handle_scfi_dot_cfi (ginsn); |
| break; |
| |
| default: |
| /* No other GINSN_TYPE_* expected. */ |
| as_bad (_("SCFI: bad ginsn for func '%s'"), |
| S_GET_NAME (func)); |
| break; |
| } |
| ginsn = ginsn->next; |
| } |
| return 0; |
| } |
| |
| #else |
| |
| int |
| scfi_emit_dw2cfi (const symbolS *func ATTRIBUTE_UNUSED) |
| { |
| as_bad (_("SCFI: unsupported for target")); |
| return 1; |
| } |
| |
| int |
| scfi_synthesize_dw2cfi (const symbolS *func ATTRIBUTE_UNUSED, |
| gcfgS *gcfg ATTRIBUTE_UNUSED, |
| gbbS *root_bb ATTRIBUTE_UNUSED) |
| { |
| as_bad (_("SCFI: unsupported for target")); |
| return 1; |
| } |
| |
| #endif /* defined (TARGET_USE_SCFI) && defined (TARGET_USE_GINSN). */ |