binutils-gdb/gas/config/tc-i386-ginsn.c
Indu Bhagat 5ffe413283 gas: ginsn: x86: pacify Wmaybe-uininitialized compiler warning
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.
2024-08-19 11:08:39 -07:00

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;
}