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. */
|
---|
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 |
|
---|
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 |
|
---|
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 |
|
---|
208 | #ifdef EMU_386_LOADALL
|
---|
209 |
|
---|
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 |
|
---|
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 | #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 | }
|
---|