VirtualBox

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

Last change on this file since 87766 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette