[49286] | 1 | /* $Id: invop.c 98103 2023-01-17 14:15:46Z vboxsync $ */
|
---|
| 2 | /** @file
|
---|
| 3 | * Real mode invalid opcode handler.
|
---|
| 4 | */
|
---|
| 5 |
|
---|
| 6 | /*
|
---|
[98103] | 7 | * Copyright (C) 2013-2023 Oracle and/or its affiliates.
|
---|
[49286] | 8 | *
|
---|
[96407] | 9 | * This file is part of VirtualBox base platform packages, as
|
---|
| 10 | * available from https://www.virtualbox.org.
|
---|
| 11 | *
|
---|
| 12 | * This program is free software; you can redistribute it and/or
|
---|
| 13 | * modify it under the terms of the GNU General Public License
|
---|
| 14 | * as published by the Free Software Foundation, in version 3 of the
|
---|
| 15 | * License.
|
---|
| 16 | *
|
---|
| 17 | * This program is distributed in the hope that it will be useful, but
|
---|
| 18 | * WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
| 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
| 20 | * General Public License for more details.
|
---|
| 21 | *
|
---|
| 22 | * You should have received a copy of the GNU General Public License
|
---|
| 23 | * along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
| 24 | *
|
---|
| 25 | * SPDX-License-Identifier: GPL-3.0-only
|
---|
[49286] | 26 | */
|
---|
| 27 |
|
---|
| 28 | #include <stdint.h>
|
---|
| 29 | #include <string.h>
|
---|
| 30 | #include "biosint.h"
|
---|
| 31 | #include "inlines.h"
|
---|
| 32 |
|
---|
[59765] | 33 | //#define EMU_386_LOADALL
|
---|
| 34 |
|
---|
[49286] | 35 | /* The layout of 286 LOADALL descriptors. */
|
---|
| 36 | typedef struct tag_ldall_desc {
|
---|
| 37 | uint16_t base_lo; /* Bits 0-15 of segment base. */
|
---|
| 38 | uint8_t base_hi; /* Bits 16-13 of segment base. */
|
---|
| 39 | uint8_t attr; /* Segment attributes. */
|
---|
| 40 | uint16_t limit; /* Segment limit. */
|
---|
| 41 | } ldall_desc;
|
---|
| 42 |
|
---|
| 43 | /* The 286 LOADALL memory buffer at physical address 800h. From
|
---|
| 44 | * The Undocumented PC.
|
---|
| 45 | */
|
---|
| 46 | typedef struct tag_ldall_286 {
|
---|
| 47 | uint16_t unused1[3];
|
---|
| 48 | uint16_t msw; /* 806h */
|
---|
| 49 | uint16_t unused2[7];
|
---|
| 50 | uint16_t tr; /* 816h */
|
---|
| 51 | uint16_t flags; /* 818h */
|
---|
| 52 | uint16_t ip; /* 81Ah */
|
---|
| 53 | uint16_t ldt; /* 81Ch */
|
---|
| 54 | uint16_t ds; /* 81Eh */
|
---|
| 55 | uint16_t ss; /* 820h */
|
---|
| 56 | uint16_t cs; /* 822h */
|
---|
| 57 | uint16_t es; /* 824h */
|
---|
| 58 | uint16_t di; /* 826h */
|
---|
| 59 | uint16_t si; /* 828h */
|
---|
| 60 | uint16_t bp; /* 82Ah */
|
---|
| 61 | uint16_t sp; /* 82Ch */
|
---|
| 62 | uint16_t bx; /* 82Eh */
|
---|
| 63 | uint16_t dx; /* 830h */
|
---|
| 64 | uint16_t cx; /* 832h */
|
---|
| 65 | uint16_t ax; /* 834h */
|
---|
| 66 | ldall_desc es_desc; /* 836h */
|
---|
| 67 | ldall_desc cs_desc; /* 83Ch */
|
---|
| 68 | ldall_desc ss_desc; /* 842h */
|
---|
| 69 | ldall_desc ds_desc; /* 848h */
|
---|
| 70 | ldall_desc gdt_desc; /* 84Eh */
|
---|
| 71 | ldall_desc ldt_desc; /* 854h */
|
---|
| 72 | ldall_desc idt_desc; /* 85Ah */
|
---|
| 73 | ldall_desc tss_desc; /* 860h */
|
---|
| 74 | } ldall_286_s;
|
---|
| 75 | ct_assert(sizeof(ldall_286_s) == 0x66);
|
---|
| 76 |
|
---|
[59765] | 77 | #ifdef EMU_386_LOADALL
|
---|
| 78 |
|
---|
| 79 | /* The layout of 386 LOADALL descriptors. */
|
---|
| 80 | typedef struct tag_ldal3_desc {
|
---|
| 81 | uint32_t attr; /* Segment attributes. */
|
---|
| 82 | uint32_t base; /* Expanded segment base. */
|
---|
| 83 | uint32_t limit; /* Expanded segment limit. */
|
---|
| 84 | } ldal3_desc;
|
---|
| 85 |
|
---|
| 86 | /* The 386 LOADALL memory buffer pointed to by ES:EDI.
|
---|
| 87 | */
|
---|
| 88 | typedef struct tag_ldall_386 {
|
---|
| 89 | uint32_t cr0; /* 00h */
|
---|
| 90 | uint32_t eflags; /* 04h */
|
---|
| 91 | uint32_t eip; /* 08h */
|
---|
| 92 | uint32_t edi; /* 0Ch */
|
---|
| 93 | uint32_t esi; /* 10h */
|
---|
| 94 | uint32_t ebp; /* 14h */
|
---|
| 95 | uint32_t esp; /* 18h */
|
---|
| 96 | uint32_t ebx; /* 1Ch */
|
---|
| 97 | uint32_t edx; /* 20h */
|
---|
| 98 | uint32_t ecx; /* 24h */
|
---|
| 99 | uint32_t eax; /* 28h */
|
---|
| 100 | uint32_t dr6; /* 2Ch */
|
---|
| 101 | uint32_t dr7; /* 30h */
|
---|
| 102 | uint32_t tr; /* 34h */
|
---|
| 103 | uint32_t ldt; /* 38h */
|
---|
| 104 | uint32_t gs; /* 3Ch */
|
---|
| 105 | uint32_t fs; /* 40h */
|
---|
| 106 | uint32_t ds; /* 44h */
|
---|
| 107 | uint32_t ss; /* 4Ch */
|
---|
| 108 | uint32_t cs; /* 48h */
|
---|
| 109 | uint32_t es; /* 50h */
|
---|
| 110 | ldal3_desc tss_desc; /* 54h */
|
---|
| 111 | ldal3_desc idt_desc; /* 60h */
|
---|
| 112 | ldal3_desc gdt_desc; /* 6Ch */
|
---|
| 113 | ldal3_desc ldt_desc; /* 78h */
|
---|
| 114 | ldal3_desc gs_desc; /* 84h */
|
---|
| 115 | ldal3_desc fs_desc; /* 90h */
|
---|
| 116 | ldal3_desc ds_desc; /* 9Ch */
|
---|
| 117 | ldal3_desc ss_desc; /* A8h */
|
---|
| 118 | ldal3_desc cs_desc; /* B4h */
|
---|
| 119 | ldal3_desc es_desc; /* C0h */
|
---|
| 120 | } ldall_386_s;
|
---|
| 121 | ct_assert(sizeof(ldall_386_s) == 0xCC);
|
---|
| 122 |
|
---|
| 123 | #endif
|
---|
| 124 |
|
---|
[49286] | 125 | /*
|
---|
| 126 | * LOADALL emulation assumptions:
|
---|
| 127 | * - MSW indicates real mode
|
---|
| 128 | * - Standard real mode CS and SS is to be used
|
---|
| 129 | * - Segment values of non-RM segments (if any) do not matter
|
---|
| 130 | * - Standard segment attributes are used
|
---|
| 131 | */
|
---|
| 132 |
|
---|
| 133 | /* A wrapper for LIDT. */
|
---|
| 134 | void load_idtr(uint32_t base, uint16_t limit);
|
---|
| 135 | #pragma aux load_idtr = \
|
---|
| 136 | ".286p" \
|
---|
| 137 | "mov bx, sp" \
|
---|
| 138 | "lidt fword ptr ss:[bx]"\
|
---|
| 139 | parm caller reverse [] modify [bx] exact;
|
---|
| 140 |
|
---|
| 141 | /* A wrapper for LGDT. */
|
---|
| 142 | void load_gdtr(uint32_t base, uint16_t limit);
|
---|
| 143 | #pragma aux load_gdtr = \
|
---|
| 144 | ".286p" \
|
---|
| 145 | "mov bx, sp" \
|
---|
| 146 | "lgdt fword ptr ss:[bx]"\
|
---|
| 147 | parm caller reverse [] modify [bx] exact;
|
---|
| 148 |
|
---|
| 149 | /* Load DS/ES as real-mode segments. May be overwritten later.
|
---|
| 150 | * NB: Loads SS with 80h to address the LOADALL buffer. Must
|
---|
| 151 | * not touch CX!
|
---|
| 152 | */
|
---|
| 153 | void load_rm_segs(int seg_flags);
|
---|
| 154 | #pragma aux load_rm_segs = \
|
---|
| 155 | "mov ax, 80h" \
|
---|
| 156 | "mov ss, ax" \
|
---|
| 157 | "mov ax, ss:[1Eh]" \
|
---|
| 158 | "mov ds, ax" \
|
---|
| 159 | "mov ax, ss:[24h]" \
|
---|
| 160 | "mov es, ax" \
|
---|
| 161 | parm [cx] nomemory modify nomemory;
|
---|
| 162 |
|
---|
| 163 | /* Briefly switch to protected mode and load ES and/or DS if necessary.
|
---|
| 164 | * NB: Trashes high bits of EAX, but that should be safe. Expects flags
|
---|
| 165 | * in CX.
|
---|
| 166 | */
|
---|
| 167 | void load_pm_segs(void);
|
---|
| 168 | #pragma aux load_pm_segs = \
|
---|
| 169 | ".386p" \
|
---|
| 170 | "smsw ax" \
|
---|
| 171 | "inc ax" \
|
---|
| 172 | "lmsw ax" \
|
---|
| 173 | "mov ax, 8" \
|
---|
| 174 | "test cx, 1" \
|
---|
| 175 | "jz skip_es" \
|
---|
| 176 | "mov es, ax" \
|
---|
| 177 | "skip_es:" \
|
---|
| 178 | "test cx, 2" \
|
---|
| 179 | "jz skip_ds" \
|
---|
| 180 | "mov bx,ss:[00h]" \
|
---|
| 181 | "mov ss:[08h], bx" \
|
---|
| 182 | "mov bx,ss:[02h]" \
|
---|
| 183 | "mov ss:[0Ah], bx" \
|
---|
| 184 | "mov bx,ss:[04h]" \
|
---|
| 185 | "mov ss:[0Ch], bx" \
|
---|
| 186 | "mov ds, ax" \
|
---|
| 187 | "skip_ds:" \
|
---|
| 188 | "mov eax, cr0" \
|
---|
| 189 | "dec ax" \
|
---|
| 190 | "mov cr0, eax" \
|
---|
| 191 | parm nomemory modify nomemory;
|
---|
| 192 |
|
---|
| 193 | /* Complete LOADALL emulation: Restore general-purpose registers, stack
|
---|
| 194 | * pointer, and CS:IP. NB: The LOADALL instruction stores registers in
|
---|
| 195 | * the same order as PUSHA. Surprise, surprise!
|
---|
| 196 | */
|
---|
| 197 | void ldall_finish(void);
|
---|
| 198 | #pragma aux ldall_finish = \
|
---|
| 199 | ".286" \
|
---|
| 200 | "mov sp, 26h" \
|
---|
| 201 | "popa" \
|
---|
| 202 | "mov sp, ss:[2Ch]" \
|
---|
| 203 | "sub sp, 6" \
|
---|
| 204 | "mov ss, ss:[20h]" \
|
---|
| 205 | "iret" \
|
---|
| 206 | parm nomemory modify nomemory aborts;
|
---|
| 207 |
|
---|
[59765] | 208 | #ifdef EMU_386_LOADALL
|
---|
[49286] | 209 |
|
---|
[59765] | 210 | /* 386 version of the above. */
|
---|
| 211 | void ldal3_finish(void);
|
---|
| 212 | #pragma aux ldal3_finish = \
|
---|
| 213 | ".386" \
|
---|
| 214 | "mov sp, 28h" \
|
---|
| 215 | "popad" \
|
---|
| 216 | "mov sp, ss:[18h]" \
|
---|
| 217 | "sub sp, 6" \
|
---|
| 218 | "mov ss, ss:[48h]" \
|
---|
| 219 | "iret" \
|
---|
| 220 | parm nomemory modify nomemory aborts;
|
---|
| 221 |
|
---|
| 222 | /* 386 version of load_rm_segs.
|
---|
| 223 | * NB: Must not touch CX!
|
---|
| 224 | */
|
---|
| 225 | void load_rm_seg3(int seg_flags, uint16_t ss_base);
|
---|
| 226 | #pragma aux load_rm_seg3 = \
|
---|
| 227 | "mov ss, ax" \
|
---|
| 228 | "mov ax, ss:[44h]" \
|
---|
| 229 | "mov ds, ax" \
|
---|
| 230 | "mov ax, ss:[50h]" \
|
---|
| 231 | "mov es, ax" \
|
---|
| 232 | parm [ax] [cx] nomemory modify nomemory;
|
---|
| 233 |
|
---|
| 234 | #endif
|
---|
| 235 |
|
---|
[49286] | 236 | #define LOAD_ES 0x01 /* ES needs to be loaded in protected mode. */
|
---|
| 237 | #define LOAD_DS 0x02 /* DS needs to be loaded in protected mode. */
|
---|
| 238 |
|
---|
| 239 | /*
|
---|
| 240 | * The invalid opcode handler exists to work around fishy application
|
---|
| 241 | * code and paper over CPU generation differences:
|
---|
| 242 | *
|
---|
| 243 | * - Skip redundant LOCK prefixes (allowed on 8086, #UD on 286+).
|
---|
| 244 | * - Emulate just enough of 286 LOADALL.
|
---|
| 245 | *
|
---|
| 246 | */
|
---|
| 247 | void BIOSCALL inv_op_handler(uint16_t ds, uint16_t es, pusha_regs_t gr, volatile iret_addr_t ra)
|
---|
| 248 | {
|
---|
| 249 | void __far *ins = ra.cs :> ra.ip;
|
---|
| 250 |
|
---|
| 251 | if (*(uint8_t __far *)ins == 0xF0) {
|
---|
| 252 | /* LOCK prefix - skip over it and try again. */
|
---|
| 253 | ++ra.ip;
|
---|
| 254 | } else if (*(uint16_t __far *)ins == 0x050F) {
|
---|
| 255 | /* 286 LOADALL. NB: Same opcode as SYSCALL. */
|
---|
| 256 | ldall_286_s __far *ldbuf = 0 :> 0x800;
|
---|
| 257 | iret_addr_t __far *ret_addr;
|
---|
| 258 | uint32_t seg_base;
|
---|
| 259 | int seg_flags = 0;
|
---|
| 260 |
|
---|
| 261 | /* One of the challenges is that we must restore SS:SP as well
|
---|
| 262 | * as CS:IP and FLAGS from the LOADALL buffer. We copy CS/IP/FLAGS
|
---|
| 263 | * from the buffer just below the SS:SP values from the buffer so
|
---|
| 264 | * that we can eventually IRET to the desired CS/IP/FLAGS/SS/SP
|
---|
| 265 | * values in one go.
|
---|
| 266 | */
|
---|
| 267 | ret_addr = ldbuf->ss :> (ldbuf->sp - sizeof(iret_addr_t));
|
---|
| 268 | ret_addr->ip = ldbuf->ip;
|
---|
| 269 | ret_addr->cs = ldbuf->cs;
|
---|
| 270 | ret_addr->flags.u.r16.flags = ldbuf->flags;
|
---|
| 271 |
|
---|
| 272 | /* Examine ES/DS. */
|
---|
| 273 | seg_base = ldbuf->es_desc.base_lo | (uint32_t)ldbuf->es_desc.base_hi << 16;
|
---|
| 274 | if (seg_base != (uint32_t)ldbuf->es << 4)
|
---|
| 275 | seg_flags |= LOAD_ES;
|
---|
| 276 | seg_base = ldbuf->ds_desc.base_lo | (uint32_t)ldbuf->ds_desc.base_hi << 16;
|
---|
| 277 | if (seg_base != (uint32_t)ldbuf->ds << 4)
|
---|
| 278 | seg_flags |= LOAD_DS;
|
---|
| 279 |
|
---|
| 280 | /* The LOADALL buffer doubles as a tiny GDT. */
|
---|
| 281 | load_gdtr(0x800, 4 * 8 - 1);
|
---|
| 282 |
|
---|
| 283 | /* Store the ES base/limit/attributes in the unused words (GDT selector 8). */
|
---|
| 284 | ldbuf->unused2[0] = ldbuf->es_desc.limit;
|
---|
| 285 | ldbuf->unused2[1] = ldbuf->es_desc.base_lo;
|
---|
| 286 | ldbuf->unused2[2] = (ldbuf->es_desc.attr << 8) | ldbuf->es_desc.base_hi;
|
---|
| 287 | ldbuf->unused2[3] = 0;
|
---|
| 288 |
|
---|
| 289 | /* Store the DS base/limit/attributes in other unused words. */
|
---|
| 290 | ldbuf->unused1[0] = ldbuf->ds_desc.limit;
|
---|
| 291 | ldbuf->unused1[1] = ldbuf->ds_desc.base_lo;
|
---|
| 292 | ldbuf->unused1[2] = (ldbuf->ds_desc.attr << 8) | ldbuf->ds_desc.base_hi;
|
---|
| 293 |
|
---|
| 294 | /* Load the IDTR as specified. */
|
---|
| 295 | seg_base = ldbuf->idt_desc.base_lo | (uint32_t)ldbuf->idt_desc.base_hi << 16;
|
---|
| 296 | load_idtr(seg_base, ldbuf->idt_desc.limit);
|
---|
| 297 |
|
---|
| 298 | /* Do the tricky bits now. */
|
---|
| 299 | load_rm_segs(seg_flags);
|
---|
| 300 | load_pm_segs();
|
---|
| 301 | ldall_finish();
|
---|
[59765] | 302 | #ifdef EMU_386_LOADALL
|
---|
| 303 | } else if (*(uint16_t __far *)ins == 0x070F) {
|
---|
| 304 | /* 386 LOADALL. NB: Same opcode as SYSRET. */
|
---|
| 305 | ldall_386_s __far *ldbuf = (void __far *)es :> gr.u.r16.di; /* Assume 16-bit value in EDI. */
|
---|
| 306 | ldall_286_s __far *ldbuf2 = 0 :> 0x800;
|
---|
| 307 | iret_addr_t __far *ret_addr;
|
---|
| 308 | uint32_t seg_base;
|
---|
| 309 | int seg_flags = 0;
|
---|
| 310 |
|
---|
| 311 | /* NB: BIG FAT ASSUMPTION! Users of 386 LOADALL are assumed to also
|
---|
| 312 | * have a 286 LOADALL buffer at physical address 800h. We use unused fields
|
---|
| 313 | * in that buffer for temporary storage.
|
---|
| 314 | */
|
---|
| 315 |
|
---|
| 316 | /* Set up return stack. */
|
---|
| 317 | ret_addr = ldbuf->ss :> (ldbuf->esp - sizeof(iret_addr_t));
|
---|
| 318 | ret_addr->ip = ldbuf->eip;
|
---|
| 319 | ret_addr->cs = ldbuf->cs;
|
---|
| 320 | ret_addr->flags.u.r16.flags = ldbuf->eflags;
|
---|
| 321 |
|
---|
| 322 | /* Examine ES/DS. */
|
---|
| 323 | seg_base = ldbuf->es_desc.base;
|
---|
| 324 | if (seg_base != (uint32_t)ldbuf->es << 4)
|
---|
| 325 | seg_flags |= LOAD_ES;
|
---|
| 326 | seg_base = ldbuf->ds_desc.base;
|
---|
| 327 | if (seg_base != (uint32_t)ldbuf->ds << 4)
|
---|
| 328 | seg_flags |= LOAD_DS;
|
---|
| 329 |
|
---|
| 330 | /* The LOADALL buffer doubles as a tiny GDT. */
|
---|
| 331 | load_gdtr(0x800, 4 * 8 - 1);
|
---|
| 332 |
|
---|
| 333 | /* Store the ES base/limit/attributes in the unused words (GDT selector 8). */
|
---|
| 334 | ldbuf2->unused2[0] = ldbuf->es_desc.limit;
|
---|
| 335 | ldbuf2->unused2[1] = (uint16_t)ldbuf->es_desc.base;
|
---|
| 336 | ldbuf2->unused2[2] = (ldbuf->es_desc.attr & 0xFF00) | (ldbuf->es_desc.base >> 16);
|
---|
| 337 | ldbuf2->unused2[3] = 0;
|
---|
| 338 |
|
---|
| 339 | /* Store the DS base/limit/attributes in other unused words. */
|
---|
| 340 | ldbuf2->unused1[0] = ldbuf->ds_desc.limit;
|
---|
| 341 | ldbuf2->unused1[1] = (uint16_t)ldbuf->ds_desc.base;
|
---|
| 342 | ldbuf2->unused1[2] = (ldbuf->ds_desc.attr & 0xFF00) | (ldbuf->ds_desc.base >> 16);
|
---|
| 343 |
|
---|
| 344 | /* Load the IDTR as specified. */
|
---|
| 345 | seg_base = ldbuf->idt_desc.base;
|
---|
| 346 | load_idtr(seg_base, ldbuf->idt_desc.limit);
|
---|
| 347 |
|
---|
| 348 | /* Do the tricky bits now. */
|
---|
| 349 | load_rm_seg3(es, seg_flags);
|
---|
| 350 | load_pm_segs();
|
---|
| 351 | ldal3_finish();
|
---|
| 352 | #endif
|
---|
[49286] | 353 | } else {
|
---|
| 354 | /* There isn't much point in executing the invalid opcode handler
|
---|
| 355 | * in an endless loop, so halt right here.
|
---|
| 356 | */
|
---|
| 357 | int_enable();
|
---|
| 358 | halt_forever();
|
---|
| 359 | }
|
---|
| 360 | }
|
---|