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
|
#include "kjarna.h"
#include <stdnoreturn.h>
#include <asm/x86_64/msr.h>
#include <bits/x86_64/msr.h>
#include <bits/x86_64/descriptor.h>
/*
* Fake syscall mechanism -
*
* We have a GDT with entries for supervisory mode. These serve as the
* entries to satisfy the requirements of the SYSCALL instruction, as
* for some reason (possibly intentionally) the OVMF GDT is not laid out
* in a way to make use of the SYSCALL instruction possible.
*
* This causes us to have to work inside of constraints during loading time
* - All "user" mode execution entirely blocks interrupt processing. That
* means that "user" mode code must not execute "hlt", or the system will
* be locked.
* - When (if?) user input is required, it is always buffered. It is possible
* to simulate unbuffered input, at the cost of one syscall per transfer
* from "kernel" side to "user" side. This will cause high input latency.
* We will not be running Quake in this environment.
*
* These constraints are probably fine, as the loading process only needs to
* open files, map memory, etc.
*/
void *stack_alloc(void **stack_pointer, size_t alloc_size)
{
void *block = *(char **)stack_pointer -= alloc_size;
// maintain alignment
*(char **)stack_pointer -= alloc_size % sizeof(size_t);
return block;
}
static struct segment_descriptor const fake_syscall_gdt[] =
{
{ 0 },
{ 0xffff, 0, 0, 0x9a, 0xaf, 0},
{ 0xffff, 0, 0, 0x92, 0xcf, 0}
};
SYSV_ABI static void fake_syscall_handler(void)
{
while (true);
}
static void *return_rsp;
SYSV_ABI void fake_syscall_entry(void);
SYSV_ABI void fake_syscall_return(void *target_rsp, void **return_rsp, SYSV_ABI void (*callback)());
static void install_syscall_handler(void)
{
union msr_lstar lstar = { (uintptr_t)fake_syscall_entry };
union msr_star star = { { 0, 1 << 3, 1 << 3 | 3 } };
msr_write(MSR_INDEX_LSTAR, lstar.value);
msr_write(MSR_INDEX_STAR, star.value);
uint64_t efer = msr_read(MSR_INDEX_EFER);
efer |= 1;
msr_write(MSR_INDEX_EFER, efer);
}
struct context_stack_frame
{
struct descriptor_table_register_long lret_gdtr;
uint64_t lret_ds;
uint64_t lret_rip;
uint64_t lret_cs;
};
static void enter_boot_image(struct kjarna_boot_image *image)
{
size_t boot_stack_size = 0x10000;
char *boot_image_stack = calloc(1, boot_stack_size);
void *boot_image_stack_head = boot_image_stack + boot_stack_size;
struct context_stack_frame *stack_frame = stack_alloc(&boot_image_stack_head, sizeof(*stack_frame));
stack_frame->lret_cs = 8;
stack_frame->lret_ds = 16;
stack_frame->lret_rip = (uintptr_t)image->entry;
stack_frame->lret_gdtr.limit = sizeof(fake_syscall_gdt) - 1;
stack_frame->lret_gdtr.base = (uintptr_t)fake_syscall_gdt;
fake_syscall_return(boot_image_stack_head, &return_rsp, fake_syscall_handler);
while (true);
}
#include <libc/stdio.h>
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
struct kjarna_boot_image boot_image = get_boot_image();
install_syscall_handler();
enter_boot_image(&boot_image);
}
|