/* $Id: invop.c 100658 2023-07-20 07:43:52Z vboxsync $ */ /** @file * Real mode invalid opcode handler. */ /* * Copyright (C) 2013-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ #include #include #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; #if VBOX_BIOS_CPU >= 80386 } 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 #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(); } }