NetBoot/vasm-1/cpus/x86/cpu.c

1958 lines
53 KiB
C
Raw Normal View History

2020-06-20 01:33:04 +00:00
/*
** cpu.c x86 cpu-description file
** (c) in 2005-2006,2011,2015-2019 by Frank Wille
*/
#include "vasm.h"
mnemonic mnemonics[] = {
#include "opcodes.h"
};
int mnemonic_cnt = sizeof(mnemonics)/sizeof(mnemonics[0]);
char *cpu_copyright = "vasm x86 cpu backend 0.7a (c) 2005-2006,2011,2015-2019 Frank Wille";
char *cpuname = "x86";
int bitsperbyte = 8;
int bytespertaddr = 4;
/* cpu options */
uint32_t cpu_type = CPUAny;
static long cpudebug = 0;
static regsym x86_regsyms[] = {
#include "registers.h"
};
/* opcode-prefixes recognized during parse_instruction() */
static int prefix_cnt;
static unsigned char prefix[MAX_PREFIXES];
/* opcodes */
static unsigned char OC_ADDR_PREFIX;
static unsigned char OC_DATA_PREFIX;
static unsigned char OC_WAIT_PREFIX;
static unsigned char OC_LOCK_PREFIX;
static unsigned char OC_CSEG_PREFIX;
static unsigned char OC_DSEG_PREFIX;
static unsigned char OC_ESEG_PREFIX;
static unsigned char OC_FSEG_PREFIX;
static unsigned char OC_GSEG_PREFIX;
static unsigned char OC_SSEG_PREFIX;
static unsigned char OC_REPE_PREFIX;
static unsigned char OC_REPNE_PREFIX;
static unsigned char OC_POP_SREG2;
static unsigned char OC_MOV_ACC_DISP;
static unsigned char OC_JMP_DISP;
/* opcode suffixes */
static char *b_str = "b";
static char *w_str = "w";
static char *l_str = "l";
/*static char *s_str = "s";*/
static char *q_str = "q";
/*static char *x_str = "x";*/
/* assembler mode: 16, 32 or 64 bit */
enum codetype {
CODE_32BIT,
CODE_16BIT,
CODE_64BIT
};
static enum codetype mode_flag = CODE_32BIT; /* 32 bit is default */
/* operand types for printing */
static const char *operand_type_str[] = {
"Reg8","Reg16","Reg32","Reg64","Imm8","Imm8S","Imm16","Imm32","Imm32S",
"Imm64","Imm1","Disp8","Disp16","Disp32","Disp32S","Disp64",
"BaseIndex","ShiftCntReg","IOPortReg","CtrlReg","DebugReg",
"TestReg","Acc","SegReg2","SegReg3","MMXReg","XMMReg",
"FloatReg","FloatAcc","EsSeg","JmpAbs","InvMem"
};
/* scale factors log2(scale) -> original scale factor */
static const int scale_factor_tab[4] = { 1, 2, 4, 8 };
operand *new_operand(void)
{
return mycalloc(sizeof(operand));
}
int x86_data_operand(int n)
/* return data operand type for these number of bits */
{
if (n&OPSZ_FLOAT) return OPSZ_BITS(n)>32?Float64:Float32;
if (OPSZ_BITS(n)<=8) return Data8;
if (OPSZ_BITS(n)<=16) return Data16;
if (OPSZ_BITS(n)<=32) return Data32;
if (OPSZ_BITS(n)<=64) return Data64;
cpu_error(20,n); /* data objects with n bits size are not supported */
return 0;
}
static void print_operand_type(int ot,char c)
{
int i,b;
for (i=0,b=0; i<32; i++,ot>>=1) {
if (ot & 1) {
if (b)
putchar(c);
printf("%s",operand_type_str[i]);
b = 1;
}
}
}
static void print_operands(instruction *ip,taddr pc)
{
mnemonic *mnemo = &mnemonics[ip->code];
int i;
if (pc == -1)
printf(" \"%s\".\"%s\"\n",mnemo->name,
ip->qualifiers[0] ? ip->qualifiers[0] : emptystr);
else
printf(" %08lx: \"%s\".\"%s\"\n",(unsigned long)pc,mnemo->name,
ip->qualifiers[0] ? ip->qualifiers[0] : emptystr);
for (i=0; i<MAX_OPERANDS; i++) {
if (ip->op[i] == NULL)
break;
printf("operand %d: ",i+1);
print_operand_type(ip->op[i]->type,' ');
putchar('\n');
}
}
static int prefix_index(unsigned char oc)
{
int i = -1;
if (oc == OC_WAIT_PREFIX) {
i = WAIT_PREFIX;
}
else if (oc==OC_LOCK_PREFIX || oc==OC_REPE_PREFIX ||
oc==OC_REPNE_PREFIX) {
i = LOCKREP_PREFIX;
}
else if (oc == OC_ADDR_PREFIX) {
i = ADDR_PREFIX;
}
else if (oc == OC_DATA_PREFIX) {
i = DATA_PREFIX;
}
else if (oc==OC_CSEG_PREFIX || oc==OC_DSEG_PREFIX ||
oc==OC_ESEG_PREFIX || oc==OC_FSEG_PREFIX ||
oc==OC_GSEG_PREFIX || oc==OC_SSEG_PREFIX) {
i = SEG_PREFIX;
}
else
ierror(0);
return i;
}
#if 0 /* @@@ not now */
static int add_prefix(unsigned char oc)
{
int i = prefix_index(oc);
int a = prefix[i] == 0;
prefix[i] = oc;
prefix_cnt += a;
return a;
}
#endif
static int add_ip_prefix(instruction *ip,unsigned char oc)
{
int i = prefix_index(oc);
int a = ip->ext.prefix[i] == 0;
ip->ext.prefix[i] = oc;
ip->ext.num_prefixes += a;
return a;
}
static int add_seg_prefix(instruction *ip,int segrn)
{
switch (segrn) {
case ESEG_REGNUM: return add_ip_prefix(ip,OC_ESEG_PREFIX);
case CSEG_REGNUM: return add_ip_prefix(ip,OC_CSEG_PREFIX);
case SSEG_REGNUM: return add_ip_prefix(ip,OC_SSEG_PREFIX);
case DSEG_REGNUM: return add_ip_prefix(ip,OC_DSEG_PREFIX);
case FSEG_REGNUM: return add_ip_prefix(ip,OC_FSEG_PREFIX);
case GSEG_REGNUM: return add_ip_prefix(ip,OC_GSEG_PREFIX);
default: ierror(0);
}
return 0;
}
static char suffix_from_reg(instruction *ip)
{
mnemonic *mnemo = &mnemonics[ip->code];
int i,ot,io;
/* i/o instruction? */
io = (mnemo->operand_type[0] & IOPortReg) ||
(mnemo->operand_type[1] & IOPortReg);
/* determine size qualifier from last register operand */
for (i=MAX_OPERANDS-2; i>=0; i--) {
if (ip->op[i] == NULL)
continue;
ot = ip->op[i]->parsed_type;
if ((ot & Reg) && !(io && (ot & IOPortReg))) {
if (ot & Reg8)
ip->qualifiers[0] = b_str;
else if (ot & Reg16)
ip->qualifiers[0] = w_str;
else if (ot & Reg32)
ip->qualifiers[0] = l_str;
else if (ot & Reg64)
ip->qualifiers[0] = q_str;
else
ierror(0);
break;
}
}
return ip->qualifiers[0] ?
tolower((unsigned char)ip->qualifiers[0][0]) : '\0';
}
static void chk_byte_reg(instruction *ip)
{
int i,ot;
for (i=0; i<MAX_OPERANDS; i++) {
if (ip->op[i] == NULL)
break;
ot = ip->op[i]->type;
if (ot & (AnyReg|FloatReg|FloatAcc)) {
if (ot & Reg8)
continue; /* 8-bit register is just perfect */
if ((ot & WordReg) && ip->op[i]->basereg->reg_num < 4) {
if (mode_flag!=CODE_64BIT /*@@@ || (ot & IOPortReg)*/) {
regsym *oldreg = ip->op[i]->basereg;
if (ot & Reg16)
ip->op[i]->basereg -= AX_INDEX - AL_INDEX;
else
ip->op[i]->basereg -= EAX_INDEX - AL_INDEX;
/* warning: using register x instead of y due to suffix */
cpu_error(9,ip->op[i]->basereg->reg_name,oldreg->reg_name,
ip->qualifiers[0][0]);
ip->op[i]->type &= ~Reg;
ip->op[i]->type |= Reg8;
ip->op[i]->parsed_type &= ~Reg;
ip->op[i]->parsed_type |= Reg8;
}
continue;
}
/* any other register is not allowed */
cpu_error(10,ip->op[i]->basereg->reg_name,ip->qualifiers[0][0]);
suffix_from_reg(ip);
break;
}
}
}
static void chk_word_reg(instruction *ip)
{
mnemonic *mnemo = &mnemonics[ip->code];
int i,ot;
for (i=0; i<MAX_OPERANDS; i++) {
if (ip->op[i] == NULL)
break;
ot = ip->op[i]->type;
if (ot & (AnyReg|FloatReg|FloatAcc)) {
/* allow 8-bit registers only when exclusively required */
if ((ot & Reg8) && (mnemo->operand_type[i] & (Reg16|Reg32|Acc))) {
cpu_error(10,ip->op[i]->basereg->reg_name,ip->qualifiers[0][0]);
suffix_from_reg(ip);
break;
}
else if ((ot & Reg32) &&
(mnemo->operand_type[i] & (Reg16|Acc))) {
regsym *oldreg = ip->op[i]->basereg;
if (mode_flag == CODE_64BIT) {
cpu_error(10,ip->op[i]->basereg->reg_name,ip->qualifiers[0][0]);
suffix_from_reg(ip);
break;
}
ip->op[i]->basereg -= EAX_INDEX - AX_INDEX;
/* warning: using register x instead of y due to suffix */
cpu_error(9,ip->op[i]->basereg->reg_name,oldreg->reg_name,
ip->qualifiers[0][0]);
ip->op[i]->type &= ~Reg;
ip->op[i]->type |= Reg16;
ip->op[i]->parsed_type &= ~Reg;
ip->op[i]->parsed_type |= Reg16;
}
}
}
}
static void chk_long_reg(instruction *ip)
{
mnemonic *mnemo = &mnemonics[ip->code];
int i,ot;
for (i=0; i<MAX_OPERANDS; i++) {
if (ip->op[i] == NULL)
break;
ot = ip->op[i]->type;
if (ot & (AnyReg|FloatReg|FloatAcc)) {
/* allow 8-bit registers only when exclusively required */
if ((ot & Reg8) && (mnemo->operand_type[i] & (Reg16|Reg32|Acc))) {
cpu_error(10,ip->op[i]->basereg->reg_name,ip->qualifiers[0][0]);
suffix_from_reg(ip);
break;
}
else if ((ot & Reg16) &&
(mnemo->operand_type[i] & (Reg32|Acc))) {
regsym *oldreg = ip->op[i]->basereg;
if (mode_flag == CODE_64BIT) {
cpu_error(10,ip->op[i]->basereg->reg_name,ip->qualifiers[0][0]);
suffix_from_reg(ip);
break;
}
ip->op[i]->basereg += EAX_INDEX - AX_INDEX;
/* warning: using register x instead of y due to suffix */
cpu_error(9,ip->op[i]->basereg->reg_name,oldreg->reg_name,
ip->qualifiers[0][0]);
ip->op[i]->type &= ~Reg;
ip->op[i]->type |= Reg32;
ip->op[i]->parsed_type &= ~Reg;
ip->op[i]->parsed_type |= Reg32;
}
}
}
}
static void chk_quad_reg(instruction *ip)
{
mnemonic *mnemo = &mnemonics[ip->code];
int i,ot;
for (i=0; i<MAX_OPERANDS; i++) {
if (ip->op[i] == NULL)
break;
ot = ip->op[i]->type;
if (ot & (AnyReg|FloatReg|FloatAcc)) {
/* allow 8-bit registers only when exclusively required */
if ((ot & Reg8) && (mnemo->operand_type[i] & (Reg16|Reg32|Acc))) {
cpu_error(10,ip->op[i]->basereg->reg_name,ip->qualifiers[0][0]);
suffix_from_reg(ip);
break;
}
else if ((ot & (Reg16|Reg32)) &&
(mnemo->operand_type[i] & (Reg32|Acc))) { /* @@@ Reg32? */
cpu_error(10,ip->op[i]->basereg->reg_name,ip->qualifiers[0][0]);
suffix_from_reg(ip);
break;
}
}
}
}
static int assign_suffix(instruction *ip)
{
mnemonic *mnemo = &mnemonics[ip->code];
char suffix = ip->qualifiers[0] ?
tolower((unsigned char)ip->qualifiers[0][0]) : '\0';
if (suffix == '\0' &&
(mnemo->ext.opcode_modifier &
(b_Illegal|w_Illegal|l_Illegal|s_Illegal|x_Illegal|q_Illegal))
== (b_Illegal|w_Illegal|l_Illegal|s_Illegal|x_Illegal|q_Illegal)) {
return 1; /* no suffix needed */
}
if (mnemo->ext.opcode_modifier & (Size16|Size32|Size64)) {
if (mnemo->ext.opcode_modifier & Size16)
ip->qualifiers[0] = w_str;
else if (mnemo->ext.opcode_modifier & Size64)
ip->qualifiers[0] = q_str;
else
ip->qualifiers[0] = l_str;
}
else if (ip->ext.flags & HAS_REG_OPER) {
if (suffix == '\0') {
/* a missing suffix can be determined by the last register operand */
suffix = suffix_from_reg(ip);
}
/* check if register operands match the given suffix */
else if (suffix == 'b')
chk_byte_reg(ip);
else if (suffix == 'w')
chk_word_reg(ip);
else if (suffix == 'l')
chk_long_reg(ip);
else if (suffix == 'q')
chk_quad_reg(ip);
else if (!(mnemo->ext.opcode_modifier & IgnoreSize)) {
suffix_from_reg(ip);
cpu_error(11,suffix); /* illegal suffix */
return 0;
}
}
else if (suffix=='\0' && (mnemo->ext.opcode_modifier & DefaultSize)) {
/* not needed for i386 */
}
if (suffix=='\0' && (mnemo->ext.opcode_modifier & W)) {
/* need suffix to determine size of instruction! */
cpu_error(12); /* instruction has no suffix and no regs */
ip->qualifiers[0] = b_str;
return 0;
}
return 1;
}
static int process_suffix(instruction *ip,int final)
{
char suffix = ip->qualifiers[0] ?
tolower((unsigned char)ip->qualifiers[0][0]) : '\0';
if (suffix != 'b') {
/* we have to select word or dword operation mode */
mnemonic *mnemo = &mnemonics[ip->code];
if (cpudebug & 64) {
printf("%s(%c%c): process_suffix(%dbit): %c\n",mnemo->name,
(mnemo->ext.opcode_modifier & W) ? 'W' : ' ',
(mnemo->ext.opcode_modifier & ShortForm) ? 'S' : ' ',
mode_flag==CODE_64BIT?64:(mode_flag==CODE_32BIT?32:16),
suffix ? suffix : ' ');
}
if (mnemo->ext.opcode_modifier & W) {
if (mnemo->ext.opcode_modifier & ShortForm)
ip->ext.base_opcode |= 8;
else
ip->ext.base_opcode |= 1;
}
if (suffix!='q' && !(mnemo->ext.opcode_modifier & IgnoreSize)) {
if ((suffix=='w' && mode_flag==CODE_32BIT) ||
(suffix=='l' && mode_flag==CODE_16BIT) ||
(mode_flag==CODE_64BIT &&
(mnemo->ext.opcode_modifier & JmpByte))) {
/* we need a prefix to select the correct size */
unsigned char code = OC_DATA_PREFIX;
if (mnemo->ext.opcode_modifier & JmpByte)
code = OC_ADDR_PREFIX; /* jcxz, loop */
if (cpudebug & 64)
printf("\tadding prefix %02x\n",(unsigned)code);
if (!add_ip_prefix(ip,code)) {
if (!final)
cpu_error(2); /* same type of prefix used twice */
return 0;
}
}
}
/* size floating point instruction */
if (suffix == 'l') {
if (mnemo->ext.opcode_modifier & FloatMF)
ip->ext.base_opcode ^= 4;
}
}
return 1;
}
static uint32_t suffix_flag(instruction *ip)
/* return the No_?Suf flag for the instruction's suffix */
{
uint32_t chksuffix;
switch (ip->qualifiers[0] ?
tolower((unsigned char)ip->qualifiers[0][0]) : '\0') {
case 'b': chksuffix = b_Illegal; break;
case 'w': chksuffix = w_Illegal; break;
case 'l': chksuffix = l_Illegal; break;
case 's': chksuffix = s_Illegal; break;
case 'q': chksuffix = q_Illegal; break;
case 'x': chksuffix = x_Illegal; break;
case '\0': chksuffix = 0; break;
default: ierror(0); break;
}
return chksuffix;
}
static int find_next_mnemonic(instruction *ip)
/* finds a mnemonic with the same name, which fits the given
operand types and suffix */
{
char *inst_name = mnemonics[ip->code].name;
int code = ip->code + 1;
mnemonic *mnemo = &mnemonics[code];
uint32_t chksuffix = suffix_flag(ip);
if ((cpudebug & 32) && !(cpudebug & 4)) {
printf("Finding next matching instruction for (opcode=0x%x):",
ip->ext.base_opcode);
print_operands(ip,-1);
}
while (!strcmp(inst_name,mnemo->name)) {
int i,given,allowed,overlap,new_types[MAX_OPERANDS];
for (i=0; i<MAX_OPERANDS; i++) {
if (allowed = mnemo->operand_type[i]) {
if (ip->op[i]) {
given = ip->op[i]->parsed_type;
overlap = allowed & given;
if ((overlap & ~JmpAbs) &&
(given & (BaseIndex|JmpAbs)) ==
(overlap & (BaseIndex|JmpAbs))) {
new_types[i] = overlap;
}
else
break;
}
else
break;
}
else {
if (ip->op[i])
break;
}
}
if (i == MAX_OPERANDS) {
/* all operands match, check suffix and CPU type */
if (cpudebug & 32) {
printf("\toperands match, suffixOK=%d, cpuOK=%d\n",
(mnemo->ext.opcode_modifier & chksuffix) == 0,
(mnemo->ext.available & cpu_type) == mnemo->ext.available);
}
if (!(mnemo->ext.opcode_modifier & chksuffix) &&
(mnemo->ext.available & cpu_type)==mnemo->ext.available) {
for (i=0; i<MAX_OPERANDS; i++) {
if (ip->op[i]) {
if (cpudebug & 32) {
printf("\tnew op %d: ",i+1);
print_operand_type(ip->op[i]->parsed_type,'|');
printf(" & ");
print_operand_type(new_types[i],'|');
printf(" = ");
print_operand_type(ip->op[i]->parsed_type&new_types[i],'|');
printf("\n");
}
ip->op[i]->type = ip->op[i]->parsed_type & new_types[i];
}
}
ip->code = code;
ip->ext.base_opcode = mnemo->ext.base_opcode;
assign_suffix(ip);
if (cpudebug & 32)
printf("\tNew opcode=0x%x!\n",ip->ext.base_opcode);
return 1;
}
}
/* try next entry from mnemonics table */
mnemo++;
code++;
}
if (cpudebug & 32)
printf("\tNo matching instruction found!\n");
return 0;
}
static int fix_imm_size(instruction *ip,operand *op,int final)
/* apply suffix to immediate operand */
{
char suffix = ip->qualifiers[0] ?
tolower((unsigned char)ip->qualifiers[0][0]) : '\0';
int ot = op->type;
if ((ot & (Imm8|Imm8S|Imm16|Imm32|Imm32S)) &&
ot!=Imm8 && ot!=Imm8S && ot!=Imm16 &&
ot!=Imm32 && ot!=Imm32S && ot!=Imm64) {
switch (suffix) {
case 'b': ot &= Imm8|Imm8S; break;
case 'w': ot &= Imm16; break;
case 'l': ot &= Imm32; break;
case 'q': ot &= Imm64|Imm32S; break;
case '\0':
if ((mode_flag==CODE_16BIT) ^ (ip->ext.prefix[DATA_PREFIX]!=0))
ot &= Imm16;
else
ot &= Imm32|Imm32S;
break;
default:
ierror(0);
break;
}
if (ot!=Imm8 && ot!=Imm8S && ot!=Imm16 &&
ot!=Imm32 && ot!=Imm32S && ot!=Imm64) {
if (final)
cpu_error(24); /* can't determine imm. op. size w/o suffix */
return 0;
}
}
return ot;
}
static void optimize_imm(instruction *ip,operand *op,section *sec,
taddr pc,int final)
/* determine smallest type which can hold the immediate mode */
{
taddr val;
int ot,nt;
if (!eval_expr(op->value,&val,sec,pc)) {
/* reloc or unknown symbols have always full size until they are known */
ot = Imm32; /* @@@ fixme? gas also allows Imm8-references */
}
else {
/* set valid operand types for this number */
ot = Imm64;
if (val>=-0x80000000LL && val<=0xffffffffLL) {
ot |= Imm32;
if (val<=0x7fffffffLL) {
ot |= Imm32S;
if (val>=-0x8000 && val<=0xffff) {
ot |= Imm16;
if (val>=-0x80 && val<=0xff) {
ot |= Imm8;
if (val<=0x7f)
ot |= Imm8S;
}
}
}
}
}
nt = ot & fix_imm_size(ip,op,final);
while (!nt) {
/* instruction doesn't support required modes, look for another one */
int oldtype = op->type;
op->type = ot;
if (!find_next_mnemonic(ip)) {
op->type = oldtype;
break;
}
nt = op->type & fix_imm_size(ip,op,final);
}
if (nt)
op->type = nt;
else if (final)
cpu_error(23); /* instruction doesn't support these operand sizes */
}
static void optimize_jump(instruction *ip,operand *op,section *sec,
taddr pc,int final)
{
mnemonic *mnemo = &mnemonics[ip->code];
int mod = mnemo->ext.opcode_modifier;
int label_in_sec;
symbol *base;
taddr val,diff;
if (!eval_expr(op->value,&val,sec,pc)) {
if (find_base(op->value,&base,sec,pc) != BASE_OK) {
if (final)
general_error(38); /* illegal relocation */
return;
}
label_in_sec = !is_pc_reloc(base, sec);
}
else
label_in_sec = 1;
if (mod & JmpByte) {
op->type = Disp8;
if (final && label_in_sec) {
diff = val - (pc + ip->ext.last_size);
if (diff<-0x80 || diff>0x7f)
cpu_error(22,diff); /* jump destination out of range */
}
}
else if (mod & JmpDword) {
if (mode_flag == CODE_16BIT)
op->type &= ~(Disp32|Disp32S);
else
op->type &= ~Disp16;
if (final && label_in_sec) {
diff = val - (pc + ip->ext.last_size);
if (mode_flag == CODE_16BIT) {
if (diff<-0x8000 || diff>0x7fff)
cpu_error(22,diff); /* jump destination out of range */
}
else {
if (diff<-0x80000000LL || diff>0x7fffffffLL)
cpu_error(22,diff); /* jump destination out of range */
}
}
}
else { /* Jump */
op->type = (mode_flag == CODE_16BIT) ? Disp16 : Disp32;
if (label_in_sec && ip->ext.last_size>=0) {
/* check if it fits in 8-bit */
diff = val - (pc + ip->ext.last_size);
if (diff>=-0x80 && diff<=0x7f && (ip->ext.flags&OPTFAILED)!=OPTFAILED) {
op->type = Disp8;
return;
}
else if (final) {
if (mode_flag == CODE_16BIT) {
if (diff<-0x8000 || diff>0x7fff)
cpu_error(22,diff); /* jump dest. out of range */
}
else {
if (diff<-0x80000000LL || diff>0x7fffffffLL)
cpu_error(22,diff); /* jump dest. out of range */
}
}
}
if (ip->ext.base_opcode == OC_JMP_DISP)
ip->ext.base_opcode = 0xe9; /* jmp <long distance> */
else
ip->ext.base_opcode += 0xf10; /* j<cond> <long distance> */
}
}
static void optimize_disp(operand *op,section *sec,taddr pc,int final)
{
taddr val;
if (!eval_expr(op->value,&val,sec,pc)) {
/* reloc or unknown symbols have always full size until they are known */
if (mode_flag == CODE_16BIT) {
op->type &= ~(Disp8|Disp32|Disp32S|Disp64);
if (final && !(op->type & Disp16))
cpu_error(21,16); /* need at least n bits for a relocatable symbol */
}
else { /* CODE_32BIT */
op->type &= ~(Disp8|Disp16);
if (final && !(op->type & (Disp32|Disp32S|Disp64)))
cpu_error(21,32); /* need at least n bits for a relocatable symbol */
}
}
else {
/* try to use the smallest type for absolute displacements */
if (mode_flag == CODE_16BIT) {
op->type &= ~(Disp32|Disp32S|Disp64);
if (final && (val<-0x8000 || val>0x7fff))
cpu_error(25,16); /* doesn't fit into 16 bits */
if (val>=-0x80 && val<=0x7f && (op->type & Disp8))
op->type &= ~Disp16;
else
op->type &= ~Disp8;
}
else { /* CODE_32BIT */
op->type &= ~Disp16; /* no 16bit displacements possible */
if (val>=-0x80000000LL && val<=0xffffffffLL && (op->type & Disp32)) {
op->type &= ~Disp64;
if (val<=0x7fffffffLL && (op->type & Disp32S))
op->type &= ~Disp32;
if (val>=-0x80 && val<=0x7f && (op->type & Disp8))
op->type &= ~(Disp32|Disp32S);
else
op->type &= ~Disp8;
}
else
op->type &= ~(Disp8|Disp16|Disp32|Disp32S);
}
}
}
static int mode_from_disp_size(int type)
{
return (type & Disp8) ? 1 : (type & (Disp16 | Disp32 | Disp32S)) ? 2 : 0;
}
static int make_modrm(instruction *ip,mnemonic *mnemo,int final)
{
int defaultseg = -1;
int i,reg_operands,mem_operands;
/* count number of register and memory operands */
for (i=0,reg_operands=0,mem_operands=0; i<MAX_OPERANDS; i++) {
if (ip->op[i]) {
if (ip->op[i]->type & AnyReg)
reg_operands++;
else if (ip->op[i]->type & AnyMem)
mem_operands++;
}
}
ip->ext.flags |= MODRM_BYTE;
if (final && (cpudebug & 8)) {
printf("make_modrm(): %s: %d reg operands, %d mem operands\n",
mnemo->name,reg_operands,mem_operands);
}
if (reg_operands == 2) {
i = (ip->op[0]->type & AnyReg) ? 0 : 1; /* first register operand */
ip->ext.rm.mode = 3;
if (!(mnemo->operand_type[i+1] & AnyMem)) {
ip->ext.rm.reg = ip->op[i+1]->basereg->reg_num;
ip->ext.rm.regmem = ip->op[i]->basereg->reg_num;
}
else {
ip->ext.rm.reg = ip->op[i]->basereg->reg_num;
ip->ext.rm.regmem = ip->op[i+1]->basereg->reg_num;
}
}
else {
if (mem_operands) {
operand *memop = ip->op[(ip->op[0]->type & AnyMem) ? 0 :
((ip->op[1]->type & AnyMem) ? 1 : 2)];
if (final && (cpudebug & 8)) {
if (memop->basereg) {
printf("\tbase=%%%s(",memop->basereg->reg_name);
print_operand_type(memop->basereg->reg_type,'|');
putchar(')');
}
else
printf("\tbase=none");
if (memop->indexreg) {
printf(" index=%%%s(",memop->indexreg->reg_name);
print_operand_type(memop->indexreg->reg_type,'|');
putchar(')');
}
else
printf(" index=none");
printf(" scale=%d\n",scale_factor_tab[memop->log2_scale]);
}
defaultseg = DSEG_REGNUM;
if (memop->basereg == NULL) {
ip->ext.rm.mode = 0;
if (final && memop->value==NULL)
memop->value = number_expr(0);
if (memop->indexreg == NULL) {
/* no base and no index */
if ((mode_flag==CODE_16BIT) ^ (ip->ext.prefix[ADDR_PREFIX]!=0) &&
mode_flag!=CODE_64BIT) {
ip->ext.rm.regmem = NO_BASEREG16;
memop->type &= ~Disp;
memop->type |= Disp16;
}
else if (mode_flag!=CODE_64BIT || ip->ext.prefix[ADDR_PREFIX]) {
ip->ext.rm.regmem = NO_BASEREG;
memop->type &= ~Disp;
memop->type |= Disp32;
}
else {
ip->ext.rm.regmem = TWO_BYTE_ESCAPE;
ip->ext.sib.base = NO_BASEREG;
ip->ext.sib.index = NO_INDEXREG;
memop->type &= ~Disp;
memop->type |= Disp32S;
ip->ext.flags |= SIB_BYTE;
}
}
else {
/* no base, only index */
ip->ext.rm.regmem = TWO_BYTE_ESCAPE;
ip->ext.sib.base = NO_BASEREG;
ip->ext.sib.index = memop->indexreg->reg_num;
ip->ext.sib.scale = memop->log2_scale;
memop->type &= ~Disp;
if (mode_flag != CODE_64BIT)
memop->type |= Disp32;
else
memop->type |= Disp32S;
ip->ext.flags |= SIB_BYTE;
}
}
else if (memop->basereg->reg_type == BaseIndex) {
/* RIP base register for 64-bit mode */
ip->ext.rm.regmem = NO_BASEREG;
memop->type &= ~Disp;
memop->type |= Disp32S;
memop->flags |= OPER_PCREL;
}
else if (memop->basereg->reg_type & Reg16) {
/* 16-bit mode base register */
switch (memop->basereg->reg_num) {
case 3: /* %bx */
if (memop->indexreg) /* (%bx,%si) or (%bx,%di) */
ip->ext.rm.regmem = memop->indexreg->reg_num - 6;
else
ip->ext.rm.regmem = 7;
break;
case 5: /* %bp */
defaultseg = SSEG_REGNUM;
if (memop->indexreg) { /* (%bp,%si) or (%bp,%di) */
ip->ext.rm.regmem = memop->indexreg->reg_num - 4;
}
else {
ip->ext.rm.regmem = 6;
if (!(memop->type & Disp)) {
memop->type |= Disp8; /* fake 0(%bp) */
if (final)
memop->value = number_expr(0);
}
}
break;
default: /* (%si) or (%di) */
ip->ext.rm.regmem = memop->basereg->reg_num - 2;
break;
}
ip->ext.rm.mode = mode_from_disp_size(memop->type);
}
else {
/* 32/64 bit mode base register */
if (mode_flag==CODE_64BIT && (memop->type & Disp))
memop->type = (memop->type&Disp8) ? Disp8|Disp32S : Disp32S;
ip->ext.rm.regmem = memop->basereg->reg_num;
ip->ext.sib.base = memop->basereg->reg_num;
if (memop->basereg->reg_num == EBP_REGNUM) {
defaultseg = SSEG_REGNUM;
if (memop->value == NULL) {
memop->type |= Disp8; /* fake 8-bit zero-displacement */
if (final)
memop->value = number_expr(0);
}
}
else if (memop->basereg->reg_num == ESP_REGNUM) {
defaultseg = SSEG_REGNUM;
}
ip->ext.sib.scale = memop->log2_scale;
if (memop->indexreg == NULL) {
ip->ext.sib.index = NO_INDEXREG;
}
else {
ip->ext.sib.index = memop->indexreg->reg_num;
ip->ext.rm.regmem = TWO_BYTE_ESCAPE;
ip->ext.flags |= SIB_BYTE;
}
ip->ext.rm.mode = mode_from_disp_size(memop->type);
if (ip->ext.rm.regmem == TWO_BYTE_ESCAPE)
ip->ext.flags |= SIB_BYTE;
}
}
if (reg_operands) {
operand *regop = ip->op[(ip->op[0]->type & AnyReg) ? 0 :
((ip->op[1]->type & AnyReg) ? 1 : 2)];
if (final && (cpudebug & 8)) {
printf("\tregister=%%%s(",regop->basereg->reg_name);
print_operand_type(regop->basereg->reg_type,'|');
printf(")\n");
}
if (mnemo->ext.extension_opcode != NO_EXTOPCODE) {
ip->ext.rm.regmem = regop->basereg->reg_num;
}
else {
ip->ext.rm.reg = regop->basereg->reg_num;
}
if (!mem_operands)
ip->ext.rm.mode = 3;
}
/* insert extension opcode when available */
if (mnemo->ext.extension_opcode != NO_EXTOPCODE)
ip->ext.rm.reg = mnemo->ext.extension_opcode;
}
if (final && (cpudebug & 8)) {
printf("\tMod/RM: mode=%d reg=%d regmem=%d\n",
(int)ip->ext.rm.mode,(int)ip->ext.rm.reg,(int)ip->ext.rm.regmem);
if (ip->ext.flags & SIB_BYTE) {
printf("\tSIB: scale=%d index=%d base=%d\n",
(int)ip->ext.sib.scale,(int)ip->ext.sib.index,(int)ip->ext.sib.base);
}
}
return defaultseg;
}
static int process_operands(instruction *ip,int final)
{
mnemonic *mnemo = &mnemonics[ip->code];
int defaultseg = -1;
int mod = mnemo->ext.opcode_modifier;
int i;
regsym *seg;
if ((mod & FakeLastReg) && ip->op[0]) {
/* convert "imul %imm,%reg" into "imul %imm,%reg,%reg" and
convert "clr %reg" into "xor %reg,%reg" */
int regoper = (ip->op[0]->type & Reg) ? 0 : 1;
ip->op[regoper+1] = ip->op[regoper];
}
if ((mod & ShortForm) && ip->op[0]) {
/* copy register number to base-opcode in short-format */
int regoper = (ip->op[0]->type & (Reg|FloatReg)) ? 0 : 1;
ip->ext.base_opcode |= ip->op[regoper]->basereg->reg_num;
if ((mod & Deprecated) && final) {
if (ip->op[1]) {
/* translating to fxxxx %reg,%reg */
cpu_error(16,mnemo->name,ip->op[1]->basereg->reg_name,
ip->op[0]->basereg->reg_name);
}
else {
/* translating to fxxxx %reg */
cpu_error(17,mnemo->name,ip->op[0]->basereg->reg_name);
}
}
}
else if (mod & M) {
defaultseg = make_modrm(ip,mnemo,final);
}
else if (mod & (Seg2ShortForm|Seg3ShortForm)) {
if (mnemo->ext.base_opcode==OC_POP_SREG2 && ip->op[0]->basereg && final) {
if (ip->op[0]->basereg->reg_num == CSEG_REGNUM) {
cpu_error(15,ip->op[0]->basereg->reg_name); /* cannot pop %cs */
return 0;
}
}
ip->ext.base_opcode |= ip->op[0]->basereg->reg_num << 3;
}
else if (mnemo->ext.base_opcode == OC_MOV_ACC_DISP) {
defaultseg = DSEG_REGNUM;
}
else if (mod & StrInst) {
defaultseg = DSEG_REGNUM;
}
/* if a segment was explicitely specified and differs from the
instruction's default we need to select it by another opcode prefix */
for (i=0; i<MAX_OPERANDS; i++) {
if (ip->op[i] != NULL) {
if (seg = ip->op[i]->segoverride) {
if (seg->reg_num != defaultseg) {
if (!add_seg_prefix(ip,seg->reg_num)) {
if (!final)
cpu_error(2); /* same type of prefix used twice */
return 0;
}
}
}
}
}
return 1;
}
static int get_disp_bits(int t)
{
if (t & Disp8)
return 8;
if (t & Disp16)
return 16;
if (t & (Disp32|Disp32S))
return 32;
return 64;
}
static int get_imm_bits(int t)
{
if (t & (Imm8|Imm8S))
return 8;
if (t & Imm16)
return 16;
if (t & (Imm32|Imm32S))
return 32;
return 64;
}
static size_t get_opcode_size(instruction *ip)
{
uint32_t c = ip->ext.base_opcode;
size_t size = 1;
if (c > 0xff)
size++;
if (c > 0xffff)
size++;
return size;
}
static size_t imm_size(int t)
{
size_t size = 0;
if (t & Imm) {
if (t & (Imm8|Imm8S))
size++;
else if (t & Imm16)
size += 2;
else if (t & Imm64)
size += 8;
else
size += 4;
}
return size;
}
static size_t disp_size(int t)
{
size_t size = 0;
if (t & Disp) {
if (t & Disp8)
size++;
else if (t & Disp16)
size += 2;
else if (t & Disp64)
size += 8;
else
size += 4;
}
return size;
}
static size_t finalize_instruction(instruction *ip,section *sec,
taddr pc,int final)
/* execute optimizations, calculate instruction opcode, opcode-extension,
modrm- and sib-bytes, and return its size in bytes */
{
mnemonic *mnemo = &mnemonics[ip->code];
size_t size;
int i;
for (i=0; i<MAX_OPERANDS; i++) {
if (ip->op[i]) {
/* optimizations */
if (ip->op[i]->type & Imm) {
/* use smallest immediate size */
optimize_imm(ip,ip->op[i],sec,pc,final);
}
else if (ip->op[i]->type & Disp) {
if (mnemo->ext.opcode_modifier & (Jmp|JmpByte|JmpDword)) {
/* check jump-distances and try to use 8-bit form */
optimize_jump(ip,ip->op[i],sec,pc,final);
}
else {
/* use the smallest type for absolute displacements */
optimize_disp(ip->op[i],sec,pc,final);
}
}
if (final && ip->op[i]->scalefactor!=NULL) {
/* have to evaluate scale factor now */
taddr sf;
if (!eval_expr(ip->op[i]->scalefactor,&sf,sec,pc)) {
cpu_error(18); /* absolute scale factor required */
ip->op[i]->log2_scale = 0;
}
else {
switch (sf) {
case 1: sf = 0; break;
case 2: sf = 1; break;
case 4: sf = 2; break;
case 8: sf = 3; break;
default:
cpu_error(19); /* illegal scale factor */
break;
}
ip->op[i]->log2_scale = (int)sf;
}
}
else
ip->op[i]->log2_scale = 0;
}
}
/* process operation size, add prefixes when required */
process_suffix(ip,final);
/* set base opcode, process operands and generate modrm/sib if present */
process_operands(ip,final);
/* determine size of instruction */
size = get_opcode_size(ip);
size += ip->ext.num_prefixes;
if (ip->ext.flags & MODRM_BYTE)
size++;
if (ip->ext.flags & SIB_BYTE)
size++;
for (i=0; i<MAX_OPERANDS; i++) {
if (ip->op[i]) {
size += imm_size(ip->op[i]->type);
size += disp_size(ip->op[i]->type);
}
else
break;
}
if (cpudebug & 16)
printf("%08lx: (%u) %s",(unsigned long)pc,(unsigned)size,mnemo->name);
return size;
}
static unsigned char make_byte233(unsigned char b76,unsigned char b543,
unsigned char b210)
/* make a byte from three bit-fields: bit7-6, bit5-3 and bit2-0 */
{
return ((b76 & 3)<<6) | ((b543 & 7)<<3) | (b210&7);
}
static unsigned char *write_taddr(unsigned char *d,taddr val,size_t bits)
{
switch (bits) {
case 8:
*d++ = val & 0xff;
break;
case 16:
case 32:
case 64:
d = setval(0,d,bits>>3,val);
break;
default:
ierror(0);
break;
}
return d;
}
static unsigned char *output_prefixes(unsigned char *d,instruction *ip)
{
unsigned char p;
int i;
for (i=0; i<MAX_PREFIXES; i++) {
if (p = ip->ext.prefix[i])
*d++ = p;
}
return d;
}
static unsigned char *output_opcodes(unsigned char *d,instruction *ip)
{
uint32_t oc = (uint32_t)ip->ext.base_opcode;
if (oc > 0xffff)
*d++ = (oc >> 16) & 0xff;
if (oc > 0xff)
*d++ = (oc >> 8) & 0xff;
*d++ = oc & 0xff;
if (ip->ext.flags & MODRM_BYTE)
*d++ = make_byte233(ip->ext.rm.mode,ip->ext.rm.reg,ip->ext.rm.regmem);
if (ip->ext.flags & SIB_BYTE)
*d++ = make_byte233(ip->ext.sib.scale,ip->ext.sib.index,ip->ext.sib.base);
return d;
}
static unsigned char *output_disp(dblock *db,unsigned char *d,
instruction *ip,section *sec,taddr pc)
{
int i;
size_t bits;
operand *op;
taddr val;
for (i=0; i<MAX_OPERANDS; i++) {
if (op = ip->op[i]) {
if (op->type & Disp) {
mnemonic *mnemo = &mnemonics[ip->code];
bits = get_disp_bits(op->type);
if (!eval_expr(op->value,&val,sec,pc)) {
symbol *base;
if (find_base(op->value,&base,sec,pc) == BASE_OK) {
if ((mnemo->ext.opcode_modifier & (Jmp|JmpByte|JmpDword))
|| (op->flags & OPER_PCREL)) {
/* handle pc-relative displacement for jumps */
if (!is_pc_reloc(base,sec)) {
val = val - (pc + (d-(unsigned char *)db->data) + (bits>>3));
}
else {
val -= bits>>3;
add_extnreloc(&db->relocs,base,val,REL_PC,0,bits,
(int)(d-(unsigned char *)db->data));
}
}
else {
/* reloc for a normal absolute displacement */
add_extnreloc(&db->relocs,base,val,REL_ABS,0,bits,
(int)(d-(unsigned char *)db->data));
}
}
else
general_error(38); /* illegal relocation */
}
else { /* constant/absolute */
if ((mnemo->ext.opcode_modifier & (Jmp|JmpByte|JmpDword))
|| (op->flags & OPER_PCREL)) {
/* handle pc-relative jumps to absolute labels */
val = val - (pc + (d-(unsigned char *)db->data) + (bits>>3));
}
}
d = write_taddr(d,val,bits);
}
}
}
return d;
}
static unsigned char *output_imm(dblock *db,unsigned char *d,
instruction *ip,section *sec,taddr pc)
{
int i,ot;
size_t bits;
operand *op;
taddr val;
for (i=0; i<MAX_OPERANDS; i++) {
if (op = ip->op[i]) {
if (ot = (op->type & Imm)) {
bits = get_imm_bits(ot);
#if 0 /* done in optimize_imm() */
if (bits>8 && ((ot & Imm16) && ((ot & Imm32) || (ot & Imm32S)))) {
/* 16 or 32 bit immediate depends on current data size */
if ((mode_flag==CODE_16BIT) ^ (ip->ext.prefix[DATA_PREFIX]!=0))
bits = 16;
else
bits = 32;
}
#endif
if (!eval_expr(op->value,&val,sec,pc)) {
symbol *base;
if (find_base(op->value,&base,sec,pc) == BASE_OK) {
add_extnreloc(&db->relocs,base,val,REL_ABS,0,bits,
(int)(d-(unsigned char *)db->data));
}
else
general_error(38); /* illegal relocation */
}
d = write_taddr(d,val,bits);
}
}
}
return d;
}
static instruction *copy_instruction(instruction *ip)
/* copy an instruction and its operands */
{
static instruction newip;
static operand newop[MAX_OPERANDS];
int i;
newip.code = ip->code;
newip.qualifiers[0] = ip->qualifiers[0];
for (i=0; i<MAX_OPERANDS; i++) {
if (ip->op[i] != NULL) {
newip.op[i] = &newop[i];
*newip.op[i] = *ip->op[i];
}
else
newip.op[i] = NULL;
}
memcpy(&newip.ext,&ip->ext,sizeof(instruction_ext));
return &newip;
}
char *parse_cpu_special(char *start)
/* parse cpu-specific directives; return pointer to end of
cpu-specific text */
{
char *name=start,*s=start;
if (ISIDSTART(*s)) {
s++;
while (ISIDCHAR(*s))
s++;
if (s-name==7 && !strncmp(name,".code16",7)) {
mode_flag = CODE_16BIT;
return s;
}
else if (s-name==7 && !strncmp(name,".code32",7)) {
mode_flag = CODE_32BIT;
return s;
}
else if (s-name==7 && !strncmp(name,".code64",7)) {
mode_flag = CODE_64BIT;
return s;
}
}
return start;
}
char *parse_instruction(char *s,int *inst_len,char **ext,int *ext_len,
int *ext_cnt)
/* parse instruction and save extension locations */
{
hashdata data;
char *inst = s;
int len;
/* reset opcode prefixes */
memset(prefix,0,sizeof(prefix));
prefix_cnt = 0;
if (cpudebug & 1)
printf("parse_instruction: \"%s\"\n",s);
for (;;) {
while (*s && !isspace((unsigned char)*s))
s++;
len = s - inst;
if (find_namelen(mnemohash,inst,len,&data)) {
#if 0 /*@@@ need a way to support prefixes at the same line with vasm */
mnemonic *mnemo = &mnemonics[data.idx];
if (mnemo->ext.opcode_modifier & IsPrefix) {
/* matched a prefix instruction, remember it and look for more */
s = skip(s);
if (ISIDSTART(*s)) {
/* real opcode or another prefix follows, save current prefix */
if (!(mnemo->ext.available & cpu_type))
cpu_error(0); /* instruction not supported on selected arch. */
/* do not allow addr16 in 16-bit and addr32 in 32-bit mode */
if (((mnemo->ext.opcode_modifier&Size16) && mode_flag==CODE_16BIT)
|| ((mnemo->ext.opcode_modifier&Size32) &&
mode_flag==CODE_32BIT)) {
cpu_error(7,mnemo->name); /* redundant prefix ignored */
}
else {
/* enter new prefix */
if (cpudebug & 1) {
printf("\tadd prefix %02x for \"%s\"\n",
(unsigned)mnemo->ext.base_opcode,mnemo->name);
}
if (!add_prefix(mnemo->ext.base_opcode))
cpu_error(2); /* same type of prefix used twice */
}
inst = s;
continue; /* parse next opcode or prefix */
}
}
#endif
}
else {
/* no direct match, so look if we can strip a suffix from it */
if (len >= 2) {
char x = *(s-1);
if (x=='b' || x=='w' || x=='l' || x=='s' || x=='q' || x=='x') {
/* a potential suffix found, save it */
int cnt = *ext_cnt;
ext[cnt] = s-1;
ext_len[cnt] = 1;
*ext_cnt = ++cnt;
--len;
}
}
}
break;
}
*inst_len = len;
if (cpudebug & 1) {
printf("\tinst=\"%.*s\" suffix=\"%.*s\"\n",len,inst,
*ext_cnt?ext_len[0]:0, *ext_cnt?ext[0]:emptystr);
}
return s;
}
int set_default_qualifiers(char **q,int *q_len)
/* fill in pointers to default qualifiers, return number of qualifiers */
{
/* FIXME: no default size for x86 instructions? */
return 0;
}
static regsym *parse_reg(char **pp)
/* parse a register name, return pointer to regsym when successful */
{
char *p,*start;
regsym *r;
p = skip(*pp);
if (*p++ == '%') {
start = p;
if (ISIDSTART(*p)) {
p++;
while (ISIDCHAR(*p))
p++;
/* special case float-registers: %st(n) */
if (p-start==2 &&
(!strncmp(start,"st(",3) || !strncmp(start,"ST(",3))) {
if (isdigit((unsigned char)*(p+1)) && *(p+2)==')')
p += 3;
}
if (r = find_regsym_nc(start,p-start)) {
if ((r->reg_flags & (RegRex64|RegRex)) && mode_flag!=CODE_64BIT)
return NULL;
*pp = p;
return r;
}
}
}
return NULL;
}
int parse_operand(char *p,int len,operand *op,int requirements)
/* Parses operands, reads expressions and assigns relocation types */
{
char *start = p;
int need_mem_oper = 0;
int given_type,overlap;
if (cpudebug & 2) {
printf("parse_operand (reqs=%08lx): \"%.*s\"\n",
(unsigned long)requirements,len,p);
}
/* @@@ This does no longer work, since save_symbols()/restore_symbols().
given_type = op->parsed_type; */
given_type = 0; /* ... so parse the operand every time again */
if (given_type == 0) {
p = skip(p);
if (*p == '%') {
/* check for a segment override first */
char *savedp = p;
regsym *sreg = parse_reg(&p);
p = skip(p);
if (sreg!=NULL && (sreg->reg_type & (SegReg2|SegReg3)) && *p++==':') {
op->segoverride = sreg;
need_mem_oper = 1; /* only memory operands may follow */
p = skip(p);
}
else {
/* no segment override - forget all we did */
p = savedp;
}
}
if (*p == '*') {
/* indicates absolute jumps */
p = skip(++p);
given_type = JmpAbs;
}
else
given_type = 0;
if (*p == '%') {
/* single register operand */
if (need_mem_oper) {
cpu_error(14); /* memory operand expected */
return PO_CORRUPT;
}
if (op->basereg = parse_reg(&p)) {
op->flags |= OPER_REG;
given_type |= op->basereg->reg_type & ~BaseIndex;
}
else {
cpu_error(8); /* unknown register specified */
return PO_CORRUPT;
}
}
else if (*p == '$') {
/* immediate operand */
if (need_mem_oper) {
cpu_error(14); /* memory operand expected */
return PO_CORRUPT;
}
if (given_type & JmpAbs) {
cpu_error(3); /* immediate operand illegal with absolute jump */
return PO_CORRUPT;
}
p++;
op->value = parse_expr(&p);
given_type = Imm; /* exact size is not available at this stage */
}
else {
if (*p != '(') {
/* read displacement (or data) */
given_type |= Disp; /* exact size is not available at this stage */
if ((requirements & FloatData) == FloatData) {
op->value = parse_expr_float(&p);
given_type |= FloatData;
}
else
op->value = parse_expr(&p);
p = skip(p);
}
if (*p == '(') {
/* BaseIndex mode: read base, index, scale */
p = skip(++p);
if (*p != ',') {
if (!(op->basereg = parse_reg(&p))) {
cpu_error(4); /* base register expected */
return PO_CORRUPT;
}
p = skip(p);
}
if (*p == ',') {
p = skip(++p);
if (!(op->indexreg = parse_reg(&p))) {
cpu_error(5); /* scale factor without index register */
return PO_CORRUPT;
}
p = skip(p);
if (*p == ',') {
p = skip(++p);
op->scalefactor = parse_expr(&p);
p = skip(p);
}
}
if (*p++ != ')') {
cpu_error(6); /* missing ')' in baseindex addressing mode */
return PO_CORRUPT;
}
given_type |= BaseIndex;
}
}
p = skip(p);
if (p - start < len)
cpu_error(1); /* trailing garbage in operand */
op->parsed_type = given_type; /* remember type for next try */
}
overlap = given_type & requirements;
if (cpudebug & 2) {
printf("\ttype given: %08lx overlap with reqs: %08lx - ",
(unsigned long)given_type,(unsigned long)overlap);
}
if ((overlap & ~JmpAbs) &&
(given_type & (BaseIndex|JmpAbs)) ==
(overlap & (BaseIndex|JmpAbs))) {
/* ok, parsed operand type matches requirements */
if (cpudebug & 2)
printf("MATCHED!\n");
op->type = overlap;
/* @@@ check register types??? */
/* If given types r0 and r1 are registers they must be of the same type
unless the expected operand type register overlap is null.
Note that Acc in a template matches every size of reg. */
return PO_MATCH;
}
if (cpudebug & 2)
printf("failed\n");
return PO_NOMATCH;
}
void init_instruction_ext(instruction_ext *ixp)
{
int i;
memset(ixp,0,sizeof(instruction_ext));
if (prefix_cnt) {
ixp->num_prefixes = prefix_cnt;
for (i=0; i<MAX_PREFIXES; i++)
ixp->prefix[i] = prefix[i];
}
ixp->last_size = -1;
}
size_t instruction_size(instruction *realip,section *sec,taddr pc)
/* Calculate the size of the current instruction; must be identical
to the data created by eval_instruction. */
{
mnemonic *mnemo = &mnemonics[realip->code];
int i,diff;
size_t size;
/* assign the suffix, which was unknown during operand evaluation */
if (!(realip->ext.flags & SUFFIX_CHECKED)) {
if (cpudebug & 4) {
printf("instruction_size():");
print_operands(realip,pc);
}
/* set base opcode from mnemonic */
realip->ext.base_opcode = mnemo->ext.base_opcode;
for (i=0; i<MAX_OPERANDS; i++) {
if (realip->op[i]) {
if (realip->op[i]->flags & OPER_REG)
realip->ext.flags |= HAS_REG_OPER;
}
else
break;
}
/* try to assign a suffix */
assign_suffix(realip);
realip->ext.flags |= SUFFIX_CHECKED;
/* and check if instruction supports suffix and matches selected cpu */
if ((mnemo->ext.opcode_modifier & suffix_flag(realip)) ||
(mnemo->ext.available & cpu_type)!=mnemo->ext.available) {
if (!find_next_mnemonic(realip))
cpu_error(0); /* instruction not supported on selected arch. */
}
}
/* work on a copy of the current instruction and finalize it */
size = finalize_instruction(copy_instruction(realip),sec,pc,0);
if (realip->ext.last_size>=0 && (diff=realip->ext.last_size-(int)size)!=0) {
if (diff > 0) {
if (cpudebug & 16)
printf(" (%d bytes gained)\n",diff);
realip->ext.flags |= POSOPT;
}
else {
if (cpudebug & 16)
printf(" (%d bytes lost)\n",-diff);
realip->ext.flags |= NEGOPT;
}
}
else {
if (cpudebug & 16)
printf("\n");
}
realip->ext.last_size = size;
return size;
}
dblock *eval_instruction(instruction *ip,section *sec,taddr pc)
/* Convert an instruction into a DATA atom including relocations,
if necessary. */
{
dblock *db = new_dblock();
unsigned char *d;
db->size = finalize_instruction(ip,sec,pc,1);
db->data = mymalloc(db->size);
if (cpudebug & 16)
printf("\n");
if (cpudebug & 4) {
printf("eval_instruction():");
print_operands(ip,pc);
}
d = output_prefixes(db->data,ip);
d = output_opcodes(d,ip);
d = output_disp(db,d,ip,sec,pc);
d = output_imm(db,d,ip,sec,pc);
return db;
}
dblock *eval_data(operand *op,size_t bitsize,section *sec,taddr pc)
/* Create a dblock (with relocs, if necessary) for size bits of data. */
{
dblock *db = new_dblock();
taddr val;
tfloat flt;
db->size = bitsize >> 3;
db->data = mymalloc(db->size);
if (type_of_expr(op->value) == FLT) {
if (!eval_expr_float(op->value,&flt))
general_error(60); /* cannot evaluate floating point */
switch (bitsize) {
case 32:
conv2ieee32(0,db->data,flt);
break;
case 64:
conv2ieee64(0,db->data,flt);
break;
default:
cpu_error(20,bitsize); /* illegal bitsize */
break;
}
}
else {
if (!eval_expr(op->value,&val,sec,pc)) {
symbol *base;
int btype;
btype = find_base(op->value,&base,sec,pc);
if (base)
add_extnreloc(&db->relocs,base,val,
btype==BASE_PCREL?REL_PC:REL_ABS,
0,bitsize,0);
else if (btype != BASE_NONE)
general_error(38); /* illegal relocation */
}
write_taddr(db->data,val,bitsize);
}
return db;
}
int init_cpu()
{
int i;
regsym *r;
if (!(cpu_type & CPU64))
cpu_type |= CPUNo64;
for (i=0; i<mnemonic_cnt; i++) {
if (!strcmp(mnemonics[i].name,"addr16"))
OC_ADDR_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"data16"))
OC_DATA_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"lock"))
OC_LOCK_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"wait"))
OC_WAIT_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"cs"))
OC_CSEG_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"ds"))
OC_DSEG_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"es"))
OC_ESEG_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"fs"))
OC_FSEG_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"gs"))
OC_GSEG_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"ss"))
OC_SSEG_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"rep"))
OC_REPE_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"repne"))
OC_REPNE_PREFIX = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"pop") &&
mnemonics[i].operand_type[0] == SegReg2)
OC_POP_SREG2 = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"mov") &&
mnemonics[i].operand_type[1] == Acc)
OC_MOV_ACC_DISP = (unsigned char)mnemonics[i].ext.base_opcode;
else if (!strcmp(mnemonics[i].name,"jmp") &&
mnemonics[i].operand_type[0] == Disp)
OC_JMP_DISP = (unsigned char)mnemonics[i].ext.base_opcode;
}
/* define all register symbols */
for (r=x86_regsyms; r->reg_name!=NULL; r++)
add_regsym(r);
return 1;
}
int cpu_args(char *p)
{
if (!strncmp(p,"-cpudebug=",7)) {
if (!sscanf(p+10,"%li",&cpudebug))
return 0;
}
else if (!strncmp(p,"-m",2)) {
p += 2;
if (!strcmp(p,"8086")) cpu_type = CPU086;
else if (!strcmp(p,"i186")) cpu_type = CPU086|CPU186;
else if (!strcmp(p,"i286")) cpu_type = CPU086|CPU186|CPU286;
else if (!strcmp(p,"i386")) cpu_type = CPU086|CPU186|CPU286|CPU386;
else if (!strcmp(p,"i486")) cpu_type = CPU086|CPU186|CPU286|CPU386|CPU486;
else if (!strcmp(p,"i586")) cpu_type = CPU086|CPU186|CPU286|CPU386|CPU486|CPU586|CPUMMX;
else if (!strcmp(p,"i686")) cpu_type = CPU086|CPU186|CPU286|CPU386|CPU486|CPU586|CPU686|CPUMMX|CPUSSE;
else if (!strcmp(p,"pentium")) cpu_type = CPU086|CPU186|CPU286|CPU386|CPU486|CPU586|CPUMMX;
else if (!strcmp(p,"pentiumpro")) cpu_type = CPU086|CPU186|CPU286|CPU386|CPU486|CPU586|CPU686|CPUMMX|CPUSSE;
else if (!strcmp(p,"pentium4")) cpu_type = CPU086|CPU186|CPU286|CPU386|CPU486|CPU586|CPU686|CPUP4|CPUMMX|CPUSSE|CPUSSE2;
else if (!strcmp(p,"k6")) cpu_type = CPU086|CPU186|CPU286|CPU386|CPU486|CPU586|CPUK6|CPUMMX|CPU3dnow;
else if (!strcmp(p,"athlon")) cpu_type = CPU086|CPU186|CPU286|CPU386|CPU486|CPU586|CPU686|CPUK6|CPUAthlon|CPUMMX|CPU3dnow;
else if (!strcmp(p,"sledgehammer")) cpu_type = CPU086|CPU186|CPU286|CPU386|CPU486|CPU586|CPU686|CPUK6|CPUAthlon|CPUSledgehammer|CPUMMX|CPU3dnow|CPUSSE|CPUSSE2;
else if (!strcmp(p,"64")) {
/* 64 bits architecture */
cpu_type = CPUAny|CPU64;
bytespertaddr = 8;
}
else return 0;
}
return 1;
}