diff options
author | Jan Beulich <jbeulich@suse.com> | 2024-07-31 12:04:03 +0200 |
---|---|---|
committer | Jan Beulich <jbeulich@suse.com> | 2024-07-31 12:04:03 +0200 |
commit | c39fbc749a545ac2b728173a340095b614585c96 (patch) | |
tree | 76535552df8a2a6bda913adda61eefb8aec1ac52 /gas/config | |
parent | 1264d4a096cead8d3e5ef20730f4a75b2931e355 (diff) | |
download | fsf-binutils-gdb-c39fbc749a545ac2b728173a340095b614585c96.zip fsf-binutils-gdb-c39fbc749a545ac2b728173a340095b614585c96.tar.gz fsf-binutils-gdb-c39fbc749a545ac2b728173a340095b614585c96.tar.bz2 |
x86: move ginsn stuff
This had been badly inserted between md_assemble() and its helpers
anyway. Follow what was done for Arm64 and move the code to its own
file, #include-d as appropriate.
Diffstat (limited to 'gas/config')
-rw-r--r-- | gas/config/tc-i386-ginsn.c | 1120 | ||||
-rw-r--r-- | gas/config/tc-i386.c | 1105 |
2 files changed, 1123 insertions, 1102 deletions
diff --git a/gas/config/tc-i386-ginsn.c b/gas/config/tc-i386-ginsn.c new file mode 100644 index 0000000..dccd675 --- /dev/null +++ b/gas/config/tc-i386-ginsn.c @@ -0,0 +1,1120 @@ +/* tc-i386-ginsn.c -- Ginsn generation for the x86-64 ISA + + Copyright (C) 2024 Free Software Foundation, Inc. + + This file is part of GAS. + + GAS is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the license, or + (at your option) any later version. + + GAS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING3. If not, + see <http://www.gnu.org/licenses/>. */ + +/* This file contains the implementation of the ginsn creation for x86-64 + instructions. */ + +/* 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) +{ + const reg_entry *temp = ireg; + unsigned int dwarf_reg = Dw2Inval, 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[object_64bit]; + + 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 < Dw2Inval); + + return 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; + + /* Skip detection of 8/16/32-bit op size; 'add/sub reg, reg/mem' ops always + make the dest reg untraceable for SCFI. */ + + /* 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; + + /* Skip detection of 8/16/32-bit op size; 'add/sub reg/mem, reg' ops always + make the dest reg untraceable for SCFI. */ + + /* 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; + + /* 8/16/32-bit op size makes the destination reg untraceable for SCFI. + Deal with this via the x86_ginsn_unhandled () code path. */ + if (i.suffix != QWORD_MNEM_SUFFIX) + 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; + + /* 8/16/32-bit op size makes the destination reg untraceable for SCFI. + Handle mov reg, reg only. mov to or from a memory operand will make + dest reg, when present, untraceable, irrespective of the op size. */ + if (i.reg_operands == 2 && i.suffix != QWORD_MNEM_SUFFIX) + 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. + + Unhandled sub-cases (marked with TBD_GINSN_GEN_NOT_SCFI) also suffer with + some loss of information in the final ginsn chosen eventually (type + GINSN_TYPE_OTHER). But this is 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 src1_reg; + const reg_entry *src1; + offsetT index_scale; + unsigned int dst_reg; + bool index_regiz_p; + + if ((!i.base_reg) != (!i.index_reg || i.index_reg->reg_num == RegIZ)) + { + /* lea disp(%base), %dst or lea disp(,%index,imm), %dst. + Either index_reg or base_reg exists, but not both. Further, as per + above, the case when just %index exists but is equal to RegIZ is + excluded. If not excluded, a GINSN_TYPE_MOV of %rsi + (GINSN_DW2_REGNUM_RSI_DUMMY) to %dst will be generated by this block. + Such a mov ginsn is imprecise; so, exclude now and generate + GINSN_TYPE_OTHER instead later via the x86_ginsn_unhandled (). + Excluding other cases is required due to + TBD_GINSN_REPRESENTATION_LIMIT. */ + + index_scale = i.log2_scale_factor; + index_regiz_p = i.index_reg && i.index_reg->reg_num == RegIZ; + src1 = i.base_reg ? i.base_reg : i.index_reg; + src1_reg = ginsn_dw2_regnum (src1); + dst_reg = ginsn_dw2_regnum (i.op[1].regs); + /* It makes sense to represent a scale factor of 1 precisely here + (i.e., not using GINSN_TYPE_OTHER, but rather similar to the + base-without-index case). A non-zero scale factor is still OK if + the index reg is zero reg. + However, skip from here the case when disp has a symbol instead. + TBD_GINSN_REPRESENTATION_LIMIT. */ + if ((!index_scale || index_regiz_p) + && (!i.disp_operands || i.op[0].disps->X_op == O_constant)) + { + 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, src1_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, src1_reg, 0, + GINSN_DST_REG, dst_reg, 0); + + ginsn_set_where (ginsn); + } + } + /* Skip handling other cases here, + - when (i.index_reg && i.base_reg) is true, + e.g., lea disp(%base,%index,imm), %dst + We do not have a ginsn representation for multiply. + - or, when (!i.index_reg && !i.base_reg) is true, + e.g., lea symbol, %dst + Not a frequent pattern. If %dst is a register of interest, the user is + likely to use a MOV op anyway. + Deal with these via the x86_ginsn_unhandled () code path to generate + GINSN_TYPE_OTHER when necessary. TBD_GINSN_GEN_NOT_SCFI. */ + + 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) + { + + /* Add opcodes 0x0/0x2 and sub opcodes 0x28/0x2a (with opcode_space + SPACE_BASE) are 8-bit ops. While they are relevant for SCFI + correctness, skip handling them here and use the x86_ginsn_unhandled + code path to generate GINSN_TYPE_OTHER when necessary. */ + + 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; +} diff --git a/gas/config/tc-i386.c b/gas/config/tc-i386.c index 68c35fb..87a1d0c 100644 --- a/gas/config/tc-i386.c +++ b/gas/config/tc-i386.c @@ -650,6 +650,7 @@ unsigned int x86_sframe_cfa_sp_reg; /* The other CFA base register for SFrame stack trace info. */ unsigned int x86_sframe_cfa_fp_reg; +static ginsnS *x86_ginsn_new (const symbolS *, enum ginsn_gen_mode); #endif /* 1 for intel syntax, @@ -6228,1108 +6229,6 @@ static INLINE bool may_need_pass2 (const insn_template *t) && (t->base_opcode | 8) == 0x2c); } -#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) -{ - const reg_entry *temp = ireg; - unsigned int dwarf_reg = Dw2Inval, 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[object_64bit]; - - 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 < Dw2Inval); - - return 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; - - /* Skip detection of 8/16/32-bit op size; 'add/sub reg, reg/mem' ops always - make the dest reg untraceable for SCFI. */ - - /* 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; - - /* Skip detection of 8/16/32-bit op size; 'add/sub reg/mem, reg' ops always - make the dest reg untraceable for SCFI. */ - - /* 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; - - /* 8/16/32-bit op size makes the destination reg untraceable for SCFI. - Deal with this via the x86_ginsn_unhandled () code path. */ - if (i.suffix != QWORD_MNEM_SUFFIX) - 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; - - /* 8/16/32-bit op size makes the destination reg untraceable for SCFI. - Handle mov reg, reg only. mov to or from a memory operand will make - dest reg, when present, untraceable, irrespective of the op size. */ - if (i.reg_operands == 2 && i.suffix != QWORD_MNEM_SUFFIX) - 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. - - Unhandled sub-cases (marked with TBD_GINSN_GEN_NOT_SCFI) also suffer with - some loss of information in the final ginsn chosen eventually (type - GINSN_TYPE_OTHER). But this is 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 src1_reg; - const reg_entry *src1; - offsetT index_scale; - unsigned int dst_reg; - bool index_regiz_p; - - if ((!i.base_reg) != (!i.index_reg || i.index_reg->reg_num == RegIZ)) - { - /* lea disp(%base), %dst or lea disp(,%index,imm), %dst. - Either index_reg or base_reg exists, but not both. Further, as per - above, the case when just %index exists but is equal to RegIZ is - excluded. If not excluded, a GINSN_TYPE_MOV of %rsi - (GINSN_DW2_REGNUM_RSI_DUMMY) to %dst will be generated by this block. - Such a mov ginsn is imprecise; so, exclude now and generate - GINSN_TYPE_OTHER instead later via the x86_ginsn_unhandled (). - Excluding other cases is required due to - TBD_GINSN_REPRESENTATION_LIMIT. */ - - index_scale = i.log2_scale_factor; - index_regiz_p = i.index_reg && i.index_reg->reg_num == RegIZ; - src1 = i.base_reg ? i.base_reg : i.index_reg; - src1_reg = ginsn_dw2_regnum (src1); - dst_reg = ginsn_dw2_regnum (i.op[1].regs); - /* It makes sense to represent a scale factor of 1 precisely here - (i.e., not using GINSN_TYPE_OTHER, but rather similar to the - base-without-index case). A non-zero scale factor is still OK if - the index reg is zero reg. - However, skip from here the case when disp has a symbol instead. - TBD_GINSN_REPRESENTATION_LIMIT. */ - if ((!index_scale || index_regiz_p) - && (!i.disp_operands || i.op[0].disps->X_op == O_constant)) - { - 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, src1_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, src1_reg, 0, - GINSN_DST_REG, dst_reg, 0); - - ginsn_set_where (ginsn); - } - } - /* Skip handling other cases here, - - when (i.index_reg && i.base_reg) is true, - e.g., lea disp(%base,%index,imm), %dst - We do not have a ginsn representation for multiply. - - or, when (!i.index_reg && !i.base_reg) is true, - e.g., lea symbol, %dst - Not a frequent pattern. If %dst is a register of interest, the user is - likely to use a MOV op anyway. - Deal with these via the x86_ginsn_unhandled () code path to generate - GINSN_TYPE_OTHER when necessary. TBD_GINSN_GEN_NOT_SCFI. */ - - 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) - { - - /* Add opcodes 0x0/0x2 and sub opcodes 0x28/0x2a (with opcode_space - SPACE_BASE) are 8-bit ops. While they are relevant for SCFI - correctness, skip handling them here and use the x86_ginsn_unhandled - code path to generate GINSN_TYPE_OTHER when necessary. */ - - 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. */ @@ -12213,6 +11112,8 @@ x86_cleanup (void) subseg_set (seg, subseg); } +#include "tc-i386-ginsn.c" + /* Whether SFrame stack trace info is supported. */ bool x86_support_sframe_p (void) |