blob: d634a89b35246dd95f5a557d99b16e2b6818c22f [file] [log] [blame] [raw]
"use strict";
#define vm86_mode (!!(flags & flag_vm))
#define read_imm16s() (read_imm16() << 16 >> 16)
#define read_imm32() (read_imm32s() >>> 0)
#define safe_read8s(addr) (safe_read8(addr) << 24 >> 24)
#define safe_read16s(addr) (safe_read16(addr) << 16 >> 16)
#define safe_read32(addr) (safe_read32s(addr) >>> 0)
#define getiopl() (flags >> 12 & 3)
var debug = {};
/** @constructor */
function v86(envapi)
{
var cpu = this;
this.run = function()
{
if(!running)
{
setTimeout(cpu_run, 0);
}
}
this.stop = cpu_stop;
this.init = cpu_init;
this.restart = cpu_restart;
this.dev = {};
this.instr_counter = 0;
var next_tick;
if(envapi.next_tick !== undefined)
{
next_tick = envapi.next_tick;
}
else
{
next_tick = function()
{
setTimeout(cpu_run, 0);
};
}
var
/** @type {number } */
memory_size = 0,
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} */
fpu,
/**
* @type {HPET}
*/
hpet,
/**
* Programmable interrupt controller
* @type {PIC}
*/
pic,
/**
* @type {IO}
*/
io,
/**
* @type {PCI}
*/
pci,
/**
* @type {IDEDevice}
*/
cdrom,
/**
* @type {IDEDevice}
*/
hda,
/**
* @type {IDEDevice}
*/
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} */
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,
/** @type {number} */
last_add_result,
/** @type {number} */
last_result,
// registers
reg32,
reg32s,
reg16,
reg16s,
reg8,
reg8s,
sreg,
dreg,
// 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_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)
{
exception_cleanup(e);
}
}
function exception_cleanup(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.instr_counter = cpu_timestamp_counter;
next_tick();
}
else
{
console.log(e);
console.log(e.stack);
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 envapi.set_tick !== "undefined")
{
envapi.set_tick(cpu_run);
}
current_settings = settings;
memory_size = settings.memory_size || 1024 * 1024 * 64;
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);
dreg = new Int32Array(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 = 1 << 30 | 1 << 29 | 1 << 4;
cr2 = 0;
cr3 = 0;
cr4 = 0;
dreg[6] = 0xFFFF0FF0;
dreg[7] = 0x400;
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;
last_add_result = 0;
flags = flags_default;
flags_changed = 0;
last_op1 = 0;
last_op2 = 0;
last_op_size = 0;
var devapi = {
memory: memory,
reboot: cpu_reboot_internal,
time: function() { return performance.now(); },
};
cpu.dev = {
};
devapi.io = cpu.dev.io = io = new IO(memory);
if(settings.bios)
{
// load bios
var data = new Uint8Array(settings.bios),
start = 0x100000 - settings.bios.byteLength;
memory.mem8.set(data, start);
if(settings.vga_bios)
{
// load vga bios
data = new Uint8Array(settings.vga_bios);
memory.mem8.set(data, 0xC0000);
}
// seabios expects the bios to be mapped to 0xFFF00000 also
io.mmap_register(0xFFF00000, 0x100000, 1,
function(addr)
{
return memory.mem8[addr];
//return data[start + addr];
},
function(addr, value)
{
memory.mem8[addr] = value;
//data[start + addr] = value;
});
// 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;
}
var a20_byte = 0;
io.register_read(0x92, function()
{
return a20_byte;
});
io.register_write(0x92, function(out_byte)
{
a20_byte = out_byte;
});
if(DEBUG)
{
// Use by linux for port-IO delay
// Avoid generating tons of debug messages
io.register_write(0x80, function(out_byte)
{
});
}
// TODO: Make this more configurable
if(settings.load_devices)
{
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, settings.vga_memory_size || 8 * 1024 * 1024);
cpu.dev.ps2 = ps2 = new PS2(devapi, settings.keyboard_adapter, settings.mouse_adapter);
fpu = new FPU(devapi);
if(settings.serial_adapter)
{
uart = new UART(devapi, 0x3F8, settings.serial_adapter);
}
else
{
uart = new UART(devapi, 0x3F8, {
put_line: envapi.log,
init: function(fn) { },
});
}
cpu.dev.fdc = fdc = new FloppyController(devapi, settings.fda, settings.fdb);
if(settings.cdrom)
{
cpu.dev.cdrom = cdrom = new IDEDevice(devapi, settings.cdrom, true, 1);
}
if(settings.hda)
{
cpu.dev.hda = hda = new IDEDevice(devapi, settings.hda, false, 0);
}
else
{
//cpu.dev.hda = hda = new IDEDevice(devapi, undefined, false, 0);
}
//if(settings.hdb)
//{
// cpu.dev.hdb = hdb = new IDEDevice(devapi, settings.hdb, false, 1);
//}
devapi.pit = timer = new PIT(devapi);
devapi.rtc = rtc = new RTC(devapi, fdc.type, settings.boot_order || 0x213);
if(ENABLE_ACPI)
{
if(ENABLE_HPET)
{
hpet = new HPET(devapi);
}
var acpi = {
pci_id: 0x07 << 3,
pci_space: [
0x86, 0x80, 0x13, 0x71, 0x07, 0x00, 0x80, 0x02, 0x08, 0x00, 0x80, 0x06, 0x00, 0x00, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00, 0x00,
],
pci_bars: [],
};
// 00:07.0 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 08)
pci.register_device(acpi);
var elcr = 0;
// ACPI, ELCR register
io.register_write(0x4d0, function(data)
{
elcr = elcr & 0xFF00 | data;
});
io.register_write(0x4d1, function(data)
{
elcr = elcr & 0xFF | data << 8;
});
io.register_read(0xb3, function()
{
return 0;
});
// ACPI, pmtimer
io.register_read(0xb008, function()
{
return 0;
});
io.register_read(0xb009, function()
{
return 0;
});
io.register_read(0xb00a, function()
{
return 0;
});
io.register_read(0xb00b, function()
{
return 0;
});
// ACPI status
io.register_read(0xb004, function(data)
{
dbg_log("b004 read");
return 1;
});
io.register_read(0xb005, function(data)
{
dbg_log("b005 read");
return 0;
});
io.mmap_register(0xFEE00000, 0x100000, 1,
function(addr)
{
dbg_log("APIC read " + h(addr), LOG_CPU);
return 0;
},
function(addr, value)
{
dbg_log("APIC write " + h(addr), LOG_CPU);
});
}
}
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}
*/
start = Date.now(),
now;
vga.timer(start);
// outer loop:
// runs cycles + timers
for(var j = loop_counter; j--;)
{
now = Date.now();
previous_ip = instruction_pointer;
if(ENABLE_HPET)
{
timer.timer(now, hpet.legacy_mode);
rtc.timer(now, hpet.legacy_mode);
hpet.timer(now);
}
else
{
timer.timer(now, false);
rtc.timer(now, false);
}
// inner loop:
// runs only cycles
for(var k = LOOP_COUNTER; k--;)
{
previous_ip = instruction_pointer;
cpu_timestamp_counter++;
cycle();
}
}
cpu.instr_counter = cpu_timestamp_counter;
if(now - start > TIME_PER_FRAME)
{
if(loop_counter > 1)
{
loop_counter--;
}
}
else
{
loop_counter++;
}
previous_ip = instruction_pointer;
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_inline1 = do_run;
window.__no_inline2 = exception_cleanup;
}
/**
* 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 >>> 0, 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 & (1 << 31)) !== 0;
if(fpu === undefined)
{
// if there's no FPU, keep emulation set
cr0 |= 4;
}
cr0 |= 0x10;
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_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.read8(instruction_pointer++);
},
read_imm8s: function()
{
return memory.read8(instruction_pointer++) << 24 >> 24;
},
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_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;
}
var data8 = memory.mem8[eip_phys ^ instruction_pointer];
instruction_pointer = instruction_pointer + 1 | 0;
// memory.read8 inlined under the assumption that code never runs in
// memory-mapped io
return data8;
},
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;
}
var data8 = memory.mem8s[eip_phys ^ instruction_pointer];
instruction_pointer = instruction_pointer + 1 | 0;
return data8;
},
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_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 mid;
if(low & 1)
{
if(low & 2)
{
// 0xFFF
mid = memory.read_aligned16(high - 2 >> 1);
}
else
{
// 0xFFD
mid = memory.read_aligned16(low + 1 >> 1);
}
}
else
{
// 0xFFE
mid = virt_boundary_read16(low + 1, high - 1);
}
return memory.read8(low) | mid << 8 | memory.read8(high) << 24;;
}
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)
{
dbg_assert(addr < 0x80000000);
return memory.read8(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)
{
dbg_assert(addr < 0x80000000);
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() | 0;
}
else
{
return get_seg_prefix(reg_ds) + read_imm16() | 0;
}
}
function get_flags()
{
return (flags & ~flags_all) | !!getcf() | !!getpf() << 2 | !!getaf() << 4 | !!getzf() << 6 | !!getsf() << 7 | !!getof() << 11;
}
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 | 0;
}
else
{
return get_seg(reg_ss) + (stack_reg[reg_vsp] + mod & 0xFFFF) | 0;
}
}
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 get_seg(reg_ss) + stack_reg[reg_vsp] + mod | 0;
}
function get_esp_pe_write(mod)
{
return get_seg(reg_ss) + stack_reg[reg_vsp] + mod | 0;
}
/*
* returns the "real" instruction pointer,
* without segment offset
*/
function get_real_ip()
{
return instruction_pointer - get_seg(reg_cs) | 0;
}
function call_interrupt_vector(interrupt_nr, is_software_int, error_code)
{
if(DEBUG)
{
ops.add(instruction_pointer >>> 0);
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]);
//}
//if(interrupt_nr === 0x13)
//{
// dump_regs_short();
//}
//if(interrupt_nr === 6)
//{
// instruction_pointer += 2;
// dbg_log("BUG()", LOG_CPU);
// dbg_log("line=" + read_imm16() + " " +
// "file=" + memory.read_string(translate_address_read(read_imm32s())), LOG_CPU);
// instruction_pointer -= 8;
// 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 >>> 0) +
// " prev=" + h(previous_ip >>> 0) +
// " cpl=" + cpl, LOG_CPU);
//}
if(in_hlt)
{
// return to the instruction following the hlt
instruction_pointer = instruction_pointer + 1 | 0;
in_hlt = false;
}
if(protected_mode)
{
if(vm86_mode && (cr4 & 1))
{
throw unimpl("VME");
}
if(vm86_mode && is_software_int && getiopl() < 3)
{
trigger_gp(0);
}
if((interrupt_nr << 3 | 7) > idtr_size)
{
dbg_log(interrupt_nr, LOG_CPU);
dbg_trace(LOG_CPU);
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(LOG_CPU);
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.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");
}
load_flags();
var old_flags = flags;
if(!info.dc_bit && info.dpl < cpl)
{
// inter privilege level interrupt
// interrupt from vm86 mode
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];
if(old_flags & flag_vm)
{
dbg_log("return from vm86 mode");
dump_regs_short();
}
cpl = info.dpl;
//dbg_log("int" + h(interrupt_nr, 2) +" from=" + h(instruction_pointer >>> 0, 8)
// + " cpl=" + cpl + " old ss:esp=" + h(old_ss, 4) + ":" + h(old_esp >>> 0, 8), LOG_CPU);
cpl_changed();
is_32 = operand_size_32 = address_size_32 = info.size;
flags &= ~flag_vm & ~flag_rf;
update_operand_size();
update_address_size();
reg32[reg_esp] = new_esp;
switch_seg(reg_ss, new_ss);
if(old_flags & flag_vm)
{
push32(sreg[reg_gs]);
push32(sreg[reg_fs]);
push32(sreg[reg_ds]);
push32(sreg[reg_es]);
}
push32(old_ss);
push32(old_esp);
}
else if(info.dc_bit || info.dpl === cpl)
{
if(flags & flag_vm)
{
trigger_gp(selector & ~3);
}
// intra privilege level interrupt
//dbg_log("int" + h(interrupt_nr, 2) +" from=" + h(instruction_pointer, 8), LOG_CPU);
}
else
{
dbg_assert(false);
}
push32(old_flags);
push32(sreg[reg_cs]);
push32(get_real_ip());
//dbg_log("pushed eip to " + h(reg32[reg_esp], 8), LOG_CPU);
if(old_flags & flag_vm)
{
switch_seg(reg_gs, 0);
switch_seg(reg_fs, 0);
switch_seg(reg_ds, 0);
switch_seg(reg_es, 0);
}
if(error_code !== false)
{
dbg_assert(typeof error_code == "number");
push32(error_code);
}
// TODO
sreg[reg_cs] = selector;
//switch_seg(reg_cs);
is_32 = operand_size_32 = address_size_32 = info.size;
update_operand_size();
update_address_size();
segment_limits[reg_cs] = info.real_limit;
segment_offsets[reg_cs] = info.base;
//dbg_log("current esp: " + h(reg32[reg_esp]), LOG_CPU);
//dbg_log("call int " + h(interrupt_nr >>> 0, 8) +
// " from " + h(instruction_pointer >>> 0, 8) +
// " to " + h(base >>> 0) +
// " if=" + +!!(is_trap && flags & flag_interrupt) +
// " 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(LOG_CPU);
//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(LOG_CPU);
//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_nm()
{
instruction_pointer = previous_ip;
raise_exception(7);
}
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)
{
dbg_assert(!page_fault);
if((flags & flag_interrupt) && !page_fault)
{
pic.handle_irqs();
}
}
}
function test_privileges_for_io(port, size)
{
if(protected_mode && (cpl > getiopl() || (flags & flag_vm)))
{
if(tsr_size >= 0x67)
{
var iomap_base = memory.read16(translate_address_system_read(tsr_offset + 0x64 + 2)),
high_port = port + size - 1;
if(tsr_size >= iomap_base + (high_port >> 3))
{
var mask = ((1 << size) - 1) << (port & 7),
addr = translate_address_system_read(tsr_offset + iomap_base + (port >> 3)),
port_info = (mask & 0xFF00) ?
memory.read16(addr) : memory.read8(addr);
if(!(port_info & mask))
{
return;
}
}
}
dbg_log("#GP for port io port=" + h(port) + " size=" + size, LOG_CPU);
trigger_gp(0);
}
}
function cpuid()
{
// cpuid
// TODO: Fill in with less bogus values
// http://lxr.linux.no/linux+%2a/arch/x86/include/asm/cpufeature.h
// http://www.sandpile.org/x86/cpuid.htm
var id = reg32s[reg_eax],
extended = reg32s[reg_eax] >>> 31;
if((id & 0x7FFFFFFF) === 0)
{
reg32[reg_eax] = 2;
if(id === 0)
{
reg32[reg_ebx] = 0x756E6547|0; // Genu
reg32[reg_edx] = 0x49656E69|0; // ineI
reg32[reg_ecx] = 0x6C65746E|0; // ntel
}
}
else if(id === 1)
{
// pentium
reg32[reg_eax] = 3 | 6 << 4 | 15 << 8;
reg32[reg_ebx] = 0;
reg32[reg_ecx] = 0;
reg32[reg_edx] = fpu !== undefined | 1 << 1 | 1 << 3 | 1 << 4 | 1 << 5 |
1 << 8 | 1 << 13 | 1 << 15;
}
else if(id === 2)
{
// Taken from http://siyobik.info.gf/main/reference/instruction/CPUID
reg32[reg_eax] = 0x665B5001|0;
reg32[reg_ebx] = 0;
reg32[reg_ecx] = 0;
reg32[reg_edx] = 0x007A7000;
}
else if(id === (0x80860000 | 0))
{
// Transmeta level
reg32[reg_eax] = 0;
reg32[reg_ebx] = 0;
reg32[reg_ecx] = 0;
reg32[reg_edx] = 0;
}
else if((id & (0xC0000000|0)) === 0x40000000)
{
// Not sure
reg32[reg_eax] = 0;
reg32[reg_ebx] = 0;
reg32[reg_ecx] = 0;
reg32[reg_edx] = 0;
}
else
{
if(DEBUG) throw "cpuid: unimplemented eax: " + h(id);
}
}
/**
* Update the flags register depending on iopl and cpl
*/
function update_flags(new_flags)
{
var mask = flag_rf | flag_vm | flag_vip | flag_vif,
clear = ~flag_vip & ~flag_vif & flags_mask;
if(flags & flag_vm)
{
// other case needs to be handled in popf or iret
dbg_assert(getiopl() === 3);
mask |= flag_iopl;
// vip and vif are preserved
clear |= flag_vip | flag_vif;
}
else
{
if(!protected_mode) dbg_assert(cpl === 0);
if(cpl)
{
// cpl > 0
// cannot update iopl
mask |= flag_iopl;
if(cpl > getiopl())
{
// cpl > iopl
// can update interrupt flag but not iopl
mask |= flag_interrupt;
}
}
}
flags = (new_flags ^ ((flags ^ new_flags) & mask)) & clear | flags_default;
flags_changed = 0;
}
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,
base: 0,
access: 0,
flags: 0,
limit: 0,
type: 0,
dpl: 0,
is_system: false,
is_present: false,
is_executable: false,
rw_bit: false,
dc_bit: false,
size: false,
granularity: false,
real_limit: false,
is_writable: false,
is_readable: false,
};
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 || vm86_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 call");
}
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;
dbg_assert(addr < 0x80000000);
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)
{
//dbg_log("page fault w=" + write + " u=" + user + " p=" + present +
// " eip=" + h(previous_ip >>> 0, 8) +
// " cr2=" + h(cr2 >>> 0, 8), LOG_CPU);
//dbg_trace(LOG_CPU);
// likely invalid pointer reference
//if((cr2 >>> 0) < 0x100)
//{
// throw "stop";
//}
if(page_fault)
{
dbg_trace(LOG_CPU);
throw unimpl("Double fault");
}
// invalidate tlb entry
var page = cr2 >>> 12;
tlb_info[page] = 0;
tlb_info_global[page] = 0;
instruction_pointer = previous_ip;
page_fault = true;
call_interrupt_vector(14, false, user << 2 | write << 1 | present);
throw 0xDEADBEE;
}
// it looks pointless to have this here, but
// Closure Compiler is able to remove unused functions
#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"
}