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
|
#include "kjarna.h"
#include <stdnoreturn.h>
#include <asm/x86_64/msr.h>
#include <bits/x86_64/msr.h>
#include <bits/x86_64/descriptor.h>
#include <libc/stdio.h>
#include <posix/sys/mman.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}
};
typedef uint64_t syscall_delegate_parameters[6];
struct syscall_context_return_state
{
uint64_t rflags; // 8
struct descriptor_table_register_long gdtr; // 24
uint64_t return_ds; // 32
uintptr_t return_rip; // 40
uint64_t return_cs; // 48
};
struct syscall_context
{
uint64_t pad; // 8
uint64_t syscall_index; // 16
syscall_delegate_parameters parameters; // 64
struct syscall_context_return_state state;
};
typedef int64_t (syscall_delegate)(syscall_delegate_parameters params);
//constexpr int NR_SYSCALLS = 10;
//static syscall_delegate *syscall_handler_delegates[NR_SYSCALLS];
SYSV_ABI int64_t fake_syscall_handler(struct syscall_context *context)
{
(void)context;
return -1;
}
SYSV_ABI void fake_syscall_entry(void);
SYSV_ABI void fake_syscall_start(void *stack);
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);
}
static void *create_fake_syscall_stack(void *entry_addr)
{
constexpr size_t fake_syscall_stack_size = 0x20000; // 128KiB stack
char *stack_pointer_base = mmap(nullptr, fake_syscall_stack_size, 0, 0, -1, 0);
void *stack_pointer_head = stack_pointer_base + fake_syscall_stack_size;
struct syscall_context *context = stack_alloc(&stack_pointer_head, sizeof(*context));
context->state.rflags = 0;
context->state.gdtr.base = (uintptr_t)&fake_syscall_gdt;
context->state.gdtr.limit = sizeof(fake_syscall_gdt) - 1;
context->state.return_ds = 16;
context->state.return_cs = 8;
context->state.return_rip = (uintptr_t)entry_addr;
return stack_pointer_head;
}
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
struct kjarna_boot_image boot_image = get_boot_image();
install_syscall_handler();
void *fake_syscall_stack = create_fake_syscall_stack((void *)boot_image.entry);
fake_syscall_start(fake_syscall_stack);
}
|