diff options
Diffstat (limited to 'src/VBox/Devices/PC/BIOS/invop.c')
-rw-r--r-- | src/VBox/Devices/PC/BIOS/invop.c | 350 |
1 files changed, 350 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..cee14a27 --- /dev/null +++ b/src/VBox/Devices/PC/BIOS/invop.c @@ -0,0 +1,350 @@ +/* $Id: invop.c $ */ +/** @file + * Real mode invalid opcode handler. + */ + +/* + * Copyright (C) 2013-2019 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" + +//#define EMU_386_LOADALL + +/* 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); + +#ifdef EMU_386_LOADALL + +/* The layout of 386 LOADALL descriptors. */ +typedef struct tag_ldal3_desc { + uint32_t attr; /* Segment attributes. */ + uint32_t base; /* Expanded segment base. */ + uint32_t limit; /* Expanded segment limit. */ +} ldal3_desc; + +/* The 386 LOADALL memory buffer pointed to by ES:EDI. + */ +typedef struct tag_ldall_386 { + uint32_t cr0; /* 00h */ + uint32_t eflags; /* 04h */ + uint32_t eip; /* 08h */ + uint32_t edi; /* 0Ch */ + uint32_t esi; /* 10h */ + uint32_t ebp; /* 14h */ + uint32_t esp; /* 18h */ + uint32_t ebx; /* 1Ch */ + uint32_t edx; /* 20h */ + uint32_t ecx; /* 24h */ + uint32_t eax; /* 28h */ + uint32_t dr6; /* 2Ch */ + uint32_t dr7; /* 30h */ + uint32_t tr; /* 34h */ + uint32_t ldt; /* 38h */ + uint32_t gs; /* 3Ch */ + uint32_t fs; /* 40h */ + uint32_t ds; /* 44h */ + uint32_t ss; /* 4Ch */ + uint32_t cs; /* 48h */ + uint32_t es; /* 50h */ + ldal3_desc tss_desc; /* 54h */ + ldal3_desc idt_desc; /* 60h */ + ldal3_desc gdt_desc; /* 6Ch */ + ldal3_desc ldt_desc; /* 78h */ + ldal3_desc gs_desc; /* 84h */ + ldal3_desc fs_desc; /* 90h */ + ldal3_desc ds_desc; /* 9Ch */ + ldal3_desc ss_desc; /* A8h */ + ldal3_desc cs_desc; /* B4h */ + ldal3_desc es_desc; /* C0h */ +} ldall_386_s; +ct_assert(sizeof(ldall_386_s) == 0xCC); + +#endif + +/* + * 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; + +#ifdef EMU_386_LOADALL + +/* 386 version of the above. */ +void ldal3_finish(void); +#pragma aux ldal3_finish = \ + ".386" \ + "mov sp, 28h" \ + "popad" \ + "mov sp, ss:[18h]" \ + "sub sp, 6" \ + "mov ss, ss:[48h]" \ + "iret" \ + parm nomemory modify nomemory aborts; + +/* 386 version of load_rm_segs. + * NB: Must not touch CX! + */ +void load_rm_seg3(int seg_flags, uint16_t ss_base); +#pragma aux load_rm_seg3 = \ + "mov ss, ax" \ + "mov ax, ss:[44h]" \ + "mov ds, ax" \ + "mov ax, ss:[50h]" \ + "mov es, ax" \ + parm [ax] [cx] nomemory modify nomemory; + +#endif + +#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(); +#ifdef EMU_386_LOADALL + } else if (*(uint16_t __far *)ins == 0x070F) { + /* 386 LOADALL. NB: Same opcode as SYSRET. */ + ldall_386_s __far *ldbuf = (void __far *)es :> gr.u.r16.di; /* Assume 16-bit value in EDI. */ + ldall_286_s __far *ldbuf2 = 0 :> 0x800; + iret_addr_t __far *ret_addr; + uint32_t seg_base; + int seg_flags = 0; + + /* NB: BIG FAT ASSUMPTION! Users of 386 LOADALL are assumed to also + * have a 286 LOADALL buffer at physical address 800h. We use unused fields + * in that buffer for temporary storage. + */ + + /* Set up return stack. */ + ret_addr = ldbuf->ss :> (ldbuf->esp - sizeof(iret_addr_t)); + ret_addr->ip = ldbuf->eip; + ret_addr->cs = ldbuf->cs; + ret_addr->flags.u.r16.flags = ldbuf->eflags; + + /* Examine ES/DS. */ + seg_base = ldbuf->es_desc.base; + if (seg_base != (uint32_t)ldbuf->es << 4) + seg_flags |= LOAD_ES; + seg_base = ldbuf->ds_desc.base; + 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). */ + ldbuf2->unused2[0] = ldbuf->es_desc.limit; + ldbuf2->unused2[1] = (uint16_t)ldbuf->es_desc.base; + ldbuf2->unused2[2] = (ldbuf->es_desc.attr & 0xFF00) | (ldbuf->es_desc.base >> 16); + ldbuf2->unused2[3] = 0; + + /* Store the DS base/limit/attributes in other unused words. */ + ldbuf2->unused1[0] = ldbuf->ds_desc.limit; + ldbuf2->unused1[1] = (uint16_t)ldbuf->ds_desc.base; + ldbuf2->unused1[2] = (ldbuf->ds_desc.attr & 0xFF00) | (ldbuf->ds_desc.base >> 16); + + /* Load the IDTR as specified. */ + seg_base = ldbuf->idt_desc.base; + load_idtr(seg_base, ldbuf->idt_desc.limit); + + /* Do the tricky bits now. */ + load_rm_seg3(es, seg_flags); + load_pm_segs(); + ldal3_finish(); +#endif + } else { + /* There isn't much point in executing the invalid opcode handler + * in an endless loop, so halt right here. + */ + int_enable(); + halt_forever(); + } +} |