mirror of
https://github.com/UberGames/lilium-voyager.git
synced 2024-12-15 06:30:49 +00:00
1420 lines
29 KiB
C
1420 lines
29 KiB
C
|
/*
|
||
|
===========================================================================
|
||
|
vm_x86_64_assembler.c -- assembler for x86-64
|
||
|
|
||
|
Copyright (C) 2007 Ludwig Nussel <ludwig.nussel@suse.de>, 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
|
||
|
===========================================================================
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <stdarg.h>
|
||
|
|
||
|
typedef unsigned char u8;
|
||
|
typedef unsigned short u16;
|
||
|
typedef unsigned int u32;
|
||
|
typedef unsigned long 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)
|
||
|
{
|
||
|
if(assembler_pass)
|
||
|
{
|
||
|
out[compiledOfs++] = v;
|
||
|
if(fout) fwrite(&v, 1, 1, fout);
|
||
|
debug("%02hhx ", 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);
|
||
|
i %= sizeof(labelhash)/sizeof(labelhash[0]);
|
||
|
h = malloc(sizeof(struct hashentry));
|
||
|
h->label = strdup(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 %lu, 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 (labs(v) <= 0x80);
|
||
|
}
|
||
|
|
||
|
static inline int isu8(u64 v)
|
||
|
{
|
||
|
return (v <= 0xff);
|
||
|
}
|
||
|
|
||
|
static inline int iss16(u64 v)
|
||
|
{
|
||
|
return (labs(v) <= 0x8000);
|
||
|
}
|
||
|
|
||
|
static inline int isu16(u64 v)
|
||
|
{
|
||
|
return (v <= 0xffff);
|
||
|
}
|
||
|
|
||
|
static inline int iss32(u64 v)
|
||
|
{
|
||
|
return (labs(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)((unsigned long) 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)((unsigned long) 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)(((unsigned long)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 || arg2.type != T_NONE)
|
||
|
CRAP_INVALID_ARGS;
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
|
||
|
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: 6, 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 = strtol(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 %hhu 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
|