VirtualBox

source: vbox/trunk/src/VBox/Devices/PC/BIOS/invop.c

Last change on this file was 100658, checked in by vboxsync, 10 months ago

BIOS: Reworked BIOS build to have a common core and add 286/386 specific modules (see bugref:6549).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 12.4 KB
Line 
1/* $Id: invop.c 100658 2023-07-20 07:43:52Z vboxsync $ */
2/** @file
3 * Real mode invalid opcode handler.
4 */
5
6/*
7 * Copyright (C) 2013-2023 Oracle and/or its affiliates.
8 *
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
26 */
27
28#include <stdint.h>
29#include <string.h>
30#include "biosint.h"
31#include "inlines.h"
32
33//#define EMU_386_LOADALL
34
35/* The layout of 286 LOADALL descriptors. */
36typedef 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 */
46typedef 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;
75ct_assert(sizeof(ldall_286_s) == 0x66);
76
77#ifdef EMU_386_LOADALL
78
79/* The layout of 386 LOADALL descriptors. */
80typedef 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 */
88typedef 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;
121ct_assert(sizeof(ldall_386_s) == 0xCC);
122
123#endif
124
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. */
134void 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. */
142void 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 */
153void 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 */
167void 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 */
197void 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
208#ifdef EMU_386_LOADALL
209
210/* 386 version of the above. */
211void 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 */
225void 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
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 */
247void 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#if VBOX_BIOS_CPU >= 80386
255 } else if (*(uint16_t __far *)ins == 0x050F) {
256 /* 286 LOADALL. NB: Same opcode as SYSCALL. */
257 ldall_286_s __far *ldbuf = 0 :> 0x800;
258 iret_addr_t __far *ret_addr;
259 uint32_t seg_base;
260 int seg_flags = 0;
261
262 /* One of the challenges is that we must restore SS:SP as well
263 * as CS:IP and FLAGS from the LOADALL buffer. We copy CS/IP/FLAGS
264 * from the buffer just below the SS:SP values from the buffer so
265 * that we can eventually IRET to the desired CS/IP/FLAGS/SS/SP
266 * values in one go.
267 */
268 ret_addr = ldbuf->ss :> (ldbuf->sp - sizeof(iret_addr_t));
269 ret_addr->ip = ldbuf->ip;
270 ret_addr->cs = ldbuf->cs;
271 ret_addr->flags.u.r16.flags = ldbuf->flags;
272
273 /* Examine ES/DS. */
274 seg_base = ldbuf->es_desc.base_lo | (uint32_t)ldbuf->es_desc.base_hi << 16;
275 if (seg_base != (uint32_t)ldbuf->es << 4)
276 seg_flags |= LOAD_ES;
277 seg_base = ldbuf->ds_desc.base_lo | (uint32_t)ldbuf->ds_desc.base_hi << 16;
278 if (seg_base != (uint32_t)ldbuf->ds << 4)
279 seg_flags |= LOAD_DS;
280
281 /* The LOADALL buffer doubles as a tiny GDT. */
282 load_gdtr(0x800, 4 * 8 - 1);
283
284 /* Store the ES base/limit/attributes in the unused words (GDT selector 8). */
285 ldbuf->unused2[0] = ldbuf->es_desc.limit;
286 ldbuf->unused2[1] = ldbuf->es_desc.base_lo;
287 ldbuf->unused2[2] = (ldbuf->es_desc.attr << 8) | ldbuf->es_desc.base_hi;
288 ldbuf->unused2[3] = 0;
289
290 /* Store the DS base/limit/attributes in other unused words. */
291 ldbuf->unused1[0] = ldbuf->ds_desc.limit;
292 ldbuf->unused1[1] = ldbuf->ds_desc.base_lo;
293 ldbuf->unused1[2] = (ldbuf->ds_desc.attr << 8) | ldbuf->ds_desc.base_hi;
294
295 /* Load the IDTR as specified. */
296 seg_base = ldbuf->idt_desc.base_lo | (uint32_t)ldbuf->idt_desc.base_hi << 16;
297 load_idtr(seg_base, ldbuf->idt_desc.limit);
298
299 /* Do the tricky bits now. */
300 load_rm_segs(seg_flags);
301 load_pm_segs();
302 ldall_finish();
303#ifdef EMU_386_LOADALL
304 } else if (*(uint16_t __far *)ins == 0x070F) {
305 /* 386 LOADALL. NB: Same opcode as SYSRET. */
306 ldall_386_s __far *ldbuf = (void __far *)es :> gr.u.r16.di; /* Assume 16-bit value in EDI. */
307 ldall_286_s __far *ldbuf2 = 0 :> 0x800;
308 iret_addr_t __far *ret_addr;
309 uint32_t seg_base;
310 int seg_flags = 0;
311
312 /* NB: BIG FAT ASSUMPTION! Users of 386 LOADALL are assumed to also
313 * have a 286 LOADALL buffer at physical address 800h. We use unused fields
314 * in that buffer for temporary storage.
315 */
316
317 /* Set up return stack. */
318 ret_addr = ldbuf->ss :> (ldbuf->esp - sizeof(iret_addr_t));
319 ret_addr->ip = ldbuf->eip;
320 ret_addr->cs = ldbuf->cs;
321 ret_addr->flags.u.r16.flags = ldbuf->eflags;
322
323 /* Examine ES/DS. */
324 seg_base = ldbuf->es_desc.base;
325 if (seg_base != (uint32_t)ldbuf->es << 4)
326 seg_flags |= LOAD_ES;
327 seg_base = ldbuf->ds_desc.base;
328 if (seg_base != (uint32_t)ldbuf->ds << 4)
329 seg_flags |= LOAD_DS;
330
331 /* The LOADALL buffer doubles as a tiny GDT. */
332 load_gdtr(0x800, 4 * 8 - 1);
333
334 /* Store the ES base/limit/attributes in the unused words (GDT selector 8). */
335 ldbuf2->unused2[0] = ldbuf->es_desc.limit;
336 ldbuf2->unused2[1] = (uint16_t)ldbuf->es_desc.base;
337 ldbuf2->unused2[2] = (ldbuf->es_desc.attr & 0xFF00) | (ldbuf->es_desc.base >> 16);
338 ldbuf2->unused2[3] = 0;
339
340 /* Store the DS base/limit/attributes in other unused words. */
341 ldbuf2->unused1[0] = ldbuf->ds_desc.limit;
342 ldbuf2->unused1[1] = (uint16_t)ldbuf->ds_desc.base;
343 ldbuf2->unused1[2] = (ldbuf->ds_desc.attr & 0xFF00) | (ldbuf->ds_desc.base >> 16);
344
345 /* Load the IDTR as specified. */
346 seg_base = ldbuf->idt_desc.base;
347 load_idtr(seg_base, ldbuf->idt_desc.limit);
348
349 /* Do the tricky bits now. */
350 load_rm_seg3(es, seg_flags);
351 load_pm_segs();
352 ldal3_finish();
353#endif
354#endif
355 } else {
356 /* There isn't much point in executing the invalid opcode handler
357 * in an endless loop, so halt right here.
358 */
359 int_enable();
360 halt_forever();
361 }
362}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use