aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gas/config/tc-aarch64-ginsn.c963
-rw-r--r--gas/config/tc-aarch64.c15
-rw-r--r--gas/config/tc-aarch64.h21
3 files changed, 999 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..0dce2a7
--- /dev/null
+++ b/gas/config/tc-aarch64-ginsn.c
@@ -0,0 +1,963 @@
+/* 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 <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
+
+/* Invalid DWARF register number. Used when WZR / XZR is seen. */
+#define GINSN_DW2_REGNUM_INVALID (~0U)
+
+/* 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. If OPND is an address,
+ the returned register is the base register. If OPND spans multiple
+ registers, the returned register is the first of those registers. */
+
+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_ADDRESS:
+ dw2reg_num = opnd->addr.base_regno;
+ break;
+ case AARCH64_OPND_CLASS_INT_REG:
+ case AARCH64_OPND_CLASS_MODIFIED_REG:
+ /* Use an invalid DWARF 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_INVALID;
+ 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 operand class in ginsn_dw2_regnum");
+ break;
+ }
+
+ return dw2reg_num;
+}
+
+static bool
+ginsn_dw2_regnum_invalid_p (unsigned int opnd_reg)
+{
+ return (opnd_reg == GINSN_DW2_REGNUM_INVALID);
+}
+
+/* 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];
+
+ 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);
+
+ dst_reg = ginsn_dw2_regnum (dst);
+ opnd_reg = ginsn_dw2_regnum (opnd);
+
+ if (ginsn_dw2_regnum_invalid_p (dst_reg)
+ || ginsn_dw2_regnum_invalid_p (opnd_reg))
+ return ginsn;
+
+ 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);
+
+ if (ginsn_dw2_regnum_invalid_p (dst_reg)
+ || ginsn_dw2_regnum_invalid_p (src1_reg)
+ || ginsn_dw2_regnum_invalid_p (src2_reg))
+ return ginsn;
+
+ /* ATM, shift amount, if any, cannot be represented in the GINSN_TYPE_ADD or
+ GINSN_TYPE_SUB. As the extra information does not impact SCFI
+ correctness, skip generating ginsn for these cases. Note
+ TBD_GINSN_INFO_LOSS. */
+ if (src2->shifter.kind != AARCH64_MOD_NONE
+ && (src2->shifter.kind != AARCH64_MOD_LSL || src2->shifter.amount != 0))
+ return ginsn;
+
+ 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;
+ ginsnS *ginsn_mem = NULL;
+ unsigned int opnd1_reg, opnd2_reg, addr_reg;
+ offsetT offset, mem_offset;
+ unsigned int width = 8;
+ bool load_p = false;
+ 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];
+
+ load_p = aarch64_opcode_subclass_p (opcode, F_LDST_LOAD);
+ store_p = aarch64_opcode_subclass_p (opcode, F_LDST_STORE);
+ other_p = aarch64_opcode_subclass_p (opcode, F_SUBCLASS_OTHER);
+ gas_assert (load_p || store_p || other_p);
+
+ 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;
+ }
+
+ /* Insns like ldpsw (marked with subclass F_SUBCLASS_OTHER) do not need to
+ generate any load or store for SCFI purposes. Next, enforce that for CFI
+ purposes, 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_get_qualifier_esize (opnd1->qualifier) < 8)
+ return ginsn_ind;
+
+ opnd1_reg = ginsn_dw2_regnum (opnd1);
+ opnd2_reg = ginsn_dw2_regnum (opnd2);
+ /* Save / restore of WZR is not of interest for SCFI. Exit now if both
+ registers are not of interest. */
+ if (ginsn_dw2_regnum_invalid_p (opnd1_reg)
+ && ginsn_dw2_regnum_invalid_p (opnd2_reg))
+ return ginsn_ind;
+
+ if (opnd1->qualifier == AARCH64_OPND_QLF_S_Q)
+ {
+ width = 16;
+ if (target_big_endian)
+ offset += 8;
+ }
+
+ /* Load store pair where only one of the opnd registers is a zero register
+ is possible. E.g., stp xzr, x19, [sp, #16]. */
+ if (!ginsn_dw2_regnum_invalid_p (opnd1_reg))
+ {
+ if (store_p)
+ {
+ ginsn_mem1 = ginsn_new_store (insn_end_sym, false,
+ GINSN_SRC_REG, opnd1_reg,
+ GINSN_DST_INDIRECT, addr_reg, offset);
+ ginsn_set_where (ginsn_mem1);
+ }
+ else
+ {
+ ginsn_mem1 = ginsn_new_load (insn_end_sym, false,
+ GINSN_SRC_INDIRECT, addr_reg, offset,
+ GINSN_DST_REG, opnd1_reg);
+ ginsn_set_where (ginsn_mem1);
+ }
+ /* Keep track of the last memory ginsn created so far. */
+ ginsn_mem = ginsn_mem1;
+ }
+ if (!ginsn_dw2_regnum_invalid_p (opnd2_reg))
+ {
+ if (store_p)
+ {
+ ginsn_mem2 = ginsn_new_store (insn_end_sym, false,
+ GINSN_SRC_REG, opnd2_reg,
+ GINSN_DST_INDIRECT, addr_reg,
+ offset + width);
+ ginsn_set_where (ginsn_mem2);
+ }
+ else
+ {
+ ginsn_mem2 = ginsn_new_load (insn_end_sym, false,
+ GINSN_SRC_INDIRECT, addr_reg, offset + width,
+ GINSN_DST_REG, opnd2_reg);
+ ginsn_set_where (ginsn_mem2);
+ }
+ /* Keep track of the last memory ginsn created so far. */
+ ginsn_mem = ginsn_mem2;
+ }
+
+ if (!ginsn_dw2_regnum_invalid_p (opnd1_reg)
+ && !ginsn_dw2_regnum_invalid_p (opnd2_reg))
+ goto link_two_ginsn_mem;
+ else
+ goto link_one_ginsn_mem;
+
+link_one_ginsn_mem:
+ /* Link the list of ginsns created. */
+ if (addr->addr.preind && addr->addr.writeback)
+ gas_assert (!ginsn_link_next (ginsn_ind, ginsn_mem));
+
+ 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;
+
+link_two_ginsn_mem:
+ /* 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_mem1 && ginsn_mem2 && ginsn_mem1 != ginsn_mem2);
+ 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 load_p = false;
+ 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];
+
+ load_p = aarch64_opcode_subclass_p (opcode, F_LDST_LOAD);
+ store_p = aarch64_opcode_subclass_p (opcode, F_LDST_STORE);
+ other_p = aarch64_opcode_subclass_p (opcode, F_SUBCLASS_OTHER);
+ gas_assert (load_p || store_p || other_p);
+
+ 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;
+ }
+
+ /* Insns like stg, prfm, ldrsw etc. (marked with subclass F_SUBCLASS_OTHER)
+ do not need to generate any load / store ginsns for SCFI purposes. Next,
+ enforce that for CFI purposes, 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_get_qualifier_esize (opnd1->qualifier) < 8)
+ return ginsn_ind;
+
+ opnd_reg = ginsn_dw2_regnum (opnd1);
+ /* Save / restore of WZR is not of interest for SCFI. */
+ if (ginsn_dw2_regnum_invalid_p (opnd_reg))
+ return ginsn_ind;
+
+ 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
+ && (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;
+
+ /* FIXME Explicitly bar GINSN_TYPE_MOV with a GINSN_DW2_REGNUM_INVALID in src
+ or dest at this time. This can be removed later when SCFI machinery is
+ more robust to deal with GINSN_DW2_REGNUM_INVALID. */
+ if (ginsn_dw2_regnum_invalid_p (dst_reg)
+ || ginsn_dw2_regnum_invalid_p (src_reg))
+ 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;
+}
+
+/* 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];
+
+ /* 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;
+
+ dst_reg = ginsn_dw2_regnum (dst);
+ /* FIXME Explicitly bar GINSN_TYPE_MOV which write to WZR / XZR at this time.
+ This can be removed later when SCFI machinery is more robust to deal with
+ GINSN_DW2_REGNUM_INVALID. */
+ if (ginsn_dw2_regnum_invalid_p (dst_reg))
+ 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;
+}
+
+enum aarch64_ginsn_unhandled_code
+{
+ AARCH64_GINSN_UNHANDLED_NONE,
+ AARCH64_GINSN_UNHANDLED_DEST_REG,
+ AARCH64_GINSN_UNHANDLED_CFG,
+ AARCH64_GINSN_UNHANDLED_STACKOP,
+ AARCH64_GINSN_UNHANDLED_UNEXPECTED,
+};
+
+/* Check the input insn for its impact on the correctness of the synthesized
+ CFI. Returns an error code to the caller. */
+
+static enum aarch64_ginsn_unhandled_code
+aarch64_ginsn_unhandled (void)
+{
+ enum aarch64_ginsn_unhandled_code 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;
+ 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))
+ {
+ for (int i = 0; i < num_opnds - 1; i++)
+ {
+ opnd = &base->operands[i];
+ 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;
+ break;
+ }
+ }
+ }
+ }
+ }
+ /* 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 (aarch64_get_operand_class (addr->type) == AARCH64_OPND_CLASS_ADDRESS
+ && (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)
+{
+ enum aarch64_ginsn_unhandled_code 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);
+ /* Sanity check. */
+ gas_assert (!ginsn_dw2_regnum_invalid_p (dw2_regnum));
+ 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 ceb0f34..e94a0cf 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 0063e85..15e2243 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