mirror of
https://github.com/autc04/Retro68.git
synced 2024-11-17 04:07:23 +00:00
d21be3b4e1
(from https://sourceware.org/elfutils/, GPL/LGPL licensed)
1150 lines
25 KiB
C
1150 lines
25 KiB
C
/* Disassembler for x86.
|
|
Copyright (C) 2007, 2008, 2009, 2011 Red Hat, Inc.
|
|
This file is part of elfutils.
|
|
Written by Ulrich Drepper <drepper@redhat.com>, 2007.
|
|
|
|
This file is free software; you can redistribute it and/or modify
|
|
it under the terms of either
|
|
|
|
* the GNU Lesser General Public License as published by the Free
|
|
Software Foundation; either version 3 of the License, or (at
|
|
your option) any later version
|
|
|
|
or
|
|
|
|
* the GNU General Public License as published by the Free
|
|
Software Foundation; either version 2 of the License, or (at
|
|
your option) any later version
|
|
|
|
or both in parallel, as here.
|
|
|
|
elfutils 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 copies of the GNU General Public License and
|
|
the GNU Lesser General Public License along with this program. If
|
|
not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <config.h>
|
|
#include <ctype.h>
|
|
#include <endian.h>
|
|
#include <errno.h>
|
|
#include <gelf.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "../libebl/libeblP.h"
|
|
|
|
#define MACHINE_ENCODING __LITTLE_ENDIAN
|
|
#include "memory-access.h"
|
|
|
|
|
|
#ifndef MNEFILE
|
|
# define MNEFILE "i386.mnemonics"
|
|
#endif
|
|
|
|
#define MNESTRFIELD(line) MNESTRFIELD1 (line)
|
|
#define MNESTRFIELD1(line) str##line
|
|
static const union mnestr_t
|
|
{
|
|
struct
|
|
{
|
|
#define MNE(name) char MNESTRFIELD (__LINE__)[sizeof (#name)];
|
|
#include MNEFILE
|
|
#undef MNE
|
|
};
|
|
char str[0];
|
|
} mnestr =
|
|
{
|
|
{
|
|
#define MNE(name) #name,
|
|
#include MNEFILE
|
|
#undef MNE
|
|
}
|
|
};
|
|
|
|
/* The index can be stored in the instrtab. */
|
|
enum
|
|
{
|
|
#define MNE(name) MNE_##name,
|
|
#include MNEFILE
|
|
#undef MNE
|
|
MNE_INVALID
|
|
};
|
|
|
|
static const unsigned short int mneidx[] =
|
|
{
|
|
#define MNE(name) \
|
|
[MNE_##name] = offsetof (union mnestr_t, MNESTRFIELD (__LINE__)),
|
|
#include MNEFILE
|
|
#undef MNE
|
|
};
|
|
|
|
|
|
enum
|
|
{
|
|
idx_rex_b = 0,
|
|
idx_rex_x,
|
|
idx_rex_r,
|
|
idx_rex_w,
|
|
idx_rex,
|
|
idx_cs,
|
|
idx_ds,
|
|
idx_es,
|
|
idx_fs,
|
|
idx_gs,
|
|
idx_ss,
|
|
idx_data16,
|
|
idx_addr16,
|
|
idx_rep,
|
|
idx_repne,
|
|
idx_lock
|
|
};
|
|
|
|
enum
|
|
{
|
|
#define prefbit(pref) has_##pref = 1 << idx_##pref
|
|
prefbit (rex_b),
|
|
prefbit (rex_x),
|
|
prefbit (rex_r),
|
|
prefbit (rex_w),
|
|
prefbit (rex),
|
|
prefbit (cs),
|
|
prefbit (ds),
|
|
prefbit (es),
|
|
prefbit (fs),
|
|
prefbit (gs),
|
|
prefbit (ss),
|
|
prefbit (data16),
|
|
prefbit (addr16),
|
|
prefbit (rep),
|
|
prefbit (repne),
|
|
prefbit (lock)
|
|
#undef prefbit
|
|
};
|
|
#define SEGMENT_PREFIXES \
|
|
(has_cs | has_ds | has_es | has_fs | has_gs | has_ss)
|
|
|
|
#define prefix_cs 0x2e
|
|
#define prefix_ds 0x3e
|
|
#define prefix_es 0x26
|
|
#define prefix_fs 0x64
|
|
#define prefix_gs 0x65
|
|
#define prefix_ss 0x36
|
|
#define prefix_data16 0x66
|
|
#define prefix_addr16 0x67
|
|
#define prefix_rep 0xf3
|
|
#define prefix_repne 0xf2
|
|
#define prefix_lock 0xf0
|
|
|
|
|
|
static const uint8_t known_prefixes[] =
|
|
{
|
|
#define newpref(pref) [idx_##pref] = prefix_##pref
|
|
newpref (cs),
|
|
newpref (ds),
|
|
newpref (es),
|
|
newpref (fs),
|
|
newpref (gs),
|
|
newpref (ss),
|
|
newpref (data16),
|
|
newpref (addr16),
|
|
newpref (rep),
|
|
newpref (repne),
|
|
newpref (lock)
|
|
#undef newpref
|
|
};
|
|
#define nknown_prefixes (sizeof (known_prefixes) / sizeof (known_prefixes[0]))
|
|
|
|
|
|
#if 0
|
|
static const char *prefix_str[] =
|
|
{
|
|
#define newpref(pref) [idx_##pref] = #pref
|
|
newpref (cs),
|
|
newpref (ds),
|
|
newpref (es),
|
|
newpref (fs),
|
|
newpref (gs),
|
|
newpref (ss),
|
|
newpref (data16),
|
|
newpref (addr16),
|
|
newpref (rep),
|
|
newpref (repne),
|
|
newpref (lock)
|
|
#undef newpref
|
|
};
|
|
#endif
|
|
|
|
|
|
static const char amd3dnowstr[] =
|
|
#define MNE_3DNOW_PAVGUSB 1
|
|
"pavgusb\0"
|
|
#define MNE_3DNOW_PFADD (MNE_3DNOW_PAVGUSB + 8)
|
|
"pfadd\0"
|
|
#define MNE_3DNOW_PFSUB (MNE_3DNOW_PFADD + 6)
|
|
"pfsub\0"
|
|
#define MNE_3DNOW_PFSUBR (MNE_3DNOW_PFSUB + 6)
|
|
"pfsubr\0"
|
|
#define MNE_3DNOW_PFACC (MNE_3DNOW_PFSUBR + 7)
|
|
"pfacc\0"
|
|
#define MNE_3DNOW_PFCMPGE (MNE_3DNOW_PFACC + 6)
|
|
"pfcmpge\0"
|
|
#define MNE_3DNOW_PFCMPGT (MNE_3DNOW_PFCMPGE + 8)
|
|
"pfcmpgt\0"
|
|
#define MNE_3DNOW_PFCMPEQ (MNE_3DNOW_PFCMPGT + 8)
|
|
"pfcmpeq\0"
|
|
#define MNE_3DNOW_PFMIN (MNE_3DNOW_PFCMPEQ + 8)
|
|
"pfmin\0"
|
|
#define MNE_3DNOW_PFMAX (MNE_3DNOW_PFMIN + 6)
|
|
"pfmax\0"
|
|
#define MNE_3DNOW_PI2FD (MNE_3DNOW_PFMAX + 6)
|
|
"pi2fd\0"
|
|
#define MNE_3DNOW_PF2ID (MNE_3DNOW_PI2FD + 6)
|
|
"pf2id\0"
|
|
#define MNE_3DNOW_PFRCP (MNE_3DNOW_PF2ID + 6)
|
|
"pfrcp\0"
|
|
#define MNE_3DNOW_PFRSQRT (MNE_3DNOW_PFRCP + 6)
|
|
"pfrsqrt\0"
|
|
#define MNE_3DNOW_PFMUL (MNE_3DNOW_PFRSQRT + 8)
|
|
"pfmul\0"
|
|
#define MNE_3DNOW_PFRCPIT1 (MNE_3DNOW_PFMUL + 6)
|
|
"pfrcpit1\0"
|
|
#define MNE_3DNOW_PFRSQIT1 (MNE_3DNOW_PFRCPIT1 + 9)
|
|
"pfrsqit1\0"
|
|
#define MNE_3DNOW_PFRCPIT2 (MNE_3DNOW_PFRSQIT1 + 9)
|
|
"pfrcpit2\0"
|
|
#define MNE_3DNOW_PMULHRW (MNE_3DNOW_PFRCPIT2 + 9)
|
|
"pmulhrw";
|
|
|
|
#define AMD3DNOW_LOW_IDX 0x0d
|
|
#define AMD3DNOW_HIGH_IDX (sizeof (amd3dnow) + AMD3DNOW_LOW_IDX - 1)
|
|
#define AMD3DNOW_IDX(val) ((val) - AMD3DNOW_LOW_IDX)
|
|
static const unsigned char amd3dnow[] =
|
|
{
|
|
[AMD3DNOW_IDX (0xbf)] = MNE_3DNOW_PAVGUSB,
|
|
[AMD3DNOW_IDX (0x9e)] = MNE_3DNOW_PFADD,
|
|
[AMD3DNOW_IDX (0x9a)] = MNE_3DNOW_PFSUB,
|
|
[AMD3DNOW_IDX (0xaa)] = MNE_3DNOW_PFSUBR,
|
|
[AMD3DNOW_IDX (0xae)] = MNE_3DNOW_PFACC,
|
|
[AMD3DNOW_IDX (0x90)] = MNE_3DNOW_PFCMPGE,
|
|
[AMD3DNOW_IDX (0xa0)] = MNE_3DNOW_PFCMPGT,
|
|
[AMD3DNOW_IDX (0xb0)] = MNE_3DNOW_PFCMPEQ,
|
|
[AMD3DNOW_IDX (0x94)] = MNE_3DNOW_PFMIN,
|
|
[AMD3DNOW_IDX (0xa4)] = MNE_3DNOW_PFMAX,
|
|
[AMD3DNOW_IDX (0x0d)] = MNE_3DNOW_PI2FD,
|
|
[AMD3DNOW_IDX (0x1d)] = MNE_3DNOW_PF2ID,
|
|
[AMD3DNOW_IDX (0x96)] = MNE_3DNOW_PFRCP,
|
|
[AMD3DNOW_IDX (0x97)] = MNE_3DNOW_PFRSQRT,
|
|
[AMD3DNOW_IDX (0xb4)] = MNE_3DNOW_PFMUL,
|
|
[AMD3DNOW_IDX (0xa6)] = MNE_3DNOW_PFRCPIT1,
|
|
[AMD3DNOW_IDX (0xa7)] = MNE_3DNOW_PFRSQIT1,
|
|
[AMD3DNOW_IDX (0xb6)] = MNE_3DNOW_PFRCPIT2,
|
|
[AMD3DNOW_IDX (0xb7)] = MNE_3DNOW_PMULHRW
|
|
};
|
|
|
|
|
|
struct output_data
|
|
{
|
|
GElf_Addr addr;
|
|
int *prefixes;
|
|
size_t opoff1;
|
|
size_t opoff2;
|
|
size_t opoff3;
|
|
char *bufp;
|
|
size_t *bufcntp;
|
|
size_t bufsize;
|
|
const uint8_t *data;
|
|
const uint8_t **param_start;
|
|
const uint8_t *end;
|
|
char *labelbuf;
|
|
size_t labelbufsize;
|
|
enum
|
|
{
|
|
addr_none = 0,
|
|
addr_abs_symbolic,
|
|
addr_abs_always,
|
|
addr_rel_symbolic,
|
|
addr_rel_always
|
|
} symaddr_use;
|
|
GElf_Addr symaddr;
|
|
};
|
|
|
|
|
|
#ifndef DISFILE
|
|
# define DISFILE "i386_dis.h"
|
|
#endif
|
|
#include DISFILE
|
|
|
|
|
|
#define ADD_CHAR(ch) \
|
|
do { \
|
|
if (unlikely (bufcnt == bufsize)) \
|
|
goto enomem; \
|
|
buf[bufcnt++] = (ch); \
|
|
} while (0)
|
|
|
|
#define ADD_STRING(str) \
|
|
do { \
|
|
const char *_str0 = (str); \
|
|
size_t _len0 = strlen (_str0); \
|
|
ADD_NSTRING (_str0, _len0); \
|
|
} while (0)
|
|
|
|
#define ADD_NSTRING(str, len) \
|
|
do { \
|
|
const char *_str = (str); \
|
|
size_t _len = (len); \
|
|
if (unlikely (bufcnt + _len > bufsize)) \
|
|
goto enomem; \
|
|
memcpy (buf + bufcnt, _str, _len); \
|
|
bufcnt += _len; \
|
|
} while (0)
|
|
|
|
|
|
int
|
|
i386_disasm (Ebl *ebl __attribute__((unused)),
|
|
const uint8_t **startp, const uint8_t *end, GElf_Addr addr,
|
|
const char *fmt, DisasmOutputCB_t outcb, DisasmGetSymCB_t symcb,
|
|
void *outcbarg, void *symcbarg)
|
|
{
|
|
const char *save_fmt = fmt;
|
|
|
|
#define BUFSIZE 512
|
|
char initbuf[BUFSIZE];
|
|
int prefixes;
|
|
size_t bufcnt;
|
|
size_t bufsize = BUFSIZE;
|
|
char *buf = initbuf;
|
|
const uint8_t *param_start;
|
|
|
|
struct output_data output_data =
|
|
{
|
|
.prefixes = &prefixes,
|
|
.bufp = buf,
|
|
.bufsize = bufsize,
|
|
.bufcntp = &bufcnt,
|
|
.param_start = ¶m_start,
|
|
.end = end
|
|
};
|
|
|
|
int retval = 0;
|
|
while (1)
|
|
{
|
|
prefixes = 0;
|
|
|
|
const uint8_t *data = *startp;
|
|
const uint8_t *begin = data;
|
|
|
|
/* Recognize all prefixes. */
|
|
int last_prefix_bit = 0;
|
|
while (data < end)
|
|
{
|
|
unsigned int i;
|
|
for (i = idx_cs; i < nknown_prefixes; ++i)
|
|
if (known_prefixes[i] == *data)
|
|
break;
|
|
if (i == nknown_prefixes)
|
|
break;
|
|
|
|
prefixes |= last_prefix_bit = 1 << i;
|
|
|
|
++data;
|
|
}
|
|
|
|
#ifdef X86_64
|
|
if (data < end && (*data & 0xf0) == 0x40)
|
|
prefixes |= ((*data++) & 0xf) | has_rex;
|
|
#endif
|
|
|
|
bufcnt = 0;
|
|
size_t cnt = 0;
|
|
|
|
const uint8_t *curr = match_data;
|
|
const uint8_t *const match_end = match_data + sizeof (match_data);
|
|
|
|
assert (data <= end);
|
|
if (data == end)
|
|
{
|
|
if (prefixes != 0)
|
|
goto print_prefix;
|
|
|
|
retval = -1;
|
|
goto do_ret;
|
|
}
|
|
|
|
next_match:
|
|
while (curr < match_end)
|
|
{
|
|
uint_fast8_t len = *curr++;
|
|
uint_fast8_t clen = len >> 4;
|
|
len &= 0xf;
|
|
const uint8_t *next_curr = curr + clen + (len - clen) * 2;
|
|
|
|
assert (len > 0);
|
|
assert (curr + clen + 2 * (len - clen) <= match_end);
|
|
|
|
const uint8_t *codep = data;
|
|
int correct_prefix = 0;
|
|
int opoff = 0;
|
|
|
|
if (data > begin && codep[-1] == *curr && clen > 0)
|
|
{
|
|
/* We match a prefix byte. This is exactly one byte and
|
|
is matched exactly, without a mask. */
|
|
--len;
|
|
--clen;
|
|
opoff = 8;
|
|
|
|
++curr;
|
|
|
|
assert (last_prefix_bit != 0);
|
|
correct_prefix = last_prefix_bit;
|
|
}
|
|
|
|
size_t avail = len;
|
|
while (clen > 0)
|
|
{
|
|
if (*codep++ != *curr++)
|
|
goto not;
|
|
--avail;
|
|
--clen;
|
|
if (codep == end && avail > 0)
|
|
goto do_ret;
|
|
}
|
|
|
|
while (avail > 0)
|
|
{
|
|
uint_fast8_t masked = *codep++ & *curr++;
|
|
if (masked != *curr++)
|
|
{
|
|
not:
|
|
curr = next_curr;
|
|
++cnt;
|
|
bufcnt = 0;
|
|
goto next_match;
|
|
}
|
|
|
|
--avail;
|
|
if (codep == end && avail > 0)
|
|
goto do_ret;
|
|
}
|
|
|
|
if (len > end - data)
|
|
/* There is not enough data for the entire instruction. The
|
|
caller can figure this out by looking at the pointer into
|
|
the input data. */
|
|
goto do_ret;
|
|
|
|
assert (correct_prefix == 0
|
|
|| (prefixes & correct_prefix) != 0);
|
|
prefixes ^= correct_prefix;
|
|
|
|
if (0)
|
|
{
|
|
/* Resize the buffer. */
|
|
char *oldbuf;
|
|
enomem:
|
|
oldbuf = buf;
|
|
if (buf == initbuf)
|
|
buf = malloc (2 * bufsize);
|
|
else
|
|
buf = realloc (buf, 2 * bufsize);
|
|
if (buf == NULL)
|
|
{
|
|
buf = oldbuf;
|
|
retval = ENOMEM;
|
|
goto do_ret;
|
|
}
|
|
bufsize *= 2;
|
|
|
|
output_data.bufp = buf;
|
|
output_data.bufsize = bufsize;
|
|
bufcnt = 0;
|
|
|
|
if (data == end)
|
|
{
|
|
assert (prefixes != 0);
|
|
goto print_prefix;
|
|
}
|
|
|
|
/* gcc is not clever enough to see the following variables
|
|
are not used uninitialized. */
|
|
asm (""
|
|
: "=mr" (opoff), "=mr" (correct_prefix), "=mr" (codep),
|
|
"=mr" (next_curr), "=mr" (len));
|
|
}
|
|
|
|
size_t prefix_size = 0;
|
|
|
|
// XXXonly print as prefix if valid?
|
|
if ((prefixes & has_lock) != 0)
|
|
{
|
|
ADD_STRING ("lock ");
|
|
prefix_size += 5;
|
|
}
|
|
|
|
if (instrtab[cnt].rep)
|
|
{
|
|
if ((prefixes & has_rep) != 0)
|
|
{
|
|
ADD_STRING ("rep ");
|
|
prefix_size += 4;
|
|
}
|
|
}
|
|
else if (instrtab[cnt].repe
|
|
&& (prefixes & (has_rep | has_repne)) != 0)
|
|
{
|
|
if ((prefixes & has_repne) != 0)
|
|
{
|
|
ADD_STRING ("repne ");
|
|
prefix_size += 6;
|
|
}
|
|
else if ((prefixes & has_rep) != 0)
|
|
{
|
|
ADD_STRING ("repe ");
|
|
prefix_size += 5;
|
|
}
|
|
}
|
|
else if ((prefixes & (has_rep | has_repne)) != 0)
|
|
{
|
|
uint_fast8_t byte;
|
|
print_prefix:
|
|
bufcnt = 0;
|
|
byte = *begin;
|
|
/* This is a prefix byte. Print it. */
|
|
switch (byte)
|
|
{
|
|
case prefix_rep:
|
|
ADD_STRING ("rep");
|
|
break;
|
|
case prefix_repne:
|
|
ADD_STRING ("repne");
|
|
break;
|
|
case prefix_cs:
|
|
ADD_STRING ("cs");
|
|
break;
|
|
case prefix_ds:
|
|
ADD_STRING ("ds");
|
|
break;
|
|
case prefix_es:
|
|
ADD_STRING ("es");
|
|
break;
|
|
case prefix_fs:
|
|
ADD_STRING ("fs");
|
|
break;
|
|
case prefix_gs:
|
|
ADD_STRING ("gs");
|
|
break;
|
|
case prefix_ss:
|
|
ADD_STRING ("ss");
|
|
break;
|
|
case prefix_data16:
|
|
ADD_STRING ("data16");
|
|
break;
|
|
case prefix_addr16:
|
|
ADD_STRING ("addr16");
|
|
break;
|
|
case prefix_lock:
|
|
ADD_STRING ("lock");
|
|
break;
|
|
#ifdef X86_64
|
|
case 0x40 ... 0x4f:
|
|
ADD_STRING ("rex");
|
|
if (byte != 0x40)
|
|
{
|
|
ADD_CHAR ('.');
|
|
if (byte & 0x8)
|
|
ADD_CHAR ('w');
|
|
if (byte & 0x4)
|
|
ADD_CHAR ('r');
|
|
if (byte & 0x3)
|
|
ADD_CHAR ('x');
|
|
if (byte & 0x1)
|
|
ADD_CHAR ('b');
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
/* Cannot happen. */
|
|
puts ("unknown prefix");
|
|
abort ();
|
|
}
|
|
data = begin + 1;
|
|
++addr;
|
|
|
|
goto out;
|
|
}
|
|
|
|
/* We have a match. First determine how many bytes are
|
|
needed for the adressing mode. */
|
|
param_start = codep;
|
|
if (instrtab[cnt].modrm)
|
|
{
|
|
uint_fast8_t modrm = codep[-1];
|
|
|
|
#ifndef X86_64
|
|
if (likely ((prefixes & has_addr16) != 0))
|
|
{
|
|
/* Account for displacement. */
|
|
if ((modrm & 0xc7) == 6 || (modrm & 0xc0) == 0x80)
|
|
param_start += 2;
|
|
else if ((modrm & 0xc0) == 0x40)
|
|
param_start += 1;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* Account for SIB. */
|
|
if ((modrm & 0xc0) != 0xc0 && (modrm & 0x7) == 0x4)
|
|
param_start += 1;
|
|
|
|
/* Account for displacement. */
|
|
if ((modrm & 0xc7) == 5 || (modrm & 0xc0) == 0x80
|
|
|| ((modrm & 0xc7) == 0x4 && (codep[0] & 0x7) == 0x5))
|
|
param_start += 4;
|
|
else if ((modrm & 0xc0) == 0x40)
|
|
param_start += 1;
|
|
}
|
|
|
|
if (unlikely (param_start > end))
|
|
goto not;
|
|
}
|
|
|
|
output_data.addr = addr + (data - begin);
|
|
output_data.data = data;
|
|
|
|
unsigned long string_end_idx = 0;
|
|
fmt = save_fmt;
|
|
const char *deferred_start = NULL;
|
|
size_t deferred_len = 0;
|
|
// XXX Can we get this from color.c?
|
|
static const char color_off[] = "\e[0m";
|
|
while (*fmt != '\0')
|
|
{
|
|
if (*fmt != '%')
|
|
{
|
|
char ch = *fmt++;
|
|
if (ch == '\\')
|
|
{
|
|
switch ((ch = *fmt++))
|
|
{
|
|
case '0' ... '7':
|
|
{
|
|
int val = ch - '0';
|
|
ch = *fmt;
|
|
if (ch >= '0' && ch <= '7')
|
|
{
|
|
val *= 8;
|
|
val += ch - '0';
|
|
ch = *++fmt;
|
|
if (ch >= '0' && ch <= '7' && val < 32)
|
|
{
|
|
val *= 8;
|
|
val += ch - '0';
|
|
++fmt;
|
|
}
|
|
}
|
|
ch = val;
|
|
}
|
|
break;
|
|
|
|
case 'n':
|
|
ch = '\n';
|
|
break;
|
|
|
|
case 't':
|
|
ch = '\t';
|
|
break;
|
|
|
|
default:
|
|
retval = EINVAL;
|
|
goto do_ret;
|
|
}
|
|
}
|
|
else if (ch == '\e' && *fmt == '[')
|
|
{
|
|
deferred_start = fmt - 1;
|
|
do
|
|
++fmt;
|
|
while (*fmt != 'm' && *fmt != '\0');
|
|
|
|
if (*fmt == 'm')
|
|
{
|
|
deferred_len = ++fmt - deferred_start;
|
|
continue;
|
|
}
|
|
|
|
fmt = deferred_start + 1;
|
|
deferred_start = NULL;
|
|
}
|
|
ADD_CHAR (ch);
|
|
continue;
|
|
}
|
|
++fmt;
|
|
|
|
int width = 0;
|
|
while (isdigit (*fmt))
|
|
width = width * 10 + (*fmt++ - '0');
|
|
|
|
int prec = 0;
|
|
if (*fmt == '.')
|
|
while (isdigit (*++fmt))
|
|
prec = prec * 10 + (*fmt - '0');
|
|
|
|
size_t start_idx = bufcnt;
|
|
size_t non_printing = 0;
|
|
switch (*fmt++)
|
|
{
|
|
char mnebuf[16];
|
|
const char *str;
|
|
|
|
case 'm':
|
|
/* Mnemonic. */
|
|
|
|
if (unlikely (instrtab[cnt].mnemonic == MNE_INVALID))
|
|
{
|
|
switch (*data)
|
|
{
|
|
#ifdef X86_64
|
|
case 0x90:
|
|
if (prefixes & has_rex_b)
|
|
goto not;
|
|
str = "nop";
|
|
break;
|
|
#endif
|
|
|
|
case 0x98:
|
|
#ifdef X86_64
|
|
if (prefixes == (has_rex_w | has_rex))
|
|
{
|
|
str = "cltq";
|
|
break;
|
|
}
|
|
#endif
|
|
if (prefixes & ~has_data16)
|
|
goto print_prefix;
|
|
str = prefixes & has_data16 ? "cbtw" : "cwtl";
|
|
break;
|
|
|
|
case 0x99:
|
|
#ifdef X86_64
|
|
if (prefixes == (has_rex_w | has_rex))
|
|
{
|
|
str = "cqto";
|
|
break;
|
|
}
|
|
#endif
|
|
if (prefixes & ~has_data16)
|
|
goto print_prefix;
|
|
str = prefixes & has_data16 ? "cwtd" : "cltd";
|
|
break;
|
|
|
|
case 0xe3:
|
|
if (prefixes & ~has_addr16)
|
|
goto print_prefix;
|
|
#ifdef X86_64
|
|
str = prefixes & has_addr16 ? "jecxz" : "jrcxz";
|
|
#else
|
|
str = prefixes & has_addr16 ? "jcxz" : "jecxz";
|
|
#endif
|
|
break;
|
|
|
|
case 0x0f:
|
|
if (data[1] == 0x0f)
|
|
{
|
|
/* AMD 3DNOW. We need one more byte. */
|
|
if (param_start >= end)
|
|
goto not;
|
|
if (*param_start < AMD3DNOW_LOW_IDX
|
|
|| *param_start > AMD3DNOW_HIGH_IDX)
|
|
goto not;
|
|
unsigned int idx
|
|
= amd3dnow[AMD3DNOW_IDX (*param_start)];
|
|
if (idx == 0)
|
|
goto not;
|
|
str = amd3dnowstr + idx - 1;
|
|
/* Eat the immediate byte indicating the
|
|
operation. */
|
|
++param_start;
|
|
break;
|
|
}
|
|
#ifdef X86_64
|
|
if (data[1] == 0xc7)
|
|
{
|
|
str = ((prefixes & has_rex_w)
|
|
? "cmpxchg16b" : "cmpxchg8b");
|
|
break;
|
|
}
|
|
#endif
|
|
if (data[1] == 0xc2)
|
|
{
|
|
if (param_start >= end)
|
|
goto not;
|
|
if (*param_start > 7)
|
|
goto not;
|
|
static const char cmpops[][9] =
|
|
{
|
|
[0] = "cmpeq",
|
|
[1] = "cmplt",
|
|
[2] = "cmple",
|
|
[3] = "cmpunord",
|
|
[4] = "cmpneq",
|
|
[5] = "cmpnlt",
|
|
[6] = "cmpnle",
|
|
[7] = "cmpord"
|
|
};
|
|
char *cp = stpcpy (mnebuf, cmpops[*param_start]);
|
|
if (correct_prefix & (has_rep | has_repne))
|
|
*cp++ = 's';
|
|
else
|
|
*cp++ = 'p';
|
|
if (correct_prefix & (has_data16 | has_repne))
|
|
*cp++ = 'd';
|
|
else
|
|
*cp++ = 's';
|
|
*cp = '\0';
|
|
str = mnebuf;
|
|
/* Eat the immediate byte indicating the
|
|
operation. */
|
|
++param_start;
|
|
break;
|
|
}
|
|
/* Fallthrough */
|
|
default:
|
|
assert (! "INVALID not handled");
|
|
}
|
|
}
|
|
else
|
|
str = mnestr.str + mneidx[instrtab[cnt].mnemonic];
|
|
|
|
if (deferred_start != NULL)
|
|
{
|
|
ADD_NSTRING (deferred_start, deferred_len);
|
|
non_printing += deferred_len;
|
|
}
|
|
|
|
ADD_STRING (str);
|
|
|
|
switch (instrtab[cnt].suffix)
|
|
{
|
|
case suffix_none:
|
|
break;
|
|
|
|
case suffix_w:
|
|
if ((codep[-1] & 0xc0) != 0xc0)
|
|
{
|
|
char ch;
|
|
|
|
if (data[0] & 1)
|
|
{
|
|
if (prefixes & has_data16)
|
|
ch = 'w';
|
|
#ifdef X86_64
|
|
else if (prefixes & has_rex_w)
|
|
ch = 'q';
|
|
#endif
|
|
else
|
|
ch = 'l';
|
|
}
|
|
else
|
|
ch = 'b';
|
|
|
|
ADD_CHAR (ch);
|
|
}
|
|
break;
|
|
|
|
case suffix_w0:
|
|
if ((codep[-1] & 0xc0) != 0xc0)
|
|
ADD_CHAR ('l');
|
|
break;
|
|
|
|
case suffix_w1:
|
|
if ((data[0] & 0x4) == 0)
|
|
ADD_CHAR ('l');
|
|
break;
|
|
|
|
case suffix_W:
|
|
if (prefixes & has_data16)
|
|
{
|
|
ADD_CHAR ('w');
|
|
prefixes &= ~has_data16;
|
|
}
|
|
#ifdef X86_64
|
|
else
|
|
ADD_CHAR ('q');
|
|
#endif
|
|
break;
|
|
|
|
case suffix_W1:
|
|
if (prefixes & has_data16)
|
|
{
|
|
ADD_CHAR ('w');
|
|
prefixes &= ~has_data16;
|
|
}
|
|
#ifdef X86_64
|
|
else if (prefixes & has_rex_w)
|
|
ADD_CHAR ('q');
|
|
#endif
|
|
break;
|
|
|
|
case suffix_tttn:;
|
|
static const char tttn[16][3] =
|
|
{
|
|
"o", "no", "b", "ae", "e", "ne", "be", "a",
|
|
"s", "ns", "p", "np", "l", "ge", "le", "g"
|
|
};
|
|
ADD_STRING (tttn[codep[-1 - instrtab[cnt].modrm] & 0x0f]);
|
|
break;
|
|
|
|
case suffix_D:
|
|
if ((codep[-1] & 0xc0) != 0xc0)
|
|
ADD_CHAR ((data[0] & 0x04) == 0 ? 's' : 'l');
|
|
break;
|
|
|
|
default:
|
|
printf("unknown suffix %d\n", instrtab[cnt].suffix);
|
|
abort ();
|
|
}
|
|
|
|
if (deferred_start != NULL)
|
|
{
|
|
ADD_STRING (color_off);
|
|
non_printing += strlen (color_off);
|
|
}
|
|
|
|
string_end_idx = bufcnt;
|
|
break;
|
|
|
|
case 'o':
|
|
if (prec == 1 && instrtab[cnt].fct1 != 0)
|
|
{
|
|
/* First parameter. */
|
|
if (deferred_start != NULL)
|
|
{
|
|
ADD_NSTRING (deferred_start, deferred_len);
|
|
non_printing += deferred_len;
|
|
}
|
|
|
|
if (instrtab[cnt].str1 != 0)
|
|
ADD_STRING (op1_str
|
|
+ op1_str_idx[instrtab[cnt].str1 - 1]);
|
|
|
|
output_data.opoff1 = (instrtab[cnt].off1_1
|
|
+ OFF1_1_BIAS - opoff);
|
|
output_data.opoff2 = (instrtab[cnt].off1_2
|
|
+ OFF1_2_BIAS - opoff);
|
|
output_data.opoff3 = (instrtab[cnt].off1_3
|
|
+ OFF1_3_BIAS - opoff);
|
|
int r = op1_fct[instrtab[cnt].fct1] (&output_data);
|
|
if (r < 0)
|
|
goto not;
|
|
if (r > 0)
|
|
goto enomem;
|
|
|
|
if (deferred_start != NULL)
|
|
{
|
|
ADD_STRING (color_off);
|
|
non_printing += strlen (color_off);
|
|
}
|
|
|
|
string_end_idx = bufcnt;
|
|
}
|
|
else if (prec == 2 && instrtab[cnt].fct2 != 0)
|
|
{
|
|
/* Second parameter. */
|
|
if (deferred_start != NULL)
|
|
{
|
|
ADD_NSTRING (deferred_start, deferred_len);
|
|
non_printing += deferred_len;
|
|
}
|
|
|
|
if (instrtab[cnt].str2 != 0)
|
|
ADD_STRING (op2_str
|
|
+ op2_str_idx[instrtab[cnt].str2 - 1]);
|
|
|
|
output_data.opoff1 = (instrtab[cnt].off2_1
|
|
+ OFF2_1_BIAS - opoff);
|
|
output_data.opoff2 = (instrtab[cnt].off2_2
|
|
+ OFF2_2_BIAS - opoff);
|
|
output_data.opoff3 = (instrtab[cnt].off2_3
|
|
+ OFF2_3_BIAS - opoff);
|
|
int r = op2_fct[instrtab[cnt].fct2] (&output_data);
|
|
if (r < 0)
|
|
goto not;
|
|
if (r > 0)
|
|
goto enomem;
|
|
|
|
if (deferred_start != NULL)
|
|
{
|
|
ADD_STRING (color_off);
|
|
non_printing += strlen (color_off);
|
|
}
|
|
|
|
string_end_idx = bufcnt;
|
|
}
|
|
else if (prec == 3 && instrtab[cnt].fct3 != 0)
|
|
{
|
|
/* Third parameter. */
|
|
if (deferred_start != NULL)
|
|
{
|
|
ADD_NSTRING (deferred_start, deferred_len);
|
|
non_printing += deferred_len;
|
|
}
|
|
|
|
if (instrtab[cnt].str3 != 0)
|
|
ADD_STRING (op3_str
|
|
+ op3_str_idx[instrtab[cnt].str3 - 1]);
|
|
|
|
output_data.opoff1 = (instrtab[cnt].off3_1
|
|
+ OFF3_1_BIAS - opoff);
|
|
output_data.opoff2 = (instrtab[cnt].off3_2
|
|
+ OFF3_2_BIAS - opoff);
|
|
#ifdef OFF3_3_BITS
|
|
output_data.opoff3 = (instrtab[cnt].off3_3
|
|
+ OFF3_3_BIAS - opoff);
|
|
#else
|
|
output_data.opoff3 = 0;
|
|
#endif
|
|
int r = op3_fct[instrtab[cnt].fct3] (&output_data);
|
|
if (r < 0)
|
|
goto not;
|
|
if (r > 0)
|
|
goto enomem;
|
|
|
|
if (deferred_start != NULL)
|
|
{
|
|
ADD_STRING (color_off);
|
|
non_printing += strlen (color_off);
|
|
}
|
|
|
|
string_end_idx = bufcnt;
|
|
}
|
|
else
|
|
bufcnt = string_end_idx;
|
|
break;
|
|
|
|
case 'e':
|
|
string_end_idx = bufcnt;
|
|
break;
|
|
|
|
case 'a':
|
|
/* Pad to requested column. */
|
|
while (bufcnt - non_printing < (size_t) width)
|
|
ADD_CHAR (' ');
|
|
width = 0;
|
|
break;
|
|
|
|
case 'l':
|
|
if (deferred_start != NULL)
|
|
{
|
|
ADD_NSTRING (deferred_start, deferred_len);
|
|
non_printing += deferred_len;
|
|
}
|
|
|
|
if (output_data.labelbuf != NULL
|
|
&& output_data.labelbuf[0] != '\0')
|
|
{
|
|
ADD_STRING (output_data.labelbuf);
|
|
output_data.labelbuf[0] = '\0';
|
|
string_end_idx = bufcnt;
|
|
}
|
|
else if (output_data.symaddr_use != addr_none)
|
|
{
|
|
GElf_Addr symaddr = output_data.symaddr;
|
|
if (output_data.symaddr_use >= addr_rel_symbolic)
|
|
symaddr += addr + param_start - begin;
|
|
|
|
// XXX Lookup symbol based on symaddr
|
|
const char *symstr = NULL;
|
|
if (symcb != NULL
|
|
&& symcb (0 /* XXX */, 0 /* XXX */, symaddr,
|
|
&output_data.labelbuf,
|
|
&output_data.labelbufsize, symcbarg) == 0)
|
|
symstr = output_data.labelbuf;
|
|
|
|
size_t bufavail = bufsize - bufcnt;
|
|
int r = 0;
|
|
if (symstr != NULL)
|
|
r = snprintf (&buf[bufcnt], bufavail, "# <%s>",
|
|
symstr);
|
|
else if (output_data.symaddr_use == addr_abs_always
|
|
|| output_data.symaddr_use == addr_rel_always)
|
|
r = snprintf (&buf[bufcnt], bufavail, "# %#" PRIx64,
|
|
(uint64_t) symaddr);
|
|
|
|
assert (r >= 0);
|
|
if ((size_t) r >= bufavail)
|
|
goto enomem;
|
|
bufcnt += r;
|
|
string_end_idx = bufcnt;
|
|
|
|
output_data.symaddr_use = addr_none;
|
|
}
|
|
if (deferred_start != NULL)
|
|
{
|
|
ADD_STRING (color_off);
|
|
non_printing += strlen (color_off);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
abort ();
|
|
}
|
|
|
|
deferred_start = NULL;
|
|
|
|
/* Pad according to the specified width. */
|
|
while (bufcnt + prefix_size - non_printing < start_idx + width)
|
|
ADD_CHAR (' ');
|
|
prefix_size = 0;
|
|
}
|
|
|
|
if ((prefixes & SEGMENT_PREFIXES) != 0)
|
|
goto print_prefix;
|
|
|
|
assert (string_end_idx != ~0ul);
|
|
bufcnt = string_end_idx;
|
|
|
|
addr += param_start - begin;
|
|
data = param_start;
|
|
|
|
goto out;
|
|
}
|
|
|
|
/* Invalid (or at least unhandled) opcode. */
|
|
if (prefixes != 0)
|
|
goto print_prefix;
|
|
assert (*startp == data);
|
|
++data;
|
|
ADD_STRING ("(bad)");
|
|
addr += data - begin;
|
|
|
|
out:
|
|
if (bufcnt == bufsize)
|
|
goto enomem;
|
|
buf[bufcnt] = '\0';
|
|
|
|
*startp = data;
|
|
retval = outcb (buf, bufcnt, outcbarg);
|
|
if (retval != 0)
|
|
goto do_ret;
|
|
}
|
|
|
|
do_ret:
|
|
free (output_data.labelbuf);
|
|
if (buf != initbuf)
|
|
free (buf);
|
|
|
|
return retval;
|
|
}
|