From f4beb510a41980e119f787746442ca1b87c06754 Mon Sep 17 00:00:00 2001 From: bellard Date: Tue, 27 May 2003 23:28:08 +0000 Subject: [PATCH] precise exceptions - more accurate interrupt semantics git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@193 c046a42c-6fe2-441c-8c8c-71466251a162 --- linux-user/main.c | 108 +++++++++++++++++-------- op-i386.c | 196 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 250 insertions(+), 54 deletions(-) diff --git a/linux-user/main.c b/linux-user/main.c index a6a84e5363..eeea342d2b 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -100,24 +100,35 @@ int cpu_x86_inl(CPUX86State *env, int addr) return 0; } -void write_dt(void *ptr, unsigned long addr, unsigned long limit, - int seg32_bit) +static void write_dt(void *ptr, unsigned long addr, unsigned long limit, + int flags) { - unsigned int e1, e2, limit_in_pages; - limit_in_pages = 0; - if (limit > 0xffff) { - limit = limit >> 12; - limit_in_pages = 1; - } + unsigned int e1, e2; e1 = (addr << 16) | (limit & 0xffff); e2 = ((addr >> 16) & 0xff) | (addr & 0xff000000) | (limit & 0x000f0000); - e2 |= limit_in_pages << 23; /* byte granularity */ - e2 |= seg32_bit << 22; /* 32 bit segment */ + e2 |= flags; + stl((uint8_t *)ptr, e1); + stl((uint8_t *)ptr + 4, e2); +} + +static void set_gate(void *ptr, unsigned int type, unsigned int dpl, + unsigned long addr, unsigned int sel) +{ + unsigned int e1, e2; + e1 = (addr & 0xffff) | (sel << 16); + e2 = (addr & 0xffff0000) | 0x8000 | (dpl << 13) | (type << 8); stl((uint8_t *)ptr, e1); stl((uint8_t *)ptr + 4, e2); } uint64_t gdt_table[6]; +uint64_t idt_table[256]; + +/* only dpl matters as we do only user space emulation */ +static void set_idt(int n, unsigned int dpl) +{ + set_gate(idt_table + n, 0, dpl, 0, 0); +} void cpu_loop(CPUX86State *env) { @@ -128,30 +139,34 @@ void cpu_loop(CPUX86State *env) for(;;) { trapnr = cpu_x86_exec(env); switch(trapnr) { + case 0x80: + /* linux syscall */ + env->regs[R_EAX] = do_syscall(env, + env->regs[R_EAX], + env->regs[R_EBX], + env->regs[R_ECX], + env->regs[R_EDX], + env->regs[R_ESI], + env->regs[R_EDI], + env->regs[R_EBP]); + break; + case EXCP0B_NOSEG: + case EXCP0C_STACK: + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = TARGET_SI_KERNEL; + info._sifields._sigfault._addr = 0; + queue_signal(info.si_signo, &info); + break; case EXCP0D_GPF: if (env->eflags & VM_MASK) { handle_vm86_fault(env); } else { - pc = env->seg_cache[R_CS].base + env->eip; - if (pc[0] == 0xcd && pc[1] == 0x80) { - /* syscall */ - env->eip += 2; - env->regs[R_EAX] = do_syscall(env, - env->regs[R_EAX], - env->regs[R_EBX], - env->regs[R_ECX], - env->regs[R_EDX], - env->regs[R_ESI], - env->regs[R_EDI], - env->regs[R_EBP]); - } else { - /* XXX: more precise info */ - info.si_signo = SIGSEGV; - info.si_errno = 0; - info.si_code = TARGET_SI_KERNEL; - info._sifields._sigfault._addr = 0; - queue_signal(info.si_signo, &info); - } + info.si_signo = SIGSEGV; + info.si_errno = 0; + info.si_code = TARGET_SI_KERNEL; + info._sifields._sigfault._addr = 0; + queue_signal(info.si_signo, &info); } break; case EXCP0E_PAGE: @@ -365,11 +380,40 @@ int main(int argc, char **argv) env->regs[R_ESP] = regs->esp; env->eip = regs->eip; + /* linux interrupt setup */ + env->idt.base = (void *)idt_table; + env->idt.limit = sizeof(idt_table) - 1; + set_idt(0, 0); + set_idt(1, 0); + set_idt(2, 0); + set_idt(3, 3); + set_idt(4, 3); + set_idt(5, 3); + set_idt(6, 0); + set_idt(7, 0); + set_idt(8, 0); + set_idt(9, 0); + set_idt(10, 0); + set_idt(11, 0); + set_idt(12, 0); + set_idt(13, 0); + set_idt(14, 0); + set_idt(15, 0); + set_idt(16, 0); + set_idt(17, 0); + set_idt(18, 0); + set_idt(19, 0); + set_idt(0x80, 3); + /* linux segment setup */ env->gdt.base = (void *)gdt_table; env->gdt.limit = sizeof(gdt_table) - 1; - write_dt(&gdt_table[__USER_CS >> 3], 0, 0xffffffff, 1); - write_dt(&gdt_table[__USER_DS >> 3], 0, 0xffffffff, 1); + write_dt(&gdt_table[__USER_CS >> 3], 0, 0xfffff, + DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK | + (3 << DESC_DPL_SHIFT) | (0xa << DESC_TYPE_SHIFT)); + write_dt(&gdt_table[__USER_DS >> 3], 0, 0xfffff, + DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK | + (3 << DESC_DPL_SHIFT) | (0x2 << DESC_TYPE_SHIFT)); cpu_x86_load_seg(env, R_CS, __USER_CS); cpu_x86_load_seg(env, R_DS, __USER_DS); cpu_x86_load_seg(env, R_ES, __USER_DS); diff --git a/op-i386.c b/op-i386.c index cfae103639..69bd4cffca 100644 --- a/op-i386.c +++ b/op-i386.c @@ -364,8 +364,10 @@ void OPPROTO op_divb_AL_T0(void) num = (EAX & 0xffff); den = (T0 & 0xff); - if (den == 0) + if (den == 0) { + EIP = PARAM1; raise_exception(EXCP00_DIVZ); + } q = (num / den) & 0xff; r = (num % den) & 0xff; EAX = (EAX & 0xffff0000) | (r << 8) | q; @@ -377,8 +379,10 @@ void OPPROTO op_idivb_AL_T0(void) num = (int16_t)EAX; den = (int8_t)T0; - if (den == 0) + if (den == 0) { + EIP = PARAM1; raise_exception(EXCP00_DIVZ); + } q = (num / den) & 0xff; r = (num % den) & 0xff; EAX = (EAX & 0xffff0000) | (r << 8) | q; @@ -390,8 +394,10 @@ void OPPROTO op_divw_AX_T0(void) num = (EAX & 0xffff) | ((EDX & 0xffff) << 16); den = (T0 & 0xffff); - if (den == 0) + if (den == 0) { + EIP = PARAM1; raise_exception(EXCP00_DIVZ); + } q = (num / den) & 0xffff; r = (num % den) & 0xffff; EAX = (EAX & 0xffff0000) | q; @@ -404,8 +410,10 @@ void OPPROTO op_idivw_AX_T0(void) num = (EAX & 0xffff) | ((EDX & 0xffff) << 16); den = (int16_t)T0; - if (den == 0) + if (den == 0) { + EIP = PARAM1; raise_exception(EXCP00_DIVZ); + } q = (num / den) & 0xffff; r = (num % den) & 0xffff; EAX = (EAX & 0xffff0000) | q; @@ -435,8 +443,10 @@ void OPPROTO op_divl_EAX_T0(void) num = EAX | ((uint64_t)EDX << 32); den = T0; - if (den == 0) + if (den == 0) { + EIP = PARAM1; raise_exception(EXCP00_DIVZ); + } #ifdef BUGGY_GCC_DIV64 r = div64(&q, num, den); #else @@ -454,8 +464,10 @@ void OPPROTO op_idivl_EAX_T0(void) num = EAX | ((uint64_t)EDX << 32); den = T0; - if (den == 0) + if (den == 0) { + EIP = PARAM1; raise_exception(EXCP00_DIVZ); + } #ifdef BUGGY_GCC_DIV64 r = idiv64(&q, num, den); #else @@ -614,12 +626,102 @@ void OPPROTO op_jmp_im(void) EIP = PARAM1; } -void OPPROTO op_int_im(void) +#if 0 +/* full interrupt support (only useful for real CPU emulation, not + finished) - I won't do it any time soon, finish it if you want ! */ +void raise_interrupt(int intno, int is_int, int error_code, + unsigned int next_eip) +{ + SegmentDescriptorTable *dt; + uint8_t *ptr; + int type, dpl, cpl; + uint32_t e1, e2; + + dt = &env->idt; + if (intno * 8 + 7 > dt->limit) + raise_exception_err(EXCP0D_GPF, intno * 8 + 2); + ptr = dt->base + intno * 8; + e1 = ldl(ptr); + e2 = ldl(ptr + 4); + /* check gate type */ + type = (e2 >> DESC_TYPE_SHIFT) & 0x1f; + switch(type) { + case 5: /* task gate */ + case 6: /* 286 interrupt gate */ + case 7: /* 286 trap gate */ + case 14: /* 386 interrupt gate */ + case 15: /* 386 trap gate */ + break; + default: + raise_exception_err(EXCP0D_GPF, intno * 8 + 2); + break; + } + dpl = (e2 >> DESC_DPL_SHIFT) & 3; + cpl = env->segs[R_CS] & 3; + /* check privledge if software int */ + if (is_int && dpl < cpl) + raise_exception_err(EXCP0D_GPF, intno * 8 + 2); + /* check valid bit */ + if (!(e2 & DESC_P_MASK)) + raise_exception_err(EXCP0B_NOSEG, intno * 8 + 2); +} + +#else + +/* + * is_int is TRUE if coming from the int instruction. next_eip is the + * EIP value AFTER the interrupt instruction. It is only relevant if + * is_int is TRUE. + */ +void raise_interrupt(int intno, int is_int, int error_code, + unsigned int next_eip) +{ + SegmentDescriptorTable *dt; + uint8_t *ptr; + int dpl, cpl; + uint32_t e2; + + dt = &env->idt; + ptr = dt->base + (intno * 8); + e2 = ldl(ptr + 4); + + dpl = (e2 >> DESC_DPL_SHIFT) & 3; + cpl = 3; + /* check privledge if software int */ + if (is_int && dpl < cpl) + raise_exception_err(EXCP0D_GPF, intno * 8 + 2); + + /* Since we emulate only user space, we cannot do more than + exiting the emulation with the suitable exception and error + code */ + if (is_int) + EIP = next_eip; + env->exception_index = intno; + env->error_code = error_code; + + cpu_loop_exit(); +} + +#endif + +/* shortcuts to generate exceptions */ +void raise_exception_err(int exception_index, int error_code) +{ + raise_interrupt(exception_index, 0, error_code, 0); +} + +void raise_exception(int exception_index) +{ + raise_interrupt(exception_index, 0, 0, 0); +} + +void OPPROTO op_raise_interrupt(void) { int intno; + unsigned int next_eip; intno = PARAM1; - EIP = PARAM2; - raise_exception_err(EXCP0D_GPF, intno * 8 + 2); + next_eip = PARAM2; + raise_interrupt(intno, 1, 0, next_eip); } void OPPROTO op_raise_exception(void) @@ -634,8 +736,7 @@ void OPPROTO op_into(void) int eflags; eflags = cc_table[CC_OP].compute_all(); if (eflags & CC_O) { - EIP = PARAM1; - raise_exception(EXCP04_INTO); + raise_interrupt(EXCP04_INTO, 1, 0, PARAM1); } FORCE_RET(); } @@ -674,8 +775,10 @@ void OPPROTO op_boundw(void) low = ldsw((uint8_t *)A0); high = ldsw((uint8_t *)A0 + 2); v = (int16_t)T0; - if (v < low || v > high) + if (v < low || v > high) { + EIP = PARAM1; raise_exception(EXCP05_BOUND); + } FORCE_RET(); } @@ -685,8 +788,10 @@ void OPPROTO op_boundl(void) low = ldl((uint8_t *)A0); high = ldl((uint8_t *)A0 + 4); v = T0; - if (v < low || v > high) + if (v < low || v > high) { + EIP = PARAM1; raise_exception(EXCP05_BOUND); + } FORCE_RET(); } @@ -1116,8 +1221,8 @@ void OPPROTO op_das(void) /* segment handling */ -/* XXX: use static VM86 information */ -void load_seg(int seg_reg, int selector) +/* only works if protected mode and not VM86 */ +void load_seg(int seg_reg, int selector, unsigned cur_eip) { SegmentCache *sc; SegmentDescriptorTable *dt; @@ -1126,21 +1231,56 @@ void load_seg(int seg_reg, int selector) uint8_t *ptr; sc = &env->seg_cache[seg_reg]; - if (env->eflags & VM_MASK) { - sc->base = (void *)(selector << 4); - sc->limit = 0xffff; - sc->seg_32bit = 0; + if ((selector & 0xfffc) == 0) { + /* null selector case */ + if (seg_reg == R_SS) { + EIP = cur_eip; + raise_exception_err(EXCP0D_GPF, selector & 0xfffc); + } else { + /* XXX: each access should trigger an exception */ + sc->base = NULL; + sc->limit = 0; + sc->seg_32bit = 1; + } } else { if (selector & 0x4) dt = &env->ldt; else dt = &env->gdt; index = selector & ~7; - if ((index + 7) > dt->limit) - raise_exception_err(EXCP0D_GPF, selector); + if ((index + 7) > dt->limit) { + EIP = cur_eip; + raise_exception_err(EXCP0D_GPF, selector & 0xfffc); + } ptr = dt->base + index; e1 = ldl(ptr); e2 = ldl(ptr + 4); + if (!(e2 & DESC_S_MASK) || + (e2 & (DESC_CS_MASK | DESC_R_MASK)) == DESC_CS_MASK) { + EIP = cur_eip; + raise_exception_err(EXCP0D_GPF, selector & 0xfffc); + } + + if (seg_reg == R_SS) { + if ((e2 & (DESC_CS_MASK | DESC_W_MASK)) == 0) { + EIP = cur_eip; + raise_exception_err(EXCP0D_GPF, selector & 0xfffc); + } + } else { + if ((e2 & (DESC_CS_MASK | DESC_R_MASK)) == DESC_CS_MASK) { + EIP = cur_eip; + raise_exception_err(EXCP0D_GPF, selector & 0xfffc); + } + } + + if (!(e2 & DESC_P_MASK)) { + EIP = cur_eip; + if (seg_reg == R_SS) + raise_exception_err(EXCP0C_STACK, selector & 0xfffc); + else + raise_exception_err(EXCP0B_NOSEG, selector & 0xfffc); + } + sc->base = (void *)((e1 >> 16) | ((e2 & 0xff) << 16) | (e2 & 0xff000000)); sc->limit = (e1 & 0xffff) | (e2 & 0x000f0000); if (e2 & (1 << 23)) @@ -1156,7 +1296,19 @@ void load_seg(int seg_reg, int selector) void OPPROTO op_movl_seg_T0(void) { - load_seg(PARAM1, T0 & 0xffff); + load_seg(PARAM1, T0 & 0xffff, PARAM2); +} + +/* faster VM86 version */ +void OPPROTO op_movl_seg_T0_vm(void) +{ + int selector; + + selector = T0 & 0xffff; + /* env->segs[] access */ + *(uint32_t *)((char *)env + PARAM1) = selector; + /* env->seg_cache[] access */ + ((SegmentCache *)((char *)env + PARAM2))->base = (void *)(selector << 4); } void OPPROTO op_movl_T0_seg(void)