/* =========================================================================== vm_x86_64_assembler.c -- assembler for x86-64 Copyright (C) 2007 Ludwig Nussel , Novell inc. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #define _ISOC99_SOURCE #include "vm_local.h" #include #include #include #include #include typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; static char* out; static unsigned compiledOfs; static unsigned assembler_pass; static const char* cur_line; static FILE* fout; #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define MAX(a,b) ((a) > (b) ? (a) : (b)) #define crap(fmt, args...) do { \ _crap(__FUNCTION__, fmt, ##args); \ } while(0) #define CRAP_INVALID_ARGS crap("invalid arguments %s, %s", argtype2str(arg1.type),argtype2str(arg2.type)); #ifdef DEBUG #define debug(fmt, args...) printf(fmt, ##args) #else #define debug(fmt, args...) #endif static void _crap(const char* func, const char* fmt, ...) { va_list ap; fprintf(stderr, "%s() - ", func); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputc('\n', stderr); if(cur_line && cur_line[0]) fprintf(stderr, "-> %s\n", cur_line); exit(1); } static void emit1(unsigned char v) { int writecnt; if(assembler_pass) { out[compiledOfs++] = v; if(fout) writecnt = fwrite(&v, 1, 1, fout); debug("%02hx ", v); } else { ++compiledOfs; } } static inline void emit2(u16 v) { emit1(v&0xFF); emit1((v>>8)&0xFF); } static inline void emit4(u32 v) { emit1(v&0xFF); emit1((v>>8)&0xFF); emit1((v>>16)&0xFF); emit1((v>>24)&0xFF); } static inline void emit8(u64 v) { emit4(v&0xFFFFFFFF); emit4((v>>32)&0xFFFFFFFF); } enum { REX_W = 0x08, REX_R = 0x04, REX_X = 0x02, REX_B = 0x01, }; enum { MODRM_MOD_00 = 0x00, MODRM_MOD_01 = 0x01 << 6, MODRM_MOD_10 = 0x02 << 6, MODRM_MOD_11 = 0x03 << 6, MODRM_RM_SIB = 0x04, }; typedef enum { T_NONE = 0x00, T_REGISTER = 0x01, T_IMMEDIATE = 0x02, T_MEMORY = 0x04, T_LABEL = 0x08, T_ABSOLUTE = 0x80 } argtype_t; typedef enum { R_8 = 0x100, R_16 = 0x200, R_64 = 0x800, R_MSZ = 0xF00, // size mask R_XMM = 0x2000, // xmm register. year, sucks R_EAX = 0x00, R_EBX = 0x03, R_ECX = 0x01, R_EDX = 0x02, R_ESI = 0x06, R_EDI = 0x07, R_ESP = 0x04, R_RAX = R_EAX | R_64, R_RBX = R_EBX | R_64, R_RCX = R_ECX | R_64, R_RDX = R_EDX | R_64, R_RSI = R_ESI | R_64, R_RDI = R_EDI | R_64, R_RSP = R_ESP | R_64, R_R8 = 0x08 | R_64, R_R9 = 0x09 | R_64, R_R10 = 0x0A | R_64, R_R15 = 0x0F | R_64, R_AL = R_EAX | R_8, R_AX = R_EAX | R_16, R_CL = R_ECX | R_8, R_XMM0 = 0x00 | R_XMM, R_MGP = 0x0F, // mask for general purpose registers } reg_t; typedef enum { MODRM_SIB = 0, MODRM_NOSIB = 0x3, } modrm_sib_t; typedef struct { unsigned disp; argtype_t basetype; union { u64 imm; reg_t reg; } base; argtype_t indextype; union { u64 imm; reg_t reg; } index; unsigned scale; } memref_t; #define LABELLEN 32 typedef struct { argtype_t type; union { u64 imm; reg_t reg; memref_t mem; char label[LABELLEN]; } v; int absolute:1; } arg_t; typedef void (*emitfunc)(const char* op, arg_t arg1, arg_t arg2, void* data); typedef struct { char* mnemonic; emitfunc func; void* data; } op_t; typedef struct { u8 xmmprefix; u8 subcode; // in modrm u8 rmcode; // opcode for reg/mem, reg u8 mrcode; // opcode for reg, reg/mem u8 rcode8; // opcode for reg8/mem8 u8 rcode; // opcode for reg/mem } opparam_t; /* ************************* */ static unsigned hashkey(const char *string, unsigned len) { unsigned register hash, i; hash = 0; for (i = 0; i < len && string[i] != '\0'; ++i) { hash += string[i] * (119 + i); } hash = (hash ^ (hash >> 10) ^ (hash >> 20)); return hash; } struct hashentry { char* label; unsigned address; struct hashentry* next; }; static struct hashentry* labelhash[1021]; // no dup check! static void hash_add_label(const char* label, unsigned address) { struct hashentry* h; unsigned i = hashkey(label, -1U); int labellen; i %= sizeof(labelhash)/sizeof(labelhash[0]); h = malloc(sizeof(struct hashentry)); labellen = strlen(label) + 1; h->label = malloc(labellen); Com_sprintf(h->label, labellen, "%s", label); h->address = address; h->next = labelhash[i]; labelhash[i] = h; } static unsigned lookup_label(const char* label) { struct hashentry* h; unsigned i = hashkey(label, -1U); i %= sizeof(labelhash)/sizeof(labelhash[0]); for(h = labelhash[i]; h; h = h->next ) { if(!strcmp(h->label, label)) return h->address; } if(assembler_pass) crap("label %s undefined", label); return 0; } static void labelhash_free(void) { struct hashentry* h; unsigned i; unsigned z = 0, min = -1U, max = 0, t = 0; for ( i = 0; i < sizeof(labelhash)/sizeof(labelhash[0]); ++i) { unsigned n = 0; h = labelhash[i]; while(h) { struct hashentry* next = h->next; free(h->label); free(h); h = next; ++n; } t+=n; if(!n) ++z; //else printf("%u\n", n); min = MIN(min, n); max = MAX(max, n); } printf("total %u, hsize %"PRIu64", zero %u, min %u, max %u\n", t, sizeof(labelhash)/sizeof(labelhash[0]), z, min, max); memset(labelhash, 0, sizeof(labelhash)); } /* ************************* */ static const char* argtype2str(argtype_t t) { switch(t) { case T_NONE: return "none"; case T_REGISTER: return "register"; case T_IMMEDIATE: return "immediate"; case T_MEMORY: return "memory"; case T_LABEL: return "label"; default: crap("invalid type"); } /* not reached */ return T_NONE; } /* ************************* */ static inline int iss8(u64 v) { return (llabs(v) <= 0x80); //llabs instead of labs required for __WIN64 } static inline int isu8(u64 v) { return (v <= 0xff); } static inline int iss16(u64 v) { return (llabs(v) <= 0x8000); } static inline int isu16(u64 v) { return (v <= 0xffff); } static inline int iss32(u64 v) { return (llabs(v) <= 0x80000000); } static inline int isu32(u64 v) { return (v <= 0xffffffff); } static void emit_opsingle(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) { u8 op = (u8)((uint64_t) data); if(arg1.type != T_NONE || arg2.type != T_NONE) CRAP_INVALID_ARGS; emit1(op); } static void emit_opsingle16(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) { emit1(0x66); emit_opsingle(mnemonic, arg1, arg2, data); } static void compute_rexmodrmsib(u8* rex_r, u8* modrm_r, u8* sib_r, arg_t* arg1, arg_t* arg2) { u8 rex = 0; u8 modrm = 0; u8 sib = 0; if((arg1->type == T_REGISTER && arg2->type == T_REGISTER) && ((arg1->v.reg & R_MSZ) != (arg2->v.reg & R_MSZ)) && !((arg1->v.reg & R_XMM) || (arg2->v.reg & R_XMM))) crap("both registers must be of same width"); if((arg1->type == T_REGISTER && arg1->v.reg & R_64) || (arg2->type == T_REGISTER && arg2->v.reg & R_64)) { rex |= REX_W; } if(arg1->type == T_REGISTER) { if((arg1->v.reg & R_MGP) > 0x07) rex |= REX_R; modrm |= (arg1->v.reg & 0x07) << 3; } if(arg2->type == T_REGISTER) { if((arg2->v.reg & R_MGP) > 0x07) rex |= REX_B; modrm |= (arg2->v.reg & 0x07); } if(arg2->type == T_MEMORY) { if((arg2->v.mem.basetype == T_REGISTER && !(arg2->v.mem.base.reg & R_64)) || (arg2->v.mem.indextype == T_REGISTER && !(arg2->v.mem.index.reg & R_64))) { crap("only 64bit base/index registers are %x %x", arg2->v.mem.base.reg, arg2->v.mem.index.reg); } if(arg2->v.mem.indextype == T_REGISTER) { modrm |= MODRM_RM_SIB; if(!arg2->v.mem.disp) { modrm |= MODRM_MOD_00; } else if(iss8(arg2->v.mem.disp)) { modrm |= MODRM_MOD_01; } else if(isu32(arg2->v.mem.disp)) { modrm |= MODRM_MOD_10; } else { crap("invalid displacement"); } if((arg2->v.mem.index.reg & R_MGP) > 0x07) rex |= REX_X; if((arg2->v.mem.base.reg & R_MGP) > 0x07) rex |= REX_B; if(arg2->v.mem.basetype != T_REGISTER) crap("base must be register"); switch(arg2->v.mem.scale) { case 1: break; case 2: sib |= 1 << 6; break; case 4: sib |= 2 << 6; break; case 8: sib |= 3 << 6; break; } sib |= (arg2->v.mem.index.reg & 0x07) << 3; sib |= (arg2->v.mem.base.reg & 0x07); } else if(arg2->v.mem.indextype == T_NONE) { if(!arg2->v.mem.disp) { modrm |= MODRM_MOD_00; } else if(iss8(arg2->v.mem.disp)) { modrm |= MODRM_MOD_01; } else if(isu32(arg2->v.mem.disp)) { modrm |= MODRM_MOD_10; } else { crap("invalid displacement"); } if(arg2->v.mem.basetype != T_REGISTER) crap("todo: base != register"); if((arg2->v.mem.base.reg & R_MGP) > 0x07) rex |= REX_B; modrm |= arg2->v.mem.base.reg & 0x07; } else { crap("invalid indextype"); } } else { modrm |= MODRM_MOD_11; } if(rex) rex |= 0x40; // XXX *rex_r = rex; *modrm_r = modrm; *sib_r = sib; } static void maybe_emit_displacement(arg_t* arg) { if(arg->type != T_MEMORY) return; if(arg->v.mem.disp) { if(iss8(arg->v.mem.disp)) { emit1((u8)arg->v.mem.disp); } else if(isu32(arg->v.mem.disp)) { emit4(arg->v.mem.disp); } else { crap("invalid displacement"); } } } /* one byte operator with register added to operator */ static void emit_opreg(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) { u8 op = (u8)((uint64_t) data); if(arg1.type != T_REGISTER || arg2.type != T_NONE) CRAP_INVALID_ARGS; if((arg1.v.reg & R_MGP) > 0x07) emit1(0x40 | REX_B); op |= (arg1.v.reg & 0x07); emit1(op); } /* operator which operates on reg/mem */ static void emit_op_rm(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) { u8 rex, modrm, sib; opparam_t* params = data; if((arg1.type != T_REGISTER && arg1.type != T_MEMORY) || arg2.type != T_NONE) CRAP_INVALID_ARGS; compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); modrm |= params->subcode << 3; if(arg1.v.reg & R_16) emit1(0x66); if(rex) emit1(rex); if(arg1.v.reg & R_8) emit1(params->rcode8); // op reg8/mem8, else emit1(params->rcode); // op reg/mem, emit1(modrm); if((modrm & 0x07) == MODRM_RM_SIB) emit1(sib); maybe_emit_displacement(&arg1); } /* operator which operates on reg/mem with cl */ static void emit_op_rm_cl(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) { u8 rex, modrm, sib; opparam_t* params = data; if(arg2.type != T_REGISTER || arg1.type != T_REGISTER) CRAP_INVALID_ARGS; if((arg1.v.reg & R_MGP) != R_ECX && !(arg1.v.reg & R_8)) crap("only cl register is valid"); arg1.type = T_NONE; // don't complain, we know it's cl anyways compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); modrm |= params->subcode << 3; if(arg2.v.reg & R_16) emit1(0x66); if(rex) emit1(rex); if(arg2.v.reg & R_8) emit1(params->rcode8); // op reg8/mem8, else emit1(params->rcode); // op reg/mem, emit1(modrm); if((modrm & 0x07) == MODRM_RM_SIB) emit1(sib); maybe_emit_displacement(&arg2); } static void emit_mov(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) { u8 rex = 0; u8 modrm = 0; u8 sib = 0; if(arg1.type == T_IMMEDIATE && arg2.type == T_REGISTER) { u8 op = 0xb8; if(arg2.v.reg & R_8) { if(!isu8(arg1.v.imm)) crap("value too large for 8bit register"); op = 0xb0; } else if(arg2.v.reg & R_16) { if(!isu16(arg1.v.imm)) crap("value too large for 16bit register"); emit1(0x66); } else if(!(arg2.v.reg & R_64)) { if(!isu32(arg1.v.imm)) crap("value too large for 32bit register"); } compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); if(rex) emit1(rex); op |= (arg2.v.reg & 0x07); emit1(op); if(arg2.v.reg & R_8) emit1(arg1.v.imm); else if(arg2.v.reg & R_16) emit2(arg1.v.imm); else if(arg2.v.reg & R_64) emit8(arg1.v.imm); else emit4(arg1.v.imm); } else if(arg1.type == T_IMMEDIATE && arg2.type == T_MEMORY) { if(!iss32(arg1.v.imm)) { crap("only 32bit immediates supported"); } compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); if(rex) emit1(rex); emit1(0xc7); // mov reg/mem, imm emit1(modrm); if((modrm & 0x07) == MODRM_RM_SIB) emit1(sib); emit4(arg1.v.imm); } else if(arg1.type == T_REGISTER && arg2.type == T_REGISTER) // XXX: same as next { if(arg1.type != T_REGISTER || arg2.type != T_REGISTER) crap("both args must be registers"); if((arg1.v.reg & R_MSZ) != (arg2.v.reg & R_MSZ)) crap("both registers must be same width"); compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); if(rex) emit1(rex); emit1(0x89); // mov reg reg/mem, emit1(modrm); } else if(arg1.type == T_REGISTER && arg2.type == T_MEMORY) { compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); if(arg1.v.reg & R_16) emit1(0x66); if(rex) emit1(rex); if(arg1.v.reg & R_8) emit1(0x88); // mov reg reg/mem, else emit1(0x89); // mov reg reg/mem, emit1(modrm); if((modrm & 0x07) == MODRM_RM_SIB) emit1(sib); maybe_emit_displacement(&arg2); } else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER) { compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); if(arg2.v.reg & R_16) emit1(0x66); if(rex) emit1(rex); if(arg2.v.reg & R_8) emit1(0x8a); // mov reg/mem, reg else emit1(0x8b); // mov reg/mem, reg emit1(modrm); if((modrm & 0x07) == MODRM_RM_SIB) emit1(sib); maybe_emit_displacement(&arg1); } else CRAP_INVALID_ARGS; } static void emit_subaddand(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) { u8 rex = 0; u8 modrm = 0; u8 sib = 0; opparam_t* params = data; if(arg1.type == T_IMMEDIATE && arg2.type == T_REGISTER) { if(!iss32(arg1.v.imm)) { crap("only 8 and 32 bit immediates supported"); } compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); modrm |= params->subcode << 3; if(rex) emit1(rex); #if 0 if(isu8(arg1.v.imm)) { emit1(0x83); // sub reg/mem, imm8 emit1(modrm); emit1(arg1.v.imm&0xFF); } else #endif { emit1(0x81); // sub reg/mem, imm32 emit1(modrm); emit4(arg1.v.imm); } } else if(arg1.type == T_REGISTER && (arg2.type == T_MEMORY || arg2.type == T_REGISTER)) { compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); if(rex) emit1(rex); emit1(params->rmcode); // sub reg/mem, reg emit1(modrm); if(arg2.type == T_MEMORY && (modrm & 0x07) == MODRM_RM_SIB) emit1(sib); maybe_emit_displacement(&arg2); } else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER && params->mrcode) { compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); if(rex) emit1(rex); emit1(params->mrcode); // sub reg, reg/mem emit1(modrm); if((modrm & 0x07) == MODRM_RM_SIB) emit1(sib); maybe_emit_displacement(&arg1); } else CRAP_INVALID_ARGS; } static void emit_condjump(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) { unsigned off; int disp; unsigned char opcode = (unsigned char)(((uint64_t)data)&0xFF); if(arg1.type != T_LABEL || arg2.type != T_NONE) crap("%s: argument must be label", mnemonic); emit1(opcode); off = lookup_label(arg1.v.label); disp = off-(compiledOfs+1); if(assembler_pass && abs(disp) > 127) crap("cannot jump that far (%x -> %x = %x)", compiledOfs, off, disp); emit1(disp); } static void emit_jmp(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) { if((arg1.type != T_LABEL && arg1.type != T_REGISTER && arg1.type != T_MEMORY) || arg2.type != T_NONE) CRAP_INVALID_ARGS; if(arg1.type == T_LABEL) { unsigned off; int disp; off = lookup_label(arg1.v.label); disp = off-(compiledOfs+5); emit1(0xe9); emit4(disp); } else { u8 rex, modrm, sib; if(arg1.type == T_REGISTER) { if(!arg1.absolute) crap("jmp must be absolute"); if((arg1.v.reg & R_64) != R_64) crap("register must be 64bit"); arg1.v.reg ^= R_64; // no rex required for call } compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); modrm |= 0x4 << 3; if(rex) emit1(rex); emit1(0xff); emit1(modrm); if((modrm & 0x07) == MODRM_RM_SIB) emit1(sib); maybe_emit_displacement(&arg1); } } static void emit_call(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) { u8 rex, modrm, sib; if((arg1.type != T_REGISTER && arg1.type != T_IMMEDIATE) || arg2.type != T_NONE) CRAP_INVALID_ARGS; if(arg1.type == T_REGISTER) { if(!arg1.absolute) crap("call must be absolute"); if((arg1.v.reg & R_64) != R_64) crap("register must be 64bit"); arg1.v.reg ^= R_64; // no rex required for call compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); modrm |= 0x2 << 3; if(rex) emit1(rex); emit1(0xff); emit1(modrm); } else { if(!isu32(arg1.v.imm)) crap("must be 32bit argument"); emit1(0xe8); emit4(arg1.v.imm); } } static void emit_twobyte(const char* mnemonic, arg_t arg1, arg_t arg2, void* data) { u8 rex, modrm, sib; opparam_t* params = data; if(arg1.type == T_REGISTER && (arg2.type == T_MEMORY || arg2.type == T_REGISTER)) { compute_rexmodrmsib(&rex, &modrm, &sib, &arg1, &arg2); if(params->xmmprefix) emit1(params->xmmprefix); if(rex) emit1(rex); emit1(0x0f); emit1(params->rmcode); // sub reg/mem, reg emit1(modrm); if((modrm & 0x07) == MODRM_RM_SIB) emit1(sib); maybe_emit_displacement(&arg2); } else if(arg1.type == T_MEMORY && arg2.type == T_REGISTER && params->mrcode) { compute_rexmodrmsib(&rex, &modrm, &sib, &arg2, &arg1); if(params->xmmprefix) emit1(params->xmmprefix); if(rex) emit1(rex); emit1(0x0f); emit1(params->mrcode); // sub reg, reg/mem emit1(modrm); if((modrm & 0x07) == MODRM_RM_SIB) emit1(sib); maybe_emit_displacement(&arg1); } else CRAP_INVALID_ARGS; } static opparam_t params_add = { subcode: 0, rmcode: 0x01, }; static opparam_t params_or = { subcode: 1, rmcode: 0x09, }; static opparam_t params_and = { subcode: 4, rmcode: 0x21, }; static opparam_t params_sub = { subcode: 5, rmcode: 0x29, }; static opparam_t params_xor = { subcode: 6, rmcode: 0x31, }; static opparam_t params_cmp = { subcode: 7, rmcode: 0x39, mrcode: 0x3b, }; static opparam_t params_dec = { subcode: 1, rcode: 0xff, rcode8: 0xfe, }; static opparam_t params_sar = { subcode: 7, rcode: 0xd3, rcode8: 0xd2, }; static opparam_t params_shl = { subcode: 4, rcode: 0xd3, rcode8: 0xd2, }; static opparam_t params_shr = { subcode: 5, rcode: 0xd3, rcode8: 0xd2, }; static opparam_t params_idiv = { subcode: 7, rcode: 0xf7, rcode8: 0xf6, }; static opparam_t params_div = { subcode: 6, rcode: 0xf7, rcode8: 0xf6, }; static opparam_t params_imul = { subcode: 5, rcode: 0xf7, rcode8: 0xf6, }; static opparam_t params_mul = { subcode: 4, rcode: 0xf7, rcode8: 0xf6, }; static opparam_t params_neg = { subcode: 3, rcode: 0xf7, rcode8: 0xf6, }; static opparam_t params_not = { subcode: 2, rcode: 0xf7, rcode8: 0xf6, }; static opparam_t params_cvtsi2ss = { xmmprefix: 0xf3, rmcode: 0x2a }; static opparam_t params_cvttss2si = { xmmprefix: 0xf3, rmcode: 0x2c }; static opparam_t params_addss = { xmmprefix: 0xf3, mrcode: 0x58 }; static opparam_t params_divss = { xmmprefix: 0xf3, mrcode: 0x5e }; static opparam_t params_movss = { xmmprefix: 0xf3, mrcode: 0x10, rmcode: 0x11 }; static opparam_t params_mulss = { xmmprefix: 0xf3, mrcode: 0x59 }; static opparam_t params_subss = { xmmprefix: 0xf3, mrcode: 0x5c }; static opparam_t params_ucomiss = { mrcode: 0x2e }; static int ops_sorted = 0; static op_t ops[] = { { "addl", emit_subaddand, ¶ms_add }, { "addq", emit_subaddand, ¶ms_add }, { "addss", emit_twobyte, ¶ms_addss }, { "andl", emit_subaddand, ¶ms_and }, { "andq", emit_subaddand, ¶ms_and }, { "callq", emit_call, NULL }, { "cbw", emit_opsingle16, (void*)0x98 }, { "cdq", emit_opsingle, (void*)0x99 }, { "cmpl", emit_subaddand, ¶ms_cmp }, { "cmpq", emit_subaddand, ¶ms_cmp }, { "cvtsi2ss", emit_twobyte, ¶ms_cvtsi2ss }, { "cvttss2si", emit_twobyte, ¶ms_cvttss2si }, { "cwde", emit_opsingle, (void*)0x98 }, { "decl", emit_op_rm, ¶ms_dec }, { "decq", emit_op_rm, ¶ms_dec }, { "divl", emit_op_rm, ¶ms_div }, { "divq", emit_op_rm, ¶ms_div }, { "divss", emit_twobyte, ¶ms_divss }, { "idivl", emit_op_rm, ¶ms_idiv }, { "imull", emit_op_rm, ¶ms_imul }, { "int3", emit_opsingle, (void*)0xcc }, { "ja", emit_condjump, (void*)0x77 }, { "jbe", emit_condjump, (void*)0x76 }, { "jb", emit_condjump, (void*)0x72 }, { "je", emit_condjump, (void*)0x74 }, { "jl", emit_condjump, (void*)0x7c }, { "jmp", emit_jmp, NULL }, { "jmpq", emit_jmp, NULL }, { "jnae", emit_condjump, (void*)0x72 }, { "jna", emit_condjump, (void*)0x76 }, { "jnbe", emit_condjump, (void*)0x77 }, { "jnb", emit_condjump, (void*)0x73 }, { "jnc", emit_condjump, (void*)0x73 }, { "jne", emit_condjump, (void*)0x75 }, { "jnge", emit_condjump, (void*)0x7c }, { "jng", emit_condjump, (void*)0x7e }, { "jnle", emit_condjump, (void*)0x7f }, { "jnl", emit_condjump, (void*)0x7d }, { "jnz", emit_condjump, (void*)0x75 }, { "jp", emit_condjump, (void*)0x7a }, { "jz", emit_condjump, (void*)0x74 }, { "movb", emit_mov, NULL }, { "movl", emit_mov, NULL }, { "movq", emit_mov, NULL }, { "movss", emit_twobyte, ¶ms_movss }, { "movw", emit_mov, NULL }, { "mull", emit_op_rm, ¶ms_mul }, { "mulss", emit_twobyte, ¶ms_mulss }, { "negl", emit_op_rm, ¶ms_neg }, { "negq", emit_op_rm, ¶ms_neg }, { "nop", emit_opsingle, (void*)0x90 }, { "notl", emit_op_rm, ¶ms_not }, { "notq", emit_op_rm, ¶ms_not }, { "or", emit_subaddand, ¶ms_or }, { "orl", emit_subaddand, ¶ms_or }, { "pop", emit_opreg, (void*)0x58 }, { "push", emit_opreg, (void*)0x50 }, { "ret", emit_opsingle, (void*)0xc3 }, { "sarl", emit_op_rm_cl, ¶ms_sar }, { "shl", emit_op_rm_cl, ¶ms_shl }, { "shrl", emit_op_rm_cl, ¶ms_shr }, { "subl", emit_subaddand, ¶ms_sub }, { "subq", emit_subaddand, ¶ms_sub }, { "subss", emit_twobyte, ¶ms_subss }, { "ucomiss", emit_twobyte, ¶ms_ucomiss }, { "xorl", emit_subaddand, ¶ms_xor }, { "xorq", emit_subaddand, ¶ms_xor }, { NULL, NULL, NULL } }; static int opsort(const void* A, const void* B) { const op_t* a = A; const op_t* b = B; return strcmp(a->mnemonic, b->mnemonic); } static op_t* getop(const char* n) { #if 0 op_t* o = ops; while(o->mnemonic) { if(!strcmp(o->mnemonic, n)) return o; ++o; } #else unsigned m, t, b; int r; t = sizeof(ops)/sizeof(ops[0])-1; b = 0; while(b <= t) { m = ((t-b)>>1) + b; if((r = strcmp(ops[m].mnemonic, n)) == 0) { return &ops[m]; } else if(r < 0) { b = m + 1; } else { t = m - 1; } } #endif return NULL; } static reg_t parsereg(const char* str) { const char* s = str; if(*s == 'a' && s[1] == 'l' && !s[2]) { return R_AL; } else if(*s == 'a' && s[1] == 'x' && !s[2]) { return R_AX; } if(*s == 'c' && s[1] == 'l' && !s[2]) { return R_CL; } if(*s == 'x') { if(!strcmp(s, "xmm0")) return R_XMM0; } else if(*s == 'r' && s[1]) { ++s; if(s[1] == 'x') { switch(*s++) { case 'a': return R_RAX; case 'b': return R_RBX; case 'c': return R_RCX; case 'd': return R_RDX; } } else if(s[1] == 'i') { switch(*s++) { case 's': return R_RSI; case 'd': return R_RDI; } } else if(s[0] == 's' && s[1] == 'p' && !s[2]) { return R_RSP; } else if(*s == '8' && !s[1]) return R_R8; else if(*s == '9' && !s[1]) return R_R9; else if(*s == '1' && s[1] == '0') return R_R10; else if(*s == '1' && s[1] == '5') return R_R15; } else if(*s == 'e' && s[1]) { ++s; if(s[1] == 'x') { switch(*s++) { case 'a': return R_EAX; case 'b': return R_EBX; case 'c': return R_ECX; case 'd': return R_EDX; } } else if(s[1] == 'i') { switch(*s++) { case 's': return R_ESI; case 'd': return R_EDI; } } } crap("invalid register %s", str); return 0; } typedef enum { TOK_LABEL = 0x80, TOK_INT = 0x81, TOK_END = 0x82, TOK_INVALID = 0x83, } token_t; static unsigned char nexttok(const char** str, char* label, u64* val) { const char* s = *str; if(label) *label = 0; if(val) *val = 0; while(*s && *s == ' ') ++s; if(!*s) { return TOK_END; } else if(*s == '$' || *s == '*' || *s == '%' || *s == '-' || *s == ')' || *s == '(' || *s == ',') { *str = s+1; return *s; } else if(*s >= 'a' && *s <= 'z') { size_t a = strspn(s+1, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"); if(a+1 >= LABELLEN) crap("label %s too long", s); if(label) { strncpy(label, s, a+1); label[a+1] = 0; } *str = s+a+1; return TOK_LABEL; } else if(*s >= '0' && *s <= '9') { char* endptr = NULL; u64 v = strtoull(s, &endptr, 0); if(endptr && (endptr-s == 0)) crap("invalid integer %s", s); if(val) *val = v; *str = endptr; return TOK_INT; } crap("can't parse '%s'", *str); return TOK_INVALID; } static arg_t parsearg(const char** str) { arg_t arg; const char* s = *str; char label[20]; u64 val; int negative = 1; unsigned ttype; arg.type = T_NONE; arg.absolute = 0; while(*s && *s == ' ') ++s; switch(nexttok(&s, label, &val)) { case '$' : ttype = nexttok(&s, NULL, &val); if(ttype == '-') { negative = -1; ttype = nexttok(&s, NULL, &val); } if(ttype != TOK_INT) crap("expected integer"); arg.type = T_IMMEDIATE; arg.v.imm = negative * val; break; case '*' : if((ttype = nexttok(&s, NULL, NULL)) != '%') { if(ttype == '(') goto tok_memory; crap("expected '%%'"); } arg.absolute = 1; /* fall through */ case '%' : if(nexttok(&s, label, &val) != TOK_LABEL) crap("expected label"); arg.type = T_REGISTER; arg.v.reg = parsereg(label); break; case TOK_LABEL: arg.type = T_LABEL; strncpy(arg.v.label, label, LABELLEN); break; case '-': negative = -1; if(nexttok(&s, NULL, &val) != TOK_INT) crap("expected integer"); /* fall through */ case TOK_INT: if(nexttok(&s, label, NULL) != '(') crap("expected '('"); // mov to/from fixed address not supported /* fall through */ case '(': tok_memory: arg.type = T_MEMORY; arg.v.mem.indextype = T_NONE; arg.v.mem.disp = negative * val; ttype = nexttok(&s, label, &val); if(ttype == '%' && nexttok(&s, label, &val) != TOK_LABEL) { crap("expected register"); } if (ttype == '%') { arg.v.mem.basetype = T_REGISTER; arg.v.mem.base.reg = parsereg(label); } else if (ttype == TOK_INT) { arg.v.mem.basetype = T_IMMEDIATE; arg.v.mem.base.imm = val; } if((ttype = nexttok(&s, NULL, NULL)) == ',') { ttype = nexttok(&s, label, &val); if(ttype == '%' && nexttok(&s, label, &val) != TOK_LABEL) { crap("expected register"); } if (ttype == '%') { arg.v.mem.indextype = T_REGISTER; arg.v.mem.index.reg = parsereg(label); } else if (ttype == TOK_INT) { crap("index must be register"); arg.v.mem.indextype = T_IMMEDIATE; arg.v.mem.index.imm = val; } if(nexttok(&s, NULL, NULL) != ',') crap("expected ','"); if(nexttok(&s, NULL, &val) != TOK_INT) crap("expected integer"); if(val != 1 && val != 2 && val != 4 && val != 8) crap("scale must 1, 2, 4 or 8"); arg.v.mem.scale = val; ttype = nexttok(&s, NULL, NULL); } if(ttype != ')') { crap("expected ')' or ','"); } break; default: crap("invalid token %hu in %s", *(unsigned char*)s, *str); break; } *str = s; return arg; } /* ************************* */ void assembler_init(int pass) { compiledOfs = 0; assembler_pass = pass; if(!pass) { labelhash_free(); cur_line = NULL; } if(!ops_sorted) { ops_sorted = 1; qsort(ops, sizeof(ops)/sizeof(ops[0])-1, sizeof(ops[0]), opsort); } } size_t assembler_get_code_size(void) { return compiledOfs; } void assembler_set_output(char* buf) { out = buf; } void assemble_line(const char* input, size_t len) { char line[4096]; char* s; op_t* o; char* opn; arg_t arg1, arg2; arg1.type = T_NONE; arg2.type = T_NONE; opn = NULL; o = NULL; if(len < 1) return; if(len >= sizeof(line)) crap("line too long"); memcpy(line, input, sizeof(line)); cur_line = input; if(line[len-1] == '\n') line[--len] = 0; if(line[len-1] == ':') { line[--len] = 0; if(assembler_pass) debug("%s: 0x%x\n", line, compiledOfs); else hash_add_label(line, compiledOfs); } else { opn = line; s = strchr(line, ' '); if(s) { *s++ = 0; arg1 = parsearg((const char**)&s); if(*s) { if(*s != ',') crap("expected ',', got '%c'", *s); ++s; arg2 = parsearg((const char**)&s); } } if(!opn) { crap("no operator in %s", line); } o = getop(opn); if(!o) { crap("cannot handle op %s", opn); } o->func(opn, arg1, arg2, o->data); if(assembler_pass) debug(" - %s%s", cur_line, cur_line[strlen(cur_line)-1]=='\n'?"":"\n"); } } #ifdef SA_STANDALONE int main(int argc, char* argv[]) { char line[4096]; size_t len; int pass; FILE* file = NULL; if(argc < 2) { crap("specify file"); } file = fopen(argv[1], "r"); if(!file) { crap("can't open file"); } if(argc > 2) { fout = fopen(argv[2], "w"); if(!fout) { crap("can't open %s for writing", argv[2]); } } for(pass = 0; pass < 2; ++pass) { if(fseek(file, 0, SEEK_SET)) crap("can't rewind file"); if(pass) { char* b = malloc(assembler_get_code_size()); if(!b) crap("cannot allocate memory"); assembler_set_output(b); } assembler_init(pass); while(fgets(line, sizeof(line), file)) { len = strlen(line); if(!len) continue; assemble_line(line, len); } } assembler_init(0); fclose(file); return 0; } #endif