kernel: ldt support

This commit is contained in:
Matheus
2026-03-20 21:23:59 -03:00
parent a73c82f912
commit 41f97c6c61
17 changed files with 315 additions and 94 deletions
+17
View File
@@ -8,11 +8,17 @@
#define ARCH_CTL_GET_GSBASE 2
#define ARCH_CTL_GET_FSBASE 3
#define ARCH_CTL_SET_SYSTRACE 4
#define ARCH_CTL_SET_LDT_ENTRY 5
#define ARCH_CTL_SYSTRACE_OFF 0
#define ARCH_CTL_SYSTRACE_SELF 1
#define ARCH_CTL_SYSTRACE_ALL 2
typedef struct {
uint64_t entry;
uint16_t seg;
} archctl_ldt_request_t;
static inline int copy_u64_to_user(void *ptr, uint64_t v) {
return usercopy_touser(ptr, &v, sizeof(v));
}
@@ -25,9 +31,11 @@ syscallret_t syscall_archctl(context_t *context, int func, void *arg) {
if (!IS_USER_ADDRESS(arg)) {
ret.errno = EFAULT;
ret.ret = -1;
return ret;
}
uint64_t scratch;
switch (func) {
case ARCH_CTL_SET_GSBASE:
// kernelgsbase because it will be switched out to user
@@ -42,6 +50,14 @@ syscallret_t syscall_archctl(context_t *context, int func, void *arg) {
case ARCH_CTL_GET_FSBASE:
ret.errno = copy_u64_to_user(arg, rdmsr(MSR_FSBASE));
break;
case ARCH_CTL_SET_LDT_ENTRY:
archctl_ldt_request_t req;
ret.errno = usercopy_fromuser(&req, arg, sizeof(req));
if (ret.errno)
break;
ret.errno = arch_ldt_set_entry(req.seg, req.entry);
break;
case ARCH_CTL_SET_SYSTRACE:
#ifdef SYSCALL_LOGGING
switch ((uint64_t)arg) {
@@ -69,5 +85,6 @@ syscallret_t syscall_archctl(context_t *context, int func, void *arg) {
break;
}
ret.ret = ret.errno ? -1 : 0;
return ret;
}
+2 -2
View File
@@ -32,8 +32,8 @@ arch_context_switch:
add rsp,8 ; remove error code
; check if swapgs is needed
cmp qword [rsp+8], 0x23
jne .notneeded2
cmp qword [rsp+8], 0x8
je .notneeded2
swapgs
.notneeded2:
o64 iret
+97 -81
View File
@@ -3,6 +3,7 @@
#include <arch/cpuid.h>
#include <kernel/slab.h>
#include <logging.h>
#include <arch/gdt.h>
#define XCR0_X87 (1ull << 0)
#define XCR0_SSE (1ull << 1)
@@ -12,120 +13,135 @@
#define XCR0_MASK (XCR0_AVX512 | XCR0_AVX | XCR0_SSE | XCR0_X87)
static enum {
FXSAVE,
XSAVE,
XSAVEOPT,
FXSAVE,
XSAVE,
XSAVEOPT,
} xsave_method;
size_t arch_xsave_size;
static uint64_t xcr0_value;
static scache_t *xsave_cache;
typedef struct {
uint16_t fcw;
uint16_t fsw;
uint8_t ftw;
uint8_t reserved1;
uint16_t fop;
uint64_t fip;
uint64_t fdp;
uint32_t mxcsr;
uint32_t mxcsr_mask;
struct {
uint64_t data[2];
} mm[8];
struct {
uint64_t data[2];
} xmm[16];
uint64_t reserved2[6];
uint64_t unused[6];
uint16_t fcw;
uint16_t fsw;
uint8_t ftw;
uint8_t reserved1;
uint16_t fop;
uint64_t fip;
uint64_t fdp;
uint32_t mxcsr;
uint32_t mxcsr_mask;
struct {
uint64_t data[2];
} mm[8];
struct {
uint64_t data[2];
} xmm[16];
uint64_t reserved2[6];
uint64_t unused[6];
} fxsave_area_t;
void arch_extracontext_detect(void) {
if (!arch_xsave_size) {
cpuid_results_t results;
cpuid(1, &results);
if (cpuid_base_max_leaf() >= 0x0d && (results.ecx & (1 << 26))) {
xsave_method = XSAVE;
if (!arch_xsave_size) {
cpuid_results_t results;
cpuid(1, &results);
if (cpuid_base_max_leaf() >= 0x0d && (results.ecx & (1 << 26))) {
xsave_method = XSAVE;
cpuid_with_ecx(0x0d, 0, &results);
xcr0_value = ((uint64_t)results.edx << 32) | results.eax;
xcr0_value &= XCR0_MASK;
} else {
xsave_method = FXSAVE;
arch_xsave_size = 512;
}
}
cpuid_with_ecx(0x0d, 0, &results);
xcr0_value = ((uint64_t)results.edx << 32) | results.eax;
xcr0_value &= XCR0_MASK;
} else {
xsave_method = FXSAVE;
arch_xsave_size = 512;
}
}
if (xsave_method == FXSAVE) return;
if (xsave_method == FXSAVE) return;
size_t cr4;
asm volatile("mov %%cr4, %0" : "=r" (cr4));
cr4 |= (1u << 18);
asm volatile("mov %0, %%cr4" :: "r" (cr4));
size_t cr4;
asm volatile("mov %%cr4, %0" : "=r" (cr4));
cr4 |= (1u << 18);
asm volatile("mov %0, %%cr4" :: "r" (cr4));
uint32_t low = xcr0_value;
uint32_t high = xcr0_value >> 32;
asm volatile("xsetbv" :: "a" (low), "d" (high), "c" (0u));
uint32_t low = xcr0_value;
uint32_t high = xcr0_value >> 32;
asm volatile("xsetbv" :: "a" (low), "d" (high), "c" (0u));
if (!arch_xsave_size) {
// after xsetbv ebx is updated to the size of the context for the enabled features
cpuid_results_t results;
cpuid_with_ecx(0x0d, 0, &results);
arch_xsave_size = results.ebx;
if (!arch_xsave_size) {
// after xsetbv ebx is updated to the size of the context for the enabled features
cpuid_results_t results;
cpuid_with_ecx(0x0d, 0, &results);
arch_xsave_size = results.ebx;
cpuid_with_ecx(0x0d, 1, &results);
if (results.eax & 1) xsave_method = XSAVEOPT;
}
cpuid_with_ecx(0x0d, 1, &results);
if (results.eax & 1) xsave_method = XSAVEOPT;
}
}
int arch_extracontext_init(extracontext_t *context) {
if (!xsave_cache) {
xsave_cache = slab_newcache(arch_xsave_size, xsave_method == FXSAVE ? 16 : 64, NULL, NULL);
__assert(xsave_cache);
}
if (!xsave_cache) {
xsave_cache = slab_newcache(arch_xsave_size, xsave_method == FXSAVE ? 16 : 64, NULL, NULL);
__assert(xsave_cache);
}
context->xsave = slab_allocate(xsave_cache);
if (!context->xsave) return 1;
context->xsave = slab_allocate(xsave_cache);
if (!context->xsave) return 1;
// all sse exceptions masked
// initialise the x87 FPU state as it would be after the FNINIT instruction
memset(context->xsave, 0, arch_xsave_size);
fxsave_area_t *area = context->xsave;
area->mxcsr = 0x1f80;
area->fcw = 0x37f;
// all sse exceptions masked
// initialise the x87 FPU state as it would be after the FNINIT instruction
memset(context->xsave, 0, arch_xsave_size);
fxsave_area_t *area = context->xsave;
area->mxcsr = 0x1f80;
area->fcw = 0x37f;
return 0;
return 0;
}
void arch_extracontext_free(extracontext_t *context) {
slab_free(xsave_cache, context->xsave);
slab_free(xsave_cache, context->xsave);
}
void arch_extracontext_save(extracontext_t *context) {
context->gsbase = rdmsr(MSR_KERNELGSBASE);
context->fsbase = rdmsr(MSR_FSBASE);
context->gsbase = rdmsr(MSR_KERNELGSBASE);
context->fsbase = rdmsr(MSR_FSBASE);
switch (xsave_method) {
case FXSAVE: asm("fxsaveq %0" ::"m"(*(char(*)[arch_xsave_size])context->xsave) : "memory"); break;
case XSAVE: asm("xsaveq %0" ::"m"(*(char(*)[arch_xsave_size])context->xsave), "d"(-1), "a"(-1) : "memory"); break;
case XSAVEOPT: asm("xsaveoptq %0" ::"m"(*(char(*)[arch_xsave_size])context->xsave), "d"(-1), "a"(-1) : "memory"); break;
}
asm volatile("mov %%gs, %0" : "=r"(context->gs));
asm volatile("mov %%fs, %0" : "=r"(context->fs));
switch (xsave_method) {
case FXSAVE: asm("fxsaveq %0" ::"m"(*(char(*)[arch_xsave_size])context->xsave) : "memory"); break;
case XSAVE: asm("xsaveq %0" ::"m"(*(char(*)[arch_xsave_size])context->xsave), "d"(-1), "a"(-1) : "memory"); break;
case XSAVEOPT: asm("xsaveoptq %0" ::"m"(*(char(*)[arch_xsave_size])context->xsave), "d"(-1), "a"(-1) : "memory"); break;
}
}
void arch_extracontext_load(extracontext_t *context) {
wrmsr(MSR_KERNELGSBASE, context->gsbase);
wrmsr(MSR_FSBASE, context->fsbase);
if (current_thread()->proc && current_thread()->proc->ldt)
arch_gdt_set_ldt(current_thread()->proc->ldt, 0xffff);
else
arch_gdt_set_ldt(NULL, 0);
switch (xsave_method) {
case FXSAVE: asm("fxrstorq %0" ::"m"(*(char(*)[arch_xsave_size])context->xsave) : "memory"); break;
case XSAVE:
case XSAVEOPT: asm("xrstorq %0" ::"m"(*(char(*)[arch_xsave_size])context->xsave), "d"(-1), "a"(-1) : "memory"); break;
}
void *old_base = current_cpu();
if (!load_seg_gs(context->gs)) load_seg_gs(0);
wrmsr(MSR_GSBASE, (uint64_t)old_base);
wrmsr(MSR_KERNELGSBASE, context->gsbase);
if (!load_seg_fs(context->fs)) load_seg_fs(0);
wrmsr(MSR_FSBASE, context->fsbase);
switch (xsave_method) {
case FXSAVE: asm("fxrstorq %0" ::"m"(*(char(*)[arch_xsave_size])context->xsave) : "memory"); break;
case XSAVE:
case XSAVEOPT: asm("xrstorq %0" ::"m"(*(char(*)[arch_xsave_size])context->xsave), "d"(-1), "a"(-1) : "memory"); break;
}
}
void arch_extracontext_copy(extracontext_t *dst, const extracontext_t *src) {
// XXX is this behavior correct? ucontext doesnt seem to save it on linux and so it will not be saved
dst->gsbase = src->gsbase;
dst->fsbase = src->fsbase;
memcpy(dst->xsave, src->xsave, arch_xsave_size);
// XXX is this behavior correct? ucontext doesnt seem to save it on linux and so it will not be saved
dst->gsbase = src->gsbase;
dst->fsbase = src->fsbase;
dst->fs = dst->fs;
dst->gs = dst->gs;
memcpy(dst->xsave, src->xsave, arch_xsave_size);
}
+10
View File
@@ -46,6 +46,13 @@ static void x87isr(isr_t *self, context_t *ctx) {
}
}
static void reload_ldt(isr_t *self, context_t *ctx) {
if (current_thread() == NULL || current_thread()->proc == NULL || current_thread()->proc->ldt == NULL)
return;
arch_ldt_invalidate();
}
void arch_nmi_isr(context_t *ctx) {
// we are in an NMI context, running on IST0
// first load the proper gsbase to get access to cpu-local variables.
@@ -219,6 +226,9 @@ void arch_cpu_init() {
interrupt_register(16, x87isr, NULL, IPL_IGNORE);
interrupt_register(19, simdisr, NULL, IPL_IGNORE);
// and the ldt reload one
interrupt_register(0xfc, (void *)reload_ldt, NULL, IPL_MAX);
// get vendor string for cpu and max cpuid eax
__get_cpuid(0, &eax, &ebx, &ecx, &edx);
int *vendor_ptr = (int *)current_cpu()->vendor;
+23
View File
@@ -0,0 +1,23 @@
; segment is in rdi
; returns 1 for success or 0 for failure
load_seg_gs:
global load_seg_gs
xor rax, rax
mov gs, rdi
inc rax
ret
load_seg_gs_fallback:
global load_seg_gs_fallback
ret
; segment is in rdi
; returns 1 for success or 0 for failure
load_seg_fs:
global load_seg_fs
xor rax, rax
mov fs, rdi
inc rax
ret
load_seg_fs_fallback:
global load_seg_fs_fallback
ret
+23 -3
View File
@@ -5,16 +5,36 @@
#include <string.h>
#include <printf.h>
static uint64_t template[7] = {
static uint64_t template[9] = {
0, // NULL 0x0
0x00af9b000000ffff, // code64 0x8
0x00af93000000ffff, // data64 0x10
0x00aff3000000ffff, // udata64 0x18
0x00eff3000000ffff, // udata64 0x18
0x00affb000000ffff, // ucode64 0x20
0x0020890000000000, // low ist 0x28
0x0000000000000000 // high ist
0x0000000000000000, // high ist
0x0000000000000000, // LDT low
0x0000000000000000 // LDT high
};
// should be called with preemption DISABLED
void arch_gdt_set_ldt(void *base, size_t limit) {
if (base == NULL) {
current_cpu()->gdt[7] = 0;
current_cpu()->gdt[8] = 0;
asm volatile("lldt %%ax" : : "a"(0));
return;
}
uint64_t base_low = (uint64_t)base & 0xffffffff;
current_cpu()->gdt[7] = 0x82000000ffff; // present, ldt, limit 64k
current_cpu()->gdt[7] |= (base_low & 0xffff) << 16;
current_cpu()->gdt[7] |= ((base_low >> 16) & 0xff) << 32;
current_cpu()->gdt[7] |= ((base_low >> 24) & 0xff) << 56;
current_cpu()->gdt[8] = ((uint64_t)base >> 32) & 0xffffffff;
asm volatile("lldt %%ax" : : "a"(0x38));
}
typedef struct {
uint16_t size;
uint64_t address;
+4 -4
View File
@@ -17,8 +17,8 @@ isr_%+ i:
isr_common:
; check if swapgs is needed
cmp qword [rsp+24], 0x23
jne .notneeded1
cmp qword [rsp+24], 0x8
je .notneeded1
swapgs
.notneeded1:
; push context
@@ -90,8 +90,8 @@ isr_common:
add rsp,8 ; remove error code
; check if swapgs is needed
cmp qword [rsp+8], 0x23
jne .notneeded2
cmp qword [rsp+8], 0x8
je .notneeded2
swapgs
.notneeded2:
o64 iret
+72
View File
@@ -0,0 +1,72 @@
#include <arch/ldt.h>
#include <arch/gdt.h>
#include <kernel/alloc.h>
#include <errno.h>
#include <arch/cpu.h>
#include <arch/smp.h>
#include <spinlock.h>
static spinlock_t ldt_invalidate_lock;
static size_t done;
void arch_ldt_invalidate(void) {
arch_gdt_set_ldt(current_thread()->proc->ldt, 0xffff);
__atomic_add_fetch(&done, 1, __ATOMIC_SEQ_CST);
}
int arch_ldt_set_entry(int which, ldt_entry_t entry) {
// is an ldt already loaded?
if (__atomic_load_n(&current_thread()->proc->ldt, __ATOMIC_RELAXED) == NULL) {
// allocate one and atomically set it
ldt_entry_t *ldt = alloc(sizeof(ldt_entry_t) * 8192);
if (ldt == NULL)
return ENOMEM;
ldt_entry_t *expected = NULL;
bool swap_ok = __atomic_compare_exchange_n(&current_thread()->proc->ldt, &expected, ldt, false, __ATOMIC_SEQ_CST, __ATOMIC_RELAXED);
if (!swap_ok)
free(ldt);
bool do_shootdown = // do shootdown if
swap_ok // we actually need to
&& arch_smp_cpusawake >= 2 // there are multiple cpus in the system
&& current_thread()->proc->runningthreadcount > 1; // in a process which has multiple threads running
if (do_shootdown) {
long old_ipl = spinlock_acquire_raise_ipl(&ldt_invalidate_lock, IPL_DPC);
size_t shootdown_total = 1;
done = 0;
for (int i = 0; i < arch_smp_cpusawake; ++i) {
// reading the vmm context like this is racey but there are no ill side effects other than spurious shootdowns
// if a cpu changes to this vmmctx, they will already have done a ldt invalidation.
// if it changes out of it, same thing
if (smp_cpus[i] == current_cpu() || (smp_cpus[i]->vmmctx != current_thread()->vmmctx))
continue;
++shootdown_total;
arch_smp_send_ipi(smp_cpus[i], &smp_cpus[i]->isr[0xfc], ARCH_SMP_IPI_TARGET, false);
}
arch_ldt_invalidate();
while (__atomic_load_n(&done, __ATOMIC_SEQ_CST) != shootdown_total) asm("pause");
spinlock_release_lower_ipl(&ldt_invalidate_lock, old_ipl);
}
}
__atomic_store_n(&current_thread()->proc->ldt[which], entry, __ATOMIC_RELAXED);
return 0;
}
int arch_ldt_fork(ldt_entry_t **dst, ldt_entry_t *src) {
*dst = alloc(sizeof(ldt_entry_t) * 8192);
if (!*dst)
return ENOMEM;
memcpy(*dst, src, sizeof(ldt_entry_t) * 8192);
return 0;
}
+10
View File
@@ -349,6 +349,16 @@ static void pfisr(isr_t *self, context_t *ctx) {
}
static void gpfisr(isr_t *self, context_t *ctx) {
if (ctx->rip >= (uintptr_t)load_seg_gs && ctx->rip <= (uintptr_t)load_seg_gs_fallback) {
ctx->rip = (uintptr_t)load_seg_gs_fallback;
return;
}
if (ctx->rip >= (uintptr_t)load_seg_fs && ctx->rip <= (uintptr_t)load_seg_fs_fallback) {
ctx->rip = (uintptr_t)load_seg_fs_fallback;
return;
}
thread_t *thread = current_thread();
if (thread && thread->usercopyctx) {
memcpy(ctx, thread->usercopyctx, sizeof(context_t));
+8
View File
@@ -13,6 +13,10 @@
#include <kernel/thread.h>
#ifdef __x86_64__
#include <arch/ldt.h>
#endif
#define PROC_STATE_NORMAL 0
#define PROC_STATE_ZOMBIE 1
@@ -46,6 +50,10 @@ typedef struct proc_t {
spinlock_t exiting;
int nice;
#ifdef __x86_64__
ldt_entry_t *ldt;
#endif
spinlock_t jobctllock;
struct {
// NULL if this process is leader
+2 -1
View File
@@ -42,6 +42,7 @@ typedef struct {
uint64_t gsbase;
uint64_t fsbase;
void *xsave;
uint16_t fs, gs;
} extracontext_t;
extern size_t arch_xsave_size;
@@ -106,6 +107,6 @@ void arch_extracontext_copy(extracontext_t *dst, const extracontext_t *src);
arch_extracontext_save(c);
#define ARCH_CONTEXT_INTSTATUS(x) ((x)->rflags & 0x200 ? true : false)
#define ARCH_CONTEXT_ISUSER(x) ((x)->cs == 0x23)
#define ARCH_CONTEXT_ISUSER(x) ((x)->cs != 0x8)
#endif
+6 -1
View File
@@ -75,7 +75,7 @@ typedef struct cpu_t {
// architecture specific, does not need to be exposed
uint64_t gdt[7];
uint64_t gdt[9];
char vendor[13];
ist_t ist;
int acpiid;
@@ -90,6 +90,11 @@ typedef struct cpu_t {
#define CPU_HALT() asm volatile("hlt")
#define CPU_PAUSE() asm volatile("pause")
bool load_seg_gs(int segment);
void load_seg_gs_fallback(void);
bool load_seg_fs(int segment);
void load_seg_fs_fallback(void);
static inline uint32_t cpu_to_be_d(uint32_t d) {
return __builtin_bswap32(d);
}
+6 -1
View File
@@ -1,6 +1,11 @@
#ifndef _GDT_H
#define _GDT_H
void arch_gdt_reload();
#include <stddef.h>
void arch_gdt_reload(void);
// should be called with preemption DISABLED
void arch_gdt_set_ldt(void *base, size_t limit);
#endif
+11
View File
@@ -0,0 +1,11 @@
#ifndef _LDT_H
#define _LDT_H
#include <stdint.h>
typedef uint64_t ldt_entry_t;
void arch_ldt_invalidate(void);
int arch_ldt_set_entry(int which, ldt_entry_t entry);
int arch_ldt_fork(ldt_entry_t **dst, ldt_entry_t *src);
#endif
+11 -1
View File
@@ -66,6 +66,12 @@ static inline void arch_sigframe_init(sigframe_t *sigframe) {
sigframe->extracontext.xsave = sigframe->xsave;
}
#define CS_sig(context) (*((WORD *)&(context)->uc_mcontext.gregs[REG_CSGSFS] + 0))
#define GS_sig(context) (*((WORD *)&(context)->uc_mcontext.gregs[REG_CSGSFS] + 1))
#define FS_sig(context) (*((WORD *)&(context)->uc_mcontext.gregs[REG_CSGSFS] + 2))
#define SS_sig(context) (*((WORD *)&(context)->uc_mcontext.gregs[REG_CSGSFS] + 3))
static inline void arch_sigframe_prepare_mcontext(void *stack, sigframe_t *sigframe) {
context_t *context = &sigframe->context;
extracontext_t *extracontext = &sigframe->extracontext;
@@ -89,7 +95,7 @@ static inline void arch_sigframe_prepare_mcontext(void *stack, sigframe_t *sigfr
gregs[MCONTEXT_REG_RSP] = context->rsp;
gregs[MCONTEXT_REG_RIP] = context->rip;
gregs[MCONTEXT_REG_EFL] = context->rflags;
gregs[MCONTEXT_REG_CSGSFS] = 0;
gregs[MCONTEXT_REG_CSGSFS] = (uint64_t)context->cs | ((uint64_t)context->gs << 16) | ((uint64_t)context->fs << 32) | ((uint64_t)context->ss << 48);
gregs[MCONTEXT_REG_ERR] = context->error;
gregs[MCONTEXT_REG_TRAPNO] = context->irq;
gregs[MCONTEXT_REG_OLDMASK] = 0;
@@ -124,6 +130,10 @@ static inline void arch_sigframe_get_context_from_mcontext(sigframe_t *sigframe)
context->rflags = gregs[MCONTEXT_REG_EFL];
context->error = gregs[MCONTEXT_REG_ERR];
context->cr2 = gregs[MCONTEXT_REG_CR2];
context->cs = gregs[MCONTEXT_REG_CSGSFS] & 0xffff;
context->gs = (gregs[MCONTEXT_REG_CSGSFS] >> 16) & 0xffff;
context->fs = (gregs[MCONTEXT_REG_CSGSFS] >> 32) & 0xffff;
context->ss = (gregs[MCONTEXT_REG_CSGSFS] >> 48) & 0xffff;
}
+3
View File
@@ -131,6 +131,9 @@ proc_t *proc_create() {
}
void proc_destroy(proc_t *proc) {
#ifdef __x86_64__
if (proc->ldt) free(proc->ldt);
#endif
free(proc->fd);
slab_free(processcache, proc);
}
+10
View File
@@ -6,6 +6,7 @@
#include <kernel/jobctl.h>
#include <kernel/interrupt.h>
#include <arch/cpu.h>
#include <logging.h>
syscallret_t syscall_fork(context_t *ctx) {
syscallret_t ret = {
@@ -37,6 +38,14 @@ syscallret_t syscall_fork(context_t *ctx) {
if (ret.errno)
goto cleanup;
#ifdef __x86_64__
if (proc->ldt) {
ret.errno = arch_ldt_fork(&nproc->ldt, proc->ldt);
if (ret.errno)
goto cleanup;
}
#endif
MUTEX_ACQUIRE(&proc->mutex);
nproc->parent = proc;
@@ -80,5 +89,6 @@ syscallret_t syscall_fork(context_t *ctx) {
PROC_RELEASE(nproc);
cleanup:
// XXX no cleanup, TODO
return ret;
}