aboutsummaryrefslogtreecommitdiff
path: root/gas/config/tc-i386.c
diff options
context:
space:
mode:
Diffstat (limited to 'gas/config/tc-i386.c')
-rw-r--r--gas/config/tc-i386.c1113
1 files changed, 1113 insertions, 0 deletions
diff --git a/gas/config/tc-i386.c b/gas/config/tc-i386.c
index b25fa37..455e5a3 100644
--- a/gas/config/tc-i386.c
+++ b/gas/config/tc-i386.c
@@ -30,6 +30,7 @@
#include "subsegs.h"
#include "dwarf2dbg.h"
#include "dw2gencfi.h"
+#include "scfi.h"
#include "gen-sframe.h"
#include "sframe.h"
#include "elf/x86-64.h"
@@ -5327,6 +5328,1095 @@ static INLINE bool may_need_pass2 (const insn_template *t)
&& t->base_opcode == 0x63);
}
+#if defined (OBJ_MAYBE_ELF) || defined (OBJ_ELF)
+
+/* DWARF register number for EFLAGS. Used for pushf/popf insns. */
+#define GINSN_DW2_REGNUM_EFLAGS 49
+/* DWARF register number for RSI. Used as dummy value when RegIP/RegIZ. */
+#define GINSN_DW2_REGNUM_RSI_DUMMY 4
+
+/* Identify the callee-saved registers in System V AMD64 ABI. */
+
+bool
+x86_scfi_callee_saved_p (unsigned int dw2reg_num)
+{
+ if (dw2reg_num == 3 /* rbx. */
+ || dw2reg_num == REG_FP /* rbp. */
+ || dw2reg_num == REG_SP /* rsp. */
+ || (dw2reg_num >= 12 && dw2reg_num <= 15) /* r12 - r15. */)
+ return true;
+
+ return false;
+}
+
+/* Check whether an instruction prefix which affects operation size
+ accompanies. For insns in the legacy space, setting REX.W takes precedence
+ over the operand-size prefix (66H) when both are used.
+
+ The current users of this API are in the handlers for PUSH, POP or other
+ instructions which affect the stack pointer implicitly: the operation size
+ (16, 32, or 64 bits) determines the amount by which the stack pointer is
+ incremented / decremented (2, 4 or 8). */
+
+static bool
+ginsn_opsize_prefix_p (void)
+{
+ return (!(i.prefix[REX_PREFIX] & REX_W) && i.prefix[DATA_PREFIX]);
+}
+
+/* Get the DWARF register number for the given register entry.
+ For specific byte/word/dword register accesses like al, cl, ah, ch, r8d,
+ r20w etc., we need to identify the DWARF register number for the
+ corresponding 8-byte GPR.
+
+ This function is a hack - it relies on relative ordering of reg entries in
+ the i386_regtab. FIXME - it will be good to allow a more direct way to get
+ this information. */
+
+static unsigned int
+ginsn_dw2_regnum (const reg_entry *ireg)
+{
+ /* PS: Note the data type here as int32_t, because of Dw2Inval (-1). */
+ int32_t dwarf_reg = Dw2Inval;
+ const reg_entry *temp = ireg;
+ unsigned int idx = 0;
+
+ /* ginsn creation is available for AMD64 abi only ATM. Other flag_code
+ are not expected. */
+ gas_assert (ireg && flag_code == CODE_64BIT);
+
+ /* Watch out for RegIP, RegIZ. These are expected to appear only with
+ base/index addressing modes. Although creating inaccurate data
+ dependencies, using a dummy value (lets say volatile register rsi) will
+ not hurt SCFI. TBD_GINSN_GEN_NOT_SCFI. */
+ if (ireg->reg_num == RegIP || ireg->reg_num == RegIZ)
+ return GINSN_DW2_REGNUM_RSI_DUMMY;
+
+ dwarf_reg = ireg->dw2_regnum[flag_code >> 1];
+
+ if (dwarf_reg == Dw2Inval)
+ {
+ if (ireg <= &i386_regtab[3])
+ /* For al, cl, dl, bl, bump over to axl, cxl, dxl, bxl respectively by
+ adding 8. */
+ temp = ireg + 8;
+ else if (ireg <= &i386_regtab[7])
+ /* For ah, ch, dh, bh, bump over to axl, cxl, dxl, bxl respectively by
+ adding 4. */
+ temp = ireg + 4;
+ else
+ {
+ /* The code relies on the relative ordering of the reg entries in
+ i386_regtab. There are 32 register entries between axl-r31b,
+ ax-r31w etc. The assertions here ensures the code does not
+ recurse indefinitely. */
+ gas_assert ((temp - &i386_regtab[0]) >= 0);
+ idx = temp - &i386_regtab[0];
+ gas_assert (idx + 32 < i386_regtab_size - 1);
+
+ temp = temp + 32;
+ }
+
+ dwarf_reg = ginsn_dw2_regnum (temp);
+ }
+
+ /* Sanity check - failure may indicate state corruption, bad ginsn or
+ perhaps the i386-reg table and the current function got out of sync. */
+ gas_assert (dwarf_reg >= 0);
+
+ return (unsigned int) dwarf_reg;
+}
+
+static ginsnS *
+x86_ginsn_addsub_reg_mem (const symbolS *insn_end_sym)
+{
+ unsigned int dw2_regnum;
+ unsigned int src1_dw2_regnum;
+ ginsnS *ginsn = NULL;
+ 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);
+ uint16_t opcode = i.tm.base_opcode;
+
+ gas_assert (i.tm.opcode_space == SPACE_BASE
+ && (opcode == 0x1 || opcode == 0x29));
+ ginsn_func = (opcode == 0x1) ? ginsn_new_add : ginsn_new_sub;
+
+ /* op %reg, symbol or even other cases where destination involves indirect
+ access are unnecessary for SCFI correctness. TBD_GINSN_GEN_NOT_SCFI. */
+ if (i.mem_operands)
+ return ginsn;
+
+ /* op reg, reg/mem. */
+ src1_dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+ /* Of interest only when second opnd is not memory. */
+ if (i.reg_operands == 2)
+ {
+ dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
+ ginsn = ginsn_func (insn_end_sym, true,
+ GINSN_SRC_REG, src1_dw2_regnum, 0,
+ GINSN_SRC_REG, dw2_regnum, 0,
+ GINSN_DST_REG, dw2_regnum, 0);
+ ginsn_set_where (ginsn);
+ }
+
+ return ginsn;
+}
+
+static ginsnS *
+x86_ginsn_addsub_mem_reg (const symbolS *insn_end_sym)
+{
+ unsigned int dw2_regnum;
+ unsigned int src1_dw2_regnum;
+ const reg_entry *mem_reg;
+ int32_t gdisp = 0;
+ ginsnS *ginsn = NULL;
+ 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);
+ uint16_t opcode = i.tm.base_opcode;
+
+ gas_assert (i.tm.opcode_space == SPACE_BASE
+ && (opcode == 0x3 || opcode == 0x2b));
+ ginsn_func = (opcode == 0x3) ? ginsn_new_add : ginsn_new_sub;
+
+ /* op symbol, %reg. */
+ if (i.mem_operands && !i.base_reg && !i.index_reg)
+ return ginsn;
+
+ /* op reg/mem, %reg. */
+ dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
+
+ if (i.reg_operands == 2)
+ {
+ src1_dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+ ginsn = ginsn_func (insn_end_sym, true,
+ GINSN_SRC_REG, src1_dw2_regnum, 0,
+ GINSN_SRC_REG, dw2_regnum, 0,
+ GINSN_DST_REG, dw2_regnum, 0);
+ ginsn_set_where (ginsn);
+ }
+ else if (i.mem_operands)
+ {
+ mem_reg = (i.base_reg) ? i.base_reg : i.index_reg;
+ src1_dw2_regnum = ginsn_dw2_regnum (mem_reg);
+ if (i.disp_operands == 1)
+ gdisp = i.op[0].disps->X_add_number;
+ ginsn = ginsn_func (insn_end_sym, true,
+ GINSN_SRC_INDIRECT, src1_dw2_regnum, gdisp,
+ GINSN_SRC_REG, dw2_regnum, 0,
+ GINSN_DST_REG, dw2_regnum, 0);
+ ginsn_set_where (ginsn);
+ }
+
+ return ginsn;
+}
+
+static ginsnS *
+x86_ginsn_alu_imm (const symbolS *insn_end_sym)
+{
+ offsetT src_imm;
+ unsigned int dw2_regnum;
+ ginsnS *ginsn = NULL;
+ enum ginsn_src_type src_type = GINSN_SRC_REG;
+ enum ginsn_dst_type dst_type = GINSN_DST_REG;
+
+ 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);
+
+ /* FIXME - create ginsn where dest is REG_SP / REG_FP only ? */
+ /* Map for insn.tm.extension_opcode
+ 000 ADD 100 AND
+ 001 OR 101 SUB
+ 010 ADC 110 XOR
+ 011 SBB 111 CMP */
+
+ /* add/sub/and imm, %reg only at this time for SCFI.
+ Although all three ('and', 'or' , 'xor') make the destination reg
+ untraceable, 'and' op is handled but not 'or' / 'xor' because we will look
+ into supporting the DRAP pattern at some point. Other opcodes ('adc',
+ 'sbb' and 'cmp') are not generated here either. The ginsn representation
+ does not have support for the latter three opcodes; GINSN_TYPE_OTHER may
+ be added for these after x86_ginsn_unhandled () invocation if the
+ destination register is REG_SP or REG_FP. */
+ if (i.tm.extension_opcode == 5)
+ ginsn_func = ginsn_new_sub;
+ else if (i.tm.extension_opcode == 4)
+ ginsn_func = ginsn_new_and;
+ else if (i.tm.extension_opcode == 0)
+ ginsn_func = ginsn_new_add;
+ else
+ return ginsn;
+
+ /* TBD_GINSN_REPRESENTATION_LIMIT: There is no representation for when a
+ symbol is used as an operand, like so:
+ addq $simd_cmp_op+8, %rdx
+ Skip generating any ginsn for this. */
+ if (i.imm_operands == 1
+ && i.op[0].imms->X_op != O_constant)
+ return ginsn;
+
+ /* addq $1, symbol
+ addq $1, -16(%rbp)
+ These are not of interest for SCFI. Also, TBD_GINSN_GEN_NOT_SCFI. */
+ if (i.mem_operands == 1)
+ return ginsn;
+
+ gas_assert (i.imm_operands == 1);
+ src_imm = i.op[0].imms->X_add_number;
+ /* The second operand may be a register or indirect access. For SCFI, only
+ the case when the second opnd is a register is interesting. Revisit this
+ if generating ginsns for a different gen mode TBD_GINSN_GEN_NOT_SCFI. */
+ if (i.reg_operands == 1)
+ {
+ dw2_regnum = ginsn_dw2_regnum (i.op[1].regs);
+ /* For ginsn, keep the imm as second src operand. */
+ ginsn = ginsn_func (insn_end_sym, true,
+ src_type, dw2_regnum, 0,
+ GINSN_SRC_IMM, 0, src_imm,
+ dst_type, dw2_regnum, 0);
+
+ ginsn_set_where (ginsn);
+ }
+
+ return ginsn;
+}
+
+/* Create ginsn(s) for MOV operations.
+
+ The generated ginsns corresponding to mov with indirect access to memory
+ (src or dest) suffer with loss of information: when both index and base
+ registers are at play, only base register gets conveyed in ginsn. Note
+ this TBD_GINSN_GEN_NOT_SCFI. */
+
+static ginsnS *
+x86_ginsn_move (const symbolS *insn_end_sym)
+{
+ ginsnS *ginsn = NULL;
+ unsigned int dst_reg;
+ unsigned int src_reg;
+ offsetT src_disp = 0;
+ offsetT dst_disp = 0;
+ const reg_entry *dst = NULL;
+ const reg_entry *src = NULL;
+ uint16_t opcode = i.tm.base_opcode;
+ enum ginsn_src_type src_type = GINSN_SRC_REG;
+ enum ginsn_dst_type dst_type = GINSN_DST_REG;
+
+ /* mov %reg, symbol or mov symbol, %reg.
+ Not of interest for SCFI. Also, TBD_GINSN_GEN_NOT_SCFI. */
+ if (i.mem_operands == 1 && !i.base_reg && !i.index_reg)
+ return ginsn;
+
+ gas_assert (i.tm.opcode_space == SPACE_BASE);
+ if (opcode == 0x8b || opcode == 0x8a)
+ {
+ /* mov disp(%reg), %reg. */
+ if (i.mem_operands)
+ {
+ src = (i.base_reg) ? i.base_reg : i.index_reg;
+ if (i.disp_operands == 1)
+ src_disp = i.op[0].disps->X_add_number;
+ src_type = GINSN_SRC_INDIRECT;
+ }
+ else
+ src = i.op[0].regs;
+
+ dst = i.op[1].regs;
+ }
+ else if (opcode == 0x89 || opcode == 0x88)
+ {
+ /* mov %reg, disp(%reg). */
+ src = i.op[0].regs;
+ if (i.mem_operands)
+ {
+ dst = (i.base_reg) ? i.base_reg : i.index_reg;
+ if (i.disp_operands == 1)
+ dst_disp = i.op[1].disps->X_add_number;
+ dst_type = GINSN_DST_INDIRECT;
+ }
+ else
+ dst = i.op[1].regs;
+ }
+
+ src_reg = ginsn_dw2_regnum (src);
+ dst_reg = ginsn_dw2_regnum (dst);
+
+ ginsn = ginsn_new_mov (insn_end_sym, true,
+ src_type, src_reg, src_disp,
+ dst_type, dst_reg, dst_disp);
+ ginsn_set_where (ginsn);
+
+ return ginsn;
+}
+
+/* Generate appropriate ginsn for lea.
+ Sub-cases marked with TBD_GINSN_INFO_LOSS indicate some loss of information
+ in the ginsn. But these are fine for now for GINSN_GEN_SCFI generation
+ mode. */
+
+static ginsnS *
+x86_ginsn_lea (const symbolS *insn_end_sym)
+{
+ offsetT src_disp = 0;
+ ginsnS *ginsn = NULL;
+ unsigned int base_reg;
+ unsigned int index_reg;
+ offsetT index_scale;
+ unsigned int dst_reg;
+
+ if (!i.index_reg && !i.base_reg)
+ {
+ /* lea symbol, %rN. */
+ dst_reg = ginsn_dw2_regnum (i.op[1].regs);
+ /* TBD_GINSN_INFO_LOSS - Skip encoding information about the symbol. */
+ ginsn = ginsn_new_mov (insn_end_sym, false,
+ GINSN_SRC_IMM, 0xf /* arbitrary const. */, 0,
+ GINSN_DST_REG, dst_reg, 0);
+ }
+ else if (i.base_reg && !i.index_reg)
+ {
+ /* lea -0x2(%base),%dst. */
+ base_reg = ginsn_dw2_regnum (i.base_reg);
+ dst_reg = ginsn_dw2_regnum (i.op[1].regs);
+
+ if (i.disp_operands)
+ src_disp = i.op[0].disps->X_add_number;
+
+ if (src_disp)
+ /* Generate an ADD ginsn. */
+ ginsn = ginsn_new_add (insn_end_sym, true,
+ GINSN_SRC_REG, base_reg, 0,
+ GINSN_SRC_IMM, 0, src_disp,
+ GINSN_DST_REG, dst_reg, 0);
+ else
+ /* Generate a MOV ginsn. */
+ ginsn = ginsn_new_mov (insn_end_sym, true,
+ GINSN_SRC_REG, base_reg, 0,
+ GINSN_DST_REG, dst_reg, 0);
+ }
+ else if (!i.base_reg && i.index_reg)
+ {
+ /* lea (,%index,imm), %dst. */
+ /* TBD_GINSN_INFO_LOSS - There is no explicit ginsn multiply operation,
+ instead use GINSN_TYPE_OTHER. Also, note that info about displacement
+ is not carried forward either. But this is fine because
+ GINSN_TYPE_OTHER will cause SCFI pass to bail out any which way if
+ dest reg is interesting. */
+ index_scale = i.log2_scale_factor;
+ index_reg = ginsn_dw2_regnum (i.index_reg);
+ dst_reg = ginsn_dw2_regnum (i.op[1].regs);
+ ginsn = ginsn_new_other (insn_end_sym, true,
+ GINSN_SRC_REG, index_reg,
+ GINSN_SRC_IMM, index_scale,
+ GINSN_DST_REG, dst_reg);
+ /* FIXME - It seems to make sense to represent a scale factor of 1
+ correctly here (i.e. not as "other", but rather similar to the
+ base-without- index case above)? */
+ }
+ else
+ {
+ /* lea disp(%base,%index,imm) %dst. */
+ /* TBD_GINSN_INFO_LOSS - Skip adding information about the disp and imm
+ for index reg. */
+ base_reg = ginsn_dw2_regnum (i.base_reg);
+ index_reg = ginsn_dw2_regnum (i.index_reg);
+ dst_reg = ginsn_dw2_regnum (i.op[1].regs);
+ /* Generate an GINSN_TYPE_OTHER ginsn. */
+ ginsn = ginsn_new_other (insn_end_sym, true,
+ GINSN_SRC_REG, base_reg,
+ GINSN_SRC_REG, index_reg,
+ GINSN_DST_REG, dst_reg);
+ }
+
+ ginsn_set_where (ginsn);
+
+ return ginsn;
+}
+
+static ginsnS *
+x86_ginsn_jump (const symbolS *insn_end_sym, bool cond_p)
+{
+ ginsnS *ginsn = NULL;
+ const symbolS *src_symbol;
+ ginsnS * (*ginsn_func) (const symbolS *sym, bool real_p,
+ enum ginsn_src_type src_type, unsigned int src_reg,
+ const symbolS *src_ginsn_sym);
+
+ gas_assert (i.disp_operands == 1);
+
+ ginsn_func = cond_p ? ginsn_new_jump_cond : ginsn_new_jump;
+ if (i.op[0].disps->X_op == O_symbol && !i.op[0].disps->X_add_number)
+ {
+ src_symbol = i.op[0].disps->X_add_symbol;
+ ginsn = ginsn_func (insn_end_sym, true,
+ GINSN_SRC_SYMBOL, 0, src_symbol);
+
+ ginsn_set_where (ginsn);
+ }
+ else
+ {
+ /* A non-zero addend in jump/JCC target makes control-flow tracking
+ difficult. Skip SCFI for now. */
+ as_bad (_("SCFI: `%s' insn with non-zero addend to sym not supported"),
+ cond_p ? "JCC" : "jmp");
+ return ginsn;
+ }
+
+ return ginsn;
+}
+
+static ginsnS *
+x86_ginsn_enter (const symbolS *insn_end_sym)
+{
+ ginsnS *ginsn = NULL;
+ ginsnS *ginsn_next = NULL;
+ ginsnS *ginsn_last = NULL;
+ /* In 64-bit mode, the default stack update size is 8 bytes. */
+ int stack_opnd_size = 8;
+
+ gas_assert (i.imm_operands == 2);
+
+ /* For non-zero size operands, bail out as untraceable for SCFI. */
+ if (i.op[0].imms->X_op != O_constant || i.op[0].imms->X_add_symbol != 0
+ || i.op[1].imms->X_op != O_constant || i.op[1].imms->X_add_symbol != 0)
+ {
+ as_bad ("SCFI: enter insn with non-zero operand not supported");
+ return ginsn;
+ }
+
+ /* Check if this is a 16-bit op. */
+ if (ginsn_opsize_prefix_p ())
+ stack_opnd_size = 2;
+
+ /* If the nesting level is 0, the processor pushes the frame pointer from
+ the BP/EBP/RBP register onto the stack, copies the current stack
+ pointer from the SP/ESP/RSP register into the BP/EBP/RBP register, and
+ loads the SP/ESP/RSP register with the current stack-pointer value
+ minus the value in the size operand. */
+ ginsn = ginsn_new_sub (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_SRC_IMM, 0, stack_opnd_size,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn);
+ ginsn_next = ginsn_new_store (insn_end_sym, false,
+ GINSN_SRC_REG, REG_FP,
+ GINSN_DST_INDIRECT, REG_SP, 0);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+ ginsn_last = ginsn_new_mov (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_DST_REG, REG_FP, 0);
+ ginsn_set_where (ginsn_last);
+ gas_assert (!ginsn_link_next (ginsn_next, ginsn_last));
+
+ return ginsn;
+}
+
+static ginsnS *
+x86_ginsn_leave (const symbolS *insn_end_sym)
+{
+ ginsnS *ginsn = NULL;
+ ginsnS *ginsn_next = NULL;
+ ginsnS *ginsn_last = NULL;
+ /* In 64-bit mode, the default stack update size is 8 bytes. */
+ int stack_opnd_size = 8;
+
+ /* Check if this is a 16-bit op. */
+ if (ginsn_opsize_prefix_p ())
+ stack_opnd_size = 2;
+
+ /* The 'leave' instruction copies the contents of the RBP register
+ into the RSP register to release all stack space allocated to the
+ procedure. */
+ ginsn = ginsn_new_mov (insn_end_sym, false,
+ GINSN_SRC_REG, REG_FP, 0,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn);
+ /* Then it restores the old value of the RBP register from the stack. */
+ ginsn_next = ginsn_new_load (insn_end_sym, false,
+ GINSN_SRC_INDIRECT, REG_SP, 0,
+ GINSN_DST_REG, REG_FP);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+ ginsn_last = ginsn_new_add (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_SRC_IMM, 0, stack_opnd_size,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn_next, ginsn_last));
+
+ return ginsn;
+}
+
+/* Check if an instruction is whitelisted.
+
+ Some instructions may appear with REG_SP or REG_FP as destination, because
+ which they are deemed 'interesting' for SCFI. Whitelist them here if they
+ do not affect SCFI correctness. */
+
+static bool
+x86_ginsn_safe_to_skip_p (void)
+{
+ bool skip_p = false;
+ uint16_t opcode = i.tm.base_opcode;
+
+ switch (opcode)
+ {
+ case 0x80:
+ case 0x81:
+ case 0x83:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* cmp imm, reg/rem. */
+ if (i.tm.extension_opcode == 7)
+ skip_p = true;
+ break;
+
+ case 0x38:
+ case 0x39:
+ case 0x3a:
+ case 0x3b:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* cmp imm/reg/mem, reg/rem. */
+ skip_p = true;
+ break;
+
+ case 0xf6:
+ case 0xf7:
+ case 0x84:
+ case 0x85:
+ /* test imm/reg/mem, reg/mem. */
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ skip_p = true;
+ break;
+
+ default:
+ break;
+ }
+
+ return skip_p;
+}
+
+#define X86_GINSN_UNHANDLED_NONE 0
+#define X86_GINSN_UNHANDLED_DEST_REG 1
+#define X86_GINSN_UNHANDLED_CFG 2
+#define X86_GINSN_UNHANDLED_STACKOP 3
+#define X86_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
+x86_ginsn_unhandled (void)
+{
+ int err = X86_GINSN_UNHANDLED_NONE;
+ const reg_entry *reg_op;
+ unsigned int dw2_regnum;
+
+ /* Keep an eye out for instructions affecting control flow. */
+ if (i.tm.opcode_modifier.jump)
+ err = X86_GINSN_UNHANDLED_CFG;
+ /* Also, for any instructions involving an implicit update to the stack
+ pointer. */
+ else if (i.tm.opcode_modifier.operandconstraint == IMPLICIT_STACK_OP)
+ err = X86_GINSN_UNHANDLED_STACKOP;
+ /* Finally, also check if the missed instructions are affecting REG_SP or
+ REG_FP. The destination operand is the last at all stages of assembly
+ (due to following AT&T syntax layout in the internal representation). In
+ case of Intel syntax input, this still remains true as swap_operands ()
+ is done by now.
+ PS: These checks do not involve index / base reg, as indirect memory
+ accesses via REG_SP or REG_FP do not affect SCFI correctness.
+ (Also note these instructions are candidates for other ginsn generation
+ modes in future. TBD_GINSN_GEN_NOT_SCFI.) */
+ else if (i.operands && i.reg_operands
+ && !(i.flags[i.operands - 1] & Operand_Mem))
+ {
+ reg_op = i.op[i.operands - 1].regs;
+ if (reg_op)
+ {
+ dw2_regnum = ginsn_dw2_regnum (reg_op);
+ if (dw2_regnum == REG_SP || dw2_regnum == REG_FP)
+ err = X86_GINSN_UNHANDLED_DEST_REG;
+ }
+ else
+ /* Something unexpected. Indicate to caller. */
+ err = X86_GINSN_UNHANDLED_UNEXPECTED;
+ }
+
+ 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 for
+ now.
+ - All change of flow instructions: conditional and unconditional branches,
+ call and return from functions.
+ - All instructions that can potentially be a register save / restore
+ operation.
+ - All instructions that perform stack manipulation implicitly: the CALL,
+ RET, PUSH, POP, ENTER, and LEAVE instructions.
+
+ 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 *
+x86_ginsn_new (const symbolS *insn_end_sym, enum ginsn_gen_mode gmode)
+{
+ int err = 0;
+ uint16_t opcode;
+ unsigned int dw2_regnum;
+ const reg_entry *mem_reg;
+ ginsnS *ginsn = NULL;
+ ginsnS *ginsn_next = NULL;
+ /* In 64-bit mode, the default stack update size is 8 bytes. */
+ int stack_opnd_size = 8;
+
+ /* Currently supports generation of selected ginsns, sufficient for
+ the use-case of SCFI only. */
+ if (gmode != GINSN_GEN_SCFI)
+ return ginsn;
+
+ opcode = i.tm.base_opcode;
+
+ /* Until it is clear how to handle APX NDD and other new opcodes, disallow
+ them from SCFI. */
+ if (is_apx_rex2_encoding ()
+ || (i.tm.opcode_modifier.evex && is_apx_evex_encoding ()))
+ {
+ as_bad (_("SCFI: unsupported APX op %#x may cause incorrect CFI"),
+ opcode);
+ return ginsn;
+ }
+
+ switch (opcode)
+ {
+ case 0x1: /* add reg, reg/mem. */
+ case 0x29: /* sub reg, reg/mem. */
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ ginsn = x86_ginsn_addsub_reg_mem (insn_end_sym);
+ break;
+
+ case 0x3: /* add reg/mem, reg. */
+ case 0x2b: /* sub reg/mem, reg. */
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ ginsn = x86_ginsn_addsub_mem_reg (insn_end_sym);
+ break;
+
+ case 0xa0: /* push fs. */
+ case 0xa8: /* push gs. */
+ /* push fs / push gs have opcode_space == SPACE_0F. */
+ if (i.tm.opcode_space != SPACE_0F)
+ break;
+ dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+ /* Check if operation size is 16-bit. */
+ if (ginsn_opsize_prefix_p ())
+ stack_opnd_size = 2;
+ ginsn = ginsn_new_sub (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_SRC_IMM, 0, stack_opnd_size,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn);
+ ginsn_next = ginsn_new_store (insn_end_sym, false,
+ GINSN_SRC_REG, dw2_regnum,
+ GINSN_DST_INDIRECT, REG_SP, 0);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+ break;
+
+ case 0xa1: /* pop fs. */
+ case 0xa9: /* pop gs. */
+ /* pop fs / pop gs have opcode_space == SPACE_0F. */
+ if (i.tm.opcode_space != SPACE_0F)
+ break;
+ dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+ /* Check if operation size is 16-bit. */
+ if (ginsn_opsize_prefix_p ())
+ stack_opnd_size = 2;
+ ginsn = ginsn_new_load (insn_end_sym, false,
+ GINSN_SRC_INDIRECT, REG_SP, 0,
+ GINSN_DST_REG, dw2_regnum);
+ ginsn_set_where (ginsn);
+ ginsn_next = ginsn_new_add (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_SRC_IMM, 0, stack_opnd_size,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+ break;
+
+ case 0x50 ... 0x57:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* push reg. */
+ dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+ /* Check if operation size is 16-bit. */
+ if (ginsn_opsize_prefix_p ())
+ stack_opnd_size = 2;
+ ginsn = ginsn_new_sub (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_SRC_IMM, 0, stack_opnd_size,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn);
+ ginsn_next = ginsn_new_store (insn_end_sym, false,
+ GINSN_SRC_REG, dw2_regnum,
+ GINSN_DST_INDIRECT, REG_SP, 0);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+ break;
+
+ case 0x58 ... 0x5f:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* pop reg. */
+ dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+ ginsn = ginsn_new_load (insn_end_sym, false,
+ GINSN_SRC_INDIRECT, REG_SP, 0,
+ GINSN_DST_REG, dw2_regnum);
+ ginsn_set_where (ginsn);
+ /* Check if operation size is 16-bit. */
+ if (ginsn_opsize_prefix_p ())
+ stack_opnd_size = 2;
+ ginsn_next = ginsn_new_add (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_SRC_IMM, 0, stack_opnd_size,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+ break;
+
+ case 0x6a: /* push imm8. */
+ case 0x68: /* push imm16/imm32. */
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* Check if operation size is 16-bit. */
+ if (ginsn_opsize_prefix_p ())
+ stack_opnd_size = 2;
+ /* Skip getting the value of imm from machine instruction
+ because this is not important for SCFI. */
+ ginsn = ginsn_new_sub (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_SRC_IMM, 0, stack_opnd_size,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn);
+ ginsn_next = ginsn_new_store (insn_end_sym, false,
+ GINSN_SRC_IMM, 0,
+ GINSN_DST_INDIRECT, REG_SP, 0);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+ break;
+
+ /* PS: Opcodes 0x80 ... 0x8f with opcode_space SPACE_0F are present
+ only after relaxation. They do not need to be handled for ginsn
+ creation. */
+ case 0x70 ... 0x7f:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ ginsn = x86_ginsn_jump (insn_end_sym, true);
+ break;
+
+ case 0x80:
+ case 0x81:
+ case 0x83:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ ginsn = x86_ginsn_alu_imm (insn_end_sym);
+ break;
+
+ case 0x8a: /* mov r/m8, r8. */
+ case 0x8b: /* mov r/m(16/32/64), r(16/32/64). */
+ case 0x88: /* mov r8, r/m8. */
+ case 0x89: /* mov r(16/32/64), r/m(16/32/64). */
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ ginsn = x86_ginsn_move (insn_end_sym);
+ break;
+
+ case 0x8d:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* lea disp(%base,%index,imm) %dst. */
+ ginsn = x86_ginsn_lea (insn_end_sym);
+ break;
+
+ case 0x8f:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* pop to reg/mem. */
+ if (i.mem_operands)
+ {
+ mem_reg = (i.base_reg) ? i.base_reg : i.index_reg;
+ /* Use dummy register if no base or index. Unlike other opcodes,
+ ginsns must be generated as this affect stack pointer. */
+ dw2_regnum = (mem_reg
+ ? ginsn_dw2_regnum (mem_reg)
+ : GINSN_DW2_REGNUM_RSI_DUMMY);
+ }
+ else
+ dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+ ginsn = ginsn_new_load (insn_end_sym, false,
+ GINSN_SRC_INDIRECT, REG_SP, 0,
+ GINSN_DST_INDIRECT, dw2_regnum);
+ ginsn_set_where (ginsn);
+ /* Check if operation size is 16-bit. */
+ if (ginsn_opsize_prefix_p ())
+ stack_opnd_size = 2;
+ ginsn_next = ginsn_new_add (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_SRC_IMM, 0, stack_opnd_size,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+ break;
+
+ case 0x9c:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* pushf / pushfq. */
+ /* Check if operation size is 16-bit. */
+ if (ginsn_opsize_prefix_p ())
+ stack_opnd_size = 2;
+ ginsn = ginsn_new_sub (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_SRC_IMM, 0, stack_opnd_size,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn);
+ /* FIXME - hardcode the actual DWARF reg number value. As for SCFI
+ correctness, although this behaves simply a placeholder value; its
+ just clearer if the value is correct. */
+ dw2_regnum = GINSN_DW2_REGNUM_EFLAGS;
+ ginsn_next = ginsn_new_store (insn_end_sym, false,
+ GINSN_SRC_REG, dw2_regnum,
+ GINSN_DST_INDIRECT, REG_SP, 0);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+ break;
+
+ case 0x9d:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* popf / popfq. */
+ /* Check if operation size is 16-bit. */
+ if (ginsn_opsize_prefix_p ())
+ stack_opnd_size = 2;
+ /* FIXME - hardcode the actual DWARF reg number value. As for SCFI
+ correctness, although this behaves simply a placeholder value; its
+ just clearer if the value is correct. */
+ dw2_regnum = GINSN_DW2_REGNUM_EFLAGS;
+ ginsn = ginsn_new_load (insn_end_sym, false,
+ GINSN_SRC_INDIRECT, REG_SP, 0,
+ GINSN_DST_REG, dw2_regnum);
+ ginsn_set_where (ginsn);
+ ginsn_next = ginsn_new_add (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_SRC_IMM, 0, stack_opnd_size,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+ break;
+
+ case 0xff:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* push from reg/mem. */
+ if (i.tm.extension_opcode == 6)
+ {
+ /* Check if operation size is 16-bit. */
+ if (ginsn_opsize_prefix_p ())
+ stack_opnd_size = 2;
+ ginsn = ginsn_new_sub (insn_end_sym, false,
+ GINSN_SRC_REG, REG_SP, 0,
+ GINSN_SRC_IMM, 0, stack_opnd_size,
+ GINSN_DST_REG, REG_SP, 0);
+ ginsn_set_where (ginsn);
+ if (i.mem_operands)
+ {
+ mem_reg = (i.base_reg) ? i.base_reg : i.index_reg;
+ /* Use dummy register if no base or index. Unlike other opcodes,
+ ginsns must be generated as this affect stack pointer. */
+ dw2_regnum = (mem_reg
+ ? ginsn_dw2_regnum (mem_reg)
+ : GINSN_DW2_REGNUM_RSI_DUMMY);
+ }
+ else
+ dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+ ginsn_next = ginsn_new_store (insn_end_sym, false,
+ GINSN_SRC_INDIRECT, dw2_regnum,
+ GINSN_DST_INDIRECT, REG_SP, 0);
+ ginsn_set_where (ginsn_next);
+ gas_assert (!ginsn_link_next (ginsn, ginsn_next));
+ }
+ else if (i.tm.extension_opcode == 4)
+ {
+ /* jmp r/m. E.g., notrack jmp *%rax. */
+ if (i.reg_operands)
+ {
+ dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+ ginsn = ginsn_new_jump (insn_end_sym, true,
+ GINSN_SRC_REG, dw2_regnum, NULL);
+ ginsn_set_where (ginsn);
+ }
+ else if (i.mem_operands && i.index_reg)
+ {
+ /* jmp *0x0(,%rax,8). */
+ dw2_regnum = ginsn_dw2_regnum (i.index_reg);
+ ginsn = ginsn_new_jump (insn_end_sym, true,
+ GINSN_SRC_REG, dw2_regnum, NULL);
+ ginsn_set_where (ginsn);
+ }
+ else if (i.mem_operands && i.base_reg)
+ {
+ dw2_regnum = ginsn_dw2_regnum (i.base_reg);
+ ginsn = ginsn_new_jump (insn_end_sym, true,
+ GINSN_SRC_REG, dw2_regnum, NULL);
+ ginsn_set_where (ginsn);
+ }
+ }
+ else if (i.tm.extension_opcode == 2)
+ {
+ /* 0xFF /2 (call). */
+ if (i.reg_operands)
+ {
+ dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
+ ginsn = ginsn_new_call (insn_end_sym, true,
+ GINSN_SRC_REG, dw2_regnum, NULL);
+ ginsn_set_where (ginsn);
+ }
+ else if (i.mem_operands && i.base_reg)
+ {
+ dw2_regnum = ginsn_dw2_regnum (i.base_reg);
+ ginsn = ginsn_new_call (insn_end_sym, true,
+ GINSN_SRC_REG, dw2_regnum, NULL);
+ ginsn_set_where (ginsn);
+ }
+ }
+ break;
+
+ case 0xc2: /* ret imm16. */
+ case 0xc3: /* ret. */
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* Near ret. */
+ ginsn = ginsn_new_return (insn_end_sym, true);
+ ginsn_set_where (ginsn);
+ break;
+
+ case 0xc8:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* enter. */
+ ginsn = x86_ginsn_enter (insn_end_sym);
+ break;
+
+ case 0xc9:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* leave. */
+ ginsn = x86_ginsn_leave (insn_end_sym);
+ break;
+
+ case 0xe0 ... 0xe2: /* loop / loope / loopne. */
+ case 0xe3: /* jecxz / jrcxz. */
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ ginsn = x86_ginsn_jump (insn_end_sym, true);
+ ginsn_set_where (ginsn);
+ break;
+
+ case 0xe8:
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* PS: SCFI machinery does not care about which func is being
+ called. OK to skip that info. */
+ ginsn = ginsn_new_call (insn_end_sym, true,
+ GINSN_SRC_SYMBOL, 0, NULL);
+ ginsn_set_where (ginsn);
+ break;
+
+ /* PS: opcode 0xe9 appears only after relaxation. Skip here. */
+ case 0xeb:
+ /* If opcode_space != SPACE_BASE, this is not a jmp insn. Skip it
+ for GINSN_GEN_SCFI. */
+ if (i.tm.opcode_space != SPACE_BASE)
+ break;
+ /* Unconditional jmp. */
+ ginsn = x86_ginsn_jump (insn_end_sym, false);
+ ginsn_set_where (ginsn);
+ break;
+
+ default:
+ /* TBD_GINSN_GEN_NOT_SCFI: Skip all other opcodes uninteresting for
+ GINSN_GEN_SCFI mode. */
+ break;
+ }
+
+ if (!ginsn && !x86_ginsn_safe_to_skip_p ())
+ {
+ /* For all unhandled insns that are not whitelisted, check that they do
+ not impact SCFI correctness. */
+ err = x86_ginsn_unhandled ();
+ switch (err)
+ {
+ case X86_GINSN_UNHANDLED_NONE:
+ break;
+ case X86_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. */
+ dw2_regnum = ginsn_dw2_regnum (i.op[i.operands - 1].regs);
+ 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 X86_GINSN_UNHANDLED_CFG:
+ case X86_GINSN_UNHANDLED_STACKOP:
+ as_bad (_("SCFI: unhandled op %#x may cause incorrect CFI"), opcode);
+ break;
+ case X86_GINSN_UNHANDLED_UNEXPECTED:
+ as_bad (_("SCFI: unexpected op %#x may cause incorrect CFI"),
+ opcode);
+ break;
+ default:
+ abort ();
+ break;
+ }
+ }
+
+ return ginsn;
+}
+
+#endif
+
/* This is the guts of the machine-dependent assembler. LINE points to a
machine dependent instruction. This function is supposed to emit
the frags/bytes it assembles to. */
@@ -5870,6 +6960,17 @@ md_assemble (char *line)
/* We are ready to output the insn. */
output_insn (last_insn);
+#if defined (OBJ_MAYBE_ELF) || defined (OBJ_ELF)
+ /* PS: SCFI is enabled only for System V AMD64 ABI. The ABI check has been
+ performed in i386_target_format. */
+ if (IS_ELF && flag_synth_cfi)
+ {
+ ginsnS *ginsn;
+ ginsn = x86_ginsn_new (symbol_temp_new_now (), frch_ginsn_gen_mode ());
+ frch_ginsn_data_append (ginsn);
+ }
+#endif
+
insert_lfence_after ();
if (i.tm.opcode_modifier.isprefix)
@@ -12144,6 +13245,13 @@ s_insn (int dummy ATTRIBUTE_UNUSED)
last_insn->name = ".insn directive";
last_insn->file = as_where (&last_insn->line);
+#if defined (OBJ_MAYBE_ELF) || defined (OBJ_ELF)
+ /* PS: SCFI is enabled only for System V AMD64 ABI. The ABI check has been
+ performed in i386_target_format. */
+ if (IS_ELF && flag_synth_cfi)
+ as_bad (_("SCFI: hand-crafting instructions not supported"));
+#endif
+
done:
*saved_ilp = saved_char;
input_line_pointer = line;
@@ -15788,6 +16896,11 @@ i386_target_format (void)
else
as_fatal (_("unknown architecture"));
+#if defined (OBJ_ELF) || defined (OBJ_MAYBE_ELF)
+ if (IS_ELF && flag_synth_cfi && x86_elf_abi != X86_64_ABI)
+ as_fatal (_("SCFI is not supported for this ABI"));
+#endif
+
if (cpu_flags_all_zero (&cpu_arch_isa_flags))
cpu_arch_isa_flags = cpu_arch[flag_code == CODE_64BIT].enable;