2385 lines
49 KiB
JavaScript
2385 lines
49 KiB
JavaScript
"use strict";
|
|
|
|
#define read_imm16s() (read_imm16() << 16 >> 16)
|
|
#define read_imm32() (read_imm32s() >>> 0)
|
|
|
|
#define safe_read16s(addr) (safe_read16(addr) << 16 >> 16)
|
|
#define safe_read32(addr) (safe_read32s(addr) >>> 0)
|
|
|
|
var debug = {};
|
|
|
|
|
|
/** @constructor */
|
|
function v86()
|
|
{
|
|
|
|
var cpu = this;
|
|
|
|
this.run = function()
|
|
{
|
|
if(!running)
|
|
{
|
|
cpu_run();
|
|
}
|
|
}
|
|
|
|
this.stop = cpu_stop;
|
|
this.init = cpu_init;
|
|
this.restart = cpu_restart;
|
|
|
|
this.dev = {};
|
|
|
|
this.instr_counter = 0;
|
|
|
|
|
|
var
|
|
segment_is_null,
|
|
segment_offsets,
|
|
segment_limits,
|
|
segment_infos,
|
|
|
|
/*
|
|
* Translation Lookaside Buffer
|
|
*/
|
|
tlb_user_read,
|
|
tlb_user_write,
|
|
tlb_system_read,
|
|
tlb_system_write,
|
|
|
|
/*
|
|
* Information about which pages are cached in the tlb.
|
|
* By bit:
|
|
* 0 system, read
|
|
* 1 system, write
|
|
* 2 user, read
|
|
* 3 user, write
|
|
*/
|
|
tlb_info,
|
|
|
|
/*
|
|
* Same as tlb_info, except it only contains global pages
|
|
*/
|
|
tlb_info_global,
|
|
|
|
/**
|
|
* Wheter or not in protected mode
|
|
* @type {boolean}
|
|
*/
|
|
protected_mode,
|
|
|
|
/**
|
|
* interrupt descriptor table
|
|
* @type {number}
|
|
*/
|
|
idtr_size,
|
|
/** @type {number} */
|
|
idtr_offset,
|
|
|
|
/**
|
|
* global descriptor table register
|
|
* @type {number}
|
|
*/
|
|
gdtr_size,
|
|
/** @type {number} */
|
|
gdtr_offset,
|
|
|
|
/**
|
|
* local desciptor table
|
|
* @type {number}
|
|
*/
|
|
ldtr_size,
|
|
/** @type {number} */
|
|
ldtr_offset,
|
|
|
|
/**
|
|
* task register
|
|
* @type {number}
|
|
*/
|
|
tsr_size,
|
|
/** @type {number} */
|
|
tsr_offset,
|
|
|
|
/*
|
|
* whether or not a page fault occured
|
|
*/
|
|
page_fault,
|
|
|
|
/** @type {number} */
|
|
cr0,
|
|
/** @type {number} */
|
|
cr2,
|
|
/** @type {number} */
|
|
cr3,
|
|
/** @type {number} */
|
|
cr4,
|
|
|
|
// current privilege level
|
|
/** @type {number} */
|
|
cpl,
|
|
|
|
// paging enabled
|
|
/** @type {boolean} */
|
|
paging,
|
|
|
|
// if false, pages are 4 KiB, else 4 Mib
|
|
/** @type {number} */
|
|
page_size_extensions,
|
|
|
|
// current operand/address/stack size
|
|
/** @type {boolean} */
|
|
is_32,
|
|
/** @type {boolean} */
|
|
operand_size_32,
|
|
/** @type {boolean} */
|
|
stack_size_32,
|
|
|
|
/**
|
|
* Cycles since last cpu reset, used by rdtsc instruction
|
|
* @type {number}
|
|
*/
|
|
cpu_timestamp_counter,
|
|
|
|
/** @type {number} */
|
|
previous_ip,
|
|
|
|
/**
|
|
* wheter or not in step mode
|
|
* used for debugging
|
|
* @type {boolean}
|
|
*/
|
|
step_mode,
|
|
|
|
/**
|
|
* was the last instruction a hlt
|
|
* @type {boolean}
|
|
*/
|
|
in_hlt,
|
|
|
|
/** @type {VGAScreen} */
|
|
vga,
|
|
|
|
/** @type {PS2} */
|
|
ps2,
|
|
|
|
/**
|
|
* Programmable interval timer
|
|
* @type {PIT}
|
|
*/
|
|
timer,
|
|
|
|
|
|
/**
|
|
* Real Time Clock
|
|
* @type {RTC}
|
|
*/
|
|
rtc,
|
|
|
|
/**
|
|
* Floppy Disk controller
|
|
* @type {FloppyController}
|
|
*/
|
|
fdc,
|
|
|
|
/**
|
|
* Serial controller
|
|
* @type {UART}
|
|
*/
|
|
uart,
|
|
|
|
/** @type {boolean} */
|
|
running,
|
|
|
|
/** @type {boolean} */
|
|
stopped,
|
|
|
|
/** @type {number} */
|
|
loop_counter,
|
|
|
|
/** @type {Memory} */
|
|
memory,
|
|
|
|
/** @type {(FPU|NoFPU)} */
|
|
fpu,
|
|
|
|
|
|
/**
|
|
* Programmable interrupt controller
|
|
* @type {PIC}
|
|
*/
|
|
pic,
|
|
|
|
/**
|
|
* @type {IO}
|
|
*/
|
|
io,
|
|
|
|
/**
|
|
* @type {PCI}
|
|
*/
|
|
pci,
|
|
|
|
/**
|
|
* @type {CDRom}
|
|
*/
|
|
cdrom,
|
|
|
|
/**
|
|
* @type {HDD}
|
|
*/
|
|
hda,
|
|
|
|
/**
|
|
* @type {HDD}
|
|
*/
|
|
hdb,
|
|
|
|
/**
|
|
* Direct Memory Access Controller
|
|
* @type {DMA}
|
|
*/
|
|
dma,
|
|
|
|
translate_address_read,
|
|
translate_address_write,
|
|
|
|
ops,
|
|
|
|
/** @type {boolean} */
|
|
address_size_32,
|
|
|
|
/** @type {number} */
|
|
instruction_pointer,
|
|
|
|
/** @type {number} */
|
|
last_virt_eip,
|
|
|
|
/** @type {number} */
|
|
eip_phys,
|
|
|
|
/** @type {number} */
|
|
last_virt_esp,
|
|
|
|
/** @type {number} */
|
|
esp_phys,
|
|
|
|
|
|
// current state of prefixes
|
|
segment_prefix,
|
|
|
|
|
|
/** @type {boolean} */
|
|
repeat_string_prefix,
|
|
|
|
/** @type {boolean} */
|
|
repeat_string_type,
|
|
|
|
/** @type {number} */
|
|
last_result,
|
|
|
|
/** @type {number} */
|
|
flags,
|
|
|
|
/**
|
|
* bitmap of flags which are not updated in the flags variable
|
|
* changed by arithmetic instructions, so only relevant to arithmetic flags
|
|
* @type {number}
|
|
*/
|
|
flags_changed,
|
|
|
|
/**
|
|
* the last 2 operators and the result and size of the last arithmetic operation
|
|
* @type {number}
|
|
*/
|
|
last_op1,
|
|
/** @type {number} */
|
|
last_op2,
|
|
/** @type {number} */
|
|
last_op_size,
|
|
|
|
|
|
// registers
|
|
reg32,
|
|
reg32s,
|
|
reg16,
|
|
reg16s,
|
|
reg8,
|
|
reg8s,
|
|
|
|
sreg,
|
|
|
|
|
|
// sp or esp, depending on stack size attribute
|
|
stack_reg,
|
|
reg_vsp,
|
|
reg_vbp,
|
|
|
|
// reg16 or reg32, depending on address size attribute
|
|
regv,
|
|
reg_vcx,
|
|
reg_vsi,
|
|
reg_vdi,
|
|
|
|
// functions that are set depending on whether paging is enabled or not
|
|
read_imm8,
|
|
read_imm8s,
|
|
read_imm16,
|
|
read_imm32s,
|
|
|
|
safe_read8,
|
|
safe_read8s,
|
|
safe_read16,
|
|
safe_read32s,
|
|
|
|
get_esp_read,
|
|
get_esp_write,
|
|
|
|
|
|
table,
|
|
table0F,
|
|
|
|
modrm_resolve,
|
|
|
|
|
|
current_settings
|
|
;
|
|
|
|
|
|
function cpu_run()
|
|
{
|
|
if(stopped)
|
|
{
|
|
stopped = running = false;
|
|
return;
|
|
}
|
|
|
|
running = true;
|
|
|
|
try {
|
|
do_run();
|
|
}
|
|
catch(e)
|
|
{
|
|
if(e === 0xDEADBEE)
|
|
{
|
|
// A legit CPU exception (for instance, a page fault happened)
|
|
// call_interrupt_vector has already been called at this point,
|
|
// so we just need to reset some state
|
|
|
|
page_fault = false;
|
|
repeat_string_prefix = false;
|
|
segment_prefix = -1;
|
|
|
|
address_size_32 = is_32;
|
|
update_address_size();
|
|
operand_size_32 = is_32;
|
|
update_operand_size();
|
|
|
|
cpu_run();
|
|
}
|
|
else
|
|
{
|
|
throw e;
|
|
}
|
|
}
|
|
};
|
|
|
|
function cpu_stop()
|
|
{
|
|
if(running)
|
|
{
|
|
stopped = true;
|
|
}
|
|
}
|
|
|
|
function cpu_restart()
|
|
{
|
|
var was_running = running;
|
|
|
|
stopped = true;
|
|
running = false;
|
|
|
|
setTimeout(function()
|
|
{
|
|
ps2.destroy();
|
|
vga.destroy();
|
|
|
|
cpu_init(current_settings);
|
|
|
|
if(was_running)
|
|
{
|
|
cpu_run();
|
|
}
|
|
}, 10);
|
|
}
|
|
|
|
function cpu_reboot_internal()
|
|
{
|
|
dbg_assert(running);
|
|
|
|
ps2.destroy();
|
|
vga.destroy();
|
|
|
|
cpu_init(current_settings);
|
|
|
|
throw 0xDEADBEE;
|
|
}
|
|
|
|
function cpu_init(settings)
|
|
{
|
|
// see browser/main.js or node/main.js
|
|
if(typeof set_tick !== "undefined")
|
|
{
|
|
set_tick(cpu_run);
|
|
}
|
|
|
|
current_settings = settings;
|
|
|
|
cpu.memory = memory = new Memory(new ArrayBuffer(memory_size), memory_size);
|
|
|
|
segment_is_null = new Uint8Array(8);
|
|
segment_limits = new Uint32Array(8);
|
|
segment_infos = new Uint32Array(8);
|
|
segment_offsets = new Int32Array(8);
|
|
|
|
// 16 MB in total
|
|
tlb_user_read = new Int32Array(1 << 20);
|
|
tlb_user_write = new Int32Array(1 << 20);
|
|
tlb_system_read = new Int32Array(1 << 20);
|
|
tlb_system_write = new Int32Array(1 << 20);
|
|
|
|
tlb_info = new Uint8Array(1 << 20);
|
|
tlb_info_global = new Uint8Array(1 << 20);
|
|
|
|
|
|
reg32 = new Uint32Array(8);
|
|
reg32s = new Int32Array(reg32.buffer);
|
|
reg16 = new Uint16Array(reg32.buffer);
|
|
reg16s = new Int16Array(reg32.buffer);
|
|
reg8 = new Uint8Array(reg32.buffer);
|
|
reg8s = new Int8Array(reg32.buffer);
|
|
sreg = new Uint16Array(8);
|
|
protected_mode = false;
|
|
|
|
idtr_size = 0;
|
|
idtr_offset = 0;
|
|
|
|
gdtr_size = 0;
|
|
gdtr_offset = 0;
|
|
|
|
ldtr_size = 0;
|
|
ldtr_offset = 0;
|
|
|
|
tsr_size = 0;
|
|
tsr_offset = 0;
|
|
|
|
page_fault = false;
|
|
cr0 = 0;
|
|
cr2 = 0;
|
|
cr3 = 0;
|
|
cr4 = 0;
|
|
cpl = 0;
|
|
paging = false;
|
|
page_size_extensions = 0;
|
|
is_32 = false;
|
|
operand_size_32 = false;
|
|
stack_size_32 = false;
|
|
address_size_32 = false;
|
|
|
|
paging_changed();
|
|
|
|
update_operand_size();
|
|
update_address_size();
|
|
|
|
stack_reg = reg16;
|
|
reg_vsp = reg_sp;
|
|
reg_vbp = reg_bp;
|
|
|
|
cpu_timestamp_counter = 0;
|
|
previous_ip = 0;
|
|
step_mode = false;
|
|
in_hlt = false;
|
|
|
|
running = false;
|
|
stopped = false;
|
|
loop_counter = 20;
|
|
|
|
translate_address_read = translate_address_disabled;
|
|
translate_address_write = translate_address_disabled;
|
|
|
|
segment_prefix = -1;
|
|
repeat_string_prefix = false;
|
|
last_result = 0;
|
|
flags = flags_default;
|
|
flags_changed = 0;
|
|
last_op1 = 0;
|
|
last_op2 = 0;
|
|
last_op_size = 0;
|
|
|
|
|
|
|
|
if(settings.bios)
|
|
{
|
|
// load bios
|
|
var data = new Uint8Array(settings.bios),
|
|
start = 0x100000 - settings.bios.byteLength;
|
|
|
|
for(var i = 0; i < settings.bios.byteLength; i++)
|
|
{
|
|
memory.mem8[start + i] = data[i];
|
|
}
|
|
|
|
if(settings.vga_bios)
|
|
{
|
|
// load vga bios
|
|
data = new Uint8Array(settings.vga_bios);
|
|
|
|
for(var i = 0; i < settings.vga_bios.byteLength; i++)
|
|
{
|
|
memory.mem8[0xC0000 + i] = data[i];
|
|
}
|
|
}
|
|
|
|
// ip initial value
|
|
instruction_pointer = 0xFFFF0;
|
|
|
|
// ss and sp inital value
|
|
switch_seg(reg_ss, 0x30);
|
|
reg16[reg_sp] = 0x100;
|
|
}
|
|
else if(settings.linux)
|
|
{
|
|
instruction_pointer = 0x10000;
|
|
|
|
memory.write_blob(new Uint8Array(settings.linux.vmlinux), 0x100000);
|
|
memory.write_blob(new Uint8Array(settings.linux.linuxstart), instruction_pointer);
|
|
|
|
if(settings.linux.root)
|
|
{
|
|
memory.write_blob(new Uint8Array(settings.linux.root), 0x00400000);
|
|
reg32[reg_ebx] = settings.linux.root.byteLength;
|
|
}
|
|
|
|
memory.write_string(settings.linux.cmdline, 0xF800);
|
|
|
|
reg32[reg_eax] = memory_size;
|
|
reg32[reg_ecx] = 0xF800;
|
|
|
|
switch_seg(reg_cs, 0);
|
|
switch_seg(reg_ss, 0);
|
|
switch_seg(reg_ds, 0);
|
|
switch_seg(reg_es, 0);
|
|
switch_seg(reg_gs, 0);
|
|
switch_seg(reg_fs, 0);
|
|
|
|
is_32 = true;
|
|
address_size_32 = true;
|
|
operand_size_32 = true;
|
|
stack_size_32 = true;
|
|
protected_mode = true;
|
|
|
|
update_operand_size();
|
|
update_address_size();
|
|
|
|
regv = reg32;
|
|
reg_vsp = reg_esp;
|
|
reg_vbp = reg_ebp;
|
|
|
|
cr0 = 1;
|
|
}
|
|
else
|
|
{
|
|
switch_seg(reg_ss, 0x30);
|
|
reg16[reg_sp] = 0x100;
|
|
|
|
instruction_pointer = 0;
|
|
}
|
|
|
|
|
|
cpu.dev = {};
|
|
|
|
if(settings.load_devices)
|
|
{
|
|
var devapi = {
|
|
memory: memory,
|
|
reboot: cpu_reboot_internal,
|
|
};
|
|
|
|
devapi.io = cpu.dev.io = io = new IO();
|
|
devapi.pic = pic = new PIC(devapi, call_interrupt_vector, handle_irqs);
|
|
devapi.pci = pci = new PCI(devapi);
|
|
devapi.dma = dma = new DMA(devapi);
|
|
|
|
|
|
cpu.dev.vga = vga = new VGAScreen(devapi, settings.screen_adapter)
|
|
cpu.dev.ps2 = ps2 = new PS2(devapi, settings.keyboard_adapter, settings.mouse_adapter);
|
|
|
|
//fpu = new NoFPU();
|
|
fpu = new FPU(devapi);
|
|
|
|
uart = new UART(devapi);
|
|
|
|
cpu.dev.fdc = fdc = new FloppyController(devapi, settings.floppy_disk);
|
|
|
|
if(settings.cdrom_disk)
|
|
{
|
|
cpu.dev.cdrom = cdrom = new CDRom(devapi, settings.cdrom_disk);
|
|
}
|
|
|
|
if(settings.hda_disk)
|
|
{
|
|
cpu.dev.hda = hda = new HDD(devapi, settings.hda_disk, 0);
|
|
}
|
|
if(settings.hdb_disk)
|
|
{
|
|
cpu.dev.hdb = hdb = new HDD(devapi, settings.hdb_disk, 1);
|
|
}
|
|
|
|
timer = new PIT(devapi);
|
|
rtc = new RTC(devapi, fdc.type);
|
|
}
|
|
|
|
if(DEBUG)
|
|
{
|
|
// used for debugging
|
|
ops = new CircularQueue(30000);
|
|
|
|
if(typeof window !== "undefined")
|
|
{
|
|
window.memory = memory;
|
|
window.vga = vga;
|
|
}
|
|
|
|
if(io)
|
|
{
|
|
// write seabios debug output to console
|
|
var seabios_debug = "";
|
|
|
|
io.register_write(0x402, function(out_byte)
|
|
{
|
|
// seabios debug
|
|
//
|
|
if(out_byte === 10)
|
|
{
|
|
dbg_log(seabios_debug, LOG_BIOS);
|
|
seabios_debug = "";
|
|
}
|
|
else
|
|
{
|
|
seabios_debug += String.fromCharCode(out_byte);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function do_run()
|
|
{
|
|
var
|
|
/**
|
|
* @type {number}
|
|
*/
|
|
now,
|
|
start = Date.now();
|
|
|
|
vga.timer(start);
|
|
|
|
// outer loop:
|
|
// runs cycles + timers
|
|
for(var j = loop_counter; j--;)
|
|
{
|
|
// inner loop:
|
|
// runs only cycles
|
|
for(var k = LOOP_COUNTER; k--;)
|
|
{
|
|
previous_ip = instruction_pointer;
|
|
|
|
cycle();
|
|
cpu_timestamp_counter++;
|
|
}
|
|
|
|
now = Date.now();
|
|
timer.timer(now);
|
|
rtc.timer(now);
|
|
}
|
|
|
|
cpu.instr_counter += loop_counter * LOOP_COUNTER;
|
|
|
|
if(now - start > TIME_PER_FRAME)
|
|
{
|
|
loop_counter--;
|
|
}
|
|
else
|
|
{
|
|
loop_counter++;
|
|
}
|
|
|
|
next_tick();
|
|
}
|
|
|
|
// do_run must not be inlined into cpu_run, because then more code
|
|
// is in the deoptimized try-catch.
|
|
// This trick is a bit ugly, but it works without further complication.
|
|
if(typeof window !== "undefined")
|
|
{
|
|
window.__no_inline = do_run;
|
|
}
|
|
|
|
|
|
/**
|
|
* execute a single instruction cycle on the cpu
|
|
* this includes reading all prefixes and the whole instruction
|
|
*/
|
|
function cycle()
|
|
{
|
|
var opcode = read_imm8();
|
|
|
|
logop(instruction_pointer - 1, opcode);
|
|
|
|
// call the instruction
|
|
table[opcode]();
|
|
|
|
// TODO
|
|
//if(flags & flag_trap)
|
|
//{
|
|
//
|
|
//}
|
|
}
|
|
|
|
cpu.cycle = function()
|
|
{
|
|
table[read_imm8()]();
|
|
}
|
|
|
|
function cr0_changed()
|
|
{
|
|
//protected_mode = (cr0 & 1) === 1;
|
|
//dbg_log("cr0 = " + h(cr0));
|
|
|
|
var new_paging = (cr0 & 0x80000000) !== 0;
|
|
|
|
if(fpu.is_fpu)
|
|
{
|
|
cr0 &= ~4;
|
|
}
|
|
else
|
|
{
|
|
cr0 |= 4;
|
|
}
|
|
|
|
if(new_paging !== paging)
|
|
{
|
|
paging = new_paging;
|
|
|
|
paging_changed();
|
|
}
|
|
}
|
|
|
|
function paging_changed()
|
|
{
|
|
var table = paging ? pe_functions : npe_functions;
|
|
|
|
read_imm8 = table.read_imm8;
|
|
read_imm8s = table.read_imm8s;
|
|
read_imm16 = table.read_imm16;
|
|
read_imm32s = table.read_imm32s;
|
|
|
|
safe_read8 = table.safe_read8;
|
|
safe_read8s = table.safe_read8s;
|
|
safe_read16 = table.safe_read16;
|
|
safe_read32s = table.safe_read32s;
|
|
|
|
get_esp_read = table.get_esp_read;
|
|
get_esp_write = table.get_esp_write;
|
|
|
|
|
|
// set translate_address_* depending on cpl and paging
|
|
cpl_changed();
|
|
}
|
|
|
|
function cpl_changed()
|
|
{
|
|
last_virt_eip = -1;
|
|
last_virt_esp = -1;
|
|
|
|
if(!paging)
|
|
{
|
|
translate_address_write = translate_address_disabled;
|
|
translate_address_read = translate_address_disabled;
|
|
}
|
|
else if(cpl)
|
|
{
|
|
translate_address_write = translate_address_user_write;
|
|
translate_address_read = translate_address_user_read;
|
|
}
|
|
else
|
|
{
|
|
translate_address_write = translate_address_system_write;
|
|
translate_address_read = translate_address_system_read;
|
|
}
|
|
}
|
|
|
|
|
|
// functions that are used when paging is disabled
|
|
var npe_functions = {
|
|
get_esp_read: get_esp_npe,
|
|
get_esp_write: get_esp_npe,
|
|
|
|
read_imm8: function()
|
|
{
|
|
return memory.mem8[instruction_pointer++];
|
|
},
|
|
|
|
read_imm8s: function()
|
|
{
|
|
return memory.mem8s[instruction_pointer++];
|
|
},
|
|
|
|
read_imm16 : function()
|
|
{
|
|
var data16 = memory.read16(instruction_pointer);
|
|
instruction_pointer = instruction_pointer + 2 | 0;
|
|
return data16;
|
|
},
|
|
|
|
read_imm32s : function()
|
|
{
|
|
var data32 = memory.read32s(instruction_pointer);
|
|
instruction_pointer = instruction_pointer + 4 | 0;
|
|
return data32;
|
|
},
|
|
|
|
safe_read8 : function(addr) { return memory.read8(addr) },
|
|
safe_read8s : function(addr) { return memory.read8s(addr); },
|
|
safe_read16 : function(addr) { return memory.read16(addr); },
|
|
safe_read32s : function(addr) { return memory.read32s(addr); },
|
|
};
|
|
|
|
// functions that are used when paging is enabled
|
|
var pe_functions =
|
|
{
|
|
get_esp_read: get_esp_pe_read,
|
|
get_esp_write: get_esp_pe_write,
|
|
|
|
read_imm8 : function()
|
|
{
|
|
if((instruction_pointer & ~0xFFF) ^ last_virt_eip)
|
|
{
|
|
eip_phys = translate_address_read(instruction_pointer) ^ instruction_pointer;
|
|
last_virt_eip = instruction_pointer & ~0xFFF;
|
|
}
|
|
|
|
// memory.read8 inlined under the assumption that code never runs in
|
|
// memory-mapped io
|
|
return memory.mem8[eip_phys ^ instruction_pointer++];
|
|
},
|
|
|
|
read_imm8s : function()
|
|
{
|
|
if((instruction_pointer & ~0xFFF) ^ last_virt_eip)
|
|
{
|
|
eip_phys = translate_address_read(instruction_pointer) ^ instruction_pointer;
|
|
last_virt_eip = instruction_pointer & ~0xFFF;
|
|
}
|
|
|
|
return memory.mem8s[eip_phys ^ instruction_pointer++];
|
|
},
|
|
|
|
read_imm16 : function()
|
|
{
|
|
// Two checks in one comparison:
|
|
// 1. Did the high 20 bits of eip change
|
|
// or 2. Are the low 12 bits of eip 0xFFF (and this read crosses a page boundary)
|
|
if((instruction_pointer ^ last_virt_eip) > 0xFFE)
|
|
{
|
|
return read_imm8() | read_imm8() << 8;
|
|
}
|
|
|
|
var data16 = memory.read16(eip_phys ^ instruction_pointer);
|
|
instruction_pointer = instruction_pointer + 2 | 0;
|
|
|
|
return data16;
|
|
},
|
|
|
|
|
|
read_imm32s : function()
|
|
{
|
|
// Analogue to the above comment
|
|
if((instruction_pointer ^ last_virt_eip) > 0xFFC)
|
|
{
|
|
return read_imm16() | read_imm16() << 16;
|
|
}
|
|
|
|
var data32 = memory.read32s(eip_phys ^ instruction_pointer);
|
|
instruction_pointer = instruction_pointer + 4 | 0;
|
|
|
|
return data32;
|
|
},
|
|
|
|
safe_read8 : do_safe_read8,
|
|
safe_read8s : do_safe_read8s,
|
|
safe_read16 : do_safe_read16,
|
|
safe_read32s : do_safe_read32s,
|
|
};
|
|
|
|
|
|
// read word from a page boundary, given 2 physical addresses
|
|
function virt_boundary_read16(low, high)
|
|
{
|
|
dbg_assert((low & 0xFFF) === 0xFFF);
|
|
dbg_assert((high & 0xFFF) === 0);
|
|
|
|
return memory.read8(low) | memory.read8(high) << 8;
|
|
}
|
|
|
|
// read doubleword from a page boundary, given 2 addresses
|
|
function virt_boundary_read32s(low, high)
|
|
{
|
|
dbg_assert((low & 0xFFF) >= 0xFFD);
|
|
dbg_assert((high - 3 & 0xFFF) === (low & 0xFFF));
|
|
|
|
var result = memory.read8(low) | memory.read8(high) << 24;
|
|
|
|
if(low & 1)
|
|
{
|
|
if(low & 2)
|
|
{
|
|
// 0xFFF
|
|
result |= memory.read8(high - 2) << 8 |
|
|
memory.read8(high - 1) << 16;
|
|
}
|
|
else
|
|
{
|
|
// 0xFFD
|
|
result |= memory.read8(low + 1) << 8 |
|
|
memory.read8(low + 2) << 16;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 0xFFE
|
|
result |= memory.read8(low + 1) << 8 |
|
|
memory.read8(high - 1) << 16;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function virt_boundary_write16(low, high, value)
|
|
{
|
|
dbg_assert((low & 0xFFF) === 0xFFF);
|
|
dbg_assert((high & 0xFFF) === 0);
|
|
|
|
memory.write8(low, value);
|
|
memory.write8(high, value >> 8);
|
|
}
|
|
|
|
function virt_boundary_write32(low, high, value)
|
|
{
|
|
dbg_assert((low & 0xFFF) >= 0xFFD);
|
|
dbg_assert((high - 3 & 0xFFF) === (low & 0xFFF));
|
|
|
|
memory.write8(low, value);
|
|
memory.write8(high, value >> 24);
|
|
|
|
if(low & 1)
|
|
{
|
|
if(low & 2)
|
|
{
|
|
// 0xFFF
|
|
memory.write8(high - 2, value >> 8);
|
|
memory.write8(high - 1, value >> 16);
|
|
}
|
|
else
|
|
{
|
|
// 0xFFD
|
|
memory.write8(low + 1, value >> 8);
|
|
memory.write8(low + 2, value >> 16);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 0xFFE
|
|
memory.write8(low + 1, value >> 8);
|
|
memory.write8(high - 1, value >> 16);
|
|
}
|
|
}
|
|
|
|
// safe_read, safe_write
|
|
// read or write byte, word or dword to the given *virtual* address,
|
|
// and be safe on page boundaries
|
|
|
|
function do_safe_read8(addr)
|
|
{
|
|
return memory.read8(translate_address_read(addr));
|
|
}
|
|
|
|
function do_safe_read8s(addr)
|
|
{
|
|
return memory.read8s(translate_address_read(addr));
|
|
}
|
|
|
|
function do_safe_read16(addr)
|
|
{
|
|
if((addr & 0xFFF) === 0xFFF)
|
|
{
|
|
return safe_read8(addr) | safe_read8(addr + 1) << 8;
|
|
}
|
|
else
|
|
{
|
|
return memory.read16(translate_address_read(addr));
|
|
}
|
|
}
|
|
|
|
function do_safe_read32s(addr)
|
|
{
|
|
if((addr & 0xFFF) >= 0xFFD)
|
|
{
|
|
return safe_read16(addr) | safe_read16(addr + 2) << 16;
|
|
}
|
|
else
|
|
{
|
|
return memory.read32s(translate_address_read(addr));
|
|
}
|
|
}
|
|
|
|
function safe_write8(addr, value)
|
|
{
|
|
memory.write8(translate_address_write(addr), value);
|
|
}
|
|
|
|
function safe_write16(addr, value)
|
|
{
|
|
var phys_low = translate_address_write(addr);
|
|
|
|
if((addr & 0xFFF) === 0xFFF)
|
|
{
|
|
virt_boundary_write16(phys_low, translate_address_write(addr + 1), value);
|
|
}
|
|
else
|
|
{
|
|
memory.write16(phys_low, value);
|
|
}
|
|
}
|
|
|
|
function safe_write32(addr, value)
|
|
{
|
|
var phys_low = translate_address_write(addr);
|
|
|
|
if((addr & 0xFFF) >= 0xFFD)
|
|
{
|
|
virt_boundary_write32(phys_low, translate_address_write(addr + 3), value);
|
|
}
|
|
else
|
|
{
|
|
memory.write32(phys_low, value);
|
|
}
|
|
}
|
|
|
|
// read 2 or 4 byte from ip, depending on address size attribute
|
|
function read_moffs()
|
|
{
|
|
if(address_size_32)
|
|
{
|
|
return get_seg_prefix(reg_ds) + read_imm32s();
|
|
}
|
|
else
|
|
{
|
|
return get_seg_prefix(reg_ds) + read_imm16();
|
|
}
|
|
}
|
|
|
|
function get_flags()
|
|
{
|
|
return (flags & ~flags_all) | getcf() | getpf() | getaf() | getzf() | getsf() | getof();
|
|
}
|
|
|
|
function load_flags()
|
|
{
|
|
flags = get_flags();
|
|
flags_changed = 0;
|
|
}
|
|
|
|
// get esp with paging disabled
|
|
function get_esp_npe(mod)
|
|
{
|
|
if(stack_size_32)
|
|
{
|
|
return get_seg(reg_ss) + stack_reg[reg_vsp] + mod;
|
|
}
|
|
else
|
|
{
|
|
return get_seg(reg_ss) + (stack_reg[reg_vsp] + mod & 0xFFFF);
|
|
}
|
|
}
|
|
|
|
function get_esp_pe_read(mod)
|
|
{
|
|
// UNSAFE: stack_reg[reg_vsp]+mod needs to be masked in 16 bit mode
|
|
// (only if paging is enabled and in 16 bit mode)
|
|
|
|
return translate_address_read(get_seg(reg_ss) + stack_reg[reg_vsp] + mod);
|
|
}
|
|
|
|
function get_esp_pe_write(mod)
|
|
{
|
|
return translate_address_write(get_seg(reg_ss) + stack_reg[reg_vsp] + mod);
|
|
}
|
|
|
|
|
|
/*
|
|
* returns the "real" instruction pointer,
|
|
* without segment offset
|
|
*/
|
|
function get_real_ip()
|
|
{
|
|
return instruction_pointer - get_seg(reg_cs);
|
|
}
|
|
|
|
function call_interrupt_vector(interrupt_nr, is_software_int, error_code)
|
|
{
|
|
if(DEBUG)
|
|
{
|
|
ops.add(instruction_pointer);
|
|
ops.add("-- INT " + h(interrupt_nr));
|
|
ops.add(1);
|
|
}
|
|
|
|
//if(interrupt_nr == 0x13)
|
|
//{
|
|
// dbg_log("INT 13");
|
|
// dbg_log(memory.read8(ch) + "/" + memory.read8(dh) + "/" + memory.read8(cl) + " |" + memory.read8(al));
|
|
// dbg_log("=> ", h(memory.read16(es) * 16 + memory.read16(bx)));
|
|
//}
|
|
|
|
|
|
//if(interrupt_nr == 0x10)
|
|
//{
|
|
// dbg_log("int10 ax=" + h(reg16[reg_ax], 4) + " '" + String.fromCharCode(reg8[reg_al]) + "'");
|
|
// dump_regs_short();
|
|
// if(reg8[reg_ah] == 0xe) vga.tt_write(reg8[reg_al]);
|
|
//}
|
|
|
|
//dbg_log("int " + h(interrupt_nr));
|
|
|
|
//if(interrupt_nr === 0x13)
|
|
//{
|
|
// dump_regs_short();
|
|
//}
|
|
|
|
//if(interrupt_nr === 0x80)
|
|
//{
|
|
// dbg_log("linux syscall");
|
|
// dump_regs_short();
|
|
//}
|
|
|
|
|
|
if(interrupt_nr === 14)
|
|
{
|
|
dbg_log("int14 error_code=" + error_code + " cr2=" + h(cr2) + " prev=" + h(previous_ip) + " cpl=" + cpl, LOG_CPU);
|
|
}
|
|
|
|
|
|
if(in_hlt)
|
|
{
|
|
// return to the instruction following the hlt
|
|
instruction_pointer++;
|
|
in_hlt = false;
|
|
}
|
|
|
|
if(protected_mode)
|
|
{
|
|
if((interrupt_nr << 3 | 7) > idtr_size)
|
|
{
|
|
dbg_log(interrupt_nr, LOG_CPU);
|
|
dbg_trace();
|
|
throw unimpl("#GP handler");
|
|
}
|
|
|
|
|
|
var addr = idtr_offset + (interrupt_nr << 3) | 0;
|
|
dbg_assert((addr & 0xFFF) < 0xFF8);
|
|
|
|
if(paging)
|
|
{
|
|
addr = translate_address_system_read(addr);
|
|
}
|
|
|
|
var base = memory.read16(addr) | memory.read16(addr + 6) << 16,
|
|
selector = memory.read16(addr + 2),
|
|
type = memory.read8(addr + 5),
|
|
dpl = type >> 5 & 3,
|
|
is_trap;
|
|
|
|
if((type & 128) === 0)
|
|
{
|
|
// present bit not set
|
|
throw unimpl("#NP handler");
|
|
}
|
|
|
|
if(is_software_int && dpl < cpl)
|
|
{
|
|
trigger_gp(interrupt_nr << 3 | 2);
|
|
}
|
|
|
|
type &= 31;
|
|
|
|
if(type === 14)
|
|
{
|
|
is_trap = false;
|
|
}
|
|
else if(type === 15)
|
|
{
|
|
is_trap = true;
|
|
}
|
|
else if(type === 5)
|
|
{
|
|
throw unimpl("call int to task gate");
|
|
}
|
|
else if(type === 6)
|
|
{
|
|
throw unimpl("16 bit interrupt gate");
|
|
}
|
|
else if(type === 7)
|
|
{
|
|
throw unimpl("16 bit trap gate");
|
|
}
|
|
else
|
|
{
|
|
// invalid type
|
|
dbg_trace();
|
|
dbg_log("invalid type: " + h(type));
|
|
dbg_log(h(addr) + " " + h(base) + " " + h(selector));
|
|
throw unimpl("#GP handler");
|
|
}
|
|
|
|
var info = lookup_segment_selector(selector);
|
|
|
|
if(info.is_null)
|
|
{
|
|
dbg_log("is null");
|
|
throw unimpl("#GP handler");
|
|
}
|
|
if(info === -1)
|
|
{
|
|
dbg_log("is -1");
|
|
throw unimpl("#GP handler");
|
|
}
|
|
if(!info.is_executable || info.dpl > cpl)
|
|
{
|
|
dbg_log("not exec");
|
|
throw unimpl("#GP handler");
|
|
}
|
|
if(!info.is_present)
|
|
{
|
|
dbg_log("not present");
|
|
throw unimpl("#NP handler");
|
|
}
|
|
|
|
if(flags & flag_vm)
|
|
{
|
|
throw unimpl("VM flag");
|
|
}
|
|
|
|
|
|
if(!info.dc_bit && info.dpl < cpl)
|
|
{
|
|
// inter privilege level interrupt
|
|
|
|
var tss_stack_addr = (info.dpl << 3) + 4;
|
|
|
|
if(tss_stack_addr + 5 > tsr_size)
|
|
{
|
|
throw unimpl("#TS handler");
|
|
}
|
|
|
|
tss_stack_addr += tsr_offset;
|
|
|
|
if(paging)
|
|
{
|
|
tss_stack_addr = translate_address_system_read(tss_stack_addr);
|
|
}
|
|
|
|
var new_esp = memory.read32s(tss_stack_addr),
|
|
new_ss = memory.read16(tss_stack_addr + 4),
|
|
ss_info = lookup_segment_selector(new_ss);
|
|
|
|
if(ss_info.is_null)
|
|
{
|
|
throw unimpl("#TS handler");
|
|
}
|
|
if(ss_info.rpl !== info.dpl)
|
|
{
|
|
throw unimpl("#TS handler");
|
|
}
|
|
if(ss_info.dpl !== info.dpl || !ss_info.rw_bit)
|
|
{
|
|
throw unimpl("#TS handler");
|
|
}
|
|
if(!ss_info.is_present)
|
|
{
|
|
throw unimpl("#TS handler");
|
|
}
|
|
|
|
var old_esp = reg32s[reg_esp],
|
|
old_ss = sreg[reg_ss];
|
|
|
|
reg32[reg_esp] = new_esp;
|
|
sreg[reg_ss] = new_ss;
|
|
|
|
cpl = info.dpl;
|
|
//dbg_log("int" + h(interrupt_nr, 2) +" from=" + h(instruction_pointer, 8)
|
|
// + " cpl=" + cpl + " old ss:esp=" + h(old_ss,4) + ":" + h(old_esp,8), LOG_CPU);
|
|
|
|
cpl_changed();
|
|
|
|
push32(old_ss);
|
|
push32(old_esp);
|
|
}
|
|
else if(info.dc_bit || info.dpl === cpl)
|
|
{
|
|
// intra privilege level interrupt
|
|
|
|
//dbg_log("int" + h(interrupt_nr, 2) +" from=" + h(instruction_pointer, 8), LOG_CPU);
|
|
}
|
|
|
|
|
|
load_flags();
|
|
push32(flags);
|
|
|
|
push32(sreg[reg_cs]);
|
|
push32(get_real_ip());
|
|
//dbg_log("pushed eip to " + h(reg32[reg_esp], 8), LOG_CPU);
|
|
|
|
|
|
if(error_code !== false)
|
|
{
|
|
dbg_assert(typeof error_code == "number");
|
|
push32(error_code);
|
|
}
|
|
|
|
|
|
// TODO
|
|
sreg[reg_cs] = selector;
|
|
//switch_seg(reg_cs);
|
|
|
|
//dbg_log("current esp: " + h(reg32[reg_esp]), LOG_CPU);
|
|
//dbg_log("call int " + h(interrupt_nr) + " from " + h(instruction_pointer) + " to " + h(base) + " with error_code=" + error_code, LOG_CPU);
|
|
|
|
instruction_pointer = get_seg(reg_cs) + base | 0;
|
|
|
|
//dbg_log("int" + h(interrupt_nr) + " trap=" + is_trap + " if=" + +!!(flags & flag_interrupt));
|
|
|
|
if(!is_trap)
|
|
{
|
|
// clear int flag for interrupt gates
|
|
flags &= ~flag_interrupt;
|
|
}
|
|
else
|
|
{
|
|
handle_irqs();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// call 4 byte cs:ip interrupt vector from ivt at memory 0
|
|
|
|
//logop(instruction_pointer, "callu " + h(interrupt_nr) + "." + h(memory.read8(ah)));
|
|
//dbg_log("callu " + h(interrupt_nr) + "." + h(memory.read8(ah)) + " at " + h(instruction_pointer, 8), LOG_CPU, LOG_CPU);
|
|
|
|
// push flags, cs:ip
|
|
load_flags();
|
|
push16(flags);
|
|
push16(sreg[reg_cs]);
|
|
push16(get_real_ip());
|
|
|
|
flags = flags & ~flag_interrupt;
|
|
|
|
switch_seg(reg_cs, memory.read16((interrupt_nr << 2) + 2));
|
|
instruction_pointer = get_seg(reg_cs) + memory.read16(interrupt_nr << 2) | 0;
|
|
}
|
|
}
|
|
|
|
// assumes ip to point to the byte before the next instruction
|
|
function raise_exception(interrupt_nr)
|
|
{
|
|
if(DEBUG)
|
|
{
|
|
// warn about error
|
|
dbg_log("Exception " + h(interrupt_nr), LOG_CPU);
|
|
dbg_trace();
|
|
//throw "exception: " + interrupt_nr;
|
|
}
|
|
|
|
|
|
// TODO
|
|
|
|
call_interrupt_vector(interrupt_nr, false, false);
|
|
throw 0xDEADBEE;
|
|
}
|
|
|
|
function raise_exception_with_code(interrupt_nr, error_code)
|
|
{
|
|
if(DEBUG)
|
|
{
|
|
dbg_log("Exception " + h(interrupt_nr) + " err=" + h(error_code), LOG_CPU);
|
|
dbg_trace();
|
|
//throw "exception: " + interrupt_nr;
|
|
}
|
|
|
|
call_interrupt_vector(interrupt_nr, false, error_code);
|
|
throw 0xDEADBEE;
|
|
}
|
|
|
|
function trigger_de()
|
|
{
|
|
instruction_pointer = previous_ip;
|
|
raise_exception(0);
|
|
}
|
|
|
|
function trigger_ud()
|
|
{
|
|
instruction_pointer = previous_ip;
|
|
raise_exception(6);
|
|
}
|
|
|
|
function trigger_gp(code)
|
|
{
|
|
instruction_pointer = previous_ip;
|
|
raise_exception_with_code(13, code);
|
|
}
|
|
|
|
function trigger_np(code)
|
|
{
|
|
instruction_pointer = previous_ip;
|
|
raise_exception_with_code(11, code);
|
|
}
|
|
|
|
function trigger_ss(code)
|
|
{
|
|
instruction_pointer = previous_ip;
|
|
raise_exception_with_code(12, code);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param {number} seg
|
|
*/
|
|
function seg_prefix(seg)
|
|
{
|
|
dbg_assert(segment_prefix === -1);
|
|
dbg_assert(seg >= 0 && seg <= 5);
|
|
|
|
segment_prefix = seg;
|
|
table[read_imm8()]();
|
|
segment_prefix = -1;
|
|
}
|
|
|
|
/**
|
|
* Get segment base by prefix or default
|
|
* @param {number} default_segment
|
|
*/
|
|
function get_seg_prefix(default_segment /*, offset*/)
|
|
{
|
|
if(segment_prefix === -1)
|
|
{
|
|
return get_seg(default_segment /*, offset*/);
|
|
}
|
|
else
|
|
{
|
|
return get_seg(segment_prefix /*, offset*/);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get segment base
|
|
* @param {number} segment
|
|
*/
|
|
function get_seg(segment /*, offset*/)
|
|
{
|
|
dbg_assert(segment >= 0 && segment < 8);
|
|
dbg_assert(protected_mode || (sreg[segment] << 4) == segment_offsets[segment]);
|
|
|
|
if(protected_mode)
|
|
{
|
|
if(segment_is_null[segment])
|
|
{
|
|
// trying to access null segment
|
|
if(DEBUG)
|
|
{
|
|
dbg_log("Load null segment: " + h(segment), LOG_CPU);
|
|
throw unimpl("#GP handler");
|
|
}
|
|
}
|
|
|
|
// TODO:
|
|
// - validate segment limits
|
|
// - validate if segment is writable
|
|
// - set accessed bit
|
|
}
|
|
|
|
return segment_offsets[segment];
|
|
}
|
|
|
|
function arpl(seg, r16)
|
|
{
|
|
flags_changed &= ~flag_zero;
|
|
|
|
if((seg & 3) < (reg16[r16] & 3))
|
|
{
|
|
flags |= flag_zero;
|
|
return seg & ~3 | reg16[r16] & 3;
|
|
}
|
|
else
|
|
{
|
|
flags &= ~flag_zero;
|
|
return seg;
|
|
}
|
|
}
|
|
|
|
|
|
function handle_irqs()
|
|
{
|
|
if(pic)
|
|
{
|
|
if((flags & flag_interrupt) && !page_fault)
|
|
{
|
|
pic.handle_irqs();
|
|
}
|
|
}
|
|
}
|
|
|
|
// any two consecutive 8-bit ports can be treated as a 16-bit port;
|
|
// and four consecutive 8-bit ports can be treated as a 32-bit port
|
|
//
|
|
// http://css.csail.mit.edu/6.858/2012/readings/i386/s08_01.htm
|
|
|
|
function out8(port_addr, out_byte)
|
|
{
|
|
if(privileges_for_io())
|
|
{
|
|
io.port_write(port_addr, out_byte);
|
|
}
|
|
else
|
|
{
|
|
trigger_gp(0);
|
|
}
|
|
}
|
|
|
|
function out16(port_addr, out_word)
|
|
{
|
|
if(privileges_for_io())
|
|
{
|
|
io.port_write(port_addr, out_word & 0xFF);
|
|
io.port_write(port_addr + 1, out_word >> 8 & 0xFF);
|
|
}
|
|
else
|
|
{
|
|
trigger_gp(0);
|
|
}
|
|
}
|
|
|
|
function out32(port_addr, out_dword)
|
|
{
|
|
if(privileges_for_io())
|
|
{
|
|
io.port_write(port_addr, out_dword & 0xFF);
|
|
io.port_write(port_addr + 1, out_dword >> 8 & 0xFF);
|
|
io.port_write(port_addr + 2, out_dword >> 16 & 0xFF);
|
|
io.port_write(port_addr + 3, out_dword >> 24 & 0xFF);
|
|
}
|
|
else
|
|
{
|
|
trigger_gp(0);
|
|
}
|
|
}
|
|
|
|
function in8(port_addr)
|
|
{
|
|
if(privileges_for_io())
|
|
{
|
|
return io.port_read(port_addr);
|
|
}
|
|
else
|
|
{
|
|
trigger_gp(0);
|
|
}
|
|
}
|
|
|
|
function in16(port_addr)
|
|
{
|
|
if(privileges_for_io())
|
|
{
|
|
return io.port_read(port_addr) |
|
|
io.port_read(port_addr + 1) << 8;
|
|
}
|
|
else
|
|
{
|
|
trigger_gp(0);
|
|
}
|
|
}
|
|
|
|
function in32(port_addr)
|
|
{
|
|
if(privileges_for_io())
|
|
{
|
|
return io.port_read(port_addr) |
|
|
io.port_read(port_addr + 1) << 8 |
|
|
io.port_read(port_addr + 2) << 16 |
|
|
io.port_read(port_addr + 3) << 24;
|
|
}
|
|
else
|
|
{
|
|
trigger_gp(0);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* returns the current iopl from the eflags register
|
|
*/
|
|
function getiopl()
|
|
{
|
|
return flags >> 12 & 3;
|
|
}
|
|
|
|
function privileges_for_io()
|
|
{
|
|
return !protected_mode || cpl <= getiopl();
|
|
}
|
|
|
|
function cpuid()
|
|
{
|
|
// cpuid
|
|
// TODO: Fill in with less bogus values
|
|
|
|
// http://lxr.linux.no/linux+%2a/arch/x86/include/asm/cpufeature.h
|
|
|
|
var id = reg32s[reg_eax];
|
|
|
|
if((id & 0x7FFFFFFF) === 0)
|
|
{
|
|
reg32[reg_eax] = 2;
|
|
|
|
if(id === 0)
|
|
{
|
|
reg32[reg_ebx] = 0x756E6547; // Genu
|
|
reg32[reg_edx] = 0x49656E69; // ineI
|
|
reg32[reg_ecx] = 0x6C65746E; // ntel
|
|
}
|
|
}
|
|
else if(id === 1)
|
|
{
|
|
// pentium
|
|
reg32[reg_eax] = 0x513;
|
|
reg32[reg_ebx] = 0;
|
|
reg32[reg_ecx] = 0;
|
|
reg32[reg_edx] = fpu.is_fpu | 1 << 3 | 1 << 4 | 1 << 8| 1 << 13 | 1 << 15;
|
|
}
|
|
else if(id === 2)
|
|
{
|
|
// Taken from http://siyobik.info.gf/main/reference/instruction/CPUID
|
|
reg32[reg_eax] = 0x665B5001;
|
|
reg32[reg_ebx] = 0;
|
|
reg32[reg_ecx] = 0;
|
|
reg32[reg_edx] = 0x007A7000;
|
|
}
|
|
else if(id === (0x80860000 | 0))
|
|
{
|
|
reg32[reg_eax] = 0;
|
|
reg32[reg_ebx] = 0;
|
|
reg32[reg_ecx] = 0;
|
|
reg32[reg_edx] = 0;
|
|
}
|
|
else if((id & 0xF0000000) === ~~0x40000000)
|
|
{
|
|
// Invalid
|
|
}
|
|
else
|
|
{
|
|
if(DEBUG) throw "cpuid: unimplemented eax: " + h(id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the flags register depending on iopl and cpl
|
|
*/
|
|
function update_flags(new_flags)
|
|
{
|
|
if(cpl === 0 || !protected_mode)
|
|
{
|
|
// can update all flags
|
|
flags = new_flags;
|
|
}
|
|
else if(cpl <= getiopl())
|
|
{
|
|
// cpl != 0 and iopl <= cpl
|
|
// can update interrupt flag but not iopl
|
|
flags = (new_flags & ~flag_iopl) | (flags & flag_iopl);
|
|
}
|
|
else
|
|
{
|
|
// cannot update interrupt flag or iopl
|
|
flags = (new_flags & ~flag_iopl & ~flag_interrupt) | (flags & (flag_iopl | flag_interrupt));
|
|
}
|
|
|
|
flags_changed = 0;
|
|
//flags = (flags & flags_mask) | flags_default;
|
|
}
|
|
|
|
function update_operand_size()
|
|
{
|
|
if(operand_size_32)
|
|
{
|
|
table = table32;
|
|
table0F = table0F_32;
|
|
}
|
|
else
|
|
{
|
|
table = table16;
|
|
table0F = table0F_16;
|
|
}
|
|
}
|
|
|
|
function update_address_size()
|
|
{
|
|
if(address_size_32)
|
|
{
|
|
modrm_resolve = modrm_resolve32;
|
|
|
|
regv = reg32;
|
|
reg_vcx = reg_ecx;
|
|
reg_vsi = reg_esi;
|
|
reg_vdi = reg_edi;
|
|
}
|
|
else
|
|
{
|
|
modrm_resolve = modrm_resolve16;
|
|
|
|
regv = reg16;
|
|
reg_vcx = reg_cx;
|
|
reg_vsi = reg_si;
|
|
reg_vdi = reg_di;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {number} selector
|
|
*/
|
|
function lookup_segment_selector(selector)
|
|
{
|
|
var is_gdt = (selector & 4) === 0,
|
|
selector_offset = selector & ~7,
|
|
info,
|
|
table_offset,
|
|
table_limit;
|
|
|
|
info = {
|
|
rpl: selector & 3,
|
|
from_gdt: is_gdt,
|
|
is_null: false,
|
|
is_valid: true,
|
|
};
|
|
|
|
if(is_gdt)
|
|
{
|
|
table_offset = gdtr_offset;
|
|
table_limit = gdtr_size;
|
|
}
|
|
else
|
|
{
|
|
table_offset = ldtr_offset
|
|
table_limit = ldtr_size;
|
|
}
|
|
|
|
if(selector_offset === 0)
|
|
{
|
|
info.is_null = true;
|
|
return info;
|
|
}
|
|
|
|
// limit is the number of entries in the table minus one
|
|
if((selector_offset >> 3) > table_limit)
|
|
{
|
|
info.is_valid = false;
|
|
return info;
|
|
}
|
|
|
|
table_offset += selector_offset;
|
|
|
|
if(paging)
|
|
{
|
|
table_offset = translate_address_system_read(table_offset);
|
|
}
|
|
|
|
info.base = memory.read16(table_offset + 2) | memory.read8(table_offset + 4) << 16 |
|
|
memory.read8(table_offset + 7) << 24,
|
|
info.access = memory.read8(table_offset + 5),
|
|
info.flags = memory.read8(table_offset + 6) >> 4,
|
|
info.limit = memory.read16(table_offset) | (memory.read8(table_offset + 6) & 0xF) << 16,
|
|
|
|
// used if system
|
|
info.type = info.access & 0xF;
|
|
|
|
info.dpl = info.access >> 5 & 3;
|
|
|
|
info.is_system = (info.access & 0x10) === 0;
|
|
info.is_present = (info.access & 0x80) === 0x80;
|
|
info.is_executable = (info.access & 8) === 8;
|
|
|
|
info.rw_bit = (info.access & 2) === 2;
|
|
info.dc_bit = (info.access & 4) === 4;
|
|
|
|
info.size = (info.flags & 4) === 4;
|
|
info.granularity = (info.flags & 8) === 8;
|
|
|
|
|
|
if(info.gr_bit)
|
|
{
|
|
info.real_limit = (info.limit << 12 | 0xFFF) >>> 0;
|
|
}
|
|
else
|
|
{
|
|
info.real_limit = info.limit;
|
|
}
|
|
|
|
info.is_writable = info.rw_bit && !info.is_executable;
|
|
info.is_readable = info.rw_bit || !info.is_executable;
|
|
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* @param {number} reg
|
|
* @param {number} selector
|
|
*/
|
|
function switch_seg(reg, selector)
|
|
{
|
|
dbg_assert(reg >= 0 && reg <= 5);
|
|
dbg_assert(typeof selector === "number" && selector < 0x10000 && selector >= 0);
|
|
|
|
if(reg === reg_cs)
|
|
{
|
|
protected_mode = (cr0 & 1) === 1;
|
|
}
|
|
|
|
if(!protected_mode)
|
|
{
|
|
sreg[reg] = selector;
|
|
segment_is_null[reg] = 0;
|
|
segment_limits[reg] = 0xFFFFF;
|
|
segment_offsets[reg] = selector << 4;
|
|
return;
|
|
}
|
|
|
|
var info = lookup_segment_selector(selector);
|
|
|
|
if(reg === reg_ss)
|
|
{
|
|
if(info.is_null)
|
|
{
|
|
trigger_gp(0);
|
|
return false;
|
|
}
|
|
if(!info.is_valid ||
|
|
info.is_system ||
|
|
info.rpl !== cpl ||
|
|
!info.is_writable ||
|
|
info.dpl !== cpl)
|
|
{
|
|
trigger_gp(selector & ~3);
|
|
return false;
|
|
}
|
|
if(!info.is_present)
|
|
{
|
|
trigger_ss(selector & ~3);
|
|
return false;
|
|
}
|
|
|
|
stack_size_32 = info.size;
|
|
|
|
if(info.size)
|
|
{
|
|
stack_reg = reg32s;
|
|
reg_vsp = reg_esp;
|
|
reg_vbp = reg_ebp;
|
|
}
|
|
else
|
|
{
|
|
stack_reg = reg16;
|
|
reg_vsp = reg_sp;
|
|
reg_vbp = reg_bp;
|
|
}
|
|
}
|
|
else if(reg === reg_cs)
|
|
{
|
|
if(!info.is_executable)
|
|
{
|
|
// cs not executable
|
|
dbg_log(info + " " + h(selector & ~3), LOG_CPU);
|
|
throw unimpl("#GP handler");
|
|
}
|
|
|
|
if(info.is_system)
|
|
{
|
|
dbg_log(info + " " + h(selector & ~3), LOG_CPU);
|
|
throw unimpl("load system segment descriptor, type = " + (info.access & 15));
|
|
}
|
|
|
|
if(info.dc_bit && (info.dpl !== info.rpl))
|
|
{
|
|
dbg_log(info + " " + h(selector & ~3), LOG_CPU);
|
|
throw unimpl("#GP handler");
|
|
}
|
|
|
|
if(info.rpl !== cpl)
|
|
{
|
|
dbg_log(info + " " + h(selector & ~3), LOG_CPU);
|
|
throw unimpl("privilege change");
|
|
}
|
|
|
|
dbg_assert(cpl === info.dpl);
|
|
|
|
if(!info.dc_bit && info.dpl < cpl)
|
|
{
|
|
throw unimpl("inter privilege interrupt");
|
|
}
|
|
else
|
|
{
|
|
if(info.dc_bit || info.dpl === cpl)
|
|
{
|
|
// ok
|
|
}
|
|
else
|
|
{
|
|
// PE = 1, interrupt or trap gate, nonconforming code segment, DPL > CPL
|
|
dbg_log(info + " " + h(selector & ~3), LOG_CPU);
|
|
throw unimpl("#GP handler");
|
|
}
|
|
}
|
|
|
|
|
|
operand_size_32 = address_size_32 = is_32 = info.size;
|
|
|
|
update_operand_size();
|
|
update_address_size();
|
|
}
|
|
else
|
|
{
|
|
// es, ds, fs, gs
|
|
if(info.is_null)
|
|
{
|
|
sreg[reg] = selector;
|
|
segment_is_null[reg] = 1;
|
|
return true;
|
|
}
|
|
if(!info.is_valid ||
|
|
info.is_system ||
|
|
!info.is_readable ||
|
|
((!info.is_executable || !info.dc_bit) &&
|
|
info.rpl > info.dpl &&
|
|
cpl > info.dpl))
|
|
{
|
|
trigger_gp(selector & ~3);
|
|
return false;
|
|
}
|
|
if(!info.is_present)
|
|
{
|
|
trigger_np(selector & ~3);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//dbg_log("seg " + reg + " " + h(info.base));
|
|
|
|
segment_is_null[reg] = 0;
|
|
segment_limits[reg] = info.real_limit;
|
|
segment_infos[reg] = 0; // TODO
|
|
|
|
segment_offsets[reg] = info.base;
|
|
|
|
sreg[reg] = selector;
|
|
|
|
return true;
|
|
}
|
|
|
|
function load_tr(selector)
|
|
{
|
|
var info = lookup_segment_selector(selector);
|
|
|
|
//dbg_log("load tr");
|
|
|
|
if(!info.from_gdt)
|
|
{
|
|
throw unimpl("TR can only be loaded from GDT");
|
|
}
|
|
|
|
if(info.is_null)
|
|
{
|
|
dbg_log("#GP(0) | tried to load null selector (ltr)");
|
|
throw unimpl("#GP handler");
|
|
}
|
|
|
|
if(!info.is_present)
|
|
{
|
|
dbg_log("#GP | present bit not set (ltr)");
|
|
throw unimpl("#GP handler");
|
|
}
|
|
|
|
if(!info.is_system)
|
|
{
|
|
dbg_log("#GP | ltr: not a system entry");
|
|
throw unimpl("#GP handler");
|
|
}
|
|
|
|
if(info.type !== 9)
|
|
{
|
|
dbg_log("#GP | ltr: invalid type (type = " + info.type + ")");
|
|
throw unimpl("#GP handler");
|
|
}
|
|
|
|
tsr_size = info.limit;
|
|
tsr_offset = info.base;
|
|
|
|
//dbg_log("tsr at " + h(tsr_offset) + "; (" + tsr_size + " bytes)");
|
|
}
|
|
|
|
function load_ldt(selector)
|
|
{
|
|
var info = lookup_segment_selector(selector);
|
|
|
|
if(info.is_null)
|
|
{
|
|
// invalid
|
|
ldtr_size = 0;
|
|
ldtr_offset = 0;
|
|
return;
|
|
}
|
|
|
|
if(!info.from_gdt)
|
|
{
|
|
throw unimpl("LDTR can only be loaded from GDT");
|
|
}
|
|
|
|
if(!info.is_present)
|
|
{
|
|
dbg_log("lldt: present bit not set");
|
|
throw unimpl("#GP handler");
|
|
}
|
|
|
|
if(!info.is_system)
|
|
{
|
|
dbg_log("lldt: not a system entry");
|
|
throw unimpl("#GP handler");
|
|
}
|
|
|
|
if(info.type !== 2)
|
|
{
|
|
dbg_log("lldt: invalid type (" + info.type + ")");
|
|
throw unimpl("#GP handler");
|
|
}
|
|
|
|
ldtr_size = info.limit;
|
|
ldtr_offset = info.base;
|
|
|
|
//dbg_log("ldt at " + h(ldtr_offset) + "; (" + ldtr_size + " bytes)");
|
|
}
|
|
|
|
|
|
|
|
function clear_tlb()
|
|
{
|
|
// clear tlb excluding global pages
|
|
last_virt_eip = -1;
|
|
last_virt_esp = -1;
|
|
|
|
tlb_info.set(tlb_info_global);
|
|
|
|
//dbg_log("page table loaded", LOG_CPU);
|
|
}
|
|
|
|
function full_clear_tlb()
|
|
{
|
|
// clear tlb including global pages
|
|
tlb_info_global = new Uint8Array(1 << 20);
|
|
|
|
clear_tlb();
|
|
|
|
}
|
|
|
|
function invlpg(addr)
|
|
{
|
|
var page = addr >>> 12;
|
|
dbg_log("invlpg: " + h(page), LOG_CPU);
|
|
|
|
tlb_info[page] = 0;
|
|
tlb_info_global[page] = 0;
|
|
|
|
last_virt_eip = -1;
|
|
last_virt_esp = -1;
|
|
}
|
|
|
|
/**
|
|
* @param {number} addr
|
|
*/
|
|
function translate_address_disabled(addr)
|
|
{
|
|
return addr;
|
|
}
|
|
|
|
function translate_address_user_write(addr)
|
|
{
|
|
var base = addr >>> 12;
|
|
|
|
if(tlb_info[base] & TLB_USER_WRITE)
|
|
{
|
|
return tlb_user_write[base] ^ addr;
|
|
}
|
|
else
|
|
{
|
|
return do_page_translation(addr, 1, 1) | addr & 0xFFF;
|
|
}
|
|
}
|
|
|
|
function translate_address_user_read(addr)
|
|
{
|
|
var base = addr >>> 12;
|
|
|
|
if(tlb_info[base] & TLB_USER_READ)
|
|
{
|
|
return tlb_user_read[base] ^ addr;
|
|
}
|
|
else
|
|
{
|
|
return do_page_translation(addr, 0, 1) | addr & 0xFFF;
|
|
}
|
|
}
|
|
|
|
function translate_address_system_write(addr)
|
|
{
|
|
var base = addr >>> 12;
|
|
|
|
if(tlb_info[base] & TLB_SYSTEM_WRITE)
|
|
{
|
|
return tlb_system_write[base] ^ addr;
|
|
}
|
|
else
|
|
{
|
|
return do_page_translation(addr, 1, 0) | addr & 0xFFF;
|
|
}
|
|
}
|
|
|
|
function translate_address_system_read(addr)
|
|
{
|
|
var base = addr >>> 12;
|
|
|
|
if(tlb_info[base] & TLB_SYSTEM_READ)
|
|
{
|
|
return tlb_system_read[base] ^ addr;
|
|
}
|
|
else
|
|
{
|
|
return do_page_translation(addr, 0, 0) | addr & 0xFFF;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return {number}
|
|
*/
|
|
function do_page_translation(addr, for_writing, user)
|
|
{
|
|
var page = addr >>> 12,
|
|
page_dir_addr = (cr3 >>> 2) + (page >> 10),
|
|
page_dir_entry = memory.mem32s[page_dir_addr],
|
|
high,
|
|
can_write = true,
|
|
global,
|
|
cachable = true,
|
|
allow_user = true;
|
|
|
|
if(!(page_dir_entry & 1))
|
|
{
|
|
// to do at this place:
|
|
//
|
|
// - set cr2 = addr (which caused the page fault)
|
|
// - call_interrupt_vector with id 14, error code 0-7 (requires information if read or write)
|
|
// - prevent execution of the function that triggered this call
|
|
dbg_log("#PF not present", LOG_CPU);
|
|
|
|
cr2 = addr;
|
|
trigger_pagefault(for_writing, user, 0);
|
|
|
|
// never reached as trigger_pagefault throws up
|
|
dbg_assert(false);
|
|
}
|
|
|
|
if((page_dir_entry & 2) === 0)
|
|
{
|
|
can_write = false;
|
|
|
|
if(for_writing)
|
|
{
|
|
cr2 = addr;
|
|
trigger_pagefault(for_writing, user, 1);
|
|
dbg_assert(false);
|
|
}
|
|
}
|
|
|
|
if((page_dir_entry & 4) === 0)
|
|
{
|
|
allow_user = false;
|
|
|
|
if(user)
|
|
{
|
|
// "Page Fault: page table accessed by non-supervisor";
|
|
dbg_log("#PF supervisor", LOG_CPU);
|
|
cr2 = addr;
|
|
trigger_pagefault(for_writing, user, 1);
|
|
dbg_assert(false);
|
|
}
|
|
}
|
|
|
|
if((page_dir_entry & 0x10) === 0)
|
|
{
|
|
cachable = false;
|
|
}
|
|
|
|
if(page_dir_entry & page_size_extensions)
|
|
{
|
|
// size bit is set
|
|
|
|
// set the accessed and dirty bits
|
|
memory.mem32s[page_dir_addr] = page_dir_entry | 0x20 | for_writing << 6;
|
|
|
|
high = (page_dir_entry & 0xFFC00000) | (page << 12 & 0x3FF000);
|
|
|
|
global = page_dir_entry & 0x100;
|
|
}
|
|
else
|
|
{
|
|
var page_table_addr = ((page_dir_entry & 0xFFFFF000) >>> 2) + (page & 0x3FF),
|
|
page_table_entry = memory.mem32s[page_table_addr];
|
|
|
|
if(!(page_table_entry & 1))
|
|
{
|
|
dbg_log("#PF not present table", LOG_CPU);
|
|
cr2 = addr;
|
|
trigger_pagefault(for_writing, user, 0);
|
|
dbg_assert(false);
|
|
}
|
|
|
|
if((page_table_entry & 2) === 0)
|
|
{
|
|
can_write = false;
|
|
|
|
if(for_writing)
|
|
{
|
|
dbg_log("#PF not writable page", LOG_CPU);
|
|
cr2 = addr;
|
|
trigger_pagefault(for_writing, user, 1);
|
|
dbg_assert(false);
|
|
}
|
|
}
|
|
|
|
if((page_table_entry & 4) === 0)
|
|
{
|
|
allow_user = false;
|
|
|
|
if(user)
|
|
{
|
|
dbg_log("#PF not supervisor page", LOG_CPU);
|
|
cr2 = addr;
|
|
trigger_pagefault(for_writing, user, 1);
|
|
dbg_assert(false);
|
|
}
|
|
}
|
|
|
|
if((page_table_entry & 0x10) === 0)
|
|
{
|
|
cachable = false;
|
|
}
|
|
|
|
// set the accessed and dirty bits
|
|
memory.mem32s[page_dir_addr] = page_dir_entry | 0x20;
|
|
memory.mem32s[page_table_addr] = page_table_entry | 0x20 | for_writing << 6;
|
|
|
|
high = page_table_entry & 0xFFFFF000;
|
|
|
|
global = page_table_entry & 0x100;
|
|
}
|
|
|
|
if(cachable)
|
|
{
|
|
var cache_entry = high ^ page << 12,
|
|
info = 0;
|
|
|
|
if(allow_user)
|
|
{
|
|
tlb_user_read[page] = cache_entry;
|
|
info |= TLB_USER_READ;
|
|
|
|
if(can_write)
|
|
{
|
|
tlb_user_write[page] = cache_entry;
|
|
info |= TLB_USER_WRITE;
|
|
}
|
|
}
|
|
|
|
tlb_system_read[page] = cache_entry;
|
|
info |= TLB_SYSTEM_READ;
|
|
|
|
if(can_write)
|
|
{
|
|
tlb_system_write[page] = cache_entry;
|
|
info |= TLB_SYSTEM_WRITE;
|
|
}
|
|
|
|
tlb_info[page] |= info;
|
|
|
|
if(global)
|
|
{
|
|
tlb_info_global[page] = info;
|
|
}
|
|
}
|
|
|
|
return high ;
|
|
}
|
|
|
|
function trigger_pagefault(write, user, present)
|
|
{
|
|
if(LOG_LEVEL & LOG_CPU)
|
|
{
|
|
dbg_trace();
|
|
}
|
|
|
|
if(page_fault)
|
|
{
|
|
dbg_trace();
|
|
throw unimpl("Double fault");
|
|
}
|
|
|
|
instruction_pointer = previous_ip;
|
|
page_fault = true;
|
|
call_interrupt_vector(14, false, user << 2 | write << 1 | present);
|
|
|
|
throw 0xDEADBEE;
|
|
}
|
|
|
|
|
|
// it looks pointless to have these two here, but
|
|
// Closure Compiler is able to remove unused functions
|
|
//#include "test_helpers.js"
|
|
#include "debug.macro.js"
|
|
|
|
|
|
#include "modrm.macro.js"
|
|
#include "arith.macro.js"
|
|
#include "misc_instr.macro.js"
|
|
#include "string.macro.js"
|
|
|
|
#include "fpu.macro.js"
|
|
|
|
#include "instructions.macro.js"
|
|
|
|
|
|
}
|