132 lines
4.6 KiB
NASM
132 lines
4.6 KiB
NASM
; ke_switch.asm - context switch and thread entry stubs (MASM64 / ML64)
|
|
;
|
|
; Struct offsets:
|
|
; sizeof(OBJECT) = 16 (type:4, refcount:4, destroy:8)
|
|
; THREAD.SavedContext offset = 88
|
|
; THREAD.TssRsp0 offset = 104
|
|
; TSS64.RSP0 offset = 4
|
|
;
|
|
THREAD_SavedContext EQU 88
|
|
THREAD_TssRsp0 EQU 104
|
|
THREAD_Teb EQU 112
|
|
TSS_RSP0_OFFSET EQU 4
|
|
|
|
EXTERN gTss:QWORD ; address of the single TSS, exported from ke_idt.c
|
|
|
|
.CODE
|
|
|
|
; -------------------------------------------------------------------------
|
|
; void KeSwitchContext(THREAD* from, THREAD* to)
|
|
; RCX = from, RDX = to (Microsoft x64)
|
|
; -------------------------------------------------------------------------
|
|
KeSwitchContext PROC
|
|
; Save non-volatile registers onto the current (outgoing) kernel stack.
|
|
push rbx
|
|
push rbp
|
|
push r12
|
|
push r13
|
|
push r14
|
|
push r15
|
|
|
|
; from->SavedContext = rsp
|
|
mov [rcx + THREAD_SavedContext], rsp
|
|
|
|
; rsp = to->SavedContext
|
|
mov rsp, [rdx + THREAD_SavedContext]
|
|
|
|
; Update MSR_GS_BASE (0xC0000101) with to->Teb
|
|
mov rax, [rdx + THREAD_Teb] ; Fetch the incoming TEB pointer
|
|
mov ecx, 0C0000101h ; IA32_GS_BASE MSR
|
|
mov r8, rdx ; Save RDX ('to' thread) as WRMSR clobbers EDX
|
|
mov rdx, rax ; Copy TEB pointer to RDX
|
|
shr rdx, 32 ; EDX = High 32 bits of TEB
|
|
; EAX = Low 32 bits of TEB (upper 32 bits ignored by CPU)
|
|
wrmsr ; Write to MSR
|
|
mov rdx, r8 ; Restore RDX
|
|
|
|
; Update TSS.RSP0 so that the next interrupt/syscall uses the
|
|
; incoming thread's kernel stack top.
|
|
mov rax, [rdx + THREAD_TssRsp0]
|
|
mov r8, gTss
|
|
mov qword ptr [r8 + TSS_RSP0_OFFSET], rax
|
|
|
|
; Restore non-volatile registers of the incoming thread.
|
|
pop r15
|
|
pop r14
|
|
pop r13
|
|
pop r12
|
|
pop rbp
|
|
pop rbx
|
|
ret
|
|
KeSwitchContext ENDP
|
|
|
|
; -------------------------------------------------------------------------
|
|
; KeThreadEntry - landing pad for brand-new threads.
|
|
;
|
|
; Stack layout when we arrive here (set up by KeCreateThread):
|
|
; [rsp+00] = KCONTEXT (already popped by KeSwitchContext ret)
|
|
; After ret from KeSwitchContext RSP points here:
|
|
; [rsp+00] = user RCX arg
|
|
; [rsp+08] = user RIP
|
|
; [rsp+16] = user CS (0x1B)
|
|
; [rsp+24] = RFLAGS (0x202)
|
|
; [rsp+32] = user RSP
|
|
; [rsp+40] = user SS (0x23)
|
|
;
|
|
; We load RCX from the stack (first arg per MS x64), then IRETQ.
|
|
; -------------------------------------------------------------------------
|
|
EXTERN KeSchedulerPostSwitch : PROC
|
|
EXTERN KeGetCurrentThread : PROC
|
|
EXTERN KeExitThread : PROC
|
|
|
|
KeThreadEntry PROC
|
|
sub rsp, 32 ; Reserve shadow space
|
|
call KeSchedulerPostSwitch
|
|
add rsp, 32
|
|
pop rcx ; load user arg
|
|
iretq ; -> ring 3
|
|
KeThreadEntry ENDP
|
|
|
|
; -------------------------------------------------------------------------
|
|
; KeKernelThreadEntry - landing pad for kernel-mode threads created by
|
|
; KeCreateKernelThread. Never touches ring 3; no IRETQ.
|
|
;
|
|
; Stack layout when we arrive here (above the KCONTEXT already popped by
|
|
; KeSwitchContext's ret):
|
|
; [rsp+00] fn ptr (kernel function: void fn(void*))
|
|
; [rsp+08] arg ptr
|
|
;
|
|
; We pop both into non-volatile registers so KeSchedulerPostSwitch can
|
|
; clobber volatile ones freely, then call fn(arg). On return we call
|
|
; KeExitThread so the thread is cleanly destroyed.
|
|
; -------------------------------------------------------------------------
|
|
KeKernelThreadEntry PROC
|
|
pop rbx ; rbx = fn ptr (non-volatile, survives the call below)
|
|
pop r12 ; r12 = arg (non-volatile)
|
|
|
|
sub rsp, 32
|
|
call KeSchedulerPostSwitch ; release scheduler spinlock, reap dead threads
|
|
add rsp, 32
|
|
|
|
mov rcx, r12 ; first arg = arg
|
|
sub rsp, 32
|
|
call rbx ; fn(arg) -- still ring 0
|
|
add rsp, 32
|
|
|
|
; Thread function returned -- exit cleanly.
|
|
sub rsp, 32
|
|
call KeGetCurrentThread ; rax = current THREAD*
|
|
add rsp, 32
|
|
mov rcx, rax ; arg1 = thread*
|
|
xor edx, edx ; arg2 = exitCode 0
|
|
sub rsp, 32
|
|
call KeExitThread
|
|
add rsp, 32
|
|
|
|
; KeExitThread -> KeYield -> KeSwitchContext never returns here, but
|
|
; the assembler needs a defined end.
|
|
@@: jmp @B
|
|
KeKernelThreadEntry ENDP
|
|
|
|
END
|