mirror of
https://sourceware.org/git/binutils-gdb.git
synced 2024-11-27 03:51:15 +08:00
5ffe413283
Fix PR binutils/32091
After commit d56083b504
, some gcc versions
may warn about unintialized usage of ginsn_func. Albeit false positive,
adapt the code to escape the warning.
gas/config/
* tc-i386-ginsn.c (x86_ginsn_indirect_branch): Early exit if
unexpected args.
1136 lines
37 KiB
C
1136 lines
37 KiB
C
/* 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_indirect_branch (const symbolS *insn_end_sym)
|
|
{
|
|
ginsnS *ginsn = NULL;
|
|
const reg_entry *mem_reg;
|
|
unsigned int dw2_regnum;
|
|
|
|
ginsnS * (*ginsn_func) (const symbolS *sym, bool real_p,
|
|
enum ginsn_src_type src_type, unsigned int src_reg,
|
|
const symbolS *src_ginsn_sym);
|
|
|
|
/* Other cases are not expected. */
|
|
gas_assert (i.tm.extension_opcode == 4 || i.tm.extension_opcode == 2);
|
|
|
|
if (i.tm.extension_opcode == 4)
|
|
/* 0xFF /4 (jmp r/m). */
|
|
ginsn_func = ginsn_new_jump;
|
|
else if (i.tm.extension_opcode == 2)
|
|
/* 0xFF /2 (call r/m). */
|
|
ginsn_func = ginsn_new_call;
|
|
else
|
|
return ginsn;
|
|
|
|
if (i.reg_operands)
|
|
{
|
|
dw2_regnum = ginsn_dw2_regnum (i.op[0].regs);
|
|
ginsn = ginsn_func (insn_end_sym, true,
|
|
GINSN_SRC_REG, dw2_regnum, NULL);
|
|
ginsn_set_where (ginsn);
|
|
}
|
|
else if (i.mem_operands)
|
|
{
|
|
/* Handle jump/call near, absolute indirect, address.
|
|
E.g., jmp/call *imm(%rN), jmp/call *sym(,%rN,imm)
|
|
or jmp/call *sym(%rN) etc. */
|
|
mem_reg = i.base_reg ? i.base_reg : i.index_reg;
|
|
/* Generate a ginsn, even if it is with TBD_GINSN_INFO_LOSS. Otherwise,
|
|
the user gets the impression of missing functionality due to this
|
|
being a COFI and alerted for via the x86_ginsn_unhandled () workflow
|
|
as unhandled operation (which can be misleading for users).
|
|
|
|
Indirect branches make the code block ineligible for SCFI; Hence, an
|
|
approximate ginsn will not affect SCFI correctness:
|
|
- Use dummy register if no base or index
|
|
- Skip symbol information, if any.
|
|
Note this case of TBD_GINSN_GEN_NOT_SCFI. */
|
|
dw2_regnum = (mem_reg
|
|
? ginsn_dw2_regnum (mem_reg)
|
|
: GINSN_DW2_REGNUM_RSI_DUMMY);
|
|
ginsn = ginsn_func (insn_end_sym, true,
|
|
GINSN_SRC_REG, dw2_regnum, NULL);
|
|
ginsn_set_where (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 || i.tm.extension_opcode == 2)
|
|
ginsn = x86_ginsn_indirect_branch (insn_end_sym);
|
|
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;
|
|
}
|