diff options
-rw-r--r-- | gas/config/tc-aarch64-ginsn.c | 797 | ||||
-rw-r--r-- | gas/config/tc-aarch64.c | 15 | ||||
-rw-r--r-- | gas/config/tc-aarch64.h | 21 |
3 files changed, 833 insertions, 0 deletions
diff --git a/gas/config/tc-aarch64-ginsn.c b/gas/config/tc-aarch64-ginsn.c new file mode 100644 index 0000000..dbee8df --- /dev/null +++ b/gas/config/tc-aarch64-ginsn.c @@ -0,0 +1,797 @@ +/* tc-aarch64-ginsn.c -- Ginsn generation for the AArch64 ISA + + Copyright (C) 2024 Free Software Foundation, Inc. + Contributed by ARM Ltd. + + This file is part of GAS. + + 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 of the license, 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 this program; see the file COPYING3. If not, + see <http://www.gnu.org/licenses/>. */ + +/* This file contains the implementation of the ginsn creation for aarch64 + instructions. Most functions will read the aarch64_instruction inst + object, but none should need to modify it. */ + +#ifdef OBJ_ELF + +/* DWARF register number for R1. Used as dummy value when WZR. */ +#define GINSN_DW2_REGNUM_R1_DUMMY 1 + +/* Return whether the given register number is a callee-saved register for + SCFI purposes. + + Apart from the callee-saved GPRs, SCFI always tracks SP, FP and LR + additionally. As for the FP/Advanced SIMD registers, v8-v15 are + callee-saved. */ + +bool +aarch64_scfi_callee_saved_p (unsigned int dw2reg_num) +{ + /* PS: Ensure SCFI_MAX_REG_ID is the max DWARF register number to cover + all the registers here. */ + if (dw2reg_num == REG_SP /* x31. */ + || dw2reg_num == REG_FP /* x29. */ + || dw2reg_num == REG_LR /* x30. */ + || (dw2reg_num >= 19 && dw2reg_num <= 28) /* x19 - x28. */ + || (dw2reg_num >= 72 && dw2reg_num <= 79) /* v8 - v15. */) + return true; + + return false; +} + +/* Get the DWARF register number for the given OPND. + Whether 31 is used to encode WZR or SP is specified via SP_ALLOWED_P. + + The caller must decide the value of SP_ALLOWED_P based on the instruction + encoding. */ + +static unsigned int +ginsn_dw2_regnum (aarch64_opnd_info *opnd, bool sp_allowed_p) +{ + enum aarch64_operand_class opnd_class; + unsigned int dw2reg_num = 0; + + opnd_class = aarch64_get_operand_class (opnd->type); + + switch (opnd_class) + { + case AARCH64_OPND_CLASS_FP_REG: + dw2reg_num = opnd->reg.regno + 64; + break; + case AARCH64_OPND_CLASS_SVE_REGLIST: + dw2reg_num = opnd->reglist.first_regno + 64; + break; + case AARCH64_OPND_CLASS_MODIFIED_REG: + case AARCH64_OPND_CLASS_INT_REG: + case AARCH64_OPND_CLASS_ADDRESS: + /* Use a dummy register value in case of WZR, else this will be an + incorrect dependency on REG_SP. */ + if (!sp_allowed_p && opnd->reg.regno == REG_SP) + dw2reg_num = GINSN_DW2_REGNUM_R1_DUMMY; + else + /* For GPRs of our interest (callee-saved regs, SP, FP, LR), + DWARF register number is the same as AArch64 register number. */ + dw2reg_num = opnd->reg.regno; + break; + case AARCH64_OPND_CLASS_SYSTEM: + /* For prfm etc., where a opnd->type AARCH64_OPND_PRFOP is seen. */ + dw2reg_num = GINSN_DW2_REGNUM_R1_DUMMY; + break; + default: + as_bad ("Unexpected value in ginsn_dw2_regnum"); + break; + } + + return dw2reg_num; +} + +/* Generate ginsn for addsub instructions with immediate opnd. */ + +static ginsnS * +aarch64_ginsn_addsub_imm (const symbolS *insn_end_sym) +{ + ginsnS *ginsn = NULL; + bool add_p, sub_p; + offsetT src_imm = 0; + unsigned int dst_reg, opnd_reg; + aarch64_opnd_info *dst, *opnd; + ginsnS *(*ginsn_func) (const symbolS *, bool, + enum ginsn_src_type, unsigned int, offsetT, + enum ginsn_src_type, unsigned int, offsetT, + enum ginsn_dst_type, unsigned int, offsetT); + + aarch64_inst *base = &inst.base; + const aarch64_opcode *opcode = base->opcode; + + add_p = aarch64_opcode_subclass_p (opcode, F_ARITH_ADD); + sub_p = aarch64_opcode_subclass_p (opcode, F_ARITH_SUB); + gas_assert (add_p || sub_p); + ginsn_func = add_p ? ginsn_new_add : ginsn_new_sub; + + gas_assert (aarch64_num_of_operands (opcode) == 3); + dst = &base->operands[0]; + opnd = &base->operands[1]; + + dst_reg = ginsn_dw2_regnum (dst, true); + + if (aarch64_gas_internal_fixup_p () && inst.reloc.exp.X_op == O_constant) + src_imm = inst.reloc.exp.X_add_number; + /* For any other relocation type, e.g., in add reg, reg, symbol, skip now + and handle via aarch64_ginsn_unhandled () code path. */ + else if (inst.reloc.type != BFD_RELOC_UNUSED) + return ginsn; + /* FIXME - verify the understanding and remove assert. */ + else + gas_assert (0); + + opnd_reg = ginsn_dw2_regnum (opnd, true); + + ginsn = ginsn_func (insn_end_sym, true, + GINSN_SRC_REG, opnd_reg, 0, + GINSN_SRC_IMM, 0, src_imm, + GINSN_DST_REG, dst_reg, 0); + ginsn_set_where (ginsn); + + return ginsn; +} + +/* Generate ginsn for addsub instructions with reg opnd. */ + +static ginsnS * +aarch64_ginsn_addsub_reg (const symbolS *insn_end_sym) +{ + ginsnS *ginsn = NULL; + bool add_p, sub_p; + unsigned int dst_reg, src1_reg, src2_reg; + aarch64_opnd_info *dst, *src1, *src2; + ginsnS *(*ginsn_func) (const symbolS *, bool, + enum ginsn_src_type, unsigned int, offsetT, + enum ginsn_src_type, unsigned int, offsetT, + enum ginsn_dst_type, unsigned int, offsetT); + + aarch64_inst *base = &inst.base; + const aarch64_opcode *opcode = base->opcode; + + add_p = aarch64_opcode_subclass_p (opcode, F_ARITH_ADD); + sub_p = aarch64_opcode_subclass_p (opcode, F_ARITH_SUB); + gas_assert (add_p || sub_p); + ginsn_func = add_p ? ginsn_new_add : ginsn_new_sub; + + gas_assert (aarch64_num_of_operands (opcode) == 3); + dst = &base->operands[0]; + src1 = &base->operands[1]; + src2 = &base->operands[2]; + + dst_reg = ginsn_dw2_regnum (dst, true); + src1_reg = ginsn_dw2_regnum (src1, true); + src2_reg = ginsn_dw2_regnum (src2, false); + + ginsn = ginsn_func (insn_end_sym, true, + GINSN_SRC_REG, src1_reg, 0, + GINSN_SRC_REG, src2_reg, 0, + GINSN_DST_REG, dst_reg, 0); + ginsn_set_where (ginsn); + + return ginsn; +} + +/* Generate ginsn for the load pair and store pair instructions. */ + +static ginsnS * +aarch64_ginsn_ldstp (const symbolS *insn_end_sym) +{ + ginsnS *ginsn = NULL; + ginsnS *ginsn_ind = NULL; + ginsnS *ginsn_mem1 = NULL; + ginsnS *ginsn_mem2 = NULL; + unsigned int opnd_reg, addr_reg; + offsetT offset, mem_offset; + unsigned int width = 8; + bool store_p = false; + + aarch64_opnd_info *opnd1, *opnd2, *addr; + aarch64_inst *base = &inst.base; + const aarch64_opcode *opcode = base->opcode; + + /* This function is for handling ldp / stp ops only. */ + gas_assert (opcode->iclass == ldstpair_indexed + || opcode->iclass == ldstpair_off); + gas_assert (aarch64_num_of_operands (opcode) == 3); + + opnd1 = &base->operands[0]; + opnd2 = &base->operands[1]; + addr = &base->operands[2]; + store_p = ((opcode->flags & F_SUBCLASS) == F_LDST_STORE); + + addr_reg = ginsn_dw2_regnum (addr, true); + gas_assert (!addr->addr.offset.is_reg); + mem_offset = addr->addr.offset.imm; + + /* ldstp may load or store two 32-bit words or two 64-bit doublewords. */ + if (opnd1->qualifier == AARCH64_OPND_QLF_W + || opnd1->qualifier == AARCH64_OPND_QLF_S_S) + width = 4; + + /* Handle address calculation. */ + if ((addr->addr.preind || addr->addr.postind) && addr->addr.writeback) + { + /* Pre-indexed store, e.g., stp x29, x30, [sp, -128]! + Pre-indexed addressing is like offset addressing, except that + the base pointer is updated as a result of the instruction. + + Post-indexed store, e.g., stp x29, x30, [sp],128 + Post-index addressing is useful for popping off the stack. The + instruction loads the value from the location pointed at by the stack + pointer, and then moves the stack pointer on to the next full location + in the stack. */ + ginsn_ind = ginsn_new_add (insn_end_sym, false, + GINSN_SRC_REG, addr_reg, 0, + GINSN_SRC_IMM, 0, mem_offset, + GINSN_DST_REG, addr_reg, 0); + ginsn_set_where (ginsn_ind); + } + + /* Save / restore of WZR is not of interest for SCFI. However, the address + processing component may have updated the stack pointer. At least, emit + that ginsn and return. PS: opnd_reg will be GINSN_DW2_REGNUM_R1_DUMMY by + now; Check explicitly the regno for WZR. Also note, + TBD_GINSN_GEN_NOT_SCFI. */ + if (opnd1->reg.regno == REG_SP /* WZR. */) + return ginsn_ind; + + /* With post-index addressing, the value is loaded from the + address in the base pointer, and then the pointer is updated. + With pre-index addressing, the addr computation has already + been explicitly done. */ + offset = mem_offset; + if ((addr->addr.postind || addr->addr.preind) && addr->addr.writeback) + offset = 0; + + if (opnd1->qualifier == AARCH64_OPND_QLF_S_Q) + { + width = 16; + if (target_big_endian) + offset += 8; + } + + opnd_reg = ginsn_dw2_regnum (opnd1, false); + if (store_p) + { + ginsn_mem1 = ginsn_new_store (insn_end_sym, false, + GINSN_SRC_REG, opnd_reg, + GINSN_DST_INDIRECT, addr_reg, offset); + ginsn_set_where (ginsn_mem1); + + opnd_reg = ginsn_dw2_regnum (opnd2, false); + ginsn_mem2 = ginsn_new_store (insn_end_sym, false, + GINSN_SRC_REG, opnd_reg, + GINSN_DST_INDIRECT, addr_reg, + offset + width); + ginsn_set_where (ginsn_mem2); + } + else + { + opnd_reg = ginsn_dw2_regnum (opnd1, false); + ginsn_mem1 = ginsn_new_load (insn_end_sym, false, + GINSN_SRC_INDIRECT, addr_reg, offset, + GINSN_DST_REG, opnd_reg); + ginsn_set_where (ginsn_mem1); + + opnd_reg = ginsn_dw2_regnum (opnd2, false); + ginsn_mem2 = ginsn_new_load (insn_end_sym, false, + GINSN_SRC_INDIRECT, addr_reg, offset + width, + GINSN_DST_REG, opnd_reg); + ginsn_set_where (ginsn_mem2); + } + + /* Link the list of ginsns created. */ + if (addr->addr.preind && addr->addr.writeback) + gas_assert (!ginsn_link_next (ginsn_ind, ginsn_mem1)); + + gas_assert (!ginsn_link_next (ginsn_mem1, ginsn_mem2)); + + if (addr->addr.postind && addr->addr.writeback) + gas_assert (!ginsn_link_next (ginsn_mem2, ginsn_ind)); + + /* Make note of the first instruction in the list. */ + ginsn = (addr->addr.preind && addr->addr.writeback) ? ginsn_ind : ginsn_mem1; + return ginsn; +} + +/* Generate ginsn for load and store instructions. */ + +static ginsnS * +aarch64_ginsn_ldstr (const symbolS *insn_end_sym) +{ + ginsnS *ginsn = NULL; + ginsnS *ginsn_ind = NULL; + ginsnS *ginsn_mem = NULL; + unsigned int opnd_reg, addr_reg; + offsetT offset, mem_offset; + bool store_p = false; + + aarch64_opnd_info *opnd1, *addr; + aarch64_inst *base = &inst.base; + const aarch64_opcode *opcode = base->opcode; + + /* This function is for handling ldr, str ops only. */ + gas_assert (opcode->iclass == ldst_imm9 || opcode->iclass == ldst_pos); + gas_assert (aarch64_num_of_operands (opcode) == 2); + + opnd1 = &base->operands[0]; + addr = &base->operands[1]; + store_p = ((opcode->flags & F_SUBCLASS) == F_LDST_STORE); + + addr_reg = ginsn_dw2_regnum (addr, true); + + /* STR <Xt>, [<Xn|SP>, (<Wm>|<Xm>){, <extend> {<amount>}}]. + LDR <Xt>, [<Xn|SP>], #<simm>. */ + opnd_reg = ginsn_dw2_regnum (opnd1, false); + + if (aarch64_gas_internal_fixup_p () && inst.reloc.exp.X_op == O_constant) + mem_offset = inst.reloc.exp.X_add_number; + else + { + gas_assert (!addr->addr.offset.is_reg); + mem_offset = addr->addr.offset.imm; + } + + /* Handle address calculation. */ + if ((addr->addr.preind || addr->addr.postind) && addr->addr.writeback) + { + ginsn_ind = ginsn_new_add (insn_end_sym, false, + GINSN_SRC_REG, addr_reg, 0, + GINSN_SRC_IMM, 0, mem_offset, + GINSN_DST_REG, addr_reg, 0); + ginsn_set_where (ginsn_ind); + } + + /* Save / restore of WZR is not of interest for SCFI. However, the address + processing component may have updated the stack pointer. At least, emit + that ginsn and return. PS: opnd_reg will be GINSN_DW2_REGNUM_R1_DUMMY by + now; Check explicitly the regno for WZR. Also note, + TBD_GINSN_GEN_NOT_SCFI. */ + if (opnd1->reg.regno == REG_SP /* WZR. */) + return ginsn_ind; + + /* With post-index addressing, the value is loaded from the + address in the base pointer, and then the pointer is updated. + With pre-index addressing, the addr computation has already + been explicitly done. */ + offset = mem_offset; + if ((addr->addr.postind || addr->addr.preind) && addr->addr.writeback) + offset = 0; + if (target_big_endian && opnd1->qualifier == AARCH64_OPND_QLF_S_Q) + offset += 8; + + if (store_p) + ginsn_mem = ginsn_new_store (insn_end_sym, false, + GINSN_SRC_REG, opnd_reg, + GINSN_DST_INDIRECT, addr_reg, offset); + else + ginsn_mem = ginsn_new_load (insn_end_sym, false, + GINSN_SRC_INDIRECT, addr_reg, offset, + GINSN_DST_REG, opnd_reg); + ginsn_set_where (ginsn_mem); + + if (addr->addr.preind && addr->addr.writeback) + gas_assert (!ginsn_link_next (ginsn_ind, ginsn_mem)); + else if (addr->addr.postind && addr->addr.writeback) + gas_assert (!ginsn_link_next (ginsn_mem, ginsn_ind)); + + /* Make note of the first instruction in the list. */ + ginsn = (addr->addr.preind && addr->addr.writeback) ? ginsn_ind : ginsn_mem; + + return ginsn; +} + +/* Generate ginsn for unconditional branch instructions. */ + +static ginsnS * +aarch64_ginsn_branch_uncond (const symbolS *insn_end_sym) +{ + ginsnS *ginsn = NULL; + const symbolS *src_symbol = NULL; + enum ginsn_src_type src_type = GINSN_SRC_UNKNOWN; + unsigned int src_reg = 0; + + aarch64_inst *base = &inst.base; + const aarch64_opcode *opcode = base->opcode; + + if (opcode->iclass == branch_imm) + { + /* b or bl. opcode 0x14000000 or 0x94000000. */ + gas_assert (inst.reloc.type == BFD_RELOC_AARCH64_CALL26 + || inst.reloc.type == BFD_RELOC_AARCH64_JUMP26); + src_symbol = inst.reloc.exp.X_add_symbol; + src_type = GINSN_SRC_SYMBOL; + } + else if (aarch64_num_of_operands (opcode) >= 1) + { + gas_assert (opcode->iclass == branch_reg); + /* Some insns (e.g., braa, blraa etc.) may have > 1 operands. For + current SCFI implementation, it suffices however to simply pass + the information about the first source. Although, strictly speaking, + (if reg) the source info is currently of no material use either. */ + src_type = GINSN_SRC_REG; + src_reg = ginsn_dw2_regnum (&base->operands[0], false); + } + + if (aarch64_opcode_subclass_p (opcode, F_BRANCH_CALL)) + { + gas_assert (src_type != GINSN_SRC_UNKNOWN); + ginsn = ginsn_new_call (insn_end_sym, true, + src_type, src_reg, src_symbol); + } + else if (aarch64_opcode_subclass_p (opcode, F_BRANCH_RET)) + /* TBD_GINSN_REPRESENTATION_LIMIT. The following function to create a + GINSN_TYPE_RETURN does not allow src info ATM. */ + ginsn = ginsn_new_return (insn_end_sym, true); + else + ginsn = ginsn_new_jump (insn_end_sym, true, + src_type, src_reg, src_symbol); + + ginsn_set_where (ginsn); + + return ginsn; +} + +/* Generate ginsn for conditional branch instructions. */ + +static ginsnS * +aarch64_ginsn_branch_cond (const symbolS *insn_end_sym) +{ + ginsnS *ginsn; + const symbolS *src_symbol; + enum ginsn_src_type src_type; + + gas_assert (inst.reloc.type == BFD_RELOC_AARCH64_BRANCH19 + || inst.reloc.type == BFD_RELOC_AARCH64_TSTBR14); + + src_symbol = inst.reloc.exp.X_add_symbol; + src_type = GINSN_SRC_SYMBOL; + ginsn = ginsn_new_jump_cond (insn_end_sym, true, src_type, 0, src_symbol); + ginsn_set_where (ginsn); + + return ginsn; +} + +/* Generate ginsn for mov instructions. */ + +static ginsnS * +aarch64_ginsn_mov (const symbolS *insn_end_sym) +{ + ginsnS *ginsn = NULL; + unsigned int src_reg = 0, dst_reg; + aarch64_opnd_info *src, *dst; + offsetT src_imm = 0; + enum ginsn_src_type src_type; + + aarch64_inst *base = &inst.base; + const aarch64_opcode *opcode = base->opcode; + + gas_assert (aarch64_num_of_operands (opcode) == 2); + + dst = &base->operands[0]; + src = &base->operands[1]; + + dst_reg = ginsn_dw2_regnum (dst, true); + + /* For some mov ops, e.g., movn, movk, or movz, there may optionally be more + work than just a simple mov. Skip handling these mov altogether and let + the aarch64_ginsn_unhandled () alert if these insns affect SCFI + correctness. TBD_GINSN_GEN_NOT_SCFI. */ + if (src->type == AARCH64_OPND_HALF) + return ginsn; + + if (src->type == AARCH64_OPND_IMM_MOV + && aarch64_gas_internal_fixup_p () && inst.reloc.exp.X_op == O_constant) + { + src_imm = inst.reloc.exp.X_add_number; + src_type = GINSN_SRC_IMM; + } + else + { + /* mov x27, sp. */ + src_reg = ginsn_dw2_regnum (src, true); + src_type = GINSN_SRC_REG; + } + + ginsn = ginsn_new_mov (insn_end_sym, false, + src_type, src_reg, src_imm, + GINSN_DST_REG, dst_reg, 0); + ginsn_set_where (ginsn); + + return ginsn; +} + +/* Check if an instruction is whitelisted. + + An instruction is a candidate for whitelisting if not generating ginsn for + it, does not affect SCFI correctness. + + TBD_GINSN_GEN_NOT_SCFI. This function assumes GINSN_GEN_SCFI is in effect. + When other ginsn_gen_mode are added, this will need fixing. */ + +static bool +aarch64_ginsn_safe_to_skip_p (void) +{ + bool skip_p = false; + aarch64_opnd_info *opnd = NULL; + unsigned int dw2_regnum; + unsigned int opnd_reg; + int num_opnds = 0; + + aarch64_inst *base = &inst.base; + const aarch64_opcode *opcode = base->opcode; + + /* ATM, whitelisting operations with no operands does not seem to be + necessary. */ + num_opnds = aarch64_num_of_operands (opcode); + if (!num_opnds) + return skip_p; + + opnd = &base->operands[0]; + + switch (opcode->iclass) + { + case ldst_regoff: + /* It is not expected to have reg offset based ld/st ops to be used + for reg save and restore operations. Warn the user though. */ + opnd_reg = ginsn_dw2_regnum (opnd, false); + if (aarch64_scfi_callee_saved_p (opnd_reg)) + { + skip_p = true; + as_warn ("SCFI: ignored probable save/restore op with reg offset"); + } + break; + + case dp_2src: + /* irg insn needs to be explicitly whitelisted. This is because the + dest is Rd_SP, but irg insn affects the tag only. To detect irg + insn, avoid an opcode-based check, however. */ + if (opnd->type == AARCH64_OPND_Rd_SP) + { + dw2_regnum = ginsn_dw2_regnum (opnd, true); + if (dw2_regnum == REG_SP) + skip_p = true; + } + break; + + default: + break; + } + + return skip_p; +} + +#define AARCH64_GINSN_UNHANDLED_NONE 0 +#define AARCH64_GINSN_UNHANDLED_DEST_REG 1 +#define AARCH64_GINSN_UNHANDLED_CFG 2 +#define AARCH64_GINSN_UNHANDLED_STACKOP 3 +#define AARCH64_GINSN_UNHANDLED_UNEXPECTED 4 + +/* Check the input insn for its impact on the correctness of the synthesized + CFI. Returns an error code to the caller. */ + +static int +aarch64_ginsn_unhandled (void) +{ + int err = AARCH64_GINSN_UNHANDLED_NONE; + aarch64_inst *base = &inst.base; + const aarch64_opcode *opcode = base->opcode; + aarch64_opnd_info *dest = &base->operands[0]; + int num_opnds = aarch64_num_of_operands (opcode); + aarch64_opnd_info *addr; + unsigned int dw2_regnum; + unsigned int addr_reg; + aarch64_opnd_info *opnd = NULL; + unsigned int opnd_reg; + bool sp_allowed_p = false; + + /* All change of flow instructions are important for SCFI. */ + if (opcode->iclass == condbranch + || opcode->iclass == compbranch + || opcode->iclass == testbranch + || opcode->iclass == branch_imm + || opcode->iclass == branch_reg) + err = AARCH64_GINSN_UNHANDLED_CFG; + /* Also, any memory instructions that may involve an update to the stack + pointer or save/restore of callee-saved registers must not be skipped. + Note that, some iclasses cannot be used to push or pop stack because of + disallowed writeback: ldst_unscaled, ldst_regoff, ldst_unpriv, ldstexcl, + loadlit, ldstnapair_offs. FIXME double-check. + Also, these iclasses do not seem to be amenable to being used for + save/restore ops either. FIXME double-check. */ + else if (opcode->iclass == ldstpair_off + || opcode->iclass == ldstpair_indexed + || opcode->iclass == ldst_imm9 + || opcode->iclass == ldst_imm10 + || opcode->iclass == ldst_pos + /* STR Zn are especially complicated as they do not store in the same byte + order for big-endian: STR Qn stores as a 128-bit integer (MSB first), + whereas STR Zn stores as a stream of bytes (LSB first). FIXME Simply punt + on the big-endian and little-endian SVE PCS case for now. */ + || opcode->iclass == sve_misc) + { + opnd = &base->operands[0]; + addr = &base->operands[num_opnds - 1]; + addr_reg = ginsn_dw2_regnum (addr, true); + opnd_reg = ginsn_dw2_regnum (opnd, false); + /* For all skipped memory operations, check if an update to REG_SP or + REG_FP is involved. */ + if ((addr_reg == REG_SP || addr_reg == REG_FP) + && (((addr->addr.postind || addr->addr.preind) && addr->addr.writeback) + || aarch64_scfi_callee_saved_p (opnd_reg))) + + err = AARCH64_GINSN_UNHANDLED_STACKOP; + } + /* Finally, irrespective of the iclass, check if the missed instructions are + affecting REG_SP or REG_FP. */ + else if (dest && (dest->type == AARCH64_OPND_Rd + || dest->type == AARCH64_OPND_Rd_SP)) + { + sp_allowed_p = (dest->type == AARCH64_OPND_Rd_SP) ? true : false; + dw2_regnum = ginsn_dw2_regnum (dest, sp_allowed_p); + + if (dw2_regnum == REG_SP || dw2_regnum == REG_FP) + err = AARCH64_GINSN_UNHANDLED_DEST_REG; + } + + return err; +} + +/* Generate one or more generic GAS instructions, a.k.a, ginsns for the + current machine instruction. + + Returns the head of linked list of ginsn(s) added, if success; Returns NULL + if failure. + + The input ginsn_gen_mode GMODE determines the set of minimal necessary + ginsns necessary for correctness of any passes applicable for that mode. + For supporting the GINSN_GEN_SCFI generation mode, following is the list of + machine instructions that must be translated into the corresponding ginsns + to ensure correctness of SCFI: + - All instructions affecting the two registers that could potentially + be used as the base register for CFA tracking. For SCFI, the base + register for CFA tracking is limited to REG_SP and REG_FP only. + - All change of flow instructions: conditional and unconditional + branches, call and return from functions. + - All instructions that can potentially be a register save / restore + operations. + - All instructions that may update the stack pointer: pre-indexed and + post-indexed stack operations with writeback. + + The function currently supports GINSN_GEN_SCFI ginsn generation mode only. + To support other generation modes will require work on this target-specific + process of creation of ginsns: + - Some of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as + possible starting points. + - Also note that ginsn representation may need enhancements. Specifically, + note some TBD_GINSN_INFO_LOSS and TBD_GINSN_REPRESENTATION_LIMIT markers. + */ + +static ginsnS * +aarch64_ginsn_new (const symbolS *insn_end_sym, enum ginsn_gen_mode gmode) +{ + int err = 0; + ginsnS *ginsn = NULL; + unsigned int dw2_regnum; + aarch64_opnd_info *dest = NULL; + bool sp_allowed_p = false; + aarch64_inst *base = &inst.base; + const aarch64_opcode *opcode = base->opcode; + + /* Currently supports generation of selected ginsns, sufficient for + the use-case of SCFI only. To remove this condition will require + work on this target-specific process of creation of ginsns. Some + of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as + examples. */ + if (gmode != GINSN_GEN_SCFI) + return ginsn; + + switch (opcode->iclass) + { + case addsub_ext: + /* TBD_GINSN_GEN_NOT_SCFI: other insns are not of interest for SCFI. */ + if (aarch64_opcode_subclass_p (opcode, F_ARITH_ADD) + || aarch64_opcode_subclass_p (opcode, F_ARITH_SUB)) + ginsn = aarch64_ginsn_addsub_reg (insn_end_sym); + break; + + case addsub_imm: + if (aarch64_opcode_subclass_p (opcode, F_ARITH_MOV)) + ginsn = aarch64_ginsn_mov (insn_end_sym); + else if (aarch64_opcode_subclass_p (opcode, F_ARITH_ADD) + || aarch64_opcode_subclass_p (opcode, F_ARITH_SUB)) + ginsn = aarch64_ginsn_addsub_imm (insn_end_sym); + break; + + case movewide: + ginsn = aarch64_ginsn_mov (insn_end_sym); + break; + + case ldst_imm9: + case ldst_pos: + ginsn = aarch64_ginsn_ldstr (insn_end_sym); + break; + + case ldstpair_indexed: + case ldstpair_off: + ginsn = aarch64_ginsn_ldstp (insn_end_sym); + break; + + case branch_imm: + case branch_reg: + ginsn = aarch64_ginsn_branch_uncond (insn_end_sym); + break; + + case compbranch: + /* Although cbz/cbnz has an additional operand and are functionally + distinct from conditional branches, it is fine to use the same ginsn + type for both from the perspective of SCFI. */ + case testbranch: + case condbranch: + ginsn = aarch64_ginsn_branch_cond (insn_end_sym); + break; + + default: + /* TBD_GINSN_GEN_NOT_SCFI: Skip all other opcodes uninteresting for + GINSN_GEN_SCFI mode. */ + break; + } + + if (!ginsn && !aarch64_ginsn_safe_to_skip_p ()) + { + /* For all unhandled insns, check that they no not impact SCFI + correctness. */ + err = aarch64_ginsn_unhandled (); + switch (err) + { + case AARCH64_GINSN_UNHANDLED_NONE: + break; + case AARCH64_GINSN_UNHANDLED_DEST_REG: + /* Not all writes to REG_FP are harmful in context of SCFI. Simply + generate a GINSN_TYPE_OTHER with destination set to the + appropriate register. The SCFI machinery will bail out if this + ginsn affects SCFI correctness. */ + dest = &base->operands[0]; + sp_allowed_p = (dest->type == AARCH64_OPND_Rd_SP) ? true : false; + dw2_regnum = ginsn_dw2_regnum (dest, sp_allowed_p); + ginsn = ginsn_new_other (insn_end_sym, true, + GINSN_SRC_IMM, 0, + GINSN_SRC_IMM, 0, + GINSN_DST_REG, dw2_regnum); + ginsn_set_where (ginsn); + break; + case AARCH64_GINSN_UNHANDLED_CFG: + case AARCH64_GINSN_UNHANDLED_STACKOP: + as_bad (_("SCFI: unhandled op %#x may cause incorrect CFI"), + opcode->opcode); + break; + case AARCH64_GINSN_UNHANDLED_UNEXPECTED: + as_bad (_("SCFI: unexpected op %#x may cause incorrect CFI"), + opcode->opcode); + break; + default: + abort (); + break; + } + } + + return ginsn; +} + +#endif /* OBJ_ELF. */ + diff --git a/gas/config/tc-aarch64.c b/gas/config/tc-aarch64.c index 42c03bd..220b37a 100644 --- a/gas/config/tc-aarch64.c +++ b/gas/config/tc-aarch64.c @@ -33,6 +33,7 @@ #include "dw2gencfi.h" #include "sframe.h" #include "gen-sframe.h" +#include "scfi.h" #endif #include "dw2gencfi.h" @@ -8591,6 +8592,10 @@ dump_opcode_operands (const aarch64_opcode *opcode) } #endif /* DEBUG_AARCH64 */ +#ifdef OBJ_ELF +# include "tc-aarch64-ginsn.c" +#endif + /* This is the guts of the machine-dependent assembler. STR points to a machine dependent instruction. This function is supposed to emit the frags/bytes it assembles to. */ @@ -8728,6 +8733,16 @@ md_assemble (char *str) output_inst (copy); } +#ifdef OBJ_ELF + if (flag_synth_cfi) + { + ginsnS *ginsn; + ginsn = aarch64_ginsn_new (symbol_temp_new_now (), + frch_ginsn_gen_mode ()); + frch_ginsn_data_append (ginsn); + } +#endif + /* Issue non-fatal messages if any. */ output_operand_error_report (str, true); return; diff --git a/gas/config/tc-aarch64.h b/gas/config/tc-aarch64.h index 1b8bada..4906a6e 100644 --- a/gas/config/tc-aarch64.h +++ b/gas/config/tc-aarch64.h @@ -263,6 +263,27 @@ extern void aarch64_after_parse_args (void); #ifdef OBJ_ELF +#define TARGET_USE_GINSN 1 +/* Allow GAS to synthesize DWARF CFI for hand-written asm. + PS: TARGET_USE_CFIPOP is a pre-condition. */ +#define TARGET_USE_SCFI 1 +/* Identify the maximum DWARF register number of all the registers being + tracked for SCFI. This is the last DWARF register number of the set + of SP, FP, and all callee-saved registers. For Aarch64, this means 79 + because FP/Advanced SIMD v8-v15 are also callee-saved registers. */ +# define SCFI_MAX_REG_ID 79 +/* Identify the DWARF register number of the frame-pointer register. */ +# define REG_FP 29 +/* Identify the DWARF register number of the link register. */ +# define REG_LR 30 +/* Identify the DWARF register number of the stack-pointer register. */ +# define REG_SP 31 + +#define SCFI_INIT_CFA_OFFSET 0 + +#define SCFI_CALLEE_SAVED_REG_P(dw2reg) aarch64_scfi_callee_saved_p (dw2reg) +extern bool aarch64_scfi_callee_saved_p (uint32_t dw2reg_num); + /* Whether SFrame stack trace info is supported. */ extern bool aarch64_support_sframe_p (void); #define support_sframe_p aarch64_support_sframe_p |