diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2014-03-26 19:21:20 +0000 |
---|---|---|
committer | <> | 2014-05-08 15:03:54 +0000 |
commit | fb123f93f9f5ce42c8e5785d2f8e0edaf951740e (patch) | |
tree | c2103d76aec5f1f10892cd1d3a38e24f665ae5db /src/VBox/Devices/PC/BIOS/invop.c | |
parent | 58ed4748338f9466599adfc8a9171280ed99e23f (diff) | |
download | VirtualBox-master.tar.gz |
Imported from /home/lorry/working-area/delta_VirtualBox/VirtualBox-4.3.10.tar.bz2.HEADVirtualBox-4.3.10master
Diffstat (limited to 'src/VBox/Devices/PC/BIOS/invop.c')
-rw-r--r-- | src/VBox/Devices/PC/BIOS/invop.c | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/src/VBox/Devices/PC/BIOS/invop.c b/src/VBox/Devices/PC/BIOS/invop.c new file mode 100644 index 00000000..bdebf54e --- /dev/null +++ b/src/VBox/Devices/PC/BIOS/invop.c @@ -0,0 +1,222 @@ +/* $Id: invop.c $ */ +/** @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(); + } +} |