[PATCH,V6 8/9] gas: aarch64: add experimental support for SCFI

Richard Sandiford richard.sandiford@arm.com
Thu Jul 18 10:42:22 GMT 2024


Indu Bhagat <indu.bhagat@oracle.com> writes:
> [...]
> +/* 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);

I think we should check ginsn_dw2_regnum_invalid_p on all three of these,
since:

	add	xzr, xzr, xzr

is a thing.

> [...]
> +/* 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 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;
> +
> +  /* Save / restore of WZR is not of interest for SCFI.  */
> +  opnd_reg = ginsn_dw2_regnum (opnd1);
> +  if (ginsn_dw2_regnum_invalid_p (opnd_reg))
> +    return ginsn_ind;

Sorry for not noticing earlier, but I think this should protect
the individual ginsn_new_*s below.  E.g.:

	stp	xzr, x19, [sp, #16]
	stp	x19, xzr, [sp, #16]

are at least vaguely plausible SCFI sequences.  E.g. the second one
might combine a register save with a variable initialisation.

The combination is less plausible for loads, but still technically
possible.

> [...]
> +/* 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 these GINSN_TYPE_MOV 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;

We should probably check src_reg too, for:

	mov	x1, xzr

(This could be modelled as a mov-immediate though, as a later follow-on.)

(I think it's ok to check ginsn_dw2_regnum_invalid_p after every call to
ginsn_dw2_regnum if you prefer, even if the operand doesn't allow WZR/XZR.)

> +
> +  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;
> +
> +  /* FIXME Explicitly bar these GINSN_TYPE_MOV 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;

Very minor, but IMO it'd be more easy to follow if this was partnered
with the ginsn_dw2_regnum call.

LGTM otherwise.

Thanks,
Richard


More information about the Binutils mailing list