Skip to content

Instantly share code, notes, and snippets.

@tom-seddon
Created May 28, 2019 00:41
Show Gist options
  • Save tom-seddon/3cd4f739ea8e88e45f4e3888c4101ef0 to your computer and use it in GitHub Desktop.
Save tom-seddon/3cd4f739ea8e88e45f4e3888c4101ef0 to your computer and use it in GitHub Desktop.
// -*- mode:c; c-file-style: "GNU" -*-
/* This file is to be #include'd into any file that uses it.
* Everything's static, so no problem.
*/
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* TODO
* ====
*
* . I think the BASIC_INSTR vs MEM_DEST_INSTR thing is stupid (and
* LEAQ could be merged into the same list...) but I'm not doing
* anything about it just yet.
*
* . <<probably more stuff>>>
*/
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* clang is good at folding the constants and following through, but
* it's foiled by memcpy - which is why constants are written out by
* hand, e.g., in gen_lit32 or gen_litn.
*
* Without memcpy:
*
* movaps LCPI0_0(%rip), %xmm0 ## xmm0 = [72,129,192,120,86,52,18,72,1,134,0,5,0,0,72,1]
* movups %xmm0, (%r14)
*
* With memcpy:
*
* movb $72, (%rbx)
* movb $-127, 1(%rbx)
* movb $-64, 2(%rbx)
* movl $305419896, 3(%rbx) ## imm = 0x12345678 [this is the memcpy]
* movb $72, 7(%rbx)
*
* Probably the opposite tradeoff for variables of course.
*
* __builtin_constant_p is presumably there to fix this, but it didn't
* improve things when I tried using it.
*/
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* As a general policy, each register is accessible only as qword,
* dword, word and (low) byte. Instructions that access the high bytes
* of [ABCD]X aren't accessible.
*
* This mostly makes things simpler, but there are a couple of
* exceptions.
*/
union X64Register
{
struct
{
uint8_t r : 3;
uint8_t rex : 1;
};
uint8_t reg_all;
};
typedef union X64Register X64Register;
static ATTRIBUTE_UNUSED const X64Register RAX = {.r = 0, .rex = 0};
static ATTRIBUTE_UNUSED const X64Register RCX = {.r = 1, .rex = 0};
static ATTRIBUTE_UNUSED const X64Register RDX = {.r = 2, .rex = 0};
static ATTRIBUTE_UNUSED const X64Register RBX = {.r = 3, .rex = 0};
static ATTRIBUTE_UNUSED const X64Register RSP = {.r = 4, .rex = 0};
static ATTRIBUTE_UNUSED const X64Register RBP = {.r = 5, .rex = 0};
static ATTRIBUTE_UNUSED const X64Register RSI = {.r = 6, .rex = 0};
static ATTRIBUTE_UNUSED const X64Register RDI = {.r = 7, .rex = 0};
static ATTRIBUTE_UNUSED const X64Register R8 = {.r = 0, .rex = 1};
static ATTRIBUTE_UNUSED const X64Register R9 = {.r = 1, .rex = 1};
static ATTRIBUTE_UNUSED const X64Register R10 = {.r = 2, .rex = 1};
static ATTRIBUTE_UNUSED const X64Register R11 = {.r = 3, .rex = 1};
static ATTRIBUTE_UNUSED const X64Register R12 = {.r = 4, .rex = 1};
static ATTRIBUTE_UNUSED const X64Register R13 = {.r = 5, .rex = 1};
static ATTRIBUTE_UNUSED const X64Register R14 = {.r = 6, .rex = 1};
static ATTRIBUTE_UNUSED const X64Register R15 = {.r = 7, .rex = 1};
static const X64Register *const REGISTERS[] =
{
&RAX, &RCX, &RDX, &RBX,
&RSP, &RBP, &RSI, &RDI,
&R8, &R9, &R10, &R11,
&R12, &R13, &R14, &R15,
NULL,
};
static const char *const REGISTER_QNAMES[] =
{
"rax", "rcx", "rdx", "rbx",
"rsp", "rbp", "rsi", "rdi",
"r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15",
};
static const char *const REGISTER_DNAMES[] =
{
"eax", "ecx", "edx", "ebx",
"esp", "ebp", "esi", "edi",
"r8d", "r9d", "r10d", "r11d",
"r12d", "r13d", "r14d", "r15d",
};
static const char *const REGISTER_WNAMES[] =
{
"ax", "cx", "dx", "bx",
"sp", "bp", "si", "di",
"r8w", "r9w", "r10w", "r11w",
"r12w", "r13w", "r14w", "r15w",
};
static const char *const REGISTER_BNAMES[] =
{
"al", "cl", "dl", "bl",
"spl", "bpl", "sil", "dil",
"r8b", "r9b", "r10b", "r11b",
"r12b", "r13b", "r14b", "r15b",
};
static const char *const *REGISTER_NAMES[] =
{
REGISTER_BNAMES,
REGISTER_WNAMES,
REGISTER_DNAMES,
REGISTER_QNAMES,
};
static const char *
get_register_name (int i, uint8_t op_size)
{
switch (op_size)
{
default:
assert (0);
/* fall through */
case 0:
return REGISTER_BNAMES[i];
case 1:
return REGISTER_WNAMES[i];
case 2:
return REGISTER_DNAMES[i];
case 3:
return REGISTER_QNAMES[i];
}
}
static const char *
get_size_keyword (uint8_t op_size)
{
switch (op_size)
{
default:
assert (0);
/* fall through */
case 0:
return "byte";
case 1:
return "word";
case 2:
return "dword";
case 3:
return "qword";
}
}
/* static const char *get_register_name (int size, X64Register reg) */
/* { */
/* for (const X64Register *const *p = REGISTERS; *p; ++p) */
/* { */
/* if ((*p)->reg_all == reg.reg_all) */
/* { */
/* ptrdiff_t i = p - REGISTERS; */
/* switch (size) */
/* { */
/* case 1: */
/* return REGISTER_BNAMES[i]; */
/* case 2: */
/* return REGISTER_WNAMES[i]; */
/* case 4: */
/* return REGISTER_DNAMES[i]; */
/* case 8: */
/* return REGISTER_QNAMES[i]; */
/* default: */
/* assert (0); */
/* break; */
/* } */
/* } */
/* } */
/* return NULL; */
/* } */
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
struct X64Scale {
struct
{
uint8_t s : 2;
};
uint8_t scale_all;
};
typedef struct X64Scale X64Scale;
static ATTRIBUTE_UNUSED const X64Scale X1 = {.s = 0};
static ATTRIBUTE_UNUSED const X64Scale X2 = {.s = 1};
static ATTRIBUTE_UNUSED const X64Scale X4 = {.s = 2};
static ATTRIBUTE_UNUSED const X64Scale X8 = {.s = 3};
/* static int */
/* get_actual_scale (X64Scale s) */
/* { */
/* switch (s.s) { */
/* default: */
/* assert (0); */
/* /\* fall through *\/ */
/* case 0: */
/* return 1; */
/* case 1: */
/* return 2; */
/* case 2: */
/* return 4; */
/* case 3: */
/* return 8; */
/* } */
/* } */
static const char *get_scale_string (X64Scale s)
{
switch (s.s)
{
default:
assert (0);
/* fall through */
case 0:
return "";
case 1:
return "*2";
case 2:
return "*4";
case 3:
return "*8";
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* struct X64Size { */
/* uint8_t rex : 1; */
/* uint8_t size_prefix : 1; */
/* uint8_t w_d_bits : 2; */
/* uint8_t imm_size : 4; */
/* }; */
/* typedef struct X64Size X64Size; */
/* static const X64Size B={.w_d_bits = 0, .imm_size = 1}; */
/* static const X64Size W={.size_prefix = 1, .w_d_bits = 1, .imm_size = 2}; */
/* static const X64Size D={.w_d_bits = 1, .imm_size = 4}; */
/* static const X64Size Q={.rex = 1, .w_d_bits = 1, .imm_size = 4}; */
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
union X64ModRM
{
struct
{
uint8_t rm : 3, reg : 3, mod : 2;
//uint8_t mod : 2, reg : 3, rm : 3;
};
uint8_t mod_rm_all;
};
typedef union X64ModRM X64ModRM;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
union X64REX
{
struct
{
uint8_t b : 1, x : 1, r : 1, w : 1, _ : 4;
};
uint8_t rex_all;
};
typedef union X64REX X64REX;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
union X64SIB
{
struct
{
uint8_t b : 3, i : 3, s : 2;
};
uint8_t sib_all;
};
typedef union X64SIB X64SIB;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
enum X64Condition {
COND_O = 0x0,
COND_NO = 0x1,
COND_B = 0x2, COND_C = 0x2, COND_NAE = 0x2,
COND_AE = 0x3, COND_NB = 0x3, COND_NC = 0x3,
COND_E = 0x4, COND_Z = 0x4,
COND_NE = 0x5, COND_NZ = 0x5,
COND_NA = 0x6, COND_BE = 0x6,
COND_A = 0x7, COND_NBE = 0x7,
COND_S = 0x8,
COND_NS = 0x9,
COND_P = 0xa, COND_PE = 0xa,
COND_NP = 0xb, COND_PO = 0xb,
COND_L = 0xc, COND_NGE = 0xc,
COND_NL = 0xd, COND_GE = 0xd,
COND_NG = 0xe, COND_LE = 0xe,
COND_NLE = 0xf, COND_G = 0xf,
};
#define ALL_CONDITIONS \
COND (O, O) /* 0 */ \
COND (NO, NO) /* 1 */ \
COND (B, C) COND (C, C) COND (NAE, C) /* 2 */ \
COND (AE, NC) COND (NB, NC) COND (NC, NC) /* 3 */ \
COND (E, Z) COND (Z, Z) /* 4 */ \
COND (NE, NZ) COND (NZ, NZ) /* 5 */ \
COND (NA, NA) COND (BE, NA) /* 6 */ \
COND (A, A) COND (NBE, A) /* 7 */ \
COND (S, S) /* 8 */ \
COND (NS, NS) /* 9 */ \
COND (P, PE) COND (PE, PE) /* A */ \
COND (NP, PO) COND (PO, PO) /* B */ \
COND (L, L) COND (NGE, L) /* C */ \
COND (NL, NL) COND (GE, NL) /* D */ \
COND (NG, NG) COND (LE, NG) /* E */ \
COND (NLE, G) COND (G, G) /* F */
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* enum X64Mnemonics */
/* { */
/* MNEM_NONE = 0, */
/* #define MNEM(X) MNEM_##X, */
/* #include "x64mnemonics.inl" */
/* #undef MNEM */
/* }; */
/* static const char *const MNEMONICS[] = */
/* { */
/* [MNEM_NONE] = NULL, */
/* #define MNEM(X) [MNEM_##X] = #X, */
/* #include "x64mnemonics.inl" */
/* #undef MNEM */
/* }; */
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* The mnemonic/name separation is a relic and needs to be removed. */
struct X64Instruction
{
/* 0 */
uint8_t modrm_reg : 3; /* ModR/M reg field value, for unary
operations or immediate values */
uint8_t op_size : 2; /* size log2 - 0=byte 1=word 2=dword
3=qword */
uint8_t imm_size : 2; /* size log2 - 0=byte 1=word 2=dword
3=qword */
/* 1 */
uint8_t f : 1; /* opcode begins with 0x0f */
uint8_t got_rm_imm : 1; /* use rm_imm_opcode for r/m,imm */
uint8_t got_r_rm : 1; /* use r_rm_opcode for r,r/m */
uint8_t got_rm_r : 1; /* use rm_r_opcode for r/m,r */
uint8_t no_r_r : 1; /* r,r is not supported */
uint8_t rel32_f : 1; /* rel32 version begins with 0x0f */
/* 2 */
union {
uint8_t r_rm_opcode; /* mov r,m */
uint8_t rel8_opcode; /* jcc rel8 */
};
/* 3 */
union {
uint8_t rm_r_opcode; /* mov m,r */
uint8_t rel32_opcode; /* jcc rel32 */
};
/* 4 */
uint8_t rm_imm_opcode; /* mov r,n */
/* 5 */
uint8_t opcode; /* various */
};
typedef struct X64Instruction X64Instruction;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
#define BASIC_INSTR_3(NAME, MNEMONIC,R_IMM, R_IMM_MODRM_REG, R_RM, RM_R, OP_SIZE) \
static ATTRIBUTE_UNUSED const X64Instruction NAME = { \
.rm_imm_opcode = (R_IMM), \
.modrm_reg = (R_IMM_MODRM_REG), \
.r_rm_opcode = (R_RM), \
.rm_r_opcode = (RM_R), \
.op_size = (OP_SIZE), \
.imm_size = (OP_SIZE) == 3 ? 2 : (OP_SIZE), \
.got_rm_imm = 1, \
.got_r_rm = 1, \
.got_rm_r = 1, \
};
#define BASIC_INSTR_2(MNEM, R_IMM, R_IMM_MODRM_REG, R_RM, RM_R) \
BASIC_INSTR_3 (MNEM##B, MNEM, R_IMM, R_IMM_MODRM_REG, R_RM, RM_R, 0) \
BASIC_INSTR_3 (MNEM##W, MNEM, R_IMM | 1, R_IMM_MODRM_REG, R_RM | 1, RM_R | 1, 1) \
BASIC_INSTR_3 (MNEM##D, MNEM, R_IMM | 1, R_IMM_MODRM_REG, R_RM | 1, RM_R | 1, 2) \
BASIC_INSTR_3 (MNEM##Q, MNEM, R_IMM | 1, R_IMM_MODRM_REG, R_RM | 1, RM_R | 1, 3)
/* 8080-style opcodes. */
#define BASIC_INSTR(MNEMONIC, INDEX) \
BASIC_INSTR_2 (MNEMONIC, 0x80, INDEX, (INDEX) * 8 + 2, (INDEX) * 8 + 0)
#define BASIC_INSTRS \
BASIC_INSTR (ADD, 0) \
BASIC_INSTR (OR, 1) \
BASIC_INSTR (ADC, 2) \
BASIC_INSTR (SBB, 3) \
BASIC_INSTR (AND, 4) \
BASIC_INSTR (SUB, 5) \
BASIC_INSTR (XOR, 6) \
BASIC_INSTR (CMP, 7)
BASIC_INSTRS
#undef BASIC_INSTR
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* MOVD and MOVQ are also SSE mnemonics... remains to be seen what, if
anything, ought to be done about this. */
BASIC_INSTR_2 (MOV, 0xc6, 0, 0x8a, 0x88)
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* These instructions can't have memory as a source. */
#define MEM_DEST_INSTR2(NAME, RM_R_OPCODE, RM_IMM_OPCODE, R, IMM_SIZE, OP_SIZE) \
static ATTRIBUTE_UNUSED const X64Instruction NAME = \
{ \
.rm_imm_opcode = (RM_IMM_OPCODE), \
.modrm_reg = (R), \
.rm_r_opcode = (RM_R_OPCODE) == -1 ? 0 : (RM_R_OPCODE), \
.op_size = (OP_SIZE), \
.imm_size = (IMM_SIZE) >= 0 ? (IMM_SIZE) : ((OP_SIZE) == 3 ? 2 : (OP_SIZE)), \
.got_rm_imm = 1, \
.got_rm_r = (RM_R_OPCODE) >= 0, \
};
#define MEM_DEST_INSTR(NAME, RM_R_OPCODE, RM_IMM_OPCODE, R, IMM_SIZE) \
MEM_DEST_INSTR2 (NAME##B, RM_R_OPCODE, RM_IMM_OPCODE, R, IMM_SIZE, 0) \
MEM_DEST_INSTR2 (NAME##W, RM_R_OPCODE, RM_IMM_OPCODE | 1, R, IMM_SIZE, 1) \
MEM_DEST_INSTR2 (NAME##D, RM_R_OPCODE, RM_IMM_OPCODE | 1, R, IMM_SIZE, 2) \
MEM_DEST_INSTR2 (NAME##Q, RM_R_OPCODE, RM_IMM_OPCODE | 1, R, IMM_SIZE, 3)
#define MEM_DEST_INSTRS \
MEM_DEST_INSTR (TEST, 0x84, 0xf6, 0, -1) \
MEM_DEST_INSTR (SHR, -1, 0xc0, 5, 0) \
MEM_DEST_INSTR (SHL, -1, 0xc0, 4, 0) \
MEM_DEST_INSTR (SAR, -1, 0xc0, 7, 0)
MEM_DEST_INSTRS
#undef MEM_DEST_INSTR
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
#define UNARY_INSTR_2(NAME, MNEMONIC, OPCODE, MODRM, OP_SIZE) \
static ATTRIBUTE_UNUSED const X64Instruction NAME = { \
.modrm_reg = (MODRM), \
.opcode = (OPCODE), \
.op_size = (OP_SIZE), \
};
#define UNARY_INSTR(MNEMONIC, OPCODE, MODRM) \
UNARY_INSTR_2 (MNEMONIC##B, MNEMONIC, OPCODE, MODRM, 0) \
UNARY_INSTR_2 (MNEMONIC##W, MNEMONIC, OPCODE | 1, MODRM, 1) \
UNARY_INSTR_2 (MNEMONIC##D, MNEMONIC, OPCODE | 1, MODRM, 2) \
UNARY_INSTR_2 (MNEMONIC##Q, MNEMONIC, OPCODE | 1, MODRM, 3)
#define UNARY_INSTRS \
UNARY_INSTR (INC, 0xfe, 0) \
UNARY_INSTR (DEC, 0xfe, 1) \
UNARY_INSTR (NOT, 0xf6, 2) \
UNARY_INSTR (NEG, 0xf6, 3) \
UNARY_INSTR (MUL, 0xf6, 4) \
UNARY_INSTR (IMUL, 0xf6, 5) \
UNARY_INSTR (DIV, 0xf6, 6) \
UNARY_INSTR (IDIV, 0xf6, 7) \
UNARY_INSTR (SHR1, 0xd0, 5) \
UNARY_INSTR (SHL1, 0xd0, 4) \
UNARY_INSTR (SAR1, 0xd0, 7)
UNARY_INSTRS
#undef UNARY_INSTR
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* Oddities */
static ATTRIBUTE_UNUSED const X64Instruction CALL =
{
.modrm_reg = 2,
.opcode = 0xff,
.op_size = 3,
.rel32_opcode = 0xe8,
};
/* static ATTRIBUTE_UNUSED const X64Instruction IMUL2W = */
/* { */
/* .r_rm_opcode = 0xAF, .f = 1, .got_r_rm = 1, .op_size = 1, */
/* }; */
/* static ATTRIBUTE_UNUSED const X64Instruction IMUL2D = */
/* { */
/* .r_rm_opcode = 0xAF, .f = 1, .got_r_rm = 1, .op_size = 2, */
/* }; */
/* static ATTRIBUTE_UNUSED const X64Instruction IMUL2Q = */
/* { */
/* .r_rm_opcode = 0xAF, .f = 1, .got_r_rm = 1, .op_size = 3, */
/* }; */
static ATTRIBUTE_UNUSED const X64Instruction LEAQ =
{
.r_rm_opcode = 0x8d,
.op_size = 3,
.got_r_rm = 1,
.no_r_r = 1,
};
static ATTRIBUTE_UNUSED const X64Instruction JMP =
{
.rel8_opcode = 0xeb,
.rel32_opcode = 0xe9,
.modrm_reg = 4,
.opcode = 0xff,
.op_size = 3,
};
static ATTRIBUTE_UNUSED const X64Instruction PUSHQ =
{
.modrm_reg = 6,
.opcode = 0xff,
.op_size = 3,
};
static ATTRIBUTE_UNUSED const X64Instruction POPQ =
{
.modrm_reg = 0,
.opcode = 0x8f,
.op_size = 3,
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
#define COND(CC, NDISASM_CC) \
static ATTRIBUTE_UNUSED const X64Instruction J##CC = \
{ \
.rel8_opcode = 0x70 | (COND_##CC), \
.rel32_f = 1, \
.rel32_opcode = 0x80 | (COND_##CC), \
.op_size = 0, \
};
ALL_CONDITIONS
#undef COND
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
#define COND(CC, NDISASM_CC) \
static ATTRIBUTE_UNUSED const X64Instruction SET##CC = \
{ \
.f = 1, \
.opcode = 0x90 | (COND_##CC), \
};
ALL_CONDITIONS
#undef COND
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
#define CMOV_INSTR(NAME, CC, OP_SIZE) \
static ATTRIBUTE_UNUSED const X64Instruction NAME = \
{ \
.f = 1, \
.r_rm_opcode = 0x40 | (COND_##CC), \
.got_r_rm = 1, \
.op_size = (OP_SIZE), \
};
/* I decided the size would go with the CMOV, rather than being a
prefix, for symmetry with MOV (for good or for ill). */
#define COND(CC, NDISASM_CC) \
CMOV_INSTR(CMOVW##CC, CC, 1) \
CMOV_INSTR(CMOVD##CC, CC, 2) \
CMOV_INSTR(CMOVQ##CC, CC, 3)
ALL_CONDITIONS
#undef COND
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
struct Gen {
uint8_t *dest;
uint8_t *base;
void *context;
};
typedef struct Gen Gen;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
init_gen (Gen *g, size_t n)
{
g->dest = calloc (n, 1);
g->base = g->dest;
}
static void
set_gen_context (Gen *g, void *context)
{
g->context = context;
}
static void
destroy_gen (Gen *g)
{
g->dest = NULL;
free (g->base);
g->base = NULL;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* static void */
/* gen_lit8(Gen *g, uint8_t value) */
/* { */
/* *g->dest++ = value; */
/* } */
/* static void */
/* gen_lit16(Gen *g, uint16_t value) */
/* { */
/* *g->dest++ = value; */
/* *g->dest++ = value >> 8; */
/* } */
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
gen_lit32 (Gen *g, uint32_t value)
{
*g->dest++ = value;
*g->dest++ = value >> 8;
*g->dest++ = value >> 16;
*g->dest++ = value >> 24;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
gen_lit64 (Gen *g, uint64_t value)
{
*g->dest++ = value;
*g->dest++ = value >> 8;
*g->dest++ = value >> 16;
*g->dest++ = value >> 24;
*g->dest++ = value >> 32;
*g->dest++ = value >> 40;
*g->dest++ = value >> 48;
*g->dest++ = value >> 56;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
gen_imm (Gen *g, uint32_t value, uint8_t imm_size)
{
*g->dest++ = value;
if (imm_size >= 1)
*g->dest++ = value >> 8;
if (imm_size >= 2)
{
*g->dest++ = value >> 16;
*g->dest++ = value >> 24;
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
gen_fixup_rel32 (Gen *g, uint8_t *rel32, uint8_t *target)
{
ptrdiff_t diff = target - (rel32 + 5);
int32_t disp = (int32_t)diff;
assert (*(int32_t *)rel32 == -5);
assert (diff == disp);
memcpy (rel32, &disp, 4);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static uint8_t *
gen_rel (Gen *g, X64Instruction instr, uint8_t *target)
{
ptrdiff_t diff;
/* 8-bit? */
if (target && instr.rel8_opcode != 0)
{
diff = target - (g->dest + 2);
if (diff == (int8_t) diff)
{
*g->dest++ = instr.rel8_opcode;
*g->dest++ = diff;
return NULL;
}
}
/* 32-bit. */
if (instr.rel32_f)
*g->dest++ = 0x0f;
*g->dest++ = instr.rel32_opcode;
diff = target - (g->dest + 4);
if (diff != (int32_t) diff)
{
assert (0);
/* Should record this error somewhere. */
}
if (target)
{
gen_lit32 (g, diff);
return NULL;
}
else
{
gen_lit32 (g, 0);
return g->dest - 4;
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* emits a size prefix, if necessay. */
static void
gen_size_prefix (Gen *g, X64Instruction instr)
{
if (instr.op_size == 1)
*g->dest++ = 0x66;
}
/* emits any opcode prefixes necessary, and the byte
itself. Call after adding the REX byte. */
static void
gen_opcode_bytes (Gen *g, X64Instruction instr, uint8_t opcode)
{
if (instr.f)
*g->dest++ = 0x0f;
*g->dest++ = opcode;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* generate REX prefix with the given bit values if necessary.
*
* The dest/src register is required so that the REX prefix can be
* added to access the low bytes of RDI/RSI/RBP/RSP. Pass RAX when
* this isn't relevant.
*/
static void
gen_rex_bytes (Gen *g, X64Instruction instr, X64Register reg, uint8_t r, uint8_t x, uint8_t b)
{
if ((instr.op_size == 3) | r | x | b)
*g->dest++ = (X64REX) {._ = 4, .w = instr.op_size == 3, .r = r, .x = x, .b = b}.rex_all;
else if (instr.op_size == 0 && reg.r >= 4)
{
/* And reg.rex is clear too. This is implied as it will
(should...) have been passed in as one of the r, x or b
arguments. */
assert (!reg.rex);
assert (!r);
assert (!x);
assert (!b);
*g->dest++ = (X64REX) {._ = 4}.rex_all;
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* Helper for the helpers. Does no error checking. */
static void
gen_mbisd_bytes_internal (Gen *g,
uint8_t reg,
X64Register base,
X64Register index,
X64Scale scale,
int32_t disp)
{
X64SIB sib = {.s = scale.s, .i = index.r, .b = base.r};
/* When the base's non-REX part is 5 (RBP/R13), a displacement is
mandatory. */
if (disp == 0 && base.r != 5)
{
*g->dest++ = (X64ModRM) {.mod = 0, .rm = 4, .reg = reg}.mod_rm_all;
*g->dest++ = sib.sib_all;
}
else if (disp == (int8_t) disp)
{
*g->dest++ = (X64ModRM) {.mod = 1, .rm = 4, .reg = reg}.mod_rm_all;
*g->dest++ = sib.sib_all;
*g->dest++ = disp;
}
else
{
*g->dest++ = (X64ModRM) {.mod = 2, .rm = 4, .reg = reg}.mod_rm_all;
*g->dest++ = sib.sib_all;
gen_lit32 (g, disp);
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* generate ModR/M and SIB bytes for the given operands. */
static void
gen_mbisd_bytes (Gen *g,
uint8_t reg,
X64Register base,
X64Register index,
X64Scale scale,
int32_t disp)
{
assert (!(index.r == 4 && index.rex == 0)); /* index can't be RSP */
gen_mbisd_bytes_internal (g, reg, base, index, scale, disp);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* generate ModR/M byte for the given operands. */
static void
gen_mbd_bytes (Gen *g, uint8_t reg, X64Register base, int32_t disp)
{
if (base.r == 4)
{
/* Have to encode this the long way round. */
gen_mbisd_bytes_internal (g, reg, base, RSP, X1, disp);
}
else
{
/* When the base's non-REX part is 5 (RBP/R13), a displacement
is mandatory. */
if (disp == 0 && base.r != 5)
*g->dest++ = (X64ModRM) {.mod = 0, .rm = base.r, .reg = reg}.mod_rm_all;
else if (disp == (int8_t)disp)
{
*g->dest++ = (X64ModRM) {.mod = 1, .rm = base.r, .reg = reg}.mod_rm_all;
*g->dest++ = disp;
}
else
{
*g->dest++ = (X64ModRM) {.mod = 2, .rm = base.r, .reg = reg}.mod_rm_all;
gen_lit32 (g, disp);
}
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
gen_nop (Gen *g)
{
*g->dest++ = 0x90;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
gen_ret (Gen *g)
{
*g->dest++ = 0xc3;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* The 64-bit MOVs are a special case encodings, so they get their own
extra specially unique functions. */
static void
gen_mov_r_imm64 (Gen *g, X64Register reg, int64_t imm64)
{
*g->dest++ = (X64REX) {._ = 4, .w = 0, .r = 0, .x = 0, .b = reg.rex}.rex_all;
*g->dest++ = 0xb8 | reg.r;
gen_lit64 (g, imm64);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* unary r */
static void
gen_r (Gen *g, X64Instruction instr, X64Register reg)
{
gen_size_prefix (g, instr);
gen_rex_bytes (g, instr, reg, 0, 0, reg.rex);
gen_opcode_bytes (g, instr, instr.opcode);
*g->dest++ = (X64ModRM) {.mod = 3, .rm = reg.r, .reg = instr.modrm_reg}.mod_rm_all;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* unary [base+disp] */
static void
gen_mbd (Gen *g, X64Instruction instr, X64Register base, int32_t disp)
{
gen_size_prefix (g, instr);
gen_rex_bytes (g, instr, RAX, 0, 0, base.rex);
gen_opcode_bytes (g, instr, instr.opcode);
gen_mbd_bytes (g, instr.modrm_reg, base, disp);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* unary [base+index*scale+disp] */
static void
gen_mbisd (Gen *g,
X64Instruction instr,
X64Register base, X64Register index, X64Scale scale, int32_t disp)
{
gen_size_prefix (g, instr);
gen_rex_bytes (g, instr, RAX, 0, index.rex, base.rex);
gen_opcode_bytes (g, instr, instr.opcode);
gen_mbisd_bytes (g, instr.modrm_reg, base, index, scale, disp);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* binary reg, imm */
static void
gen_r_imm (Gen *g, X64Instruction instr, X64Register reg, int32_t imm)
{
assert (instr.got_rm_imm);
gen_size_prefix (g, instr);
gen_rex_bytes (g, instr, reg, 0, 0, reg.rex);
gen_opcode_bytes (g, instr, instr.rm_imm_opcode);
*g->dest++ = (X64ModRM) {.mod = 3, .rm = reg.r, .reg = instr.modrm_reg}.mod_rm_all;
gen_imm (g, imm, instr.imm_size);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* binary [base+disp], imm */
static void
gen_mbd_imm (Gen *g, X64Instruction instr, X64Register base, int32_t disp, int32_t imm)
{
assert (instr.got_rm_imm);
gen_size_prefix (g, instr);
gen_rex_bytes (g, instr, RAX, 0, 0, base.rex);
gen_opcode_bytes (g, instr, instr.rm_imm_opcode);
gen_mbd_bytes (g, instr.modrm_reg, base, disp);
gen_imm (g, imm, instr.imm_size);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* binary [base+index*scale+disp], imm */
static void
gen_mbisd_imm (Gen *g,
X64Instruction instr,
X64Register base,
X64Register index,
X64Scale scale,
int32_t disp,
int32_t imm)
{
assert (instr.got_rm_imm);
gen_size_prefix (g, instr);
gen_rex_bytes (g, instr, RAX, 0, index.rex, base.rex);
gen_opcode_bytes (g, instr, instr.rm_imm_opcode);
gen_mbisd_bytes (g, instr.modrm_reg, base, index, scale, disp);
gen_imm (g, imm, instr.imm_size);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* binary reg, [base+disp]
* binary [base+disp], reg
*/
static void
gen_r_mbd_or_mbd_r (Gen *g,
X64Instruction instr,
uint8_t opcode,
X64Register reg,
X64Register base,
int32_t disp)
{
gen_size_prefix (g, instr);
gen_rex_bytes (g, instr, reg, reg.rex, 0, base.rex);
gen_opcode_bytes (g, instr, opcode);
gen_mbd_bytes (g, reg.r, base, disp);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* binary reg, [base+index*scale+disp]
* binary [base+index*scale+disp], reg
*/
static void
gen_r_mbisd_or_mbisd_r (Gen *g,
X64Instruction instr,
uint8_t opcode,
X64Register reg,
X64Register base,
X64Register index,
X64Scale scale,
int32_t disp)
{
gen_size_prefix (g, instr);
gen_rex_bytes (g, instr, reg, reg.rex, index.rex, base.rex);
gen_opcode_bytes (g, instr, opcode);
gen_mbisd_bytes (g, reg.r, base, index, scale, disp);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* binary reg, reg */
static void
gen_r_r (Gen *g,
X64Instruction instr,
X64Register dest,
X64Register src)
{
assert (!instr.no_r_r);
if (instr.got_rm_r)
{
gen_size_prefix (g, instr);
gen_rex_bytes (g, instr, (X64Register){.reg_all = dest.reg_all | src.reg_all}, src.rex, 0, dest.rex);
gen_opcode_bytes (g, instr, instr.rm_r_opcode);
*g->dest++ = (X64ModRM) {.mod = 3, .rm = dest.r, .reg = src.r}.mod_rm_all;
}
else if (instr.got_r_rm)
{
gen_size_prefix (g, instr);
gen_rex_bytes (g, instr, (X64Register){.reg_all = dest.reg_all | src.reg_all}, dest.rex, 0, src.rex);
gen_opcode_bytes (g, instr, instr.r_rm_opcode);
*g->dest++ = (X64ModRM) {.mod = 3, .rm = src.r, .reg = dest.r}.mod_rm_all;
}
else
assert (0);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* binary reg, [base+disp] */
static void
gen_r_mbd (Gen *g,
X64Instruction instr,
X64Register reg,
X64Register base,
int32_t disp)
{
assert (instr.got_r_rm);
gen_r_mbd_or_mbd_r (g, instr, instr.r_rm_opcode, reg, base, disp);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* binary [base+disp], reg */
static void
gen_mbd_r (Gen *g,
X64Instruction instr,
X64Register base,
int32_t disp,
X64Register reg)
{
assert (instr.got_rm_r);
gen_r_mbd_or_mbd_r (g, instr, instr.rm_r_opcode, reg, base, disp);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* binary reg, [base+index*scale+disp] */
static void
gen_r_mbisd (Gen *g,
X64Instruction instr,
X64Register reg,
X64Register base,
X64Register index,
X64Scale scale,
int32_t disp)
{
assert (instr.got_r_rm);
gen_r_mbisd_or_mbisd_r (g, instr, instr.r_rm_opcode, reg, base, index, scale, disp);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* binary [base+index*scale+disp], reg */
static void gen_mbisd_r (Gen *g,
X64Instruction instr,
X64Register base,
X64Register index,
X64Scale scale,
int32_t disp,
X64Register reg)
{
assert (instr.got_rm_r);
gen_r_mbisd_or_mbisd_r (g, instr, instr.rm_r_opcode, reg, base, index, scale, disp);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// -*- mode:c; c-file-style: "GNU"; compile-command: "make -f Makefile.jit_x64" -*-
#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <ctype.h>
#include <inttypes.h>
#define ATTRIBUTE_UNUSED
#include "jit_x64gen.h"
/* Test the x64gen fuctions.
*
* The highly (?) sophisticated (?) approach employed here is to
* generate all combinations of operands, along with the expected
* disassembly in each case, then save the result to a file and have
* ndisasm disassemble that. Then use GNU diff to check the expected
* disassembly matches the actual disassembly.
*
* Needs gcc/clang...
*/
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
struct TestLine
{
size_t offset;
char *text;
struct TestLine *next;
};
typedef struct TestLine TestLine;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
struct TestLines
{
TestLine *first, **next_ptr;
size_t mark;
};
typedef struct TestLines TestLines;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static Gen *
create_test_gen (void)
{
Gen *g = calloc (1, sizeof *g);
TestLines *tls = calloc (1, sizeof *tls);
init_gen (g, 16 * 1048576);
set_gen_context (g, tls);
tls->next_ptr = &tls->first;
return g;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
destroy_test_gen (Gen *g)
{
TestLines *tls = g->context;
TestLine *tl = tls->first;
while (tl)
{
TestLine *next = tl->next;
free (tl->text);
free (tl);
tl = next;
}
tls->first = NULL;
tls->next_ptr = NULL;
destroy_gen (g);
free (g);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static size_t
get_offset (Gen *g)
{
return (size_t) (g->dest - g->base);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
mark (Gen *g)
{
TestLines *tls = g->context;
assert (tls->mark == 0);
tls->mark = get_offset (g);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
__attribute__ ((format(printf, 2, 3)))
add_line (Gen *g, const char *fmt, ...)
{
TestLines *tls = g->context;
va_list v;
TestLine *tl;
tl = malloc (sizeof *tl);
if (tls->mark == 0)
tl->offset = get_offset (g);
else
{
tl->offset = tls->mark;
tls->mark = 0;
}
va_start (v, fmt);
vasprintf (&tl->text, fmt, v);
va_end (v);
tl->next = NULL;
*tls->next_ptr = tl;
tls->next_ptr = &tl->next;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
save_data (Gen *g, const char *stem)
{
TestLines *tls = g->context;
char *dat_fname, *txt_fname;
asprintf (&txt_fname, "%s/%s", GOT_FOLDER, stem);
asprintf (&dat_fname, "%s/%s", DAT_FOLDER, stem);
{
FILE *f = fopen (txt_fname, "wt");
assert (f);
for (const TestLine *tl = tls->first; tl; tl = tl->next)
{
size_t i = tl->offset, next = tl->next ? tl->next->offset : g->dest - g->base;
while (i < next)
{
int print = i == tl->offset;
if (print)
fprintf (f, "%08X ", (unsigned) tl->offset);
else
fprintf (f, " -");
for (size_t j = 0; j < 8; ++j)
{
if (i + j < next)
fprintf (f, "%02X", g->base[i + j]);
else if (print)
fprintf (f, " ");
}
if (print)
fprintf (f, " %s", tl->text);
fprintf (f, "\n");
i += 8;
}
}
fclose (f);
}
{
FILE *f = fopen (dat_fname, "wb");
assert (f);
fwrite (g->base, g->dest - g->base, 1, f);
fclose (f);
}
free (dat_fname);
dat_fname = NULL;
free (txt_fname);
txt_fname = NULL;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static char *
get_lower_case (const char *name)
{
char *tmp = strdup (name), *p;
for (p = tmp; *p != 0; ++p)
*p = tolower (*p);
return tmp;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
typedef void (*bisd_callback_fn) (Gen *g,
X64Instruction instr,
const char *mnemonic,
int base_i,
int index_i,
X64Scale scale,
int32_t disp,
void *context);
static const int32_t DISPS[] = {0, 64, 1024, -1};
static void
for_each_bisd (Gen *g,
X64Instruction instr,
const char *mnemonic,
bisd_callback_fn fn,
void *context)
{
for (int b = 0; b < 16; ++b)
{
for (int i = -1; i < 16; ++i)
{
if (i == 4)
continue;
for (int j = 0; DISPS[j] >= 0; ++j)
{
if (i >= 0)
{
(*fn) (g, instr, mnemonic, b, i, X1, DISPS[j], context);
(*fn) (g, instr, mnemonic, b, i, X2, DISPS[j], context);
(*fn) (g, instr, mnemonic, b, i, X4, DISPS[j], context);
(*fn) (g, instr, mnemonic, b, i, X8, DISPS[j], context);
}
else
(*fn) (g, instr, mnemonic, b, i, X1, DISPS[j], context);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
typedef void (*r_mbisd_callback_fn) (Gen *g,
X64Instruction instr,
const char *mnemonic,
int reg_i,
int base_i,
int index_i,
X64Scale scale,
int32_t disp,
void *context);
struct For_Each_R_MBISD_Context {
r_mbisd_callback_fn fn;
void *context;
int reg_i;
};
typedef struct For_Each_R_MBISD_Context For_Each_R_MBISD_Context;
static void
do_r_mbisd_callback (Gen *g,
X64Instruction instr,
const char *mnemonic,
int base_i,
int index_i,
X64Scale scale,
int32_t disp,
void *context)
{
For_Each_R_MBISD_Context *c = context;
(*c->fn) (g, instr, mnemonic, c->reg_i, base_i, index_i, scale, disp, c->context);
}
static void
for_each_r_mbisd (Gen *g,
X64Instruction instr,
const char *mnemonic,
r_mbisd_callback_fn fn,
void *context)
{
For_Each_R_MBISD_Context c;
c.fn = fn;
c.context = context;
for (c.reg_i = 0; c.reg_i < 16; ++c.reg_i)
{
for_each_bisd (g, instr, mnemonic, &do_r_mbisd_callback, &c);
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static char *
get_bisd_string (X64Instruction instr,
int base_i,
int index_i,
X64Scale scale,
int32_t disp)
{
char *str;
/* When the base's non-REX part is 5 (i.e., RBP or R13), a
displacement is mandatory, and ndisasm will then print one. So
this function checks for that case specially. */
if (index_i >= 0)
{
if (disp == 0 && (base_i & 7) != 5)
{
asprintf (&str, "[%s+%s%s]",
REGISTER_QNAMES[base_i],//get_register_name (base_i, instr.op_size),
REGISTER_QNAMES[index_i],//get_register_name (index_i, instr.op_size),
get_scale_string (scale));
}
else
{
asprintf (&str, "[%s+%s%s+0x%x]",
REGISTER_QNAMES[base_i],//get_register_name (base_i, instr.op_size),
REGISTER_QNAMES[index_i],//get_register_name (index_i, instr.op_size),
get_scale_string (scale),
disp);
}
}
else
{
if (disp == 0 && (base_i & 7) != 5)
{
asprintf (&str, "[%s]",
REGISTER_QNAMES[base_i]);//get_register_name (base_i, instr.op_size));
}
else
{
asprintf (&str, "[%s+0x%x]",
REGISTER_QNAMES[base_i],//get_register_name (base_i, instr.op_size),
disp);
}
}
return str;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static const int32_t IMM_VALUES[] =
{
0x12,
0x1234,
0x12345678,
0x12345678
};
static void
do_basic_instr_r_mbisd_callback(Gen *g,
X64Instruction instr,
const char *mnemonic,
int reg_i,
int base_i,
int index_i,
X64Scale scale,
int32_t disp,
void *context)
{
char *bisd = get_bisd_string (instr, base_i, index_i, scale, disp);
X64Register rr = *REGISTERS[reg_i];
X64Register rb = *REGISTERS[base_i];
X64Register ri = *REGISTERS[index_i];
if (instr.got_r_rm)
{
/* r,m */
add_line (g, "%s %s,%s", mnemonic, get_register_name (reg_i, instr.op_size), bisd);
if (index_i >= 0)
gen_r_mbisd (g, instr, rr, rb, ri, scale, disp);
else
gen_r_mbd (g, instr, rr, rb, disp);
}
if (instr.got_rm_r)
{
/* m,r */
add_line (g, "%s %s,%s", mnemonic, bisd, get_register_name (reg_i, instr.op_size));
if (index_i >= 0)
gen_mbisd_r (g, instr, rb, ri, scale, disp, rr);
else
gen_mbd_r (g, instr, rb, disp, rr);
}
free (bisd);
bisd = NULL;
}
static void
do_basic_instr_m_imm_callback (Gen *g,
X64Instruction instr,
const char *mnemonic,
int base_i,
int index_i,
X64Scale scale,
int32_t disp,
void *context)
{
char *bisd = get_bisd_string (instr, base_i, index_i, scale, disp);
if (instr.got_rm_imm)
{
/* m,imm */
int32_t imm = IMM_VALUES[instr.op_size];
add_line (g, "%s %s %s,0x%x", mnemonic, get_size_keyword (instr.op_size), bisd, imm);
if (index_i >= 0)
gen_mbisd_imm (g, instr, *REGISTERS[base_i], *REGISTERS[index_i], scale, disp, imm);
else
gen_mbd_imm (g, instr, *REGISTERS[base_i], disp, imm);
}
free (bisd);
bisd = NULL;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
test_basic_instr (const char *name,
const X64Instruction *instrb,
const X64Instruction *instrw,
const X64Instruction *instrd,
const X64Instruction *instrq)
{
fprintf (stderr, "generating %s...\n", name);
const X64Instruction *instrs[4] =
{
instrb,
instrw,
instrd,
instrq,
};
Gen *g = create_test_gen ();
char *mnemonic = get_lower_case (name);
/* r,m; m,r */
for (int i = 0; i < 4; ++i)
{
if (instrs[i])
for_each_r_mbisd (g, *instrs[i], mnemonic, &do_basic_instr_r_mbisd_callback, NULL);
}
/* r,imm */
for (unsigned i = 0; i < 16; ++i)
{
for (int j = 0; j < 4; ++j)
{
if (instrs[j] && instrs[j]->got_rm_imm)
{
add_line (g, "%s %s,0x%x", mnemonic, REGISTER_NAMES[j][i], IMM_VALUES[j]);
gen_r_imm (g, *instrs[j], *REGISTERS[i], IMM_VALUES[j]);
}
}
}
/* m,imm */
for (int i = 0; i < 4; ++i)
{
if (instrs[i])
for_each_bisd (g, *instrs[i], mnemonic, &do_basic_instr_m_imm_callback, NULL);
}
/* r,r */
for (unsigned i = 0; i < 16; ++i)
{
for (unsigned j = 0; j < 16; ++j)
{
for (unsigned k = 0; k < 4; ++k)
{
if (instrs[k] && !instrs[k]->no_r_r)
{
add_line (g, "%s %s,%s", mnemonic, REGISTER_NAMES[k][i], REGISTER_NAMES[k][j]);
gen_r_r (g, *instrs[k], *REGISTERS[i], *REGISTERS[j]);
}
}
}
}
save_data (g, mnemonic);
free (mnemonic);
mnemonic = NULL;
destroy_test_gen (g);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
do_unary_instr_callback(Gen *g,
X64Instruction instr,
const char *mnemonic,
int reg_i,
int base_i,
int index_i,
X64Scale scale,
int32_t disp,
void *context)
{
char *bisd = get_bisd_string (instr, base_i, index_i, scale, disp);
add_line (g, "%s %s %s%s", mnemonic, get_size_keyword (instr.op_size), bisd, context);
if (index_i >= 0)
gen_mbisd (g, instr, *REGISTERS[base_i], *REGISTERS[index_i], scale, disp);
else
gen_mbd (g, instr, *REGISTERS[base_i], disp);
free (bisd);
bisd = NULL;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
test_unary_instr (const char *name,
const X64Instruction *instrb,
const X64Instruction *instrw,
const X64Instruction *instrd,
const X64Instruction *instrq)
{
fprintf (stderr, "generating %s...\n", name);
Gen *g = create_test_gen ();
char *mnemonic = get_lower_case (name);
char *fname = get_lower_case (name);
char *extra = "";
/* Instruction-specific bodges :( */
{
char *c = &mnemonic[strlen (mnemonic) - 1];
char *dot = strchr (mnemonic, '.');
if (*c == '1')
{
/* Bodge for the shift instructions' syntax. */
extra = ",1";
*c = 0;
}
else if (dot)
{
/* Bodge for any instruction, as required - everything after
the '.' is part of the file name, and not the mnemonic. */
*dot = 0;
}
}
const X64Instruction *instrs[4] =
{
instrb,
instrw,
instrd,
instrq,
};
for (int i = 0; i < 4; ++i)
{
if (instrs[i])
for_each_r_mbisd (g, *instrs[i], mnemonic, &do_unary_instr_callback, extra);
}
/* if (instrb) */
/* for_each_r_mbisd (g, *instrb, mnemonic, &do_unary_instr_callback, extra); */
/* if (instrw) */
/* for_each_r_mbisd (g, *instrw, mnemonic, &do_unary_instr_callback, extra); */
/* if (instrd) */
/* for_each_r_mbisd (g, *instrd, mnemonic, &do_unary_instr_callback, extra); */
/* if (instrq) */
/* for_each_r_mbisd (g, *instrq, mnemonic, &do_unary_instr_callback, extra); */
for (unsigned i = 0; i < 16; ++i)
{
for (int j = 0; j < 4; ++j)
{
if (instrs[j])
{
add_line (g, "%s %s%s", mnemonic, REGISTER_NAMES[j][i], extra);
gen_r (g, *instrs[j], *REGISTERS[i]);
}
}
}
/* if (instrb) */
/* { */
/* add_line (g, "%s %s%s", mnemonic, REGISTER_BNAMES[i], extra); */
/* gen_r (g, *instrb, *REGISTERS[i]); */
/* } */
/* if (instrw) */
/* { */
/* add_line (g, "%s %s%s", mnemonic, REGISTER_WNAMES[i], extra); */
/* gen_r (g, *instrw, *REGISTERS[i]); */
/* } */
/* if (instrd) */
/* { */
/* add_line (g, "%s %s%s", mnemonic, REGISTER_DNAMES[i], extra); */
/* gen_r (g, *instrd, *REGISTERS[i]); */
/* } */
/* if (instrq) */
/* { */
/* add_line (g, "%s %s%s", mnemonic, REGISTER_QNAMES[i], extra); */
/* gen_r (g, *instrq, *REGISTERS[i]); */
/* } */
/* } */
save_data (g, fname);
free (fname);
fname = NULL;
free (mnemonic);
mnemonic = NULL;
destroy_test_gen (g);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
do_setcc_callback (Gen *g,
X64Instruction instr,
const char *mnemonic,
int reg_i,
int base_i,
int index_i,
X64Scale scale,
int32_t disp,
void *context)
{
char *bisd = get_bisd_string (instr, base_i, index_i, scale, disp);
add_line (g, "%s %s", mnemonic, bisd);
if (index_i >= 0)
gen_mbisd (g, instr, *REGISTERS[base_i], *REGISTERS[index_i], scale, disp);
else
gen_mbd (g, instr, *REGISTERS[base_i], disp);
free (bisd);
bisd = NULL;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
test_setcc (const char *name, const char *ndisasm_name, X64Instruction instr)
{
fprintf (stderr, "generating %s...\n", name);
Gen *g = create_test_gen ();
char *mnemonic = get_lower_case (ndisasm_name);
char *name_lc = get_lower_case (name);
for_each_r_mbisd (g, instr, mnemonic, &do_setcc_callback, NULL);
for (unsigned i = 0; i < 16; ++i)
{
add_line (g, "%s %s", mnemonic, REGISTER_BNAMES[i]);
gen_r (g, instr, *REGISTERS[i]);
}
save_data (g, name_lc);
free (name_lc);
name_lc = NULL;
free (mnemonic);
mnemonic = NULL;
destroy_test_gen (g);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
/* static void */
/* nops (Gen *g, int n) */
/* { */
/* } */
static void
test_jcc (const char *name, const char *ndisasm_name, X64Instruction instr)
{
fprintf (stderr, "generating %s...\n", name);
Gen *g = create_test_gen ();
char *mnemonic = get_lower_case (ndisasm_name);
char *name_lc = get_lower_case (name);
for (int i = 0; i < 123; ++i)
{
add_line (g, "nop");
gen_nop (g);
}
/* syntax hack. */
const char *byte_extra = "";
if (strcmp (mnemonic, "jmp") == 0)
{
byte_extra = "short ";
}
if (instr.rel8_opcode != 0)
{
/* byte backwards */
{
uint8_t *dest = g->dest - 123;
add_line (g, "%s %s0x%x", mnemonic, byte_extra, (unsigned) (dest - g->base));
gen_rel (g, instr, dest);
}
/* byte, forwards. */
{
uint8_t *dest = g->dest + 129;
add_line (g, "%s %s0x%x", mnemonic, byte_extra, (unsigned) (dest - g->base));
gen_rel (g, instr, dest);
}
}
/* dword, backwards */
{
uint8_t *dest = g->dest - 128;
add_line (g, "%s qword 0x%" PRIx64, mnemonic, (int64_t) (dest - g->base));
gen_rel (g, instr, dest);
}
/* dword, forwards */
{
uint8_t *dest = g->dest + 1024;
add_line (g, "%s qword 0x%" PRIx64, mnemonic, (int64_t) (dest - g->base));
gen_rel (g, instr, dest);
}
save_data (g, name_lc);
free (name_lc);
name_lc = NULL;
free (mnemonic);
mnemonic = NULL;
destroy_test_gen (g);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
do_test_mem_dest_callback (Gen *g,
X64Instruction instr,
const char *mnemonic,
int base_i,
int index_i,
X64Scale scale,
int32_t disp,
void *context)
{
int shift = *(int *) context;
char *bisd = get_bisd_string (instr, base_i, index_i, scale, disp);
int32_t imm = IMM_VALUES[instr.imm_size];
add_line (g, "%s %s %s,%s%s0x%x",
mnemonic,
get_size_keyword (instr.op_size),
bisd,
shift ? get_size_keyword (instr.imm_size) : "",
shift ? " " : "",
imm);
if (index_i >= 0)
gen_mbisd_imm (g, instr, *REGISTERS[base_i], *REGISTERS[index_i], scale, disp, imm);
else
gen_mbd_imm (g, instr, *REGISTERS[base_i], disp, imm);
free (bisd);
bisd = NULL;
}
static void
test_mem_dest_instr (const char *name, const X64Instruction *instrs)
{
fprintf (stderr, "generating %s...\n", name);
Gen *g = create_test_gen ();
char *mnemonic = get_lower_case (name);
/* Annoying inconsistency... I should probably actually be
treating the shift instruction as their own thing. */
int shift = (strcmp (mnemonic, "shl") == 0
|| strcmp (mnemonic, "shr") == 0
|| strcmp (mnemonic, "sar") == 0);
for (int size = 0; size < 4; ++size)
{
X64Instruction instr = instrs[size];
for (int i = 0; i < 16; ++i)
{
add_line (g, "%s %s,%s%s0x%x",
mnemonic,
REGISTER_NAMES[size][i],
shift ? get_size_keyword (instr.imm_size) : "",
shift ? " " : "",
IMM_VALUES[instr.imm_size]);
gen_r_imm (g, instr, *REGISTERS[i], IMM_VALUES[instr.imm_size]);
}
for_each_bisd (g, instr, mnemonic, &do_test_mem_dest_callback, &shift);
}
save_data (g, mnemonic);
free (mnemonic);
mnemonic = NULL;
destroy_test_gen (g);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
do_test_cmovcc_callback (Gen *g,
X64Instruction instr,
const char *mnemonic,
int reg_i,
int base_i,
int index_i,
X64Scale scale,
int32_t disp,
void *context)
{
char *bisd = get_bisd_string (instr, base_i, index_i, scale, disp);
add_line (g, "%s %s,%s", mnemonic, REGISTER_NAMES[instr.op_size][reg_i], bisd);
if (index_i >= 0)
gen_r_mbisd (g, instr, *REGISTERS[reg_i], *REGISTERS[base_i], *REGISTERS[index_i], scale, disp);
else
gen_r_mbd (g, instr, *REGISTERS[reg_i], *REGISTERS[base_i], disp);
free (bisd);
bisd = NULL;
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
test_cmovcc (const char *name,
const char *ndisasm_name,
const X64Instruction *instrs)
{
fprintf (stderr, "generating %s...\n", name);
Gen *g = create_test_gen ();
char *mnemonic = get_lower_case (ndisasm_name);
char *name_lc = get_lower_case (name);
/* There's no byte CMOV. */
for (int size = 1; size < 4; ++size)
{
for_each_r_mbisd (g, instrs[size], mnemonic, &do_test_cmovcc_callback, NULL);
}
save_data (g, name_lc);
free (name_lc);
name_lc = NULL;
free (mnemonic);
mnemonic = NULL;
destroy_test_gen (g);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void
test_misc (void)
{
Gen *g = create_test_gen ();
add_line (g, "ret");
gen_ret (g);
add_line (g, "nop");
gen_nop (g);
for (int i = 0; i < 16; ++i)
{
int64_t imm64 = 0x123456789abcedf0ull;
add_line (g, "mov %s,0x%" PRIx64, REGISTER_NAMES[i], imm64);
gen_mov_r_imm64 (g, *REGISTERS[i], imm64);
}
destroy_test_gen (g);
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
int main (int argc, char *argv[])
{
const char *what = NULL;
if (argc >= 2)
what = argv[1];
assert ((X64ModRM) {.mod = 3}.mod_rm_all == 0300);
assert ((X64ModRM) {.reg=7}.mod_rm_all == 0070);
assert ((X64ModRM) {.rm = 7}.mod_rm_all == 0007);
assert ((X64SIB) {.s = 3}.sib_all == 0300);
assert ((X64SIB) {.i = 7}.sib_all == 0070);
assert ((X64SIB) {.b = 7}.sib_all == 0007);
assert ((X64REX) {.b = 1}.rex_all == 0x01);
assert ((X64REX) {.x = 1}.rex_all == 0x02);
assert ((X64REX) {.r = 1}.rex_all == 0x04);
assert ((X64REX) {.w = 1}.rex_all == 0x08);
assert ((X64REX) {._ = 15}.rex_all == 0xf0);
/*
a
b - basic
c - cmovcc
d
e
f
g
h
i
j - jcc
k
l
m - mem dest
n
o - other
p
q
r
s - setcc
t
u - unary
v
w
x
y
z
*/
if (!what || strchr (what, 'b'))
{
#define BASIC_INSTR(NAME, INDEX) test_basic_instr (#NAME, &(NAME##B), &(NAME##W), &(NAME##D), &(NAME##Q));
BASIC_INSTRS
;
#undef BASIC_INSTR
test_basic_instr ("mov", &MOVB, &MOVW, &MOVD, &MOVQ);
}
if (!what || strchr (what, 'm'))
{
#define MEM_DEST_INSTR(NAME, RM, RM_IMM, R, IMM_SIZE) test_mem_dest_instr (#NAME, (X64Instruction[]) {NAME##B, NAME##W, NAME##D, NAME##Q});
MEM_DEST_INSTRS
;
#undef MEM_DEST_INSTR
}
if (!what || strchr (what, 'u'))
{
#define UNARY_INSTR(NAME, OP, INDEX) test_unary_instr (#NAME, &(NAME##B), &(NAME##W), &(NAME##D), &(NAME##Q));
UNARY_INSTRS
;
#undef UNARY_INSTR
}
if (!what || strchr (what, 'o'))
{
test_jcc ("call", "call", CALL);
test_unary_instr ("call.unary", NULL, NULL, NULL, &CALL);
test_jcc ("jmp", "jmp", JMP);
test_unary_instr ("jmp.unary", NULL, NULL, NULL, &JMP);
test_unary_instr ("push", NULL, NULL, NULL, &PUSHQ);
test_unary_instr ("pop", NULL, NULL, NULL, &POPQ);
test_basic_instr ("lea", NULL, NULL, NULL, &LEAQ);
test_misc ();
}
#define COND(CC, NDISASM_CC) \
if (!what || strchr (what, 's')) \
test_setcc ("set" #CC, "set" #NDISASM_CC, SET##CC); \
if (!what || strchr (what, 'j')) \
test_jcc ("j" #CC, "j" #NDISASM_CC, J##CC); \
if (!what || strchr (what, 'c')) \
test_cmovcc ("cmov" #CC, "cmov" #NDISASM_CC, (X64Instruction[]) {[1] = CMOVW##CC, CMOVD##CC, CMOVQ##CC});
ALL_CONDITIONS
;
#undef COND
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment