duct taped posix.dll (just like microsoft), new cursor, more info in the fault handlers, processes now have a parse-it-yourself cmdline, add SYS_WAIT_THREAD and SYS_GET_PROCESS_PID, writing still doesn't work
This commit is contained in:
1 parent
a37f5b1581
commit
b5c831f593
26 files changed
+1107
-56
No files matched your search
@@ -16,6 +16,11 @@ typedef struct PROCESS {
|
||||
uint32_t ProcessId;
|
||||
char Name[PROCESS_NAME_MAX]; /* UTF-8 */
|
||||
|
||||
// Windows-style flat command line, stored at launch by SysCreateProcess.
|
||||
// The initial thread receives this pointer as its UserArg so _start can
|
||||
// parse it into argc/argv without an extra syscall.
|
||||
char Cmdline[512];
|
||||
|
||||
char CurrentDirectory[512];
|
||||
SPINLOCK CwdLock;
|
||||
|
||||
|
||||
+73
-16
@@ -37,35 +37,92 @@ KeSyscallEntry PROC
|
||||
KeSyscallEntry ENDP
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; Fault stubs - print a message and halt. A real kernel would do a bugcheck.
|
||||
; Fault stubs - print a message and halt.
|
||||
; ---------------------------------------------------------------------------
|
||||
EXTERN KePanic:PROC
|
||||
|
||||
FaultMsg DB "KERNEL FAULT: vector %d at RIP %016llx", 0Ah, 0
|
||||
.data
|
||||
; We define separate strings to maximize the info we can print
|
||||
StrKernel DB "Kernel Mode", 0
|
||||
StrUser DB "User Mode", 0
|
||||
|
||||
MsgFaultDE DB "PANIC: Divide Error (#0) | RIP: %016llx | Mode: %s", 0Ah, 0
|
||||
MsgFaultGP DB "PANIC: General Protection (#13) | RIP: %016llx | Code: %04x | Mode: %s", 0Ah, 0
|
||||
MsgFaultPF DB "PANIC: Page Fault (#14) | RIP: %016llx | Code: %04x | CR2: %016llx | %s", 0Ah, 0
|
||||
|
||||
.code
|
||||
|
||||
; ---------------------------------------------------------
|
||||
; Divide Error (#0) - NO Error Code pushed by CPU
|
||||
; Stack: [rsp] = RIP, [rsp+8] = CS, [rsp+16] = RFLAGS
|
||||
; ---------------------------------------------------------
|
||||
KeFaultDE PROC
|
||||
sub rsp, 28h
|
||||
lea rcx, FaultMsg
|
||||
mov rdx, 0
|
||||
pop r8 ; RIP from the fault frame
|
||||
mov rdx, [rsp] ; Arg 2: RIP
|
||||
|
||||
; Determine User/Kernel mode from CS
|
||||
mov rax, [rsp + 8] ; Read CS
|
||||
and rax, 3 ; Isolate the bottom 2 bits (RPL)
|
||||
lea r8, StrKernel
|
||||
lea r9, StrUser
|
||||
test rax, rax ; If 0, it's Kernel. If >0, it's User.
|
||||
cmovnz r8, r9 ; Arg 3: Mode String (moves r9 into r8 if Not Zero)
|
||||
|
||||
lea rcx, MsgFaultDE ; Arg 1: Format string
|
||||
|
||||
; Safely align stack and allocate shadow space for the call
|
||||
and rsp, -16 ; Force 16-byte ABI stack alignment
|
||||
sub rsp, 20h ; Allocate 32 bytes of shadow space
|
||||
call KePanic
|
||||
KeFaultDE ENDP
|
||||
|
||||
; ---------------------------------------------------------
|
||||
; General Protection Fault (#13) - Error Code PUSHED
|
||||
; Stack: [rsp] = ErrCode, [rsp+8] = RIP, [rsp+16] = CS
|
||||
; ---------------------------------------------------------
|
||||
KeFaultGP PROC
|
||||
sub rsp, 28h
|
||||
lea rcx, FaultMsg
|
||||
mov rdx, 13
|
||||
pop rax ; error code
|
||||
pop r8 ; RIP
|
||||
mov r8, [rsp] ; Arg 3: Error Code
|
||||
mov rdx, [rsp + 8] ; Arg 2: RIP
|
||||
|
||||
; Determine User/Kernel mode from CS
|
||||
mov rax, [rsp + 16] ; Read CS
|
||||
and rax, 3
|
||||
lea r9, StrKernel
|
||||
lea r10, StrUser
|
||||
test rax, rax
|
||||
cmovnz r9, r10 ; Arg 4: Mode String
|
||||
|
||||
lea rcx, MsgFaultGP ; Arg 1: Format string
|
||||
|
||||
and rsp, -16 ; Align stack
|
||||
sub rsp, 20h ; 32 bytes shadow space
|
||||
call KePanic
|
||||
KeFaultGP ENDP
|
||||
|
||||
; ---------------------------------------------------------
|
||||
; Page Fault (#14) - Error Code PUSHED
|
||||
; Stack: [rsp] = ErrCode, [rsp+8] = RIP, [rsp+16] = CS
|
||||
; ---------------------------------------------------------
|
||||
KeFaultPF PROC
|
||||
sub rsp, 28h
|
||||
lea rcx, FaultMsg
|
||||
mov rdx, 14
|
||||
pop rax ; error code
|
||||
pop r8 ; RIP
|
||||
mov r8, [rsp] ; Arg 3: Error Code
|
||||
mov rdx, [rsp + 8] ; Arg 2: RIP
|
||||
mov r9, cr2 ; Arg 4: CR2 (The faulting virtual address)
|
||||
|
||||
; Determine User/Kernel mode from CS
|
||||
mov rax, [rsp + 16] ; Read CS
|
||||
and rax, 3
|
||||
lea r10, StrKernel
|
||||
lea r11, StrUser
|
||||
test rax, rax
|
||||
cmovnz r10, r11 ; r10 now holds the Mode String
|
||||
|
||||
lea rcx, MsgFaultPF ; Arg 1: Format string
|
||||
|
||||
; We have 5 arguments for PF! (rcx, rdx, r8, r9, and the stack)
|
||||
; x64 ABI dictates the 5th argument goes on the stack just above the shadow space.
|
||||
and rsp, -16 ; Align stack
|
||||
sub rsp, 30h ; 32 bytes shadow space + 16 bytes for 5th arg and padding
|
||||
mov [rsp + 20h], r10 ; Arg 5: Mode String (placed exactly after shadow space)
|
||||
|
||||
call KePanic
|
||||
KeFaultPF ENDP
|
||||
|
||||
|
||||
@@ -683,14 +683,15 @@ static uint64_t SyscallDebugPrint(uint64_t a0, uint64_t a1, uint64_t a2,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SYS_CREATE_PROCESS (40) a0=*path a1=*handleOut
|
||||
// SYS_CREATE_PROCESS (40) a0=*path a1=*handleOut a2=*cmdline (optional)
|
||||
// ---------------------------------------------------------------------------
|
||||
static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a2; (void)a3; (void)a4; (void)a5;
|
||||
(void)a3; (void)a4; (void)a5;
|
||||
|
||||
const char* path = (const char*)(uintptr_t)a0;
|
||||
const char* path = (const char*)(uintptr_t)a0;
|
||||
uint32_t* handleOut = (uint32_t*)(uintptr_t)a1;
|
||||
const char* cmdline = (const char*)(uintptr_t)a2; // NULL = no args
|
||||
if (!path || !handleOut) return STATUS_INVALID_PARAMETER;
|
||||
|
||||
// Read the executable from disk.
|
||||
@@ -712,16 +713,33 @@ static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2,
|
||||
s = KeCreateProcess(path, &proc);
|
||||
if (!KSUCCESS(s)) { MmFreeUserMemory(mem, size); return s; }
|
||||
|
||||
// Store the command line in the process so _start can pick it up.
|
||||
// If no cmdline was supplied default to just the image path.
|
||||
if (cmdline && cmdline[0])
|
||||
strncpy(proc->Cmdline, cmdline, sizeof(proc->Cmdline) - 1);
|
||||
else
|
||||
strncpy(proc->Cmdline, path, sizeof(proc->Cmdline) - 1);
|
||||
proc->Cmdline[sizeof(proc->Cmdline) - 1] = '\0';
|
||||
|
||||
// proc->Cmdline is kernel pool - CPL=3 code can't read it.
|
||||
// Copy into user memory so the initial thread can access it directly.
|
||||
size_t cmdlineLen = strlen(proc->Cmdline) + 1;
|
||||
char* userCmdline = (char*)MmAllocateUserMemory(cmdlineLen);
|
||||
if (!userCmdline) { ObDereferenceObject(&proc->Header); return STATUS_NO_MEMORY; }
|
||||
memcpy(userCmdline, proc->Cmdline, cmdlineLen);
|
||||
|
||||
// Map the PE image into memory.
|
||||
uintptr_t imageBase = 0;
|
||||
s = LdrLoadImage(mem, size, &imageBase, true);
|
||||
MmFreeUserMemory(mem, size);
|
||||
if (!KSUCCESS(s)) { ObDereferenceObject(&proc->Header); return s; }
|
||||
|
||||
// Spin up the initial thread at the PE entry point.
|
||||
// Spin up the initial thread. Pass userCmdline as UserArg so _start
|
||||
// receives it directly in RCX without an extra syscall.
|
||||
THREAD* thread;
|
||||
uintptr_t entryPoint = LdrGetEntryPoint(imageBase);
|
||||
s = KeCreateThread(proc, entryPoint, 0, PriorityNormal, &thread);
|
||||
s = KeCreateThread(proc, entryPoint, (uintptr_t)userCmdline,
|
||||
PriorityNormal, &thread);
|
||||
if (!KSUCCESS(s)) { ObDereferenceObject(&proc->Header); return s; }
|
||||
|
||||
// Give the caller a handle to the new process so they can wait on it.
|
||||
@@ -850,6 +868,69 @@ static uint64_t SyscallGetCwd(uint64_t a0, uint64_t a1, uint64_t a2,
|
||||
return IoGetCwd(buf, size);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SYS_WAIT_THREAD (53) a0=tid
|
||||
// Blocks the caller until the thread with the given TID exits.
|
||||
// Searches the current process's thread list; cross-process join is not
|
||||
// supported in v1 (POSIX pthread_join is always intra-process).
|
||||
// Returns STATUS_NOT_FOUND if the TID is unknown (already reaped or never
|
||||
// existed) - treat the same as a successful join at the POSIX layer.
|
||||
// ---------------------------------------------------------------------------
|
||||
static uint64_t SyscallWaitThread(uint64_t a0, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a1; (void)a2; (void)a3; (void)a4; (void)a5;
|
||||
uint32_t targetTid = (uint32_t)a0;
|
||||
|
||||
PROCESS* proc = KeGetCurrentProcess();
|
||||
if (!proc) return STATUS_FAILURE;
|
||||
|
||||
// Walk the thread list under the spinlock to find the target, then bump
|
||||
// its ExitEvent refcount so the event survives even if the thread exits
|
||||
// and is destroyed while we wait.
|
||||
EVENT* exitEvent = (EVENT*)0;
|
||||
uintptr_t flags = KeAcquireSpinlock(&proc->HandleLock);
|
||||
LIST_ENTRY* entry = proc->ThreadList.Fwd;
|
||||
while (entry != &proc->ThreadList) {
|
||||
THREAD* t = CONTAINING_RECORD(entry, THREAD, ProcessLink);
|
||||
if (t->ThreadId == targetTid && t->ExitEvent) {
|
||||
exitEvent = t->ExitEvent;
|
||||
ObReferenceObject(&exitEvent->Header); // keep event alive
|
||||
break;
|
||||
}
|
||||
entry = entry->Fwd;
|
||||
}
|
||||
KeReleaseSpinlock(&proc->HandleLock, flags);
|
||||
|
||||
if (!exitEvent) {
|
||||
// TID not found - either already exited and reaped, or invalid.
|
||||
// Treat as success at the POSIX layer (join on an already-dead thread).
|
||||
return STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
KSTATUS s = KeWaitForEvent(exitEvent);
|
||||
ObDereferenceObject(&exitEvent->Header);
|
||||
return s;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SYS_GET_PROCESS_PID (54) a0=procHandle a1=*pidOut
|
||||
// Returns the numeric PID of a process identified by handle.
|
||||
// Used by posix_spawn to map the kernel handle back to a pid_t for waitpid.
|
||||
// ---------------------------------------------------------------------------
|
||||
static uint64_t SyscallGetProcessPid(uint64_t a0, uint64_t a1, uint64_t a2,
|
||||
uint64_t a3, uint64_t a4, uint64_t a5) {
|
||||
(void)a2; (void)a3; (void)a4; (void)a5;
|
||||
|
||||
PROCESS* proc = (PROCESS*)HandleToObject((uint32_t)a0, ObjectTypeProcess);
|
||||
uint32_t* pidOut = (uint32_t*)(uintptr_t)a1;
|
||||
if (!proc) return STATUS_INVALID_HANDLE;
|
||||
if (!pidOut) { ObDereferenceObject(&proc->Header); return STATUS_INVALID_PARAMETER; }
|
||||
|
||||
*pidOut = proc->ProcessId;
|
||||
ObDereferenceObject(&proc->Header);
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Dispatch table - index must match SYS_* in syscall_nr.h exactly.
|
||||
// ===========================================================================
|
||||
@@ -910,6 +991,8 @@ static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = {
|
||||
/* 50 SYS_READ_DIR */ SyscallReadDir,
|
||||
/* 51 SYS_SET_CWD */ SyscallSetCwd,
|
||||
/* 52 SYS_GET_CWD */ SyscallGetCwd,
|
||||
/* 53 SYS_WAIT_THREAD */ SyscallWaitThread,
|
||||
/* 54 SYS_GET_PROCESS_PID */ SyscallGetProcessPid,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <intrin.h>
|
||||
#include <ke_thread.h>
|
||||
#include <ke_sched.h>
|
||||
#include <ke_event.h>
|
||||
#include <mm_virt.h>
|
||||
#include <ob_object.h>
|
||||
|
||||
@@ -12,6 +13,11 @@ extern void KeThreadEntry(void);
|
||||
static void ThreadDestroy(OBJECT* obj) {
|
||||
THREAD* thread = CONTAINING_RECORD(obj, THREAD, Header);
|
||||
|
||||
if (thread->ExitEvent) {
|
||||
ObDereferenceObject(&thread->ExitEvent->Header);
|
||||
thread->ExitEvent = (EVENT*)0;
|
||||
}
|
||||
|
||||
if (thread->Owner) {
|
||||
uintptr_t flags = KeAcquireSpinlock(&thread->Owner->HandleLock);
|
||||
ListRemove(&thread->ProcessLink);
|
||||
@@ -100,6 +106,19 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui
|
||||
ObReferenceObject(&owner->Header);
|
||||
thread->Owner = owner;
|
||||
|
||||
// Allocate the exit event before the thread becomes visible. Manual-reset
|
||||
// so multiple joiners (if any) all wake up when the thread exits.
|
||||
EVENT* exitEvent;
|
||||
KSTATUS s = KeEventCreate(EventManualReset, 0, &exitEvent);
|
||||
if (!KSUCCESS(s)) {
|
||||
MmFreeUserMemory(ustack, USTACK_SIZE);
|
||||
MmFreePool(kstack);
|
||||
ObDereferenceObject(&owner->Header);
|
||||
ObDereferenceObject(&thread->Header);
|
||||
return s;
|
||||
}
|
||||
thread->ExitEvent = exitEvent;
|
||||
|
||||
uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock);
|
||||
ListInsertTail(&owner->ThreadList, &thread->ProcessLink);
|
||||
owner->ThreadCount++;
|
||||
@@ -114,6 +133,11 @@ __declspec(dllexport) void KeExitThread(THREAD* thread, int code) {
|
||||
(void)code;
|
||||
if (!thread) return;
|
||||
|
||||
// Wake any pthread_join waiters before the thread is marked dead and
|
||||
// potentially freed. Manual-reset means all concurrent waiters unblock.
|
||||
if (thread->ExitEvent)
|
||||
KeSignalEvent(thread->ExitEvent);
|
||||
|
||||
thread->State = ThreadDead;
|
||||
|
||||
KeYield();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <ob_object.h>
|
||||
#include <ke_list.h>
|
||||
#include <ke_process.h>
|
||||
#include <ke_event.h>
|
||||
|
||||
#define KSTACK_SIZE (64 * 1024) /* 64 KB kernel stack */
|
||||
#define USTACK_SIZE (512 * 1024) /* 512 KB user stack */
|
||||
@@ -67,6 +68,11 @@ typedef struct THREAD {
|
||||
|
||||
uintptr_t SchedLockFlags; // Stores RFLAGS while thread is context switched out
|
||||
void* UserStackBase; // Tracks user stack allocation to prevent memory leaks
|
||||
|
||||
// Signaled (manual-reset) when the thread exits.
|
||||
// Allocated in KeCreateThread, signaled in KeExitThread before teardown.
|
||||
// Waiters block on this via SYS_WAIT_THREAD -> KeWaitForEvent.
|
||||
EVENT* ExitEvent;
|
||||
} THREAD;
|
||||
|
||||
__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
|
||||
|
||||
+13
-1
@@ -238,6 +238,18 @@ static PROCESS* LaunchUserProcess(const char* filePath) {
|
||||
}
|
||||
DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, filePath);
|
||||
|
||||
strncpy(proc->Cmdline, filePath, sizeof(proc->Cmdline) - 1);
|
||||
|
||||
// proc->Cmdline is kernel pool (ffffc000...) - not readable at CPL=3.
|
||||
// Copy into user memory before handing the pointer to the initial thread.
|
||||
size_t cmdlineLen = strlen(proc->Cmdline) + 1;
|
||||
char* userCmdline = (char*)MmAllocateUserMemory(cmdlineLen);
|
||||
if (!userCmdline) {
|
||||
DbgPrint("LaunchUserProcess: Failed to allocate user cmdline buffer.\n");
|
||||
return (PROCESS*)0;
|
||||
}
|
||||
memcpy(userCmdline, proc->Cmdline, cmdlineLen);
|
||||
|
||||
// Parse the raw PE file and map it into executable memory
|
||||
uintptr_t imageBase = 0;
|
||||
s = LdrLoadImage(mem, size, &imageBase, true);
|
||||
@@ -248,7 +260,7 @@ static PROCESS* LaunchUserProcess(const char* filePath) {
|
||||
DbgPrint("LaunchUserProcess: PE Image mapped! Base 0x%llx, Entry 0x%llX\n", imageBase, entryPoint);
|
||||
|
||||
// Create the main thread, pointing it to the PE Entry Point
|
||||
s = KeCreateThread(proc, (uintptr_t)entryPoint, 0, PriorityNormal, &thread);
|
||||
s = KeCreateThread(proc, (uintptr_t)entryPoint, (uintptr_t)userCmdline, PriorityNormal, &thread);
|
||||
if (!KSUCCESS(s)) {
|
||||
DbgPrint("LaunchUserProcess: Failed to create thread. Status: %d\n", s);
|
||||
}
|
||||
|
||||
@@ -130,7 +130,19 @@
|
||||
#define SYS_SET_CWD 51 // (const char* path) -> KSTATUS
|
||||
#define SYS_GET_CWD 52 // (char* buf, size_t size) -> KSTATUS
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Thread join
|
||||
// ---------------------------------------------------------------------------
|
||||
#define SYS_WAIT_THREAD 53 // (uint32_t tid) -> KSTATUS
|
||||
// Blocks until the thread with that TID exits.
|
||||
// Returns STATUS_NOT_FOUND if TID unknown.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Process PID query (needed by posix_spawn -> waitpid)
|
||||
// ---------------------------------------------------------------------------
|
||||
#define SYS_GET_PROCESS_PID 54 // (uint32_t procHandle, uint32_t* pidOut) -> KSTATUS
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sentinel
|
||||
// ---------------------------------------------------------------------------
|
||||
#define SYS_MAX 53
|
||||
#define SYS_MAX 55
|
||||
Reference in new issue
Block a user