]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
x86: move ginsn stuff
authorJan Beulich <jbeulich@suse.com>
Wed, 31 Jul 2024 10:04:03 +0000 (12:04 +0200)
committerJan Beulich <jbeulich@suse.com>
Wed, 31 Jul 2024 10:04:03 +0000 (12:04 +0200)
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.

gas/config/tc-i386-ginsn.c [new file with mode: 0644]
gas/config/tc-i386.c

diff --git a/gas/config/tc-i386-ginsn.c b/gas/config/tc-i386-ginsn.c
new file mode 100644 (file)
index 0000000..dccd675
--- /dev/null
@@ -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;
+}
index 68c35fbdd331892eb1396aedb8552afe0e9e77d6..87a1d0cf27036fd4cd858e8a5dfe7a9d28eff376 100644 (file)
@@ -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)