559 lines
13 KiB
JavaScript
559 lines
13 KiB
JavaScript
/*
|
|
* Some miscellaneous instructions:
|
|
*
|
|
* jmpcc16, jmpcc32, jmp16
|
|
* loop, loope, loopne, jcxz
|
|
* test_cc
|
|
*
|
|
* mov, push, pop
|
|
* pusha, popa
|
|
* xchg, lss
|
|
* lea
|
|
* enter
|
|
* bswap
|
|
* fxsave, fxrstor
|
|
*/
|
|
"use strict";
|
|
|
|
CPU.prototype.jmpcc8 = function(condition)
|
|
{
|
|
var imm8 = this.read_op8s();
|
|
if(condition)
|
|
{
|
|
this.instruction_pointer = this.instruction_pointer + imm8 | 0;
|
|
this.branch_taken();
|
|
}
|
|
else
|
|
{
|
|
this.branch_not_taken();
|
|
}
|
|
};
|
|
|
|
CPU.prototype.jmp_rel16 = function(rel16)
|
|
{
|
|
var current_cs = this.get_seg(reg_cs);
|
|
|
|
// limit ip to 16 bit
|
|
// ugly
|
|
this.instruction_pointer -= current_cs;
|
|
this.instruction_pointer = (this.instruction_pointer + rel16) & 0xFFFF;
|
|
this.instruction_pointer = this.instruction_pointer + current_cs | 0;
|
|
};
|
|
|
|
CPU.prototype.jmpcc16 = function(condition)
|
|
{
|
|
var imm16 = this.read_op16();
|
|
if(condition)
|
|
{
|
|
this.jmp_rel16(imm16);
|
|
this.branch_taken();
|
|
}
|
|
else
|
|
{
|
|
this.branch_not_taken();
|
|
}
|
|
}
|
|
|
|
|
|
CPU.prototype.jmpcc32 = function(condition)
|
|
{
|
|
var imm32s = this.read_op32s();
|
|
if(condition)
|
|
{
|
|
// don't change to `this.instruction_pointer += this.read_op32s()`,
|
|
// since read_op32s modifies instruction_pointer
|
|
|
|
this.instruction_pointer = this.instruction_pointer + imm32s | 0;
|
|
this.branch_taken();
|
|
}
|
|
else
|
|
{
|
|
this.branch_not_taken();
|
|
}
|
|
};
|
|
|
|
CPU.prototype.cmovcc16 = function(condition)
|
|
{
|
|
var data = this.read_e16();
|
|
if(condition)
|
|
{
|
|
this.write_g16(data);
|
|
}
|
|
};
|
|
|
|
CPU.prototype.cmovcc32 = function(condition)
|
|
{
|
|
var data = this.read_e32s();
|
|
if(condition)
|
|
{
|
|
this.write_g32(data);
|
|
}
|
|
};
|
|
|
|
CPU.prototype.setcc = function(condition)
|
|
{
|
|
this.set_e8(condition ? 1 : 0)
|
|
};
|
|
|
|
CPU.prototype.loopne = function(imm8s)
|
|
{
|
|
if(this.decr_ecx_asize() && !this.getzf())
|
|
{
|
|
this.instruction_pointer = this.instruction_pointer + imm8s | 0;
|
|
this.branch_taken();
|
|
}
|
|
else
|
|
{
|
|
this.branch_not_taken();
|
|
}
|
|
}
|
|
|
|
CPU.prototype.loope = function(imm8s)
|
|
{
|
|
if(this.decr_ecx_asize() && this.getzf())
|
|
{
|
|
this.instruction_pointer = this.instruction_pointer + imm8s | 0;
|
|
this.branch_taken();
|
|
}
|
|
else
|
|
{
|
|
this.branch_not_taken();
|
|
}
|
|
}
|
|
|
|
CPU.prototype.loop = function(imm8s)
|
|
{
|
|
if(this.decr_ecx_asize())
|
|
{
|
|
this.instruction_pointer = this.instruction_pointer + imm8s | 0;
|
|
this.branch_taken();
|
|
}
|
|
else
|
|
{
|
|
this.branch_not_taken();
|
|
}
|
|
}
|
|
|
|
CPU.prototype.jcxz = function(imm8s)
|
|
{
|
|
if(this.get_reg_asize(reg_ecx) === 0)
|
|
{
|
|
this.instruction_pointer = this.instruction_pointer + imm8s | 0;
|
|
this.branch_taken();
|
|
}
|
|
else
|
|
{
|
|
this.branch_not_taken();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @return {number}
|
|
* @const
|
|
*/
|
|
CPU.prototype.getcf = function()
|
|
{
|
|
if(this.flags_changed & 1)
|
|
{
|
|
return (this.last_op1 ^ (this.last_op1 ^ this.last_op2) & (this.last_op2 ^ this.last_add_result)) >>> this.last_op_size & 1;
|
|
}
|
|
else
|
|
{
|
|
return this.flags & 1;
|
|
}
|
|
};
|
|
|
|
/** @return {number} */
|
|
CPU.prototype.getpf = function()
|
|
{
|
|
if(this.flags_changed & flag_parity)
|
|
{
|
|
// inverted lookup table
|
|
return 0x9669 << 2 >> ((this.last_result ^ this.last_result >> 4) & 0xF) & flag_parity;
|
|
}
|
|
else
|
|
{
|
|
return this.flags & flag_parity;
|
|
}
|
|
};
|
|
|
|
/** @return {number} */
|
|
CPU.prototype.getaf = function()
|
|
{
|
|
if(this.flags_changed & flag_adjust)
|
|
{
|
|
return (this.last_op1 ^ this.last_op2 ^ this.last_add_result) & flag_adjust;
|
|
}
|
|
else
|
|
{
|
|
return this.flags & flag_adjust;
|
|
}
|
|
};
|
|
|
|
/** @return {number} */
|
|
CPU.prototype.getzf = function()
|
|
{
|
|
if(this.flags_changed & flag_zero)
|
|
{
|
|
return (~this.last_result & this.last_result - 1) >>> this.last_op_size & 1;
|
|
}
|
|
else
|
|
{
|
|
return this.flags & flag_zero;
|
|
}
|
|
};
|
|
|
|
/** @return {number} */
|
|
CPU.prototype.getsf = function()
|
|
{
|
|
if(this.flags_changed & flag_sign)
|
|
{
|
|
return this.last_result >>> this.last_op_size & 1;
|
|
}
|
|
else
|
|
{
|
|
return this.flags & flag_sign;
|
|
}
|
|
};
|
|
|
|
/** @return {number} */
|
|
CPU.prototype.getof = function()
|
|
{
|
|
if(this.flags_changed & flag_overflow)
|
|
{
|
|
return ((this.last_op1 ^ this.last_add_result) & (this.last_op2 ^ this.last_add_result)) >>> this.last_op_size & 1;
|
|
}
|
|
else
|
|
{
|
|
return this.flags & flag_overflow;
|
|
}
|
|
};
|
|
|
|
CPU.prototype.test_o = CPU.prototype.getof;
|
|
CPU.prototype.test_b = CPU.prototype.getcf;
|
|
CPU.prototype.test_z = CPU.prototype.getzf;
|
|
CPU.prototype.test_s = CPU.prototype.getsf;
|
|
CPU.prototype.test_p = CPU.prototype.getpf;
|
|
|
|
CPU.prototype.test_be = function()
|
|
{
|
|
// Idea:
|
|
// return this.last_op1 <= this.last_op2;
|
|
return this.getcf() || this.getzf();
|
|
}
|
|
|
|
CPU.prototype.test_l = function()
|
|
{
|
|
// Idea:
|
|
// return this.last_add_result < this.last_op2;
|
|
return !this.getsf() !== !this.getof();
|
|
}
|
|
|
|
CPU.prototype.test_le = function()
|
|
{
|
|
// Idea:
|
|
// return this.last_add_result <= this.last_op2;
|
|
return this.getzf() || !this.getsf() !== !this.getof();
|
|
}
|
|
|
|
|
|
|
|
CPU.prototype.push16 = function(imm16)
|
|
{
|
|
var sp = this.get_stack_pointer(-2);
|
|
|
|
this.safe_write16(sp, imm16);
|
|
this.adjust_stack_reg(-2);
|
|
}
|
|
|
|
CPU.prototype.push32 = function(imm32)
|
|
{
|
|
var sp = this.get_stack_pointer(-4);
|
|
|
|
this.safe_write32(sp, imm32);
|
|
this.adjust_stack_reg(-4);
|
|
}
|
|
|
|
CPU.prototype.pop16 = function()
|
|
{
|
|
var sp = this.get_seg(reg_ss) + this.get_stack_reg() | 0,
|
|
result = this.safe_read16(sp);
|
|
|
|
this.adjust_stack_reg(2);
|
|
return result;
|
|
}
|
|
|
|
CPU.prototype.pop32s = function()
|
|
{
|
|
var sp = this.get_seg(reg_ss) + this.get_stack_reg() | 0,
|
|
result = this.safe_read32s(sp);
|
|
|
|
this.adjust_stack_reg(4);
|
|
return result;
|
|
}
|
|
|
|
CPU.prototype.pusha16 = function()
|
|
{
|
|
var temp = this.reg16[reg_sp];
|
|
|
|
// make sure we don't get a pagefault after having
|
|
// pushed several registers already
|
|
this.writable_or_pagefault(this.get_stack_pointer(-16), 16);
|
|
|
|
this.push16(this.reg16[reg_ax]);
|
|
this.push16(this.reg16[reg_cx]);
|
|
this.push16(this.reg16[reg_dx]);
|
|
this.push16(this.reg16[reg_bx]);
|
|
this.push16(temp);
|
|
this.push16(this.reg16[reg_bp]);
|
|
this.push16(this.reg16[reg_si]);
|
|
this.push16(this.reg16[reg_di]);
|
|
}
|
|
|
|
CPU.prototype.pusha32 = function()
|
|
{
|
|
var temp = this.reg32s[reg_esp];
|
|
|
|
this.writable_or_pagefault(this.get_stack_pointer(-32), 32);
|
|
|
|
this.push32(this.reg32s[reg_eax]);
|
|
this.push32(this.reg32s[reg_ecx]);
|
|
this.push32(this.reg32s[reg_edx]);
|
|
this.push32(this.reg32s[reg_ebx]);
|
|
this.push32(temp);
|
|
this.push32(this.reg32s[reg_ebp]);
|
|
this.push32(this.reg32s[reg_esi]);
|
|
this.push32(this.reg32s[reg_edi]);
|
|
}
|
|
|
|
CPU.prototype.popa16 = function()
|
|
{
|
|
this.translate_address_read(this.get_stack_pointer(0));
|
|
this.translate_address_read(this.get_stack_pointer(15));
|
|
|
|
this.reg16[reg_di] = this.pop16();
|
|
this.reg16[reg_si] = this.pop16();
|
|
this.reg16[reg_bp] = this.pop16();
|
|
this.adjust_stack_reg(2);
|
|
this.reg16[reg_bx] = this.pop16();
|
|
this.reg16[reg_dx] = this.pop16();
|
|
this.reg16[reg_cx] = this.pop16();
|
|
this.reg16[reg_ax] = this.pop16();
|
|
}
|
|
|
|
CPU.prototype.popa32 = function()
|
|
{
|
|
this.translate_address_read(this.get_stack_pointer(0));
|
|
this.translate_address_read(this.get_stack_pointer(31));
|
|
|
|
this.reg32s[reg_edi] = this.pop32s();
|
|
this.reg32s[reg_esi] = this.pop32s();
|
|
this.reg32s[reg_ebp] = this.pop32s();
|
|
this.adjust_stack_reg(4);
|
|
this.reg32s[reg_ebx] = this.pop32s();
|
|
this.reg32s[reg_edx] = this.pop32s();
|
|
this.reg32s[reg_ecx] = this.pop32s();
|
|
this.reg32s[reg_eax] = this.pop32s();
|
|
}
|
|
|
|
CPU.prototype.xchg8 = function(memory_data, modrm_byte)
|
|
{
|
|
var mod = modrm_byte >> 1 & 0xC | modrm_byte >> 5 & 1,
|
|
tmp = this.reg8[mod];
|
|
|
|
this.reg8[mod] = memory_data;
|
|
|
|
return tmp;
|
|
}
|
|
|
|
CPU.prototype.xchg16 = function(memory_data, modrm_byte)
|
|
{
|
|
var mod = modrm_byte >> 2 & 14,
|
|
tmp = this.reg16[mod];
|
|
|
|
this.reg16[mod] = memory_data;
|
|
|
|
return tmp;
|
|
}
|
|
|
|
CPU.prototype.xchg16r = function(operand)
|
|
{
|
|
var temp = this.reg16[reg_ax];
|
|
this.reg16[reg_ax] = this.reg16[operand];
|
|
this.reg16[operand] = temp;
|
|
}
|
|
|
|
CPU.prototype.xchg32 = function(memory_data, modrm_byte)
|
|
{
|
|
var mod = modrm_byte >> 3 & 7,
|
|
tmp = this.reg32s[mod];
|
|
|
|
this.reg32s[mod] = memory_data;
|
|
|
|
return tmp;
|
|
}
|
|
|
|
CPU.prototype.xchg32r = function(operand)
|
|
{
|
|
var temp = this.reg32s[reg_eax];
|
|
this.reg32s[reg_eax] = this.reg32s[operand];
|
|
this.reg32s[operand] = temp;
|
|
}
|
|
|
|
CPU.prototype.lss16 = function(seg)
|
|
{
|
|
if(this.modrm_byte >= 0xC0)
|
|
{
|
|
// 0xc4c4 #ud (EMULATOR_BOP) is used by reactos and windows to exit vm86 mode
|
|
this.trigger_ud();
|
|
}
|
|
|
|
var addr = this.modrm_resolve(this.modrm_byte);
|
|
|
|
var new_reg = this.safe_read16(addr),
|
|
new_seg = this.safe_read16(addr + 2 | 0);
|
|
|
|
this.switch_seg(seg, new_seg);
|
|
|
|
this.reg16[this.modrm_byte >> 2 & 14] = new_reg;
|
|
}
|
|
|
|
CPU.prototype.lss32 = function(seg)
|
|
{
|
|
if(this.modrm_byte >= 0xC0)
|
|
{
|
|
this.trigger_ud();
|
|
}
|
|
|
|
var addr = this.modrm_resolve(this.modrm_byte);
|
|
|
|
var new_reg = this.safe_read32s(addr),
|
|
new_seg = this.safe_read16(addr + 4 | 0);
|
|
|
|
this.switch_seg(seg, new_seg);
|
|
|
|
this.reg32s[this.modrm_byte >> 3 & 7] = new_reg;
|
|
}
|
|
|
|
CPU.prototype.enter16 = function(size, nesting_level)
|
|
{
|
|
nesting_level &= 31;
|
|
|
|
if(nesting_level) dbg_log("enter16 stack=" + (this.stack_size_32 ? 32 : 16) + " size=" + size + " nest=" + nesting_level, LOG_CPU);
|
|
this.push16(this.reg16[reg_bp]);
|
|
var frame_temp = this.reg16[reg_sp];
|
|
|
|
if(nesting_level > 0)
|
|
{
|
|
var tmp_ebp = this.reg16[reg_ebp];
|
|
for(var i = 1; i < nesting_level; i++)
|
|
{
|
|
tmp_ebp -= 2;
|
|
this.push16(this.safe_read16(this.get_seg(reg_ss) + tmp_ebp | 0));
|
|
}
|
|
this.push16(frame_temp);
|
|
}
|
|
this.reg16[reg_bp] = frame_temp;
|
|
this.adjust_stack_reg(-size);
|
|
};
|
|
|
|
CPU.prototype.enter32 = function(size, nesting_level)
|
|
{
|
|
nesting_level &= 31;
|
|
|
|
if(nesting_level) dbg_log("enter32 stack=" + (this.stack_size_32 ? 32 : 16) + " size=" + size + " nest=" + nesting_level, LOG_CPU);
|
|
this.push32(this.reg32s[reg_ebp]);
|
|
var frame_temp = this.reg32s[reg_esp];
|
|
|
|
if(nesting_level > 0)
|
|
{
|
|
var tmp_ebp = this.reg32s[reg_ebp];
|
|
for(var i = 1; i < nesting_level; i++)
|
|
{
|
|
tmp_ebp -= 4;
|
|
this.push32(this.safe_read32s(this.get_seg(reg_ss) + tmp_ebp | 0));
|
|
}
|
|
this.push32(frame_temp);
|
|
}
|
|
this.reg32s[reg_ebp] = frame_temp;
|
|
this.adjust_stack_reg(-size);
|
|
};
|
|
|
|
CPU.prototype.bswap = function(reg)
|
|
{
|
|
var temp = this.reg32s[reg];
|
|
|
|
this.reg32s[reg] = temp >>> 24 | temp << 24 | (temp >> 8 & 0xFF00) | (temp << 8 & 0xFF0000);
|
|
}
|
|
|
|
CPU.prototype.fxsave = function(addr)
|
|
{
|
|
this.writable_or_pagefault(addr, 512);
|
|
|
|
this.safe_write16(addr + 0 | 0, this.fpu.control_word);
|
|
this.safe_write16(addr + 2 | 0, this.fpu.load_status_word());
|
|
this.safe_write8( addr + 4 | 0, ~this.fpu.stack_empty & 0xFF);
|
|
this.safe_write16(addr + 6 | 0, this.fpu.fpu_opcode);
|
|
this.safe_write32(addr + 8 | 0, this.fpu.fpu_ip);
|
|
this.safe_write16(addr + 12 | 0, this.fpu.fpu_ip_selector);
|
|
this.safe_write32(addr + 16 | 0, this.fpu.fpu_dp);
|
|
this.safe_write16(addr + 20 | 0, this.fpu.fpu_dp_selector);
|
|
|
|
this.safe_write32(addr + 24 | 0, this.mxcsr);
|
|
this.safe_write32(addr + 28 | 0, MXCSR_MASK);
|
|
|
|
for(let i = 0; i < 8; i++)
|
|
{
|
|
this.fpu.store_m80(addr + 32 + (i << 4) | 0, this.fpu.st[this.fpu.stack_ptr + i & 7]);
|
|
}
|
|
|
|
// If the OSFXSR bit in control register CR4 is not set, the FXSAVE
|
|
// instruction may not save these registers. This behavior is
|
|
// implementation dependent.
|
|
for(let i = 0; i < 8; i++)
|
|
{
|
|
this.safe_write32(addr + 160 + (i << 4) + 0 | 0, this.reg_xmm32s[i << 2 | 0]);
|
|
this.safe_write32(addr + 160 + (i << 4) + 4 | 0, this.reg_xmm32s[i << 2 | 1]);
|
|
this.safe_write32(addr + 160 + (i << 4) + 8 | 0, this.reg_xmm32s[i << 2 | 2]);
|
|
this.safe_write32(addr + 160 + (i << 4) + 12 | 0, this.reg_xmm32s[i << 2 | 3]);
|
|
}
|
|
};
|
|
|
|
CPU.prototype.fxrstor = function(addr)
|
|
{
|
|
this.translate_address_read(addr | 0);
|
|
this.translate_address_read(addr + 511 | 0);
|
|
|
|
var new_mxcsr = this.safe_read32s(addr + 24 | 0);
|
|
|
|
if(new_mxcsr & ~MXCSR_MASK)
|
|
{
|
|
dbg_log("Invalid mxcsr bits: " + h((new_mxcsr & ~MXCSR_MASK) >>> 0, 8));
|
|
this.trigger_gp(0);
|
|
}
|
|
|
|
this.fpu.control_word = this.safe_read16(addr + 0 | 0);
|
|
this.fpu.set_status_word(this.safe_read16(addr + 2 | 0));
|
|
this.fpu.stack_empty = ~this.safe_read8(addr + 4 | 0) & 0xFF;
|
|
this.fpu.fpu_opcode = this.safe_read16(addr + 6 | 0);
|
|
this.fpu.fpu_ip = this.safe_read32s(addr + 8 | 0);
|
|
this.fpu.fpu_ip = this.safe_read16(addr + 12 | 0);
|
|
this.fpu.fpu_dp = this.safe_read32s(addr + 16 | 0);
|
|
this.fpu.fpu_dp_selector = this.safe_read16(addr + 20 | 0);
|
|
|
|
this.mxcsr = new_mxcsr;
|
|
|
|
for(let i = 0; i < 8; i++)
|
|
{
|
|
this.fpu.st[this.fpu.stack_ptr + i & 7] = this.fpu.load_m80(addr + 32 + (i << 4) | 0);
|
|
}
|
|
|
|
for(let i = 0; i < 8; i++)
|
|
{
|
|
this.reg_xmm32s[i << 2 | 0] = this.safe_read32s(addr + 160 + (i << 4) + 0 | 0);
|
|
this.reg_xmm32s[i << 2 | 1] = this.safe_read32s(addr + 160 + (i << 4) + 4 | 0);
|
|
this.reg_xmm32s[i << 2 | 2] = this.safe_read32s(addr + 160 + (i << 4) + 8 | 0);
|
|
this.reg_xmm32s[i << 2 | 3] = this.safe_read32s(addr + 160 + (i << 4) + 12 | 0);
|
|
}
|
|
};
|