Port far_jump to Rust

This commit is contained in:
Fabian 2020-12-31 19:14:31 -06:00
parent 3711ed7f23
commit d735d5d34e
4 changed files with 346 additions and 379 deletions

View file

@ -117,7 +117,6 @@ function V86Starter(options)
dbg_trace();
},
"far_jump": function(eip, selector, is_call, is_osize_32) { return cpu.far_jump(eip, selector, !!is_call, !!is_osize_32); },
"far_return": function(eip, selector, stack_adjust, is_osize_32) { return cpu.far_return(eip, selector, stack_adjust, !!is_osize_32); },
"pic_acknowledge": function() { cpu.pic_acknowledge(); },

View file

@ -1720,382 +1720,6 @@ CPU.prototype.far_return = function(eip, selector, stack_adjust, is_osize_32)
CPU_LOG_VERBOSE && this.debug.dump_state("far ret end");
};
CPU.prototype.far_jump = function(eip, selector, is_call, is_osize_32)
{
is_call = !!is_call;
dbg_assert(typeof selector === "number" && selector < 0x10000 && selector >= 0);
//dbg_log("far " + ["jump", "call"][+is_call] + " eip=" + h(eip >>> 0, 8) + " cs=" + h(selector, 4), LOG_CPU);
CPU_LOG_VERBOSE && this.debug.dump_state("far " + ["jump", "call"][+is_call]);
if(!this.protected_mode[0] || this.vm86_mode())
{
if(is_call)
{
if(is_osize_32)
{
if(!this.writable_or_pagefault(this.get_stack_pointer(-8), 8))
{
return;
}
this.push32(this.sreg[reg_cs]);
this.push32(this.get_real_eip());
}
else
{
if(!this.writable_or_pagefault(this.get_stack_pointer(-4), 4))
{
return;
}
this.push16(this.sreg[reg_cs]);
this.push16(this.get_real_eip());
}
}
this.switch_cs_real_mode(selector);
this.instruction_pointer[0] = this.get_seg_cs() + eip | 0;
return;
}
var info = this.lookup_segment_selector(selector);
if(info.is_null)
{
dbg_log("#gp null cs", LOG_CPU);
this.trigger_gp(0);
return;
}
if(!info.is_valid)
{
dbg_log("#gp invalid cs: " + h(selector), LOG_CPU);
this.trigger_gp(selector & ~3);
return;
}
if(info.is_system)
{
dbg_assert(is_call, "TODO: Jump");
dbg_log("system type cs: " + h(selector), LOG_CPU);
if(info.type === 0xC || info.type === 4)
{
// call gate
var is_16 = info.type === 4;
if(info.dpl < this.cpl[0] || info.dpl < info.rpl)
{
dbg_log("#gp cs gate dpl < cpl or dpl < rpl: " + h(selector), LOG_CPU);
this.trigger_gp(selector & ~3);
return;
}
if(!info.is_present)
{
dbg_log("#NP for loading not-present in gate cs sel=" + h(selector, 4), LOG_CPU);
this.trigger_np(selector & ~3);
return;
}
var cs_selector = info.raw0 >>> 16;
var cs_info = this.lookup_segment_selector(cs_selector);
if(cs_info.is_null)
{
dbg_log("#gp null cs", LOG_CPU);
this.trigger_gp(0);
return;
}
if(!cs_info.is_valid)
{
dbg_log("#gp invalid cs: " + h(cs_selector), LOG_CPU);
this.trigger_gp(cs_selector & ~3);
return;
}
if(!cs_info.is_executable)
{
dbg_log("#gp non-executable cs: " + h(cs_selector), LOG_CPU);
this.trigger_gp(cs_selector & ~3);
return;
}
if(cs_info.dpl > this.cpl[0])
{
dbg_log("#gp dpl > cpl: " + h(cs_selector), LOG_CPU);
this.trigger_gp(cs_selector & ~3);
return;
}
if(!cs_info.is_present)
{
dbg_log("#NP for loading not-present in cs sel=" + h(cs_selector, 4), LOG_CPU);
this.trigger_np(cs_selector & ~3);
return;
}
if(!cs_info.dc_bit && cs_info.dpl < this.cpl[0])
{
dbg_log("more privilege call gate is_16=" + is_16 + " from=" + this.cpl[0] + " to=" + cs_info.dpl);
var tss_stack_addr = this.get_tss_stack_addr(cs_info.dpl);
if(this.tss_size_32[0])
{
var new_esp = this.read32s(tss_stack_addr);
var new_ss = this.read16(tss_stack_addr + 4 | 0);
}
else
{
var new_esp = this.read16(tss_stack_addr);
var new_ss = this.read16(tss_stack_addr + 2 | 0);
}
var ss_info = this.lookup_segment_selector(new_ss);
// Disabled: Incorrect handling of direction bit
// See http://css.csail.mit.edu/6.858/2014/readings/i386/s06_03.htm
//if(!((new_esp >>> 0) <= ss_info.effective_limit))
// debugger;
//dbg_assert((new_esp >>> 0) <= ss_info.effective_limit);
dbg_assert(ss_info.is_valid && !ss_info.is_system && ss_info.is_writable);
if(ss_info.is_null)
{
throw this.debug.unimpl("#TS handler");
}
if(ss_info.rpl !== cs_info.dpl) // xxx: 0 in v86 mode
{
throw this.debug.unimpl("#TS handler");
}
if(ss_info.dpl !== cs_info.dpl || !ss_info.rw_bit)
{
throw this.debug.unimpl("#TS handler");
}
if(!ss_info.is_present)
{
throw this.debug.unimpl("#SS handler");
}
var parameter_count = info.raw1 & 0x1F;
var stack_space = is_16 ? 4 : 8;
if(is_call)
{
stack_space += is_16 ? 4 + 2 * parameter_count : 8 + 4 * parameter_count;
}
if(ss_info.size)
{
if(!this.writable_or_pagefault(ss_info.base + new_esp - stack_space | 0, stack_space)) // , cs_info.dpl
{
return;
}
}
else
{
if(!this.writable_or_pagefault(ss_info.base + (new_esp - stack_space & 0xFFFF) | 0, stack_space)) // , cs_info.dpl
{
return;
}
}
var old_esp = this.reg32[reg_esp];
var old_ss = this.sreg[reg_ss];
var old_stack_pointer = this.get_stack_pointer(0);
//dbg_log("old_esp=" + h(old_esp));
this.cpl[0] = cs_info.dpl;
this.cpl_changed();
this.update_cs_size(cs_info.size);
// XXX: Should be checked before side effects
if(!this.switch_seg(reg_ss, new_ss)) dbg_assert(false);
this.set_stack_reg(new_esp);
//dbg_log("parameter_count=" + parameter_count);
//dbg_assert(parameter_count === 0, "TODO");
if(is_16)
{
this.push16(old_ss);
this.push16(old_esp);
//dbg_log("old esp written to " + h(this.translate_address_system_read(this.get_stack_pointer(0))));
}
else
{
this.push32(old_ss);
this.push32(old_esp);
//dbg_log("old esp written to " + h(this.translate_address_system_read(this.get_stack_pointer(0))));
}
if(is_call)
{
if(is_16)
{
for(var i = parameter_count - 1; i >= 0; i--)
{
var parameter = this.safe_read16(old_stack_pointer + 2 * i);
this.push16(parameter);
}
//this.writable_or_pagefault(this.get_stack_pointer(-4), 4);
this.push16(this.sreg[reg_cs]);
this.push16(this.get_real_eip());
}
else
{
for(var i = parameter_count - 1; i >= 0; i--)
{
var parameter = this.safe_read32s(old_stack_pointer + 4 * i);
this.push32(parameter);
}
//this.writable_or_pagefault(this.get_stack_pointer(-8), 8);
this.push32(this.sreg[reg_cs]);
this.push32(this.get_real_eip());
}
}
}
else
{
dbg_log("same privilege call gate is_16=" + is_16 + " from=" + this.cpl[0] + " to=" + cs_info.dpl + " conforming=" + cs_info.dc_bit);
// ok
if(is_call)
{
if(is_16)
{
if(!this.writable_or_pagefault(this.get_stack_pointer(-4), 4))
{
return;
}
this.push16(this.sreg[reg_cs]);
this.push16(this.get_real_eip());
}
else
{
if(!this.writable_or_pagefault(this.get_stack_pointer(-8), 8))
{
return;
}
this.push32(this.sreg[reg_cs]);
this.push32(this.get_real_eip());
}
}
}
// Note: eip from call is ignored
var new_eip = info.raw0 & 0xFFFF;
if(!is_16)
{
new_eip |= info.raw1 & 0xFFFF0000;
}
dbg_log("call gate eip=" + h(new_eip >>> 0) + " cs=" + h(cs_selector) + " conforming=" + cs_info.dc_bit);
dbg_assert((new_eip >>> 0) <= cs_info.effective_limit, "todo: #gp");
this.update_cs_size(cs_info.size);
this.segment_is_null[reg_cs] = 0;
this.segment_limits[reg_cs] = cs_info.effective_limit;
//this.segment_infos[reg_cs] = 0; // TODO
this.segment_offsets[reg_cs] = cs_info.base;
this.sreg[reg_cs] = cs_selector & ~3 | this.cpl[0];
dbg_assert((this.sreg[reg_cs] & 3) === this.cpl[0]);
this.instruction_pointer[0] = this.get_seg_cs() + new_eip | 0;
}
else
{
var types = { 9: "Available 386 TSS", 0xb: "Busy 386 TSS", 4: "286 Call Gate", 0xc: "386 Call Gate" };
throw this.debug.unimpl("load system segment descriptor, type = " + (info.access & 15) + " (" + types[info.access & 15] + ")");
}
}
else
{
if(!info.is_executable)
{
dbg_log("#gp non-executable cs: " + h(selector), LOG_CPU);
this.trigger_gp(selector & ~3);
return;
}
if(info.dc_bit)
{
// conforming code segment
if(info.dpl > this.cpl[0])
{
dbg_log("#gp cs dpl > cpl: " + h(selector), LOG_CPU);
this.trigger_gp(selector & ~3);
return;
}
}
else
{
// non-conforming code segment
if(info.rpl > this.cpl[0] || info.dpl !== this.cpl[0])
{
dbg_log("#gp cs rpl > cpl or dpl != cpl: " + h(selector), LOG_CPU);
this.trigger_gp(selector & ~3);
return;
}
}
if(!info.is_present)
{
dbg_log("#NP for loading not-present in cs sel=" + h(selector, 4), LOG_CPU);
dbg_trace(LOG_CPU);
this.trigger_np(selector & ~3);
return;
}
if(is_call)
{
if(is_osize_32)
{
if(!this.writable_or_pagefault(this.get_stack_pointer(-8), 8))
{
return;
}
this.push32(this.sreg[reg_cs]);
this.push32(this.get_real_eip());
}
else
{
if(!this.writable_or_pagefault(this.get_stack_pointer(-4), 4))
{
return;
}
this.push16(this.sreg[reg_cs]);
this.push16(this.get_real_eip());
}
}
dbg_assert((eip >>> 0) <= info.effective_limit, "todo: #gp");
this.update_cs_size(info.size);
this.segment_is_null[reg_cs] = 0;
this.segment_limits[reg_cs] = info.effective_limit;
//this.segment_infos[reg_cs] = 0; // TODO
this.segment_offsets[reg_cs] = info.base;
this.sreg[reg_cs] = selector & ~3 | this.cpl[0];
this.instruction_pointer[0] = this.get_seg_cs() + eip | 0;
}
//dbg_log("far " + ["jump", "call"][+is_call] + " to:", LOG_CPU)
CPU_LOG_VERBOSE && this.debug.dump_state("far " + ["jump", "call"][+is_call] + " end");
};
CPU.prototype.do_task_switch = function(selector, has_error_code, error_code)
{
dbg_assert(this.tss_size_32[0], "TODO");

View file

@ -317,6 +317,8 @@ impl SegmentDescriptor {
pub fn flags(&self) -> u8 { ((self.raw >> 48 >> 4) & 0xf) as u8 }
pub fn is_system(&self) -> bool { self.access_byte() & 0x10 == 0 }
pub fn system_type(&self) -> u8 { self.access_byte() & 0xF }
pub fn is_rw(&self) -> bool { self.access_byte() & 2 == 2 }
pub fn is_dc(&self) -> bool { self.access_byte() & 4 == 4 }
pub fn is_executable(&self) -> bool { self.access_byte() & 8 == 8 }
@ -972,6 +974,350 @@ pub unsafe fn call_interrupt_vector(
}
}
pub unsafe fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool) {
dbg_assert!(selector < 0x10000 && selector >= 0);
//dbg_log("far " + ["jump", "call"][+is_call] + " eip=" + h(eip >>> 0, 8) + " cs=" + h(selector, 4), LOG_CPU);
//CPU_LOG_VERBOSE && this.debug.dump_state("far " + ["jump", "call"][+is_call]);
if !*protected_mode || vm86_mode() {
if is_call {
if is_osize_32 {
return_on_pagefault!(writable_or_pagefault(get_stack_pointer(-8), 8));
push32(*sreg.offset(CS as isize) as i32).unwrap();
push32(get_real_eip()).unwrap();
}
else {
return_on_pagefault!(writable_or_pagefault(get_stack_pointer(-4), 4));
push16(*sreg.offset(CS as isize) as i32).unwrap();
push16(get_real_eip()).unwrap();
}
}
switch_cs_real_mode(selector);
*instruction_pointer = get_seg_cs() + eip;
return;
}
let (info, cs_selector) = match return_on_pagefault!(lookup_segment_selector(selector)) {
Ok((desc, sel)) => (desc, sel),
Err(selector_unusable) => match selector_unusable {
SelectorNullOrInvalid::IsNull => {
dbg_log!("#gp null cs");
trigger_gp(0);
return;
},
SelectorNullOrInvalid::IsInvalid => {
dbg_log!("#gp invalid cs: {:x}", selector);
trigger_gp(selector & !3);
return;
},
},
};
if info.is_system() {
dbg_assert!(is_call, "TODO: Jump");
dbg_log!("system type cs: {:x}", selector);
if info.system_type() == 0xC || info.system_type() == 4 {
// call gate
let is_16 = info.system_type() == 4;
if info.dpl() < *cpl || info.dpl() < cs_selector.rpl() {
dbg_log!("#gp cs gate dpl < cpl or dpl < rpl: {:x}", selector);
trigger_gp(selector & !3);
return;
}
if !info.is_present() {
dbg_log!("#NP for loading not-present in gate cs sel={:x}", selector);
trigger_np(selector & !3);
return;
}
let cs_selector = (info.raw >> 16) as i32;
let (cs_info, _) = match return_on_pagefault!(lookup_segment_selector(cs_selector)) {
Ok((desc, sel)) => (desc, sel),
Err(selector_unusable) => match selector_unusable {
SelectorNullOrInvalid::IsNull => {
dbg_log!("#gp null cs");
trigger_gp(0);
return;
},
SelectorNullOrInvalid::IsInvalid => {
dbg_log!("#gp invalid cs: {:x}", selector);
trigger_gp(selector & !3);
return;
},
},
};
if !cs_info.is_executable() {
dbg_log!("#gp non-executable cs: {:x}", cs_selector);
trigger_gp(cs_selector & !3);
return;
}
if cs_info.dpl() > *cpl {
dbg_log!("#gp dpl > cpl: {:x}", cs_selector);
trigger_gp(cs_selector & !3);
return;
}
if !cs_info.is_present() {
dbg_log!("#NP for loading not-present in cs sel={:x}", cs_selector);
trigger_np(cs_selector & !3);
return;
}
if !cs_info.is_dc() && cs_info.dpl() < *cpl {
dbg_log!(
"more privilege call gate is_16={} from={} to={}",
is_16,
*cpl,
cs_info.dpl()
);
let tss_stack_addr = return_on_pagefault!(get_tss_stack_addr(cs_info.dpl()));
let new_esp;
let new_ss;
if *tss_size_32 {
new_esp = read32s(tss_stack_addr);
new_ss = read16(tss_stack_addr + 4);
}
else {
new_esp = read16(tss_stack_addr);
new_ss = read16(tss_stack_addr + 2);
}
let (ss_info, ss_selector) =
match return_on_pagefault!(lookup_segment_selector(new_ss)) {
Ok((desc, sel)) => (desc, sel),
Err(selector_unusable) => match selector_unusable {
SelectorNullOrInvalid::IsNull => {
panic!("null ss: {}", new_ss);
},
SelectorNullOrInvalid::IsInvalid => {
panic!("invalid ss: {}", new_ss);
},
},
};
// Disabled: Incorrect handling of direction bit
// See http://css.csail.mit.edu/6.858/2014/readings/i386/s06_03.htm
//if(!((new_esp >>> 0) <= ss_info.effective_limit))
// debugger;
//dbg_assert!((new_esp >>> 0) <= ss_info.effective_limit);
dbg_assert!(!ss_info.is_system() && ss_info.is_writable());
if ss_selector.rpl() != cs_info.dpl()
// xxx: 0 in v86 mode
{
panic!("#TS handler");
}
if ss_info.dpl() != cs_info.dpl() || !ss_info.is_writable() {
panic!("#TS handler");
}
if !ss_info.is_present() {
panic!("#SS handler");
}
let parameter_count = (info.raw >> 32 & 0x1F) as i32;
let mut stack_space = if is_16 { 4 } else { 8 };
if is_call {
stack_space +=
if is_16 { 4 + 2 * parameter_count } else { 8 + 4 * parameter_count };
}
if ss_info.is_32() {
return_on_pagefault!(writable_or_pagefault(
ss_info.base() + new_esp - stack_space,
stack_space
)); // , cs_info.dpl
}
else {
return_on_pagefault!(writable_or_pagefault(
ss_info.base() + (new_esp - stack_space & 0xFFFF),
stack_space
)); // , cs_info.dpl
}
let old_esp = *reg32.offset(ESP as isize);
let old_ss = *sreg.offset(SS as isize);
let old_stack_pointer = get_stack_pointer(0);
//dbg_log!("old_esp=" + h(old_esp));
*cpl = cs_info.dpl();
cpl_changed();
update_cs_size(cs_info.is_32());
// XXX: Should be checked before side effects
if !switch_seg(SS, new_ss) {
dbg_assert!(false)
};
set_stack_reg(new_esp);
//dbg_log!("parameter_count=" + parameter_count);
//dbg_assert!(parameter_count == 0, "TODO");
if is_16 {
push16(old_ss as i32).unwrap();
push16(old_esp).unwrap();
}
else {
push32(old_ss as i32).unwrap();
push32(old_esp).unwrap();
}
if is_call {
if is_16 {
for i in (0..parameter_count).rev() {
//for(var i = parameter_count - 1; i >= 0; i--)
let parameter = safe_read16(old_stack_pointer + 2 * i).unwrap();
push16(parameter).unwrap();
}
//writable_or_pagefault(get_stack_pointer(-4), 4);
push16(*sreg.offset(CS as isize) as i32).unwrap();
push16(get_real_eip()).unwrap();
}
else {
for i in (0..parameter_count).rev() {
//for(var i = parameter_count - 1; i >= 0; i--)
let parameter = safe_read32s(old_stack_pointer + 4 * i).unwrap();
push32(parameter).unwrap();
}
//writable_or_pagefault(get_stack_pointer(-8), 8);
push32(*sreg.offset(CS as isize) as i32).unwrap();
push32(get_real_eip()).unwrap();
}
}
}
else {
dbg_log!(
"same privilege call gate is_16={} from={} to={} conforming={}",
is_16,
*cpl,
cs_info.dpl(),
cs_info.is_dc()
);
// ok
if is_call {
if is_16 {
return_on_pagefault!(writable_or_pagefault(get_stack_pointer(-4), 4));
push16(*sreg.offset(CS as isize) as i32).unwrap();
push16(get_real_eip()).unwrap();
}
else {
return_on_pagefault!(writable_or_pagefault(get_stack_pointer(-8), 8));
push32(*sreg.offset(CS as isize) as i32).unwrap();
push32(get_real_eip()).unwrap();
}
}
}
// Note: eip from call is ignored
let mut new_eip = (info.raw & 0xFFFF) as i32;
if !is_16 {
new_eip |= ((info.raw >> 32) & 0xFFFF0000) as i32;
}
dbg_log!(
"call gate eip={:x} cs={:x} conforming={}",
new_eip as u32,
cs_selector,
cs_info.is_dc()
);
dbg_assert!((new_eip as u32) <= cs_info.effective_limit(), "todo: #gp");
update_cs_size(cs_info.is_32());
*segment_is_null.offset(CS as isize) = false;
*segment_limits.offset(CS as isize) = cs_info.effective_limit();
*segment_offsets.offset(CS as isize) = cs_info.base();
*sreg.offset(CS as isize) = cs_selector as u16 & !3 | *cpl as u16;
dbg_assert!(*sreg.offset(CS as isize) & 3 == *cpl as u16);
*instruction_pointer = get_seg_cs() + new_eip;
}
else {
dbg_assert!(false);
//var types = { 9: "Available 386 TSS", 0xb: "Busy 386 TSS", 4: "286 Call Gate", 0xc: "386 Call Gate" };
//throw debug.unimpl("load system segment descriptor, type = " + (info.access & 15) + " (" + types[info.access & 15] + ")");
}
}
else {
if !info.is_executable() {
dbg_log!("#gp non-executable cs: {:x}", selector);
trigger_gp(selector & !3);
return;
}
if info.is_dc() {
// conforming code segment
if info.dpl() > *cpl {
dbg_log!("#gp cs dpl > cpl: {:x}", selector);
trigger_gp(selector & !3);
return;
}
}
else {
// non-conforming code segment
if cs_selector.rpl() > *cpl || info.dpl() != *cpl {
dbg_log!("#gp cs rpl > cpl or dpl != cpl: {:x}", selector);
trigger_gp(selector & !3);
return;
}
}
if !info.is_present() {
dbg_log!("#NP for loading not-present in cs sel={:x}", selector);
dbg_trace();
trigger_np(selector & !3);
return;
}
if is_call {
if is_osize_32 {
return_on_pagefault!(writable_or_pagefault(get_stack_pointer(-8), 8));
push32(*sreg.offset(CS as isize) as i32).unwrap();
push32(get_real_eip()).unwrap();
}
else {
return_on_pagefault!(writable_or_pagefault(get_stack_pointer(-4), 4));
push16(*sreg.offset(CS as isize) as i32).unwrap();
push16(get_real_eip()).unwrap();
}
}
dbg_assert!((eip as u32) <= info.effective_limit(), "todo: #gp");
update_cs_size(info.is_32());
*segment_is_null.offset(CS as isize) = false;
*segment_limits.offset(CS as isize) = info.effective_limit();
*segment_offsets.offset(CS as isize) = info.base();
*sreg.offset(CS as isize) = selector as u16 & !3 | *cpl as u16;
*instruction_pointer = get_seg_cs() + eip;
}
//dbg_log!("far " + ["jump", "call"][+is_call] + " to:", LOG_CPU)
//CPU_LOG_VERBOSE && debug.dump_state("far " + ["jump", "call"][+is_call] + " end");
}
//pub fn call_indirect1(f: fn(u16), x: u16) { f(x); }
pub unsafe fn after_block_boundary() { jit_block_boundary = true; }

View file

@ -4,8 +4,6 @@ extern "C" {
#[no_mangle]
fn arpl(seg: i32, r: i32) -> i32;
#[no_mangle]
fn far_jump(eip: i32, selector: i32, is_call: bool, is_osize_32: bool);
#[no_mangle]
fn far_return(eip: i32, selector: i32, stack_adjust: i32, is_osize_32: bool);
#[no_mangle]