aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gas/config/tc-aarch64-ginsn.c743
-rw-r--r--gas/config/tc-aarch64.c15
-rw-r--r--gas/config/tc-aarch64.h20
3 files changed, 778 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..923d210
--- /dev/null
+++ b/gas/config/tc-aarch64-ginsn.c
@@ -0,0 +1,743 @@
+/* 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. SCFI always tracks SP, FP and LR additionally. */
+
+bool
+aarch64_scfi_callee_saved_p (uint32_t dw2reg_num)
+{
+ if (dw2reg_num == REG_SP /* x31. */
+ || dw2reg_num == REG_FP /* x29. */
+ || dw2reg_num == REG_LR /* x30. */
+ || (dw2reg_num >= 19 && dw2reg_num <= 28) /* x19 - x28. */)
+ 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 uint32_t
+ginsn_dw2_regnum (aarch64_opnd_info *opnd, bool sp_allowed_p)
+{
+ /* 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)
+ return GINSN_DW2_REGNUM_R1_DUMMY;
+ /* For registers of our interest (callee-saved regs, SP, FP, LR),
+ DWARF register number is the same as AArch64 register number. */
+ return opnd->reg.regno;
+}
+
+/* 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;
+ int32_t src_imm = 0;
+ uint32_t 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 = opcode->flags & F_ARITH_ADD;
+ sub_p = opcode->flags & 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;
+ uint32_t 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 = opcode->flags & F_ARITH_ADD;
+ sub_p = opcode->flags & 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;
+ uint32_t opnd_reg, addr_reg;
+ int32_t 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_LDST_STORE;
+
+ addr_reg = ginsn_dw2_regnum (addr, true);
+ gas_assert (!addr->addr.offset.is_reg);
+ mem_offset = addr->addr.offset.imm;
+ if (opnd1->qualifier == AARCH64_OPND_QLF_W)
+ 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 floating point register or 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 (aarch64_get_operand_class (opnd1->type) != AARCH64_OPND_CLASS_INT_REG
+ || 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;
+
+ 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;
+ uint32_t opnd_reg, addr_reg;
+ int32_t offset = 0;
+ int32_t mem_offset = 0;
+ 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_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 floating point register or 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 (aarch64_get_operand_class (opnd1->type) != AARCH64_OPND_CLASS_INT_REG
+ || 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 (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;
+ uint32_t src_value = 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_value = ginsn_dw2_regnum (&base->operands[0], false);
+ }
+
+ if (opcode->flags & F_BRANCH_CALL)
+ {
+ gas_assert (src_type != GINSN_SRC_UNKNOWN);
+ ginsn = ginsn_new_call (insn_end_sym, true,
+ src_type, src_value, src_symbol);
+ }
+ else if (opcode->flags & 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_value, 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;
+ uint32_t 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);
+
+ 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;
+ aarch64_opnd_info *addr = NULL;
+ unsigned int dw2_regnum;
+ uint32_t opnd_reg;
+ int num_opnds = 0;
+
+ aarch64_inst *base = &inst.base;
+ const aarch64_opcode *opcode = base->opcode;
+
+ 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 = &base->operands[0];
+ 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 ldstpair_off:
+ /* Load/store FP register pair (offset) ... */
+ case ldst_pos:
+ /* ... and load/store FP register (unsigned immediate) must be
+ whitelisted. This is because the addr register may be SP-based, but
+ there cannot be writeback with pre- or post- indexing. */
+ opnd = &base->operands[0];
+ if (aarch64_get_operand_class (opnd->type) != AARCH64_OPND_CLASS_INT_REG)
+ {
+ num_opnds = aarch64_num_of_operands (opcode);
+ addr = &base->operands[num_opnds - 1];
+ /* There cannot be a writeback here. Confirm? FIXME.
+ if not true, need to generate addr gen opcodes at the least. */
+ gas_assert (!addr->addr.writeback);
+ skip_p = true;
+ }
+ 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. */
+ opnd = &base->operands[0];
+ 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;
+ uint32_t addr_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. Some classes can be skipped altogether though, as they 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. */
+ else if (opcode->iclass == ldstpair_off
+ || 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, true);
+ /* 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)
+ err = AARCH64_GINSN_UNHANDLED_STACKOP;
+ }
+ /* Finally, also 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 (opcode->flags & F_ARITH_ADD || opcode->flags & F_ARITH_SUB)
+ ginsn = aarch64_ginsn_addsub_reg (insn_end_sym);
+ break;
+
+ case addsub_imm:
+ if (opcode->flags & F_ARITH_MOV)
+ ginsn = aarch64_ginsn_mov (insn_end_sym);
+ else if (opcode->flags & F_ARITH_ADD || opcode->flags & 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 fec17c4..94b6d74 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"
@@ -8452,6 +8453,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. */
@@ -8589,6 +8594,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..97147bd 100644
--- a/gas/config/tc-aarch64.h
+++ b/gas/config/tc-aarch64.h
@@ -263,6 +263,26 @@ 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 31. */
+# define SCFI_MAX_REG_ID 31
+/* 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