diff --git a/gas/config/tc-aarch64-ginsn.c b/gas/config/tc-aarch64-ginsn.c new file mode 100644 index 00000000000..f6f6b2e4133 --- /dev/null +++ b/gas/config/tc-aarch64-ginsn.c @@ -0,0 +1,874 @@ +/* tc-aarch64-ginsn.c -- Ginsn generation for the AArch64 ISA + + Copyright (C) 2024 Free Software Foundation, Inc. + + 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 . */ + +/* 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. */ + +static unsigned int +ginsn_dw2_regnum (aarch64_opnd_info *opnd) +{ + 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: + dw2reg_num = opnd->addr.base_regno; + break; + 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 (aarch64_zero_register_p (opnd)) + 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; + 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); + + 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); + + 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); + src1_reg = ginsn_dw2_regnum (src1); + src2_reg = ginsn_dw2_regnum (src2); + + /* Ignoring shift amount, if any, does not affect SCFI correctness. + Note TBD_GINSN_INFO_LOSS. */ + 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; + bool other_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 == ldstnapair_offs + || 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 = aarch64_opcode_subclass_p (opcode, F_LDST_STORE); + other_p = aarch64_opcode_subclass_p (opcode, F_SUBCLASS_OTHER); + + addr_reg = ginsn_dw2_regnum (addr); + gas_assert (!addr->addr.offset.is_reg); + mem_offset = addr->addr.offset.imm; + + offset = mem_offset; + /* 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); + + /* 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 = 0; + } + + /* Save / restore of WZR is not of interest for SCFI. Also, insns like ldpsw + (marked with subclass F_SUBCLASS_OTHER) do not need to generate any load + or store for SCFI purposes. Lastly, for CFI puposes, the width of save / + restore operation has to be 8 bytes or more. However, the address + processing component may have updated the stack pointer. At least, emit + that ginsn and return. Also note, TBD_GINSN_GEN_NOT_SCFI. */ + if (other_p || aarch64_zero_register_p (opnd1) + || aarch64_get_qualifier_esize (opnd1->qualifier) < 8) + return ginsn_ind; + + /* 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; + else if (opnd1->qualifier == AARCH64_OPND_QLF_S_Q) + { + width = 16; + if (target_big_endian) + offset += 8; + } + + opnd_reg = ginsn_dw2_regnum (opnd1); + 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); + 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); + 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); + 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; + bool other_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 = aarch64_opcode_subclass_p (opcode, F_LDST_STORE); + other_p = aarch64_opcode_subclass_p (opcode, F_SUBCLASS_OTHER); + + addr_reg = ginsn_dw2_regnum (addr); + + 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; + } + + offset = mem_offset; + /* 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); + + /* 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 = 0; + } + + /* Save / restore of WZR is not of interest for SCFI. Also insns like + stg, prfm, ldrsw etc. (marked with subclass F_SUBCLASS_OTHER) do not need + to generate any load / store ginsns for SCFI purposes. Lastly, for CFI + puposes, the width of save / restore operation has to be 8 bytes or more. + That said, the address processing component may have updated the stack + pointer. At least, emit that ginsn and return. + Also note, TBD_GINSN_GEN_NOT_SCFI. */ + if (other_p || aarch64_zero_register_p (opnd1) + || aarch64_get_qualifier_esize (opnd1->qualifier) < 8) + return ginsn_ind; + + if (target_big_endian && opnd1->qualifier == AARCH64_OPND_QLF_S_Q) + offset += 8; + + /* STR , [, (|){, {}}]. + LDR , [], #. */ + opnd_reg = ginsn_dw2_regnum (opnd1); + + 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 + && (inst.reloc.type == BFD_RELOC_AARCH64_CALL26 + || inst.reloc.type == BFD_RELOC_AARCH64_JUMP26)) + { + if (inst.reloc.exp.X_add_number) + { + /* A non-zero addend in b/bl target makes control-flow tracking + difficult. Skip SCFI for now. */ + as_bad (_("SCFI: %#x op with non-zero addend to sym not supported"), + opcode->opcode); + return ginsn; + } + /* b or bl. */ + src_symbol = inst.reloc.exp.X_add_symbol; + src_type = GINSN_SRC_SYMBOL; + } + else if (opcode->iclass == branch_reg + && aarch64_num_of_operands (opcode) >= 1) + { + /* 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]); + } + else + /* Skip insns like branch imm. */ + return ginsn; + + 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 = NULL; + const symbolS *src_symbol; + enum ginsn_src_type src_type; + + aarch64_inst *base = &inst.base; + const aarch64_opcode *opcode = base->opcode; + + if (inst.reloc.type == BFD_RELOC_AARCH64_BRANCH19 + || inst.reloc.type == BFD_RELOC_AARCH64_TSTBR14) + { + if (inst.reloc.exp.X_add_number) + { + /* A non-zero addend in target makes control-flow tracking + difficult. Skip SCFI for now. */ + as_bad (_("SCFI: %#x op with non-zero addend to sym not supported"), + opcode->opcode); + return ginsn; + } + + 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 with reg opnd. */ + +static ginsnS * +aarch64_ginsn_mov_reg (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); + src_reg = ginsn_dw2_regnum (src); + 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; +} + +/* Generate ginsn for mov instructions with imm opnd. */ + +static ginsnS * +aarch64_ginsn_mov_imm (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); + + /* 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 + /* Skip now and handle via aarch64_ginsn_unhandled () code path. */ + return ginsn; + + 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 opnd_reg; + int num_opnds = 0; + bool dp_tag_only_p = false; + + aarch64_inst *base = &inst.base; + const aarch64_opcode *opcode = base->opcode; + + /* ATM, whitelisting operations with no operands does not seem to be + necessary. In fact, whitelisting insns like ERET will be dangerous for + SCFI. So, return false now and bar any such insns from being whitelisted + altogether. */ + num_opnds = aarch64_num_of_operands (opcode); + if (!num_opnds) + return false; + + 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); + 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. */ + dp_tag_only_p = aarch64_opcode_subclass_p (opcode, F_DP_TAG_ONLY); + if (dp_tag_only_p) + 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; + + /* All change of flow instructions (COFI) are important for SCFI. + N.B. New iclasses for COFI when defined must be added here too. */ + 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. Except ldstnapair_offs from the afore-mentioned + list, these iclasses do not seem to be amenable to being used for + save/restore ops either. */ + else if (opcode->iclass == ldstpair_off + || opcode->iclass == ldstnapair_offs + || opcode->iclass == ldstpair_indexed + || opcode->iclass == ldst_imm9 + || opcode->iclass == ldst_imm10 + || opcode->iclass == ldst_pos) + { + addr = &base->operands[num_opnds - 1]; + addr_reg = ginsn_dw2_regnum (addr); + if (addr_reg == REG_SP || addr_reg == REG_FP) + { + /* For all skipped memory operations, check if an update to REG_SP or + REG_FP is involved. */ + if ((addr->addr.postind || addr->addr.preind) && addr->addr.writeback) + err = AARCH64_GINSN_UNHANDLED_STACKOP; + /* Also check if a save / restore of a callee-saved register has been + missed. */ + else if (!aarch64_opcode_subclass_p (opcode, F_SUBCLASS_OTHER)) + { + opnd = &base->operands[0]; + opnd_reg = ginsn_dw2_regnum (opnd); + if (aarch64_scfi_callee_saved_p (opnd_reg) + && aarch64_get_qualifier_esize (opnd->qualifier) >= 8) + err = AARCH64_GINSN_UNHANDLED_STACKOP; + } + } + } + /* 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. */ + else if (opcode->iclass == sve_misc) + { + opnd = &base->operands[0]; + addr = &base->operands[num_opnds - 1]; + addr_reg = ginsn_dw2_regnum (addr); + opnd_reg = ginsn_dw2_regnum (opnd); + /* 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)) + { + dw2_regnum = ginsn_dw2_regnum (dest); + + 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; + 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_reg (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); + /* Note how addg, subg involving tags have F_SUBCLASS_OTHER flag. These + insns will see a GINSN_TYPE_OTHER created for them if the destination + register is of interest via the aarch64_ginsn_unhandled () + codepath. */ + break; + + case movewide: + ginsn = aarch64_ginsn_mov_imm (insn_end_sym); + break; + + case ldst_imm9: + case ldst_pos: + ginsn = aarch64_ginsn_ldstr (insn_end_sym); + break; + + case ldstpair_indexed: + case ldstpair_off: + case ldstnapair_offs: + 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]; + dw2_regnum = ginsn_dw2_regnum (dest); + 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 ceb0f34c885..e94a0cff406 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" @@ -8614,6 +8615,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. */ @@ -8751,6 +8756,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 0063e85a7f1..15e22436bf7 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