diff options
Diffstat (limited to 'gas/config/tc-i386.c')
-rw-r--r-- | gas/config/tc-i386.c | 1113 |
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; |