@@ -2375,6 +2375,7 @@ objdump [@option{-a}|@option{--archive-headers}]
[@option{--prefix=}@var{prefix}]
[@option{--prefix-strip=}@var{level}]
[@option{--insn-width=}@var{width}]
+ [@option{--fpu-emulation=}@var{borland|ibm|intel|microsoft|watcom}]
[@option{--visualize-jumps[=color|=extended-color|=off]}
[@option{--disassembler-color=[off|terminal|on|extended]}
[@option{-U} @var{method}] [@option{--unicode=}@var{method}]
@@ -2743,6 +2744,20 @@ Display the symbol associated with immediate values, should there be
one. Only works on executable binaries and shared libraries, not
static object files.
+@item fpu-emulation=@var{borland|ibm|intel|microsoft|watcom}
+Decode 8087 emulation @code{interrupt} instructions assuming the code
+uses conventions @code{intel}, @code{microsoft}, @code{borland},
+@code{watcom} or @code{ibm}. Without this option @code{interrupt},
+instructions are left untouched. The @code{intel} convention uses
+@code{INT 18h-1Fh} (plus @code{INT 14h-17h} for segment overrides); the
+other conventions use the DOS range @code{INT 34h-3Dh}. Each decoded
+sequence shows the @code{interrupt} call, aligned with plain
+@code{interrupt} lines, and a comment mapping each byte group to the
+element it represents: the instruction bytes to the base FPU mnemonic,
+and each displacement argument to its value, e.g. @code{int $0x3a #
+fmulp %st,%st(1)}. An in-convention @code{interrupt} whose arguments do
+not decode is shown as a bare @code{interrupt} with a @code{# ?} marker.
+
@end table
For PowerPC, the @option{-M} argument @option{raw} selects
@@ -2975,6 +2990,18 @@ This is the default when @option{--prefix-addresses} is used.
Display @var{width} bytes on a single line when disassembling
instructions.
+@item --fpu-emulation=@var{borland|ibm|intel|microsoft|watcom}
+@cindex FPU emulation convention
+Decode 8087 emulation @code{interrupt} instructions assuming the code
+uses conventions @code{borland}, @code{ibm}, @code{intel},
+@code{microsoft} or @code{watcom}. This is equivalent to @option{-M
+fpu-emulation=...}. Without this option, the related
+@code{interrupt} instruction decoding will decode the sequence as a
+confusing series of ghost instructions. Comment dialect and stability
+are as described under @option{-M fpu-emulation} above: parenthesized
+byte groups mapping bytes to elements, a @code{# ?} marker for
+undecodable sequences.
+
@item --visualize-jumps[=color|=extended-color|=off]
Visualize jumps that stay inside a function by drawing ASCII art between
the start and target addresses. The optional @option{=color} argument
@@ -371,6 +371,10 @@ usage (FILE *stream, int status)
fprintf (stream, _("\
-M, --disassembler-options=OPT Pass text OPT on to the disassembler\n"));
fprintf (stream, _("\
+ --fpu-emulation=CONV Decode x87 emulation interrupts for code using\n\
+ the specified convention.\n\
+ CONV=[intel|microsoft|borland|watcom|ibm]\n"));
+ fprintf (stream, _("\
-EB --endian=big Assume big endian format when disassembling\n"));
fprintf (stream, _("\
-EL --endian=little Assume little endian format when disassembling\n"));
@@ -502,7 +506,8 @@ enum option_values
OPTION_SFRAME,
OPTION_VISUALIZE_JUMPS,
OPTION_DISASSEMBLER_COLOR,
- OPTION_MAP_GLOBAL_VARS
+ OPTION_MAP_GLOBAL_VARS,
+ OPTION_FPU_EMULATION
};
static struct option long_options[]=
@@ -525,6 +530,7 @@ static struct option long_options[]=
{"disassemble-all", no_argument, NULL, 'D'},
{"disassemble-zeroes", no_argument, NULL, 'z'},
{"disassembler-options", required_argument, NULL, 'M'},
+ {"fpu-emulation", required_argument, NULL, OPTION_FPU_EMULATION},
{"dwarf", optional_argument, NULL, OPTION_DWARF},
{"dwarf-check", no_argument, 0, OPTION_DWARF_CHECK},
{"dwarf-depth", required_argument, 0, OPTION_DWARF_DEPTH},
@@ -6127,6 +6133,25 @@ main (int argc, char **argv)
free (options);
}
break;
+ case OPTION_FPU_EMULATION:
+ {
+ char *emu_opt;
+ char *options;
+ emu_opt = concat ("fpu-emulation=", optarg, (const char *) NULL);
+ if (disassembler_options)
+ {
+ options = concat (disassembler_options, ",",
+ emu_opt, (const char *) NULL);
+ free (disassembler_options);
+ free (emu_opt);
+ }
+ else
+ options = emu_opt;
+ disassembler_options = remove_whitespace_and_extra_commas (options);
+ if (!disassembler_options)
+ free (options);
+ }
+ break;
case 'j':
add_only (optarg);
break;
@@ -289,6 +289,29 @@ struct dis_private {
uint8_t the_buffer[2 * MAX_CODE_LENGTH - 1];
};
+/* 8087 emulation conventions for 8086/8088/186/286/386 code without
+ a coprocessor. Language translators emitted normal WAIT/ESC code
+ with fix-ups; the linker replaced WAIT/ESC pairs with software INT
+ instructions when linking with an emulation library (see fixup.txt).
+ Decoding such binaries as plain INT bytes hides the original FPU
+ instruction and misaligns the following bytes, so the disassembler
+ can optionally swallow the emulator arguments and show the
+ equivalent FPU instruction as a comment. */
+enum emu8087_kind
+{
+ emu8087_none,
+ emu8087_intel,
+ emu8087_microsoft,
+ emu8087_unknown
+};
+
+static enum emu8087_kind emu8087_value_from_name (const char *);
+static enum emu8087_kind emu8087_parse_token (const char *);
+static int try_emu8087 (instr_info *, enum emu8087_kind, int);
+static int emu_failed_int (instr_info *, int);
+static bool OP_ST (instr_info *, int, int);
+static bool OP_STi (instr_info *, int, int);
+
/* Mark parts used in the REX prefix. When we are testing for
empty prefix (for 8bit register REX extension), just mask it
out. Otherwise test for REX bit is excuse for existence of REX
@@ -9069,6 +9092,35 @@ prefix_name (enum address_mode mode, uint8_t pref, int sizeflag)
}
}
+static enum emu8087_kind
+emu8087_value_from_name (const char *name)
+{
+ if (strcasecmp (name, "intel") == 0)
+ return emu8087_intel;
+ if (strcasecmp (name, "microsoft") == 0
+ ||strcasecmp (name, "borland") == 0
+ ||strcasecmp (name, "watcom") == 0
+ ||strcasecmp (name, "ibm") == 0)
+ return emu8087_microsoft;
+ return emu8087_unknown;
+}
+
+
+/* Recognize the fpu-emulation=CONV token, where CONV names the 8087
+ emulation convention used in the code being disassembled.
+ Return the new emu kind, or -1 when TOKEN is not such an option. */
+static enum emu8087_kind
+emu8087_parse_token (const char *token)
+{
+ static const char FPU_EMULATION_PREFIX[] = "fpu-emulation=";
+
+ if (!startswith (token, FPU_EMULATION_PREFIX))
+ return emu8087_none;
+
+ const char *name = token + sizeof(FPU_EMULATION_PREFIX) - 1;
+ return emu8087_value_from_name (name);
+}
+
void
print_i386_disassembler_options (FILE *stream)
{
@@ -9094,6 +9146,10 @@ with the -M switch (multiple options should be separated by commas):\n"));
fprintf (stream, _(" amd64 Display instruction in AMD64 ISA\n"));
fprintf (stream, _(" intel64 Display instruction in Intel64 ISA\n"));
fprintf (stream, _(" annotate-immediates Annotate immediate operands that match symbols\n"));
+ fprintf (stream, _(" fpu-emulation=intel|microsoft|borland|watcom|ibm\n"));
+ fprintf (stream, _(" Decode 8087 emulation INT sequences for code\n"));
+ fprintf (stream, _(" using the given convention and show the\n"));
+ fprintf (stream, _(" equivalent FPU instruction as a comment\n"));
}
/* Bad opcode. */
@@ -9777,6 +9833,7 @@ print_insn (bfd_vma pc, disassemble_info *info, int intel_syntax)
int needcomma;
bool intel_swap_2_3;
int sizeflag, orig_sizeflag;
+ enum emu8087_kind emu8087;
const char *p;
struct dis_private priv;
int prefix_length;
@@ -9806,6 +9863,7 @@ print_insn (bfd_vma pc, disassemble_info *info, int intel_syntax)
char cm_out[MAX_OPERANDS][COMMENT_BUFFER_SIZE];
priv.orig_sizeflag = AFLAG | DFLAG;
+ emu8087 = emu8087_none;
if ((info->mach & bfd_mach_i386_i386) != 0)
ins.address_mode = mode_32bit;
else if (info->mach == bfd_mach_i386_i8086)
@@ -9878,6 +9936,12 @@ print_insn (bfd_vma pc, disassemble_info *info, int intel_syntax)
priv.orig_sizeflag |= SUFFIX_ALWAYS;
else if (startswith (p, "annotate"))
ins.annotate_immediates = true;
+ else
+ {
+ enum emu8087_kind emu_value = emu8087_parse_token (p);
+ if (emu_value < emu8087_unknown)
+ emu8087 = emu_value;
+ }
p = strchr (p, ',');
if (p != NULL)
@@ -9953,6 +10017,24 @@ print_insn (bfd_vma pc, disassemble_info *info, int intel_syntax)
goto out;
}
+ if (emu8087 != emu8087_none)
+ {
+ int emu_len = try_emu8087 (&ins, emu8087, sizeflag);
+ if (emu_len <= 0)
+ emu_len = emu_failed_int (&ins, emu8087);
+ if (emu_len > 0)
+ {
+ info->insn_info_valid = 0;
+ info->branch_delay_insns = 0;
+ info->data_size = 0;
+ info->insn_type = dis_noninsn;
+ info->target = 0;
+ info->target2 = 0;
+ ret = emu_len;
+ goto out;
+ }
+ }
+
ins.two_source_ops = (*ins.codep == 0x62 || *ins.codep == 0xc8);
if ((ins.prefixes & PREFIX_FWAIT)
@@ -10849,6 +10931,887 @@ dofloat (instr_info *ins, int sizeflag)
return true;
}
+/* True when MODRM/SIB addressing uses the 32-bit (SIB capable) form.
+ Both 32-bit and 64-bit modes always use it; 16-bit mode uses it only
+ with an address-size override. Emulator binaries are 16-bit history,
+ but the same INT conventions decode with 32-bit addressing. */
+static bool
+emu_is_32bit_addr (enum address_mode mode, int sizeflag)
+{
+ if (mode == mode_64bit)
+ return true;
+ return (sizeflag & AFLAG) != 0;
+}
+
+static bool
+emu_ensure (instr_info *ins, int total)
+{
+ return fetch_code (ins->info, ins->codep + total);
+}
+
+static bool
+emu_peek (instr_info *ins, int offset, unsigned int *val)
+{
+ if (!emu_ensure (ins, offset + 1))
+ return false;
+ *val = ins->codep[offset];
+ return true;
+}
+
+static int
+emu_disp_len_16 (int mod, int rm)
+{
+ if (mod == 1)
+ return 1;
+ if (mod == 2)
+ return 2;
+ if (mod == 0 && rm == 6)
+ return 2;
+ return 0;
+}
+
+static bool
+emu_sib_present_32 (int mod, int rm)
+{
+ return mod != 3 && rm == 4;
+}
+
+static int
+emu_disp_len_32 (int mod, int rm, bool has_sib, int sib_base)
+{
+ if (mod == 1)
+ return 1;
+ if (mod == 2)
+ return 4;
+ if (mod != 0)
+ return 0;
+ if (has_sib)
+ return sib_base == 5 ? 4 : 0;
+ return rm == 5 ? 4 : 0;
+}
+
+/* Split emulator arguments at MODRM_OFF into instruction bytes and
+ the displacement argument. Sets *DISP_OFF to the first displacement
+ byte (the end of the instruction bytes) and *DISP_LEN to its length
+ (0 when the instruction takes no argument). Returns false when the
+ bytes are not yet available. The caller validated INT + ESC bytes. */
+static bool
+emu_split_args (instr_info *ins, int modrm_off, bool is32,
+ int *disp_off, int *disp_len)
+{
+ unsigned int modrm;
+ int mod;
+ int rm;
+
+ if (!emu_peek (ins, modrm_off, &modrm))
+ return false;
+ mod = (modrm >> 6) & 3;
+ rm = modrm & 7;
+ if (mod == 3)
+ {
+ *disp_off = modrm_off + 1;
+ *disp_len = 0;
+ return true;
+ }
+ if (!is32)
+ {
+ *disp_len = emu_disp_len_16 (mod, rm);
+ *disp_off = modrm_off + 1;
+ return emu_ensure (ins, *disp_off + *disp_len);
+ }
+ if (!emu_sib_present_32 (mod, rm))
+ {
+ *disp_len = emu_disp_len_32 (mod, rm, false, 0);
+ *disp_off = modrm_off + 1;
+ return emu_ensure (ins, *disp_off + *disp_len);
+ }
+ {
+ unsigned int sib;
+
+ if (!emu_peek (ins, modrm_off + 1, &sib))
+ return false;
+ *disp_len = emu_disp_len_32 (mod, rm, true, sib & 7);
+ *disp_off = modrm_off + 2;
+ return emu_ensure (ins, *disp_off + *disp_len);
+ }
+}
+
+/* Advance past "{alt|...}": Intel syntax takes the second
+ alternative, AT&T syntax just steps past the opening brace. */
+static const char *
+emu_template_alt (const char *tmpl, bool intel_syntax)
+{
+ if (!intel_syntax)
+ return tmpl + 1;
+ do
+ tmpl++;
+ while (*tmpl != '|' && *tmpl != '}' && *tmpl != '\0');
+ if (*tmpl == '|')
+ tmpl++;
+ return tmpl;
+}
+
+/* Advance past the rest of a "{...}" template to its closing brace. */
+static const char *
+emu_template_end (const char *tmpl)
+{
+ while (*tmpl != '}' && *tmpl != '\0')
+ tmpl++;
+ if (*tmpl == '}')
+ tmpl++;
+ return tmpl;
+}
+
+/* Append the 'r' of a "{[!]M|r}" reverse form when INTEL_MNEMONIC
+ disagrees with COND, as putop does. Returns the new length. */
+static size_t
+emu_append_mark (char *out, size_t size, size_t i,
+ bool intel_mnemonic, int cond)
+{
+ if (i + 1 < size && intel_mnemonic != cond)
+ {
+ out[i] = 'r';
+ return i + 1;
+ }
+ return i;
+}
+
+/* Copy the mnemonic of a float template the way putop renders it
+ for the wait-inclusive instruction: "{s|}"-style size selections
+ are kept verbatim in AT&T ("fadds") and dropped in Intel ("fadd"),
+ "{!M|r}"/"{M|}" reverse forms take their 'r' ("fsubrp" and
+ friends), the lone "C" code is consumed without output ("fldenv",
+ never "fldenvC"), and the "N" wait code is consumed without output
+ too: an emulated sequence stands for the whole wait-inclusive
+ instruction (the wait itself is emulated separately), so "fNinit"
+ renders as "finit", never "fninit". */
+static void
+emu_mnemonic_base (const char *tmpl, char *out, size_t size,
+ bool intel_syntax, bool intel_mnemonic)
+{
+ size_t i = 0;
+ int cond = 1;
+
+ if (tmpl == NULL)
+ tmpl = "(bad)";
+ while (i + 1 < size && *tmpl != '\0')
+ if (*tmpl == '{')
+ tmpl = emu_template_alt (tmpl, intel_syntax);
+ else if (*tmpl == '|' || *tmpl == '}')
+ tmpl = emu_template_end (tmpl);
+ else if (*tmpl == '!')
+ {
+ cond = 0;
+ tmpl++;
+ }
+ else if (*tmpl == 'M')
+ {
+ i = emu_append_mark (out, size, i, intel_mnemonic, cond);
+ tmpl++;
+ }
+ else if (*tmpl == 'C' || *tmpl == 'N')
+ tmpl++;
+ else
+ {
+ out[i] = *tmpl;
+ i++;
+ tmpl++;
+ }
+ out[i] = '\0';
+}
+
+/* Read a little-endian displacement of DISP_LEN bytes at DISP_OFF. */
+static unsigned int
+emu_read_disp (instr_info *ins, int disp_off, int disp_len)
+{
+ int i;
+ unsigned int value = 0;
+
+ for (i = 0; i < disp_len; i++)
+ {
+ unsigned int byte;
+
+ if (!emu_peek (ins, disp_off + i, &byte))
+ return 0;
+ value |= byte << (8 * i);
+ }
+ return value;
+}
+
+/* Sign-extend a DISP_LEN-byte displacement for display, so a negative
+ register-relative offset prints as "-0x2" rather than "+0xfe". */
+static int
+emu_signed_disp (unsigned int disp, int disp_len)
+{
+ if (disp_len == 1)
+ return (int) (int8_t) disp;
+ if (disp_len == 2)
+ return (int) (int16_t) disp;
+ return (int) disp;
+}
+
+/* Format a register-relative displacement: "+0x10" or "-0x2" joined
+ after registers in Intel syntax, "0x10" or "-0x2" before "(...)"
+ in AT&T syntax, or "" when DISP_LEN is 0. */
+static void
+emu_format_rel_disp (char *out, size_t size, unsigned int disp, int disp_len,
+ bool intel_syntax)
+{
+ int sdisp;
+ unsigned int mag;
+
+ if (disp_len == 0)
+ {
+ out[0] = '\0';
+ return;
+ }
+ sdisp = emu_signed_disp (disp, disp_len);
+ if (sdisp < 0)
+ {
+ mag = 0u - (unsigned int) sdisp;
+ snprintf (out, size, "-0x%x", mag);
+ }
+ else if (intel_syntax)
+ snprintf (out, size, "+0x%x", disp);
+ else
+ snprintf (out, size, "0x%x", disp);
+}
+
+/* Segment register name in the selected syntax: AT&T unless Intel
+ was requested. SEG is 0=ES,1=CS,2=SS,3=DS. */
+static const char *
+emu_seg_name (int seg, bool intel_syntax)
+{
+ if (intel_syntax)
+ switch (seg)
+ {
+ case 0:
+ return "es";
+ case 1:
+ return "cs";
+ case 2:
+ return "ss";
+ case 3:
+ return "ds";
+ default:
+ return "ds";
+ }
+ switch (seg)
+ {
+ case 0:
+ return "%es";
+ case 1:
+ return "%cs";
+ case 2:
+ return "%ss";
+ case 3:
+ return "%ds";
+ default:
+ return "%ds";
+ }
+}
+
+/* 32-bit register name in the selected syntax: AT&T unless Intel
+ was requested. */
+static const char *
+emu_reg32_name (int reg, bool intel_syntax)
+{
+ if (intel_syntax)
+ switch (reg)
+ {
+ case 0:
+ return "eax";
+ case 1:
+ return "ecx";
+ case 2:
+ return "edx";
+ case 3:
+ return "ebx";
+ case 4:
+ return "esp";
+ case 5:
+ return "ebp";
+ case 6:
+ return "esi";
+ case 7:
+ return "edi";
+ default:
+ return "eax";
+ }
+ switch (reg)
+ {
+ case 0:
+ return "%eax";
+ case 1:
+ return "%ecx";
+ case 2:
+ return "%edx";
+ case 3:
+ return "%ebx";
+ case 4:
+ return "%esp";
+ case 5:
+ return "%ebp";
+ case 6:
+ return "%esi";
+ case 7:
+ return "%edi";
+ default:
+ return "%eax";
+ }
+}
+
+/* Intel memory size directive (with trailing space) for an emulated
+ operand mode, or the empty string when the instruction takes none. */
+static const char *
+emu_size_prefix (int mode)
+{
+ switch (mode)
+ {
+ case w_mode:
+ return "WORD PTR ";
+ case d_mode:
+ return "DWORD PTR ";
+ case q_mode:
+ return "QWORD PTR ";
+ case t_mode:
+ return "TBYTE PTR ";
+ default:
+ return "";
+ }
+}
+
+/* Format a 16-bit MODRM address. SEG is -1 or 0=ES,1=CS,2=SS,3=DS.
+ DISP_LEN is 0 when no displacement is present. */
+static void
+emu_format_addr_16 (char *out, size_t size, bool intel_syntax,
+ int mod, int rm, unsigned int disp, int disp_len, int seg)
+{
+ static const char * const base_att[8] =
+ { "%bx", "%bx", "%bp", "%bp", "%si", "%di", "%bp", "%bx" };
+ static const char * const base_intel[8] =
+ { "bx", "bx", "bp", "bp", "si", "di", "bp", "bx" };
+ static const char * const index_att[8] =
+ { "%si", "%di", "%si", "%di", NULL, NULL, NULL, NULL };
+ static const char * const index_intel[8] =
+ { "si", "di", "si", "di", NULL, NULL, NULL, NULL };
+ const char *base = NULL;
+ const char *index = NULL;
+
+ if (!(mod == 0 && rm == 6))
+ {
+ base = intel_syntax ? base_intel[rm] : base_att[rm];
+ index = intel_syntax ? index_intel[rm] : index_att[rm];
+ }
+ if (intel_syntax)
+ {
+ char core[96];
+ const char *seg_name = NULL;
+
+ core[0] = '\0';
+ if (seg >= 0)
+ seg_name = emu_seg_name (seg, true);
+ if (base != NULL)
+ snprintf (core + strlen (core), sizeof core - strlen (core),
+ "%s", base);
+ if (index != NULL)
+ snprintf (core + strlen (core), sizeof core - strlen (core),
+ "%s%s", base != NULL ? "+" : "", index);
+ if (base != NULL || index != NULL)
+ {
+ char rel[24];
+
+ emu_format_rel_disp (rel, sizeof rel, disp, disp_len, true);
+ snprintf (core + strlen (core), sizeof core - strlen (core),
+ "%s", rel);
+ }
+ else if (disp_len != 0)
+ snprintf (core + strlen (core), sizeof core - strlen (core),
+ "%s0x%x", core[0] != '\0' ? "+" : "", disp);
+ if (core[0] == '\0')
+ snprintf (core, sizeof core, "0x%x", disp);
+ if (base != NULL || index != NULL)
+ {
+ if (seg_name != NULL)
+ snprintf (out, size, "%s:[%s]", seg_name, core);
+ else
+ snprintf (out, size, "[%s]", core);
+ }
+ else if (seg_name != NULL)
+ snprintf (out, size, "%s:%s", seg_name, core);
+ else
+ snprintf (out, size, "%s:%s", emu_seg_name (3, true), core);
+ return;
+ }
+ {
+ char segbuf[8];
+ const char *seg_pfx = "";
+
+ if (seg >= 0)
+ {
+ snprintf (segbuf, sizeof segbuf, "%s:",
+ emu_seg_name (seg, false));
+ seg_pfx = segbuf;
+ }
+ if (base == NULL)
+ snprintf (out, size, "%s0x%x", seg_pfx, disp);
+ else
+ {
+ char rel[24];
+
+ emu_format_rel_disp (rel, sizeof rel, disp, disp_len, false);
+ if (index == NULL)
+ snprintf (out, size, "%s%s(%s)", seg_pfx, rel, base);
+ else
+ snprintf (out, size, "%s%s(%s,%s)", seg_pfx, rel, base, index);
+ }
+ }
+}
+
+/* Print the "int" head padded like the core disassembler pads
+ mnemonics (to 7 columns), so $0xNN aligns with neighbouring lines. */
+static void
+emu_print_int_op (instr_info *ins, unsigned int int_no)
+{
+ bool intel_syntax = ins->intel_syntax != 0;
+
+ i386_dis_printf (ins->info, dis_style_mnemonic, "int ");
+ if (intel_syntax)
+ i386_dis_printf (ins->info, dis_style_text, "%#x", int_no);
+ else
+ i386_dis_printf (ins->info, dis_style_text, "$%#x", int_no);
+}
+
+static void emu_format_addr_32 (char *, size_t, bool,
+ int, int, unsigned int, bool,
+ unsigned int, int, int);
+
+/* "%st(i)"/"st(i)" for an STi operand, "%st"/"st" for ST, in the
+ selected syntax: AT&T unless Intel was requested. */
+static void
+emu_st_text (bool is_sti, int rm, bool intel_syntax, char *out, size_t size)
+{
+ if (is_sti)
+ {
+ if (intel_syntax)
+ snprintf (out, size, "st(%d)", rm);
+ else
+ snprintf (out, size, "%%st(%d)", rm);
+ }
+ else if (intel_syntax)
+ snprintf (out, size, "st");
+ else
+ snprintf (out, size, "%%st");
+}
+
+/* FPU register form (MOD==3) as comment text, WAIT form. Operands
+ follow the selected syntax: AT&T unless Intel was requested. Like
+ the core printer, two operands print as op[0],op[1] in Intel order
+ and reversed in AT&T order. */
+static void
+emu_fpu_reg_text (int esc, unsigned int modrm, bool intel_syntax,
+ bool intel_mnemonic, char *out, size_t size)
+{
+ int reg = (modrm >> 3) & 7;
+ int rm = modrm & 7;
+ const struct dis386 *dp = &float_reg[esc - 0xd8][reg];
+ char base[32];
+ char first[16];
+ char second[16];
+
+ if (dp->name == NULL)
+ {
+ const char *tmpl = fgrps[dp->op[0].bytemode][rm];
+ emu_mnemonic_base (tmpl, base, sizeof base,
+ intel_syntax, intel_mnemonic);
+ if (esc == 0xdf && modrm == 0xe0)
+ {
+ if (intel_syntax)
+ snprintf (out, size, "%s ax", base);
+ else
+ snprintf (out, size, "%s %%ax", base);
+ return;
+ }
+ snprintf (out, size, "%s", base);
+ return;
+ }
+ emu_mnemonic_base (dp->name, base, sizeof base,
+ intel_syntax, intel_mnemonic);
+ emu_st_text (dp->op[0].rtn == OP_STi, rm, intel_syntax,
+ first, sizeof first);
+ if (dp->op[1].rtn == NULL)
+ {
+ snprintf (out, size, "%s %s", base, first);
+ return;
+ }
+ emu_st_text (dp->op[1].rtn == OP_STi, rm, intel_syntax,
+ second, sizeof second);
+ if (intel_syntax)
+ snprintf (out, size, "%s %s,%s", base, first, second);
+ else
+ snprintf (out, size, "%s %s,%s", base, second, first);
+}
+
+/* FPU memory form (MOD!=3) as comment text. SEG is -1 or segment. */
+static void
+emu_fpu_mem_text (instr_info *ins, int esc, int modrm_off,
+ int seg, bool is32, char *out, size_t size)
+{
+ unsigned int modrm = ins->codep[modrm_off];
+ int mod = (modrm >> 6) & 3;
+ int reg = (modrm >> 3) & 7;
+ int rm = modrm & 7;
+ int fp_indx = (esc - 0xd8) * 8 + reg;
+ char base[32];
+ char addr[128];
+ bool intel_syntax = ins->intel_syntax != 0;
+
+ emu_mnemonic_base (float_mem[fp_indx], base, sizeof base,
+ intel_syntax, ins->intel_mnemonic);
+ if (!is32)
+ {
+ int disp_len = emu_disp_len_16 (mod, rm);
+ unsigned int disp = emu_read_disp (ins, modrm_off + 1, disp_len);
+ emu_format_addr_16 (addr, sizeof addr, intel_syntax,
+ mod, rm, disp, disp_len, seg);
+ }
+ else
+ {
+ unsigned int sib = 0;
+ bool sib_valid = emu_sib_present_32 (mod, rm);
+ int disp_off = modrm_off + 1;
+ int disp_len;
+ unsigned int disp;
+ if (sib_valid)
+ {
+ sib = ins->codep[modrm_off + 1];
+ disp_off = modrm_off + 2;
+ }
+ disp_len = emu_disp_len_32 (mod, rm, sib_valid, sib & 7);
+ disp = emu_read_disp (ins, disp_off, disp_len);
+ emu_format_addr_32 (addr, sizeof addr, intel_syntax,
+ mod, rm, sib, sib_valid, disp, disp_len, seg);
+ }
+ if (intel_syntax)
+ snprintf (out, size, "%s %s%s", base,
+ emu_size_prefix (float_mem_mode[fp_indx]), addr);
+ else
+ snprintf (out, size, "%s %s", base, addr);
+}
+
+static void
+emu_fpu_text (instr_info *ins, int esc, int modrm_off,
+ int seg, bool is32, char *out, size_t size)
+{
+ unsigned int modrm = ins->codep[modrm_off];
+ int mod = (modrm >> 6) & 3;
+ bool intel_syntax = ins->intel_syntax != 0;
+
+ if (mod == 3)
+ emu_fpu_reg_text (esc, modrm, intel_syntax, ins->intel_mnemonic,
+ out, size);
+ else
+ emu_fpu_mem_text (ins, esc, modrm_off, seg, is32, out, size);
+}
+
+/* Format a 32-bit (or 64-bit) MODRM/SIB address. SIB_VALID is false
+ when no SIB byte is present. SEG is -1 or 0=ES,1=CS,2=SS,3=DS. */
+static void
+emu_format_addr_32 (char *out, size_t size, bool intel_syntax,
+ int mod, int rm, unsigned int sib, bool sib_valid,
+ unsigned int disp, int disp_len, int seg)
+{
+ const char *base = NULL;
+ const char *index = NULL;
+ int scale = 1;
+ char base_buf[16];
+ char index_buf[16];
+
+ if (sib_valid)
+ {
+ int sib_base = sib & 7;
+ int sib_index = (sib >> 3) & 7;
+ scale = 1 << ((sib >> 6) & 3);
+ if (sib_index != 4)
+ {
+ snprintf (index_buf, sizeof index_buf, "%s",
+ emu_reg32_name (sib_index, intel_syntax));
+ index = index_buf;
+ }
+ if (!(mod == 0 && sib_base == 5))
+ {
+ snprintf (base_buf, sizeof base_buf, "%s",
+ emu_reg32_name (sib_base, intel_syntax));
+ base = base_buf;
+ }
+ }
+ else if (!(mod == 0 && rm == 5))
+ base = emu_reg32_name (rm, intel_syntax);
+ if (intel_syntax)
+ {
+ char core[96];
+ const char *seg_name = NULL;
+ core[0] = '\0';
+ if (seg >= 0)
+ seg_name = emu_seg_name (seg, true);
+ if (base != NULL)
+ snprintf (core + strlen (core), sizeof core - strlen (core),
+ "%s", base);
+ if (index != NULL)
+ snprintf (core + strlen (core), sizeof core - strlen (core),
+ "%s%s*%d", base != NULL ? "+" : "", index, scale);
+ if (base != NULL || index != NULL)
+ {
+ char rel[24];
+
+ emu_format_rel_disp (rel, sizeof rel, disp, disp_len, true);
+ snprintf (core + strlen (core), sizeof core - strlen (core),
+ "%s", rel);
+ }
+ else if (disp_len != 0)
+ snprintf (core + strlen (core), sizeof core - strlen (core),
+ "%s0x%x", core[0] != '\0' ? "+" : "", disp);
+ if (core[0] == '\0')
+ snprintf (core, sizeof core, "0x%x", disp);
+ if (base != NULL || index != NULL)
+ {
+ if (seg_name != NULL)
+ snprintf (out, size, "%s:[%s]", seg_name, core);
+ else
+ snprintf (out, size, "[%s]", core);
+ }
+ else if (seg_name != NULL)
+ snprintf (out, size, "%s:%s", seg_name, core);
+ else
+ snprintf (out, size, "%s:%s", emu_seg_name (3, true), core);
+ return;
+ }
+ {
+ char base_s[32];
+ char index_s[32];
+ char segbuf[8];
+ const char *seg_pfx = "";
+ base_s[0] = '\0';
+ index_s[0] = '\0';
+ if (seg >= 0)
+ {
+ snprintf (segbuf, sizeof segbuf, "%s:",
+ emu_seg_name (seg, false));
+ seg_pfx = segbuf;
+ }
+ if (base != NULL)
+ snprintf (base_s, sizeof base_s, "%s", base);
+ if (index != NULL)
+ snprintf (index_s, sizeof index_s, ",%s,%d", index, scale);
+ if (base == NULL && index == NULL)
+ snprintf (out, size, "%s0x%x", seg_pfx, disp);
+ else
+ {
+ char rel[24];
+
+ emu_format_rel_disp (rel, sizeof rel, disp, disp_len, false);
+ snprintf (out, size, "%s%s(%s%s)", seg_pfx, rel, base_s, index_s);
+ }
+ }
+}
+
+/* Print "int $0xNN # <fpu>" for a decoded emulator sequence,
+ with the FPU instruction and its operands in the selected syntax
+ (AT&T unless Intel was requested). */
+static void
+emu_print_decoded (instr_info *ins, unsigned int int_no, const char *fpu)
+{
+ emu_print_int_op (ins, int_no);
+ i386_dis_printf (ins->info, dis_style_comment_start, " # ");
+ i386_dis_printf (ins->info, dis_style_text, "%s", fpu);
+}
+
+static int
+emu_microsoft_wait (instr_info *ins, unsigned int int_no)
+{
+ if (int_no != 0x3d)
+ return 0;
+ emu_print_decoded (ins, int_no, "fwait");
+ return 2;
+}
+
+static int
+emu_microsoft_normal (instr_info *ins, unsigned int int_no, bool is32)
+{
+ int esc;
+ int disp_off;
+ int disp_len;
+ char fpu[256];
+
+ if (int_no < 0x34 || int_no > 0x3b)
+ return 0;
+ esc = 0xd8 + (int_no - 0x34);
+ if (!emu_split_args (ins, 2, is32, &disp_off, &disp_len))
+ return 0;
+ emu_fpu_text (ins, esc, 2, -1, is32, fpu, sizeof fpu);
+ emu_print_decoded (ins, int_no, fpu);
+ return disp_off + disp_len;
+}
+
+/* Segment index (0=ES,1=CS,2=SS,3=DS) coded in a Microsoft INT 3Ch
+ prime byte's top two bits (00=DS,01=SS,10=CS,11=ES). */
+static int
+emu_seg_from_prime (unsigned int prime)
+{
+ if ((prime & 0xc0) == 0x00)
+ return 3;
+ if ((prime & 0xc0) == 0x40)
+ return 2;
+ if ((prime & 0xc0) == 0x80)
+ return 1;
+ return 0;
+}
+
+static int
+emu_microsoft_seg (instr_info *ins, unsigned int int_no, bool is32)
+{
+ unsigned int prime;
+ int esc;
+ int disp_off;
+ int disp_len;
+ char fpu[256];
+
+ if (int_no != 0x3c)
+ return 0;
+ if (!emu_peek (ins, 2, &prime))
+ return 0;
+ if ((prime & 0x38) != 0x18)
+ return 0;
+ esc = 0xd8 | (prime & 7);
+ if (!emu_split_args (ins, 3, is32, &disp_off, &disp_len))
+ return 0;
+ emu_fpu_text (ins, esc, 3, emu_seg_from_prime (prime), is32,
+ fpu, sizeof fpu);
+ emu_print_decoded (ins, int_no, fpu);
+ return disp_off + disp_len;
+}
+
+static int
+emu_try_microsoft (instr_info *ins, unsigned int int_no, bool is32)
+{
+ int len = emu_microsoft_wait (ins, int_no);
+ if (len != 0)
+ return len;
+ len = emu_microsoft_normal (ins, int_no, is32);
+ if (len != 0)
+ return len;
+ return emu_microsoft_seg (ins, int_no, is32);
+}
+
+static int
+emu_intel_normal (instr_info *ins, unsigned int int_no, bool is32)
+{
+ int esc;
+ int disp_off;
+ int disp_len;
+ char fpu[256];
+
+ if (int_no < 0x18 || int_no > 0x1f)
+ return 0;
+ esc = 0xd8 + (int_no - 0x18);
+ if (!emu_split_args (ins, 2, is32, &disp_off, &disp_len))
+ return 0;
+ emu_fpu_text (ins, esc, 2, -1, is32, fpu, sizeof fpu);
+ emu_print_decoded (ins, int_no, fpu);
+ return disp_off + disp_len;
+}
+
+static int
+emu_intel_seg (instr_info *ins, unsigned int int_no, bool is32)
+{
+ unsigned int esc_byte;
+ int disp_off;
+ int disp_len;
+ char fpu[256];
+
+ if (int_no < 0x14 || int_no > 0x17)
+ return 0;
+ if (!emu_peek (ins, 2, &esc_byte))
+ return 0;
+ if (esc_byte < 0xd8 || esc_byte > 0xdf)
+ return 0;
+ /* INT 14h-17h selects ES,CS,SS,DS in that order. */
+ if (!emu_split_args (ins, 3, is32, &disp_off, &disp_len))
+ return 0;
+ emu_fpu_text (ins, esc_byte, 3, (int) (int_no - 0x14), is32,
+ fpu, sizeof fpu);
+ emu_print_decoded (ins, int_no, fpu);
+ return disp_off + disp_len;
+}
+
+static int
+emu_try_intel (instr_info *ins, unsigned int int_no, bool is32)
+{
+ int len = emu_intel_normal (ins, int_no, is32);
+ if (len != 0)
+ return len;
+ return emu_intel_seg (ins, int_no, is32);
+}
+
+/* True when INT_NO belongs to the convention's emulator range,
+ whether or not its arguments go on to decode. */
+static bool
+emu_int_in_convention (int emu_kind, unsigned int int_no)
+{
+ if (emu_kind == emu8087_microsoft)
+ return int_no >= 0x34 && int_no <= 0x3d;
+ if (emu_kind == emu8087_intel)
+ return (int_no >= 0x14 && int_no <= 0x17)
+ || (int_no >= 0x18 && int_no <= 0x1f);
+ return false;
+}
+
+/* Print a bare INT with a "# ?" marker for an in-convention interrupt
+ whose emulator arguments failed to decode (truncated or corrupt),
+ so it cannot pass as a genuine plain interrupt. Returns 2 when
+ printed, else 0. Only runs after try_emu8087 declined the bytes. */
+static int
+emu_failed_int (instr_info *ins, int emu_kind)
+{
+ unsigned int b0;
+ unsigned int int_no;
+
+ if (ins->nr_prefixes != 0)
+ return 0;
+ if (!emu_peek (ins, 0, &b0) || b0 != 0xcd)
+ return 0;
+ if (!emu_peek (ins, 1, &int_no))
+ return 0;
+ if (!emu_int_in_convention (emu_kind, int_no))
+ return 0;
+ emu_print_int_op (ins, int_no);
+ i386_dis_printf (ins->info, dis_style_comment_start, " # ?");
+ return 2;
+}
+
+static int
+try_emu8087 (instr_info *ins, enum emu8087_kind emu_kind, int sizeflag)
+{
+ unsigned int b0;
+ unsigned int int_no;
+ bool is32;
+
+ if (ins->nr_prefixes != 0)
+ return 0;
+ if (!emu_peek (ins, 0, &b0))
+ return 0;
+ if (b0 != 0xcd)
+ return 0;
+ if (!emu_peek (ins, 1, &int_no))
+ return 0;
+ is32 = emu_is_32bit_addr (ins->address_mode, sizeflag);
+ if (emu_kind == emu8087_microsoft)
+ return emu_try_microsoft (ins, int_no, is32);
+ if (emu_kind == emu8087_intel)
+ return emu_try_intel (ins, int_no, is32);
+ return 0;
+}
+
static bool
OP_ST (instr_info *ins, int bytemode ATTRIBUTE_UNUSED,
int sizeflag ATTRIBUTE_UNUSED)