summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/PC/BIOS/invop.c
blob: cee14a27c1829fd742f4bc6a6e549e52543f77d7 (plain)
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();
    }
}