invop.c revision d68b16b8f5d785797e8d14ce1f8aaf12dd9975c1
/* $Id$ */
/** @file
* Real mode invalid opcode handler.
*/
/*
* Copyright (C) 2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#include <stdint.h>
#include <string.h>
#include "biosint.h"
#include "inlines.h"
/* The layout of 286 LOADALL descriptors. */
typedef struct tag_ldall_desc {
uint16_t base_lo; /* Bits 0-15 of segment base. */
uint8_t base_hi; /* Bits 16-13 of segment base. */
uint8_t attr; /* Segment attributes. */
uint16_t limit; /* Segment limit. */
} ldall_desc;
/* The 286 LOADALL memory buffer at physical address 800h. From
* The Undocumented PC.
*/
typedef struct tag_ldall_286 {
uint16_t unused1[3];
uint16_t msw; /* 806h */
uint16_t unused2[7];
uint16_t tr; /* 816h */
uint16_t flags; /* 818h */
uint16_t ip; /* 81Ah */
uint16_t ldt; /* 81Ch */
uint16_t ds; /* 81Eh */
uint16_t ss; /* 820h */
uint16_t cs; /* 822h */
uint16_t es; /* 824h */
uint16_t di; /* 826h */
uint16_t si; /* 828h */
uint16_t bp; /* 82Ah */
uint16_t sp; /* 82Ch */
uint16_t bx; /* 82Eh */
uint16_t dx; /* 830h */
uint16_t cx; /* 832h */
uint16_t ax; /* 834h */
ldall_desc es_desc; /* 836h */
ldall_desc cs_desc; /* 83Ch */
ldall_desc ss_desc; /* 842h */
ldall_desc ds_desc; /* 848h */
ldall_desc gdt_desc; /* 84Eh */
ldall_desc ldt_desc; /* 854h */
ldall_desc idt_desc; /* 85Ah */
ldall_desc tss_desc; /* 860h */
} ldall_286_s;
ct_assert(sizeof(ldall_286_s) == 0x66);
/*
* LOADALL emulation assumptions:
* - MSW indicates real mode
* - Standard real mode CS and SS is to be used
* - Segment values of non-RM segments (if any) do not matter
* - Standard segment attributes are used
*/
/* A wrapper for LIDT. */
void load_idtr(uint32_t base, uint16_t limit);
#pragma aux load_idtr = \
".286p" \
"mov bx, sp" \
"lidt fword ptr ss:[bx]"\
parm caller reverse [] modify [bx] exact;
/* A wrapper for LGDT. */
void load_gdtr(uint32_t base, uint16_t limit);
#pragma aux load_gdtr = \
".286p" \
"mov bx, sp" \
"lgdt fword ptr ss:[bx]"\
parm caller reverse [] modify [bx] exact;
/* Load DS/ES as real-mode segments. May be overwritten later.
* NB: Loads SS with 80h to address the LOADALL buffer. Must
* not touch CX!
*/
void load_rm_segs(int seg_flags);
#pragma aux load_rm_segs = \
"mov ax, 80h" \
"mov ss, ax" \
"mov ax, ss:[1Eh]" \
"mov ds, ax" \
"mov ax, ss:[24h]" \
"mov es, ax" \
parm [cx] nomemory modify nomemory;
/* Briefly switch to protected mode and load ES and/or DS if necessary.
* NB: Trashes high bits of EAX, but that should be safe. Expects flags
* in CX.
*/
void load_pm_segs(void);
#pragma aux load_pm_segs = \
".386p" \
"smsw ax" \
"inc ax" \
"lmsw ax" \
"mov ax, 8" \
"test cx, 1" \
"jz skip_es" \
"mov es, ax" \
"skip_es:" \
"test cx, 2" \
"jz skip_ds" \
"mov bx,ss:[00h]" \
"mov ss:[08h], bx" \
"mov bx,ss:[02h]" \
"mov ss:[0Ah], bx" \
"mov bx,ss:[04h]" \
"mov ss:[0Ch], bx" \
"mov ds, ax" \
"skip_ds:" \
"mov eax, cr0" \
"dec ax" \
"mov cr0, eax" \
parm nomemory modify nomemory;
/* Complete LOADALL emulation: Restore general-purpose registers, stack
* pointer, and CS:IP. NB: The LOADALL instruction stores registers in
* the same order as PUSHA. Surprise, surprise!
*/
void ldall_finish(void);
#pragma aux ldall_finish = \
".286" \
"mov sp, 26h" \
"popa" \
"mov sp, ss:[2Ch]" \
"sub sp, 6" \
"mov ss, ss:[20h]" \
"iret" \
parm nomemory modify nomemory aborts;
#define LOAD_ES 0x01 /* ES needs to be loaded in protected mode. */
#define LOAD_DS 0x02 /* DS needs to be loaded in protected mode. */
/*
* The invalid opcode handler exists to work around fishy application
* code and paper over CPU generation differences:
*
* - Skip redundant LOCK prefixes (allowed on 8086, #UD on 286+).
* - Emulate just enough of 286 LOADALL.
*
*/
void BIOSCALL inv_op_handler(uint16_t ds, uint16_t es, pusha_regs_t gr, volatile iret_addr_t ra)
{
void __far *ins = ra.cs :> ra.ip;
if (*(uint8_t __far *)ins == 0xF0) {
/* LOCK prefix - skip over it and try again. */
++ra.ip;
} else if (*(uint16_t __far *)ins == 0x050F) {
/* 286 LOADALL. NB: Same opcode as SYSCALL. */
ldall_286_s __far *ldbuf = 0 :> 0x800;
iret_addr_t __far *ret_addr;
uint32_t seg_base;
int seg_flags = 0;
/* One of the challenges is that we must restore SS:SP as well
* as CS:IP and FLAGS from the LOADALL buffer. We copy CS/IP/FLAGS
* from the buffer just below the SS:SP values from the buffer so
* that we can eventually IRET to the desired CS/IP/FLAGS/SS/SP
* values in one go.
*/
ret_addr = ldbuf->ss :> (ldbuf->sp - sizeof(iret_addr_t));
ret_addr->ip = ldbuf->ip;
ret_addr->cs = ldbuf->cs;
ret_addr->flags.u.r16.flags = ldbuf->flags;
/* Examine ES/DS. */
seg_base = ldbuf->es_desc.base_lo | (uint32_t)ldbuf->es_desc.base_hi << 16;
if (seg_base != (uint32_t)ldbuf->es << 4)
seg_flags |= LOAD_ES;
seg_base = ldbuf->ds_desc.base_lo | (uint32_t)ldbuf->ds_desc.base_hi << 16;
if (seg_base != (uint32_t)ldbuf->ds << 4)
seg_flags |= LOAD_DS;
/* The LOADALL buffer doubles as a tiny GDT. */
load_gdtr(0x800, 4 * 8 - 1);
/* Store the ES base/limit/attributes in the unused words (GDT selector 8). */
ldbuf->unused2[0] = ldbuf->es_desc.limit;
ldbuf->unused2[1] = ldbuf->es_desc.base_lo;
ldbuf->unused2[2] = (ldbuf->es_desc.attr << 8) | ldbuf->es_desc.base_hi;
ldbuf->unused2[3] = 0;
/* Store the DS base/limit/attributes in other unused words. */
ldbuf->unused1[0] = ldbuf->ds_desc.limit;
ldbuf->unused1[1] = ldbuf->ds_desc.base_lo;
ldbuf->unused1[2] = (ldbuf->ds_desc.attr << 8) | ldbuf->ds_desc.base_hi;
/* Load the IDTR as specified. */
seg_base = ldbuf->idt_desc.base_lo | (uint32_t)ldbuf->idt_desc.base_hi << 16;
load_idtr(seg_base, ldbuf->idt_desc.limit);
/* Do the tricky bits now. */
load_rm_segs(seg_flags);
load_pm_segs();
ldall_finish();
} else {
/* There isn't much point in executing the invalid opcode handler
* in an endless loop, so halt right here.
*/
int_enable();
halt_forever();
}
}