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
|
/* $Id: invop.c $ */
/** @file
* Real mode invalid opcode handler.
*/
/*
* Copyright (C) 2013 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"
/* 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);
/*
* 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;
#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();
} else {
/* There isn't much point in executing the invalid opcode handler
* in an endless loop, so halt right here.
*/
int_enable();
halt_forever();
}
}
|