1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
|
/* $Id: invop.c $ */
/** @file
* Real mode invalid opcode handler.
*/
/*
* Copyright (C) 2013-2019 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#include <stdint.h>
#include <string.h>
#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;
} 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
} else {
/* There isn't much point in executing the invalid opcode handler
* in an endless loop, so halt right here.
*/
int_enable();
halt_forever();
}
}
|