doom is now using the native api, add a PEB + TEB to replace the dummy and the awkward argument passing to processes, all allocations are zeroed because i had enough, small readme changes and dedication.

This commit is contained in:
maksw committed 2026-05-18 21:30:07 +02:00
1 parent ddb5d5f5d8
commit f5fd7bdd75
28 files changed
+6334 -5722

No files matched your search

+443 -424
View File
@@ -1,424 +1,443 @@
// io_file.c - VFS dispatch layer
#include <string.h>
#include <ke_list.h>
#include <ke_process.h>
#include <io_file.h>
#include <mm_virt.h>
#include <ob_object.h>
#define MAX_DRIVES 8
typedef struct {
IO_FILESYSTEM *Fs;
IO_VOLUME_PARAMS Vol;
} VOLUME_ENTRY;
static VOLUME_ENTRY gVolumes[MAX_DRIVES];
// ---------------------------------------------------------------------------
// Current Working Directory (drive 0 relative, no trailing backslash)
// e.g. "" = root, "apps" = root\apps, "apps\\data" = nested
// ---------------------------------------------------------------------------
#define CWD_MAX 512
__declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size) {
if (!buf || size == 0) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
size_t cwdLen = strlen(proc->CurrentDirectory);
if (cwdLen + 1 > size) {
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_BUFFER_TOO_SMALL;
}
memcpy(buf, proc->CurrentDirectory, cwdLen + 1);
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_SUCCESS;
}
// Resolve a possibly-relative path component against the CWD.
// Writes the fully-resolved, drive-stripped relative path into 'resolved'
// (caller supplies buf of at least CWD_MAX bytes).
// Returns the drive index.
static uint8_t ResolvePath(const char* path, char* resolved, size_t resolvedSize) {
// Strip explicit drive prefix "N:\"
uint8_t drive = 0;
bool fromDriveRoot = false;
if (path[0] >= '0' && path[0] <= '7' &&
path[1] == ':' && (path[2] == '\\' || path[2] == '/')) {
drive = (uint8_t)(path[0] - '0');
path = path + 3;
fromDriveRoot = true;
}
char work[CWD_MAX * 2];
work[0] = '\0';
// Initialize our working base
if (path[0] == '\\' || path[0] == '/' || fromDriveRoot) {
// Absolute path: start from the root (empty work buffer)
// The segment loop below will automatically skip the leading separators.
} else {
// Relative path: start from the current process's CWD
PROCESS* proc = KeGetCurrentProcess();
if (proc) {
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
size_t wlen = strlen(proc->CurrentDirectory);
if (wlen >= sizeof(work)) wlen = sizeof(work) - 1;
memcpy(work, proc->CurrentDirectory, wlen);
work[wlen] = '\0';
KeReleaseSpinlock(&proc->CwdLock, flags);
}
}
// Segment-by-segment canonicalization
const char* p = path;
while (*p) {
// Skip separators
while (*p == '\\' || *p == '/') p++;
if (!*p) break;
// Read next component
char comp[256];
int clen = 0;
while (*p && *p != '\\' && *p != '/' && clen < 255)
comp[clen++] = *p++;
comp[clen] = '\0';
if (clen == 0) continue;
if (strcmp(comp, ".") == 0) {
// Stay in place
} else if (strcmp(comp, "..") == 0) {
// Remove last component from work
int i = (int)strlen(work) - 1;
while (i >= 0 && work[i] != '\\' && work[i] != '/') i--;
if (i < 0) i = 0;
work[i] = '\0';
} else {
// Append separator + component
size_t wl = strlen(work);
if (wl > 0 && work[wl-1] != '\\' && work[wl-1] != '/') {
if (wl + 1 < sizeof(work)) { work[wl] = '\\'; work[wl+1] = '\0'; }
}
size_t wl2 = strlen(work);
if (wl2 + clen + 1 < sizeof(work)) {
memcpy(work + wl2, comp, clen);
work[wl2 + clen] = '\0';
}
}
}
// Output the final canonicalized, drive-stripped path
size_t finalLen = strlen(work);
if (finalLen + 1 > resolvedSize) finalLen = resolvedSize - 1;
memcpy(resolved, work, finalLen);
resolved[finalLen] = '\0';
return drive;
}
__declspec(dllexport) KSTATUS IoSetCwd(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
// Resolve the new path (this also validates ".." navigation).
// Note: ResolvePath already uses proc->CurrentDirectory internally!
char resolved[CWD_MAX];
uint8_t drive = ResolvePath(path, resolved, sizeof(resolved));
// Validate: try to open the directory to confirm it actually exists on disk
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->OpenDir) return STATUS_NOT_SUPPORTED;
void* dirCtx = NULL;
KSTATUS s = fs->OpenDir(fs, resolved, &dirCtx);
if (!KSUCCESS(s)) return s;
fs->CloseDir(fs, dirCtx);
// Commit the new CWD to the process block
size_t len = strlen(resolved);
if (len + 1 > sizeof(proc->CurrentDirectory)) return STATUS_BUFFER_TOO_SMALL;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
memcpy(proc->CurrentDirectory, resolved, len + 1);
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Registration
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t volumeIndex,
IO_FILESYSTEM *fs,
const IO_VOLUME_PARAMS *vol) {
if (volumeIndex >= MAX_DRIVES || !fs || !vol)
return STATUS_INVALID_PARAMETER;
if (gVolumes[volumeIndex].Fs)
return STATUS_ALREADY_EXISTS;
gVolumes[volumeIndex].Fs = fs;
gVolumes[volumeIndex].Vol = *vol;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Destructor
// ---------------------------------------------------------------------------
static void FileDestroy(OBJECT* obj) {
FILE_OBJECT* f = CONTAINING_RECORD(obj, FILE_OBJECT, Header);
if (f->Fs && f->FileCtx) f->Fs->Close(f->Fs, f->FileCtx);
}
// ---------------------------------------------------------------------------
// IoOpenFile
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) {
if (!path || !out) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
void* fileCtx;
KSTATUS s = fs->Open(fs, relPath, &fileCtx);
if (!KSUCCESS(s)) return s;
FILE_OBJECT* f = (FILE_OBJECT *)ObAllocateObject(ObjectTypeFile /* reuse slot */,
sizeof(FILE_OBJECT));
if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; }
f->Header.Destroy = FileDestroy;
f->Fs = fs;
f->FileCtx = fileCtx;
f->Size = fs->GetSize(fs, fileCtx);
f->Offset = 0;
*out = f;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// IoReadFile - sequential read at current offset
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t size, size_t* bytesRead) {
if (!file || !buf) return STATUS_INVALID_PARAMETER;
if (file->Offset >= file->Size) { *bytesRead = 0; return STATUS_SUCCESS; }
// Clamp to remaining bytes.
size_t remaining = (size_t)(file->Size - file->Offset);
if (size > remaining) size = remaining;
KSTATUS s = file->Fs->Read(file->Fs, file->FileCtx,
file->Offset, buf, size, bytesRead);
if (KSUCCESS(s)) file->Offset += *bytesRead;
return s;
}
// ---------------------------------------------------------------------------
// IoWriteFile - sequential write at current offset
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf,
size_t size, size_t* bytesWritten) {
if (!file || !buf || !bytesWritten) return STATUS_INVALID_PARAMETER;
if (!file->Fs->Write) return STATUS_NOT_SUPPORTED;
size_t written = 0;
KSTATUS s = file->Fs->Write(file->Fs, file->FileCtx,
file->Offset, buf, size, &written);
if (KSUCCESS(s)) {
file->Offset += written;
// Keep the cached size in sync if the file grew.
if (file->Offset > file->Size)
file->Size = file->Offset;
}
*bytesWritten = written;
return s;
}
// ---------------------------------------------------------------------------
// IoCreateFile - create a new file and return it open for writing
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoCreateFile(const char* path, FILE_OBJECT** out) {
if (!path || !out) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Create) return STATUS_NOT_SUPPORTED;
void* fileCtx;
KSTATUS s = fs->Create(fs, relPath, &fileCtx);
if (!KSUCCESS(s)) return s;
FILE_OBJECT* f = (FILE_OBJECT*)ObAllocateObject(ObjectTypeFile,
sizeof(FILE_OBJECT));
if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; }
f->Header.Destroy = FileDestroy;
f->Fs = fs;
f->FileCtx = fileCtx;
f->Size = 0;
f->Offset = 0;
*out = f;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// IoDeleteFile - delete a file by path (does not require an open handle)
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoDeleteFile(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Delete) return STATUS_NOT_SUPPORTED;
return fs->Delete(fs, relPath);
}
__declspec(dllexport) KSTATUS IoRenameFile(const char* oldPath, const char* newPath) {
if (!oldPath || !newPath) return STATUS_INVALID_PARAMETER;
char oldRel[CWD_MAX];
char newRel[CWD_MAX];
uint8_t oldDrive = ResolvePath(oldPath, oldRel, sizeof(oldRel));
uint8_t newDrive = ResolvePath(newPath, newRel, sizeof(newRel));
if (oldDrive != newDrive) return STATUS_NOT_SUPPORTED;
IO_FILESYSTEM* fs = gVolumes[oldDrive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Rename) return STATUS_NOT_SUPPORTED;
return fs->Rename(fs, oldRel, newRel);
}
__declspec(dllexport) KSTATUS IoCreateDir(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->CreateDir) return STATUS_NOT_SUPPORTED;
return fs->CreateDir(fs, relPath);
}
__declspec(dllexport) KSTATUS IoRemoveDir(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->RemoveDir) return STATUS_NOT_SUPPORTED;
return fs->RemoveDir(fs, relPath);
}
// ---------------------------------------------------------------------------
// IoTruncateFile - resize the file; updates the cached Size on success
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize) {
if (!file) return STATUS_INVALID_PARAMETER;
if (!file->Fs->Truncate) return STATUS_NOT_SUPPORTED;
KSTATUS s = file->Fs->Truncate(file->Fs, file->FileCtx, newSize);
if (KSUCCESS(s)) {
file->Size = newSize;
// Pull the file pointer back if it now sits past EOF.
if (file->Offset > file->Size)
file->Offset = file->Size;
}
return s;
}
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset) {
if (!file) return STATUS_INVALID_PARAMETER;
file->Offset = offset;
return STATUS_SUCCESS;
}
__declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file) {
return file ? file->Size : 0;
}
__declspec(dllexport) void IoCloseFile(FILE_OBJECT* file) {
if (file) ObDereferenceObject(&file->Header);
}
// ---------------------------------------------------------------------------
// Directory enumeration
// ---------------------------------------------------------------------------
static void DirDestroy(OBJECT* obj) {
DIR_OBJECT* d = (DIR_OBJECT*)obj; // Header is first member
if (d->Fs && d->DirCtx) d->Fs->CloseDir(d->Fs, d->DirCtx);
}
__declspec(dllexport) KSTATUS IoOpenDir(const char* dirPath, DIR_OBJECT** out) {
if (!out) return STATUS_INVALID_PARAMETER;
// NULL or empty path means the CWD itself
char relPath[CWD_MAX];
uint8_t drive;
if (!dirPath || dirPath[0] == '\0') {
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
memcpy(relPath, proc->CurrentDirectory, strlen(proc->CurrentDirectory) + 1);
KeReleaseSpinlock(&proc->CwdLock, flags);
// Note: For now, this assumes drive 0. If you add multi-drive support
// later, you should add a CurrentDrive field to the PROCESS struct.
drive = 0;
} else {
drive = ResolvePath(dirPath, relPath, sizeof(relPath));
}
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->OpenDir) return STATUS_NOT_SUPPORTED;
void* dirCtx = NULL;
KSTATUS s = fs->OpenDir(fs, relPath, &dirCtx);
if (!KSUCCESS(s)) return s;
DIR_OBJECT* d = (DIR_OBJECT*)ObAllocateObject(ObjectTypeDir, sizeof(DIR_OBJECT));
if (!d) { fs->CloseDir(fs, dirCtx); return STATUS_NO_MEMORY; }
d->Header.Destroy = DirDestroy;
d->Fs = fs;
d->DirCtx = dirCtx;
*out = d;
return STATUS_SUCCESS;
}
__declspec(dllexport) KSTATUS IoReadDir(DIR_OBJECT* dir, KDIR_ENTRY* entryOut) {
if (!dir || !entryOut) return STATUS_INVALID_PARAMETER;
if (!dir->Fs->ReadDir) return STATUS_NOT_SUPPORTED;
return dir->Fs->ReadDir(dir->Fs, dir->DirCtx, entryOut);
}
__declspec(dllexport) void IoCloseDir(DIR_OBJECT* dir) {
if (dir) ObDereferenceObject(&dir->Header);
}
// io_file.c - VFS dispatch layer
#include <string.h>
#include <ke_list.h>
#include <ke_process.h>
#include <io_file.h>
#include <mm_virt.h>
#include <ob_object.h>
#include <ke_userenv.h>
#define MAX_DRIVES 8
typedef struct {
IO_FILESYSTEM *Fs;
IO_VOLUME_PARAMS Vol;
} VOLUME_ENTRY;
static VOLUME_ENTRY gVolumes[MAX_DRIVES];
// ---------------------------------------------------------------------------
// Current Working Directory (drive 0 relative, no trailing backslash)
// e.g. "" = root, "apps" = root\apps, "apps\\data" = nested
// ---------------------------------------------------------------------------
#define CWD_MAX 512
__declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size) {
if (!buf || size == 0) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
size_t cwdLen = strlen(proc->CurrentDirectory);
if (cwdLen + 1 > size) {
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_BUFFER_TOO_SMALL;
}
memcpy(buf, proc->CurrentDirectory, cwdLen + 1);
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_SUCCESS;
}
// Resolve a possibly-relative path component against the CWD.
// Writes the fully-resolved, drive-stripped relative path into 'resolved'
// (caller supplies buf of at least CWD_MAX bytes).
// Returns the drive index.
static uint8_t ResolvePath(const char* path, char* resolved, size_t resolvedSize) {
// Strip explicit drive prefix "N:\"
uint8_t drive = 0;
bool fromDriveRoot = false;
if (path[0] >= '0' && path[0] <= '7' &&
path[1] == ':' && (path[2] == '\\' || path[2] == '/')) {
drive = (uint8_t)(path[0] - '0');
path = path + 3;
fromDriveRoot = true;
}
char work[CWD_MAX * 2];
work[0] = '\0';
// Initialize our working base
if (path[0] == '\\' || path[0] == '/' || fromDriveRoot) {
// Absolute path: start from the root (empty work buffer)
// The segment loop below will automatically skip the leading separators.
} else {
// Relative path: start from the current process's CWD
PROCESS* proc = KeGetCurrentProcess();
if (proc) {
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
size_t wlen = strlen(proc->CurrentDirectory);
if (wlen >= sizeof(work)) wlen = sizeof(work) - 1;
memcpy(work, proc->CurrentDirectory, wlen);
work[wlen] = '\0';
KeReleaseSpinlock(&proc->CwdLock, flags);
}
}
// Segment-by-segment canonicalization
const char* p = path;
while (*p) {
// Skip separators
while (*p == '\\' || *p == '/') p++;
if (!*p) break;
// Read next component
char comp[256];
int clen = 0;
while (*p && *p != '\\' && *p != '/' && clen < 255)
comp[clen++] = *p++;
comp[clen] = '\0';
if (clen == 0) continue;
if (strcmp(comp, ".") == 0) {
// Stay in place
} else if (strcmp(comp, "..") == 0) {
// Remove last component from work
int i = (int)strlen(work) - 1;
while (i >= 0 && work[i] != '\\' && work[i] != '/') i--;
if (i < 0) i = 0;
work[i] = '\0';
} else {
// Append separator + component
size_t wl = strlen(work);
if (wl > 0 && work[wl-1] != '\\' && work[wl-1] != '/') {
if (wl + 1 < sizeof(work)) { work[wl] = '\\'; work[wl+1] = '\0'; }
}
size_t wl2 = strlen(work);
if (wl2 + clen + 1 < sizeof(work)) {
memcpy(work + wl2, comp, clen);
work[wl2 + clen] = '\0';
}
}
}
// Output the final canonicalized, drive-stripped path
size_t finalLen = strlen(work);
if (finalLen + 1 > resolvedSize) finalLen = resolvedSize - 1;
memcpy(resolved, work, finalLen);
resolved[finalLen] = '\0';
return drive;
}
__declspec(dllexport) KSTATUS IoSetCwd(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
// Resolve the new path (this also validates ".." navigation).
// Note: ResolvePath already uses proc->CurrentDirectory internally!
char resolved[CWD_MAX];
uint8_t drive = ResolvePath(path, resolved, sizeof(resolved));
// Validate: try to open the directory to confirm it actually exists on disk
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->OpenDir) return STATUS_NOT_SUPPORTED;
void* dirCtx = NULL;
KSTATUS s = fs->OpenDir(fs, resolved, &dirCtx);
if (!KSUCCESS(s)) return s;
fs->CloseDir(fs, dirCtx);
// Commit the new CWD to the process block
size_t len = strlen(resolved);
if (len + 1 > sizeof(proc->CurrentDirectory)) return STATUS_BUFFER_TOO_SMALL;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
memcpy(proc->CurrentDirectory, resolved, len + 1);
// Keep the user-visible PEB mirror in sync. The PEB's CWD buffer is a
// fixed SYS_CWD_MAX allocation made at process creation, so the pointer
// remains stable for personalities that cache SYS_PEB*.
if (proc->UserCwdBase && proc->UserCwdSize) {
size_t copyLen = len;
if (copyLen + 1 > proc->UserCwdSize) copyLen = proc->UserCwdSize - 1;
memcpy((void*)proc->UserCwdBase, resolved, copyLen);
((char*)proc->UserCwdBase)[copyLen] = '\0';
SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase;
if (peb && peb->Magic == SYS_PEB_MAGIC) {
peb->CurrentDirectory = (const char*)proc->UserCwdBase;
peb->CurrentDirectoryLength = copyLen;
}
}
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Registration
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t volumeIndex,
IO_FILESYSTEM *fs,
const IO_VOLUME_PARAMS *vol) {
if (volumeIndex >= MAX_DRIVES || !fs || !vol)
return STATUS_INVALID_PARAMETER;
if (gVolumes[volumeIndex].Fs)
return STATUS_ALREADY_EXISTS;
gVolumes[volumeIndex].Fs = fs;
gVolumes[volumeIndex].Vol = *vol;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Destructor
// ---------------------------------------------------------------------------
static void FileDestroy(OBJECT* obj) {
FILE_OBJECT* f = CONTAINING_RECORD(obj, FILE_OBJECT, Header);
if (f->Fs && f->FileCtx) f->Fs->Close(f->Fs, f->FileCtx);
}
// ---------------------------------------------------------------------------
// IoOpenFile
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) {
if (!path || !out) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
void* fileCtx;
KSTATUS s = fs->Open(fs, relPath, &fileCtx);
if (!KSUCCESS(s)) return s;
FILE_OBJECT* f = (FILE_OBJECT *)ObAllocateObject(ObjectTypeFile /* reuse slot */,
sizeof(FILE_OBJECT));
if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; }
f->Header.Destroy = FileDestroy;
f->Fs = fs;
f->FileCtx = fileCtx;
f->Size = fs->GetSize(fs, fileCtx);
f->Offset = 0;
*out = f;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// IoReadFile - sequential read at current offset
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t size, size_t* bytesRead) {
if (bytesRead) *bytesRead = 0;
if (!file || !buf || !bytesRead) return STATUS_INVALID_PARAMETER;
if (file->Offset >= file->Size || size == 0) return STATUS_SUCCESS;
// Clamp to remaining bytes.
size_t remaining = (size_t)(file->Size - file->Offset);
if (size > remaining) size = remaining;
KSTATUS s = file->Fs->Read(file->Fs, file->FileCtx,
file->Offset, buf, size, bytesRead);
if (KSUCCESS(s)) file->Offset += *bytesRead;
return s;
}
// ---------------------------------------------------------------------------
// IoWriteFile - sequential write at current offset
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf,
size_t size, size_t* bytesWritten) {
if (!file || !buf || !bytesWritten) return STATUS_INVALID_PARAMETER;
if (!file->Fs->Write) return STATUS_NOT_SUPPORTED;
size_t written = 0;
KSTATUS s = file->Fs->Write(file->Fs, file->FileCtx,
file->Offset, buf, size, &written);
if (KSUCCESS(s)) {
file->Offset += written;
// Keep the cached size in sync if the file grew.
if (file->Offset > file->Size)
file->Size = file->Offset;
}
*bytesWritten = written;
return s;
}
// ---------------------------------------------------------------------------
// IoCreateFile - create a new file and return it open for writing
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoCreateFile(const char* path, FILE_OBJECT** out) {
if (!path || !out) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Create) return STATUS_NOT_SUPPORTED;
void* fileCtx;
KSTATUS s = fs->Create(fs, relPath, &fileCtx);
if (!KSUCCESS(s)) return s;
FILE_OBJECT* f = (FILE_OBJECT*)ObAllocateObject(ObjectTypeFile,
sizeof(FILE_OBJECT));
if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; }
f->Header.Destroy = FileDestroy;
f->Fs = fs;
f->FileCtx = fileCtx;
f->Size = 0;
f->Offset = 0;
*out = f;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// IoDeleteFile - delete a file by path (does not require an open handle)
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoDeleteFile(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Delete) return STATUS_NOT_SUPPORTED;
return fs->Delete(fs, relPath);
}
__declspec(dllexport) KSTATUS IoRenameFile(const char* oldPath, const char* newPath) {
if (!oldPath || !newPath) return STATUS_INVALID_PARAMETER;
char oldRel[CWD_MAX];
char newRel[CWD_MAX];
uint8_t oldDrive = ResolvePath(oldPath, oldRel, sizeof(oldRel));
uint8_t newDrive = ResolvePath(newPath, newRel, sizeof(newRel));
if (oldDrive != newDrive) return STATUS_NOT_SUPPORTED;
IO_FILESYSTEM* fs = gVolumes[oldDrive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Rename) return STATUS_NOT_SUPPORTED;
return fs->Rename(fs, oldRel, newRel);
}
__declspec(dllexport) KSTATUS IoCreateDir(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->CreateDir) return STATUS_NOT_SUPPORTED;
return fs->CreateDir(fs, relPath);
}
__declspec(dllexport) KSTATUS IoRemoveDir(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->RemoveDir) return STATUS_NOT_SUPPORTED;
return fs->RemoveDir(fs, relPath);
}
// ---------------------------------------------------------------------------
// IoTruncateFile - resize the file; updates the cached Size on success
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize) {
if (!file) return STATUS_INVALID_PARAMETER;
if (!file->Fs->Truncate) return STATUS_NOT_SUPPORTED;
KSTATUS s = file->Fs->Truncate(file->Fs, file->FileCtx, newSize);
if (KSUCCESS(s)) {
file->Size = newSize;
// Pull the file pointer back if it now sits past EOF.
if (file->Offset > file->Size)
file->Offset = file->Size;
}
return s;
}
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset) {
if (!file) return STATUS_INVALID_PARAMETER;
file->Offset = offset;
return STATUS_SUCCESS;
}
__declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file) {
return file ? file->Size : 0;
}
__declspec(dllexport) void IoCloseFile(FILE_OBJECT* file) {
if (file) ObDereferenceObject(&file->Header);
}
// ---------------------------------------------------------------------------
// Directory enumeration
// ---------------------------------------------------------------------------
static void DirDestroy(OBJECT* obj) {
DIR_OBJECT* d = (DIR_OBJECT*)obj; // Header is first member
if (d->Fs && d->DirCtx) d->Fs->CloseDir(d->Fs, d->DirCtx);
}
__declspec(dllexport) KSTATUS IoOpenDir(const char* dirPath, DIR_OBJECT** out) {
if (!out) return STATUS_INVALID_PARAMETER;
// NULL or empty path means the CWD itself
char relPath[CWD_MAX];
uint8_t drive;
if (!dirPath || dirPath[0] == '\0') {
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
memcpy(relPath, proc->CurrentDirectory, strlen(proc->CurrentDirectory) + 1);
KeReleaseSpinlock(&proc->CwdLock, flags);
// Note: For now, this assumes drive 0. If you add multi-drive support
// later, you should add a CurrentDrive field to the PROCESS struct.
drive = 0;
} else {
drive = ResolvePath(dirPath, relPath, sizeof(relPath));
}
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->OpenDir) return STATUS_NOT_SUPPORTED;
void* dirCtx = NULL;
KSTATUS s = fs->OpenDir(fs, relPath, &dirCtx);
if (!KSUCCESS(s)) return s;
DIR_OBJECT* d = (DIR_OBJECT*)ObAllocateObject(ObjectTypeDir, sizeof(DIR_OBJECT));
if (!d) { fs->CloseDir(fs, dirCtx); return STATUS_NO_MEMORY; }
d->Header.Destroy = DirDestroy;
d->Fs = fs;
d->DirCtx = dirCtx;
*out = d;
return STATUS_SUCCESS;
}
__declspec(dllexport) KSTATUS IoReadDir(DIR_OBJECT* dir, KDIR_ENTRY* entryOut) {
if (!dir || !entryOut) return STATUS_INVALID_PARAMETER;
if (!dir->Fs->ReadDir) return STATUS_NOT_SUPPORTED;
return dir->Fs->ReadDir(dir->Fs, dir->DirCtx, entryOut);
}
__declspec(dllexport) void IoCloseDir(DIR_OBJECT* dir) {
if (dir) ObDereferenceObject(&dir->Header);
}
+2 -2
View File
@@ -36,7 +36,7 @@ __declspec(dllexport) void DbgPrint(const char* fmt, ...) {
}
// KePanic
__declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...) {
__declspec(dllexport) _Noreturn void KePanic(const char* fmt, ...) {
// Silence! Stop the world!
_disable();
@@ -44,7 +44,7 @@ __declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...) {
// We call PrintChar directly so we can set a red foreground.
// (DbgPrint uses green; we want this visually distinct.)
static const char banner[] = "\n*** KERNEL PANIC ***\n";
for (const char *p = banner; *p; p++)
for (const char* p = banner; *p; p++)
PrintChar(*p, 0xFF2222, 0x000000);
// Now print the caller's message through the normal vprintf path.
+1 -1
View File
@@ -6,7 +6,7 @@
void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), int v);
__declspec(dllexport) void DbgPrint(const char* fmt, ...);
__declspec(dllexport) void KePrintConsole(const char* buf, size_t len);
__declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...);
__declspec(dllexport) _Noreturn void KePanic(const char* fmt, ...);
// ---------------------------------------------------------------------------
// Fault dispatch - called from the three IDT fault stubs (ke_syscall.asm).
+369 -240
View File
@@ -1,240 +1,369 @@
// ke_process.c - process model
#include <string.h>
#include <intrin.h>
#include <ke_process.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ke_event.h>
#include <mm_virt.h>
#include <ob_object.h>
// ---------------------------------------------------------------------------
// Global process list
// ---------------------------------------------------------------------------
static LIST_ENTRY gProcessList;
static SPINLOCK gProcessListLock;
static uint32_t gNextProcessId = 1;
static void ProcessInit(void) {
static int inited = 0;
if (!inited) {
LIST_INIT(&gProcessList);
gProcessListLock = (SPINLOCK)SPINLOCK_INIT;
inited = 1;
}
}
LIST_ENTRY* KeGetProcessList(void) {
ProcessInit();
return &gProcessList;
}
PROCESS* KeGetCurrentProcess(void) {
THREAD* currentThread = KeGetCurrentThread();
if (currentThread) {
return currentThread->Owner;
}
return NULL;
}
size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount) {
ProcessInit();
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
size_t count = 0;
LIST_ENTRY* head = &gProcessList;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) {
PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink);
if (pidsOut && count < maxCount) pidsOut[count] = p->ProcessId;
count++;
}
KeReleaseSpinlock(&gProcessListLock, flags);
return count;
}
// ---------------------------------------------------------------------------
// Destructor (called by ObDereferenceObject when RefCount hits zero)
// ---------------------------------------------------------------------------
static void ProcessDestroy(OBJECT* obj) {
PROCESS* proc = CONTAINING_RECORD(obj, PROCESS, Header);
// Release all remaining handles.
for (int i = 1; i < HANDLE_TABLE_SIZE; i++) {
if (proc->Handles[i]) {
ObDereferenceObject(proc->Handles[i]);
proc->Handles[i] = (OBJECT *)0;
}
}
// Release the main executable image.
if (proc->ImageBase && proc->ImageSize) {
MmFreeUserMemory((void *)proc->ImageBase, proc->ImageSize);
proc->ImageBase = 0;
proc->ImageSize = 0;
}
// Release per-process modules.
for (uint32_t i = 0; i < proc->ModuleCount; i++) {
if (proc->Modules[i].Base && proc->Modules[i].Size) {
MmFreeUserMemory((void *)proc->Modules[i].Base, proc->Modules[i].Size);
}
proc->Modules[i].Base = 0;
proc->Modules[i].Size = 0;
proc->Modules[i].Name[0] = '\0';
}
proc->ModuleCount = 0;
// Release the user command line buffer.
if (proc->UserCmdlineBase && proc->UserCmdlineSize) {
MmFreeUserMemory((void *)proc->UserCmdlineBase, proc->UserCmdlineSize);
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
}
// Release the exit event.
if (proc->ExitEvent) {
ObDereferenceObject(&proc->ExitEvent->Header);
proc->ExitEvent = (EVENT *)0;
}
// Remove from global list.
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
ListRemove(&proc->GlobalLink);
KeReleaseSpinlock(&gProcessListLock, flags);
// Memory for the struct itself is freed by ObDereferenceObject after
// Destroy returns.
}
// ---------------------------------------------------------------------------
// KeCreateProcess
// ---------------------------------------------------------------------------
KSTATUS KeCreateProcess(const char* name, PROCESS **out) {
ProcessInit();
if (!name || !out) return STATUS_INVALID_PARAMETER;
PROCESS* proc = (PROCESS *)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS));
if (!proc) return STATUS_NO_MEMORY;
proc->Header.Destroy = ProcessDestroy;
proc->ProcessId = _InterlockedExchangeAdd((volatile long *)&gNextProcessId, 1);
strncpy(proc->Name, name, PROCESS_NAME_MAX - 1);
proc->Name[PROCESS_NAME_MAX - 1] = '\0';
LIST_INIT(&proc->ThreadList);
proc->ThreadCount = 0;
proc->HandleLock = (SPINLOCK)SPINLOCK_INIT;
proc->ExitCode = 0;
proc->ImageBase = 0;
proc->ImageSize = 0;
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
proc->ModuleCount = 0;
// Slot 0 is the invalid handle - leave it NULL.
memset(proc->Handles, 0, sizeof(proc->Handles));
LIST_INIT(&proc->GlobalLink);
// Create the manual-reset exit event (initially unsignaled).
if (!KSUCCESS(KeEventCreate(EventManualReset, 0, &proc->ExitEvent))) {
ObDereferenceObject(&proc->Header);
return STATUS_NO_MEMORY;
}
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
ListInsertTail(&gProcessList, &proc->GlobalLink);
KeReleaseSpinlock(&gProcessListLock, flags);
*out = proc;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// KeExitProcess
// ---------------------------------------------------------------------------
void KeExitProcess(PROCESS* process, int exitCode) {
if (!process) return;
process->ExitCode = exitCode;
// Wake any threads waiting for this process to finish.
if (process->ExitEvent) KeSignalEvent(process->ExitEvent);
// The last thread exiting via KeExitThread will drop the process refcount.
// This function is a policy hook; actual cleanup is in ProcessDestroy.
ObDereferenceObject(&process->Header);
}
// ---------------------------------------------------------------------------
// KeGetProcessExitCode
// ---------------------------------------------------------------------------
KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut) {
if (!process || !exitCodeOut) return STATUS_INVALID_PARAMETER;
*exitCodeOut = process->ExitCode;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Handle table
// ---------------------------------------------------------------------------
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) {
if (!process || !object || !handleOut) return STATUS_INVALID_PARAMETER;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
for (int i = 1; i < HANDLE_TABLE_SIZE; i++) {
if (!process->Handles[i]) {
ObReferenceObject(object);
process->Handles[i] = object;
*handleOut = (uint32_t)i;
KeReleaseSpinlock(&process->HandleLock, flags);
return STATUS_SUCCESS;
}
}
KeReleaseSpinlock(&process->HandleLock, flags);
return STATUS_NO_MEMORY;
}
void KeCloseHandle(PROCESS* process, uint32_t handle) {
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
OBJECT* obj = process->Handles[handle];
if (obj) {
process->Handles[handle] = (OBJECT *)0;
KeReleaseSpinlock(&process->HandleLock, flags);
ObDereferenceObject(obj);
return;
}
KeReleaseSpinlock(&process->HandleLock, flags);
}
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle) {
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE)
return (OBJECT *)0;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
OBJECT* obj = process->Handles[handle];
if (obj) ObReferenceObject(obj);
KeReleaseSpinlock(&process->HandleLock, flags);
return obj;
}
PROCESS* KeGetProcessById(uint32_t pid) {
ProcessInit();
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
LIST_ENTRY* head = &gProcessList;
LIST_ENTRY* node = head->Fwd;
while (node != head) {
PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink);
if (p->ProcessId == pid) {
ObReferenceObject(&p->Header);
KeReleaseSpinlock(&gProcessListLock, flags);
return p;
}
node = node->Fwd;
}
KeReleaseSpinlock(&gProcessListLock, flags);
return (PROCESS*)0;
}
// ke_process.c - process model
#include <string.h>
#include <intrin.h>
#include <ke_process.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ke_event.h>
#include <ke_userenv.h>
#include <mm_virt.h>
#include <ob_object.h>
// ---------------------------------------------------------------------------
// Global process list
// ---------------------------------------------------------------------------
static LIST_ENTRY gProcessList;
static SPINLOCK gProcessListLock;
static uint32_t gNextProcessId = 1;
static void ProcessInit(void) {
static int inited = 0;
if (!inited) {
LIST_INIT(&gProcessList);
gProcessListLock = (SPINLOCK)SPINLOCK_INIT;
inited = 1;
}
}
LIST_ENTRY* KeGetProcessList(void) {
ProcessInit();
return &gProcessList;
}
PROCESS* KeGetCurrentProcess(void) {
THREAD* currentThread = KeGetCurrentThread();
if (currentThread) {
return currentThread->Owner;
}
return NULL;
}
size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount) {
ProcessInit();
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
size_t count = 0;
LIST_ENTRY* head = &gProcessList;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) {
PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink);
if (pidsOut && count < maxCount) pidsOut[count] = p->ProcessId;
count++;
}
KeReleaseSpinlock(&gProcessListLock, flags);
return count;
}
// ---------------------------------------------------------------------------
// Destructor (called by ObDereferenceObject when RefCount hits zero)
// ---------------------------------------------------------------------------
static void ProcessDestroy(OBJECT* obj) {
PROCESS* proc = CONTAINING_RECORD(obj, PROCESS, Header);
// Release all remaining handles.
for (int i = 1; i < HANDLE_TABLE_SIZE; i++) {
if (proc->Handles[i]) {
ObDereferenceObject(proc->Handles[i]);
proc->Handles[i] = (OBJECT *)0;
}
}
// Release the main executable image.
if (proc->ImageBase && proc->ImageSize) {
MmFreeUserMemory((void *)proc->ImageBase, proc->ImageSize);
proc->ImageBase = 0;
proc->ImageSize = 0;
}
// Release per-process modules.
for (uint32_t i = 0; i < proc->ModuleCount; i++) {
if (proc->Modules[i].Base && proc->Modules[i].Size) {
MmFreeUserMemory((void *)proc->Modules[i].Base, proc->Modules[i].Size);
}
proc->Modules[i].Base = 0;
proc->Modules[i].Size = 0;
proc->Modules[i].Name[0] = '\0';
}
proc->ModuleCount = 0;
// Release the user-mode process environment.
if (proc->UserPebBase && proc->UserPebSize) {
MmFreeUserMemory((void *)proc->UserPebBase, proc->UserPebSize);
proc->UserPebBase = 0;
proc->UserPebSize = 0;
}
if (proc->UserImagePathBase && proc->UserImagePathSize) {
MmFreeUserMemory((void *)proc->UserImagePathBase, proc->UserImagePathSize);
proc->UserImagePathBase = 0;
proc->UserImagePathSize = 0;
}
if (proc->UserCmdlineBase && proc->UserCmdlineSize) {
MmFreeUserMemory((void *)proc->UserCmdlineBase, proc->UserCmdlineSize);
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
}
if (proc->UserCwdBase && proc->UserCwdSize) {
MmFreeUserMemory((void *)proc->UserCwdBase, proc->UserCwdSize);
proc->UserCwdBase = 0;
proc->UserCwdSize = 0;
}
// Release the exit event.
if (proc->ExitEvent) {
ObDereferenceObject(&proc->ExitEvent->Header);
proc->ExitEvent = (EVENT *)0;
}
// Remove from global list.
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
ListRemove(&proc->GlobalLink);
KeReleaseSpinlock(&gProcessListLock, flags);
// Memory for the struct itself is freed by ObDereferenceObject after
// Destroy returns.
}
// ---------------------------------------------------------------------------
// KeCreateProcess
// ---------------------------------------------------------------------------
KSTATUS KeCreateProcess(const char* name, PROCESS **out) {
ProcessInit();
if (!name || !out) return STATUS_INVALID_PARAMETER;
PROCESS* proc = (PROCESS *)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS));
if (!proc) return STATUS_NO_MEMORY;
proc->Header.Destroy = ProcessDestroy;
proc->ProcessId = _InterlockedExchangeAdd((volatile long *)&gNextProcessId, 1);
strncpy(proc->Name, name, PROCESS_NAME_MAX - 1);
proc->Name[PROCESS_NAME_MAX - 1] = '\0';
LIST_INIT(&proc->ThreadList);
proc->ThreadCount = 0;
proc->HandleLock = (SPINLOCK)SPINLOCK_INIT;
proc->ExitCode = 0;
proc->ImageBase = 0;
proc->ImageSize = 0;
proc->UserPebBase = 0;
proc->UserPebSize = 0;
proc->UserImagePathBase = 0;
proc->UserImagePathSize = 0;
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
proc->UserCwdBase = 0;
proc->UserCwdSize = 0;
proc->ModuleCount = 0;
proc->CurrentDirectory[0] = '\0';
proc->CwdLock = (SPINLOCK)SPINLOCK_INIT;
// Slot 0 is the invalid handle - leave it NULL.
memset(proc->Handles, 0, sizeof(proc->Handles));
LIST_INIT(&proc->GlobalLink);
// Create the manual-reset exit event (initially unsignaled).
if (!KSUCCESS(KeEventCreate(EventManualReset, 0, &proc->ExitEvent))) {
ObDereferenceObject(&proc->Header);
return STATUS_NO_MEMORY;
}
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
ListInsertTail(&gProcessList, &proc->GlobalLink);
KeReleaseSpinlock(&gProcessListLock, flags);
*out = proc;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// KeSetupUserProcessEnvironment
// ---------------------------------------------------------------------------
static KSTATUS CopyEnvironmentString(const char* src, uintptr_t* baseOut, size_t* sizeOut) {
if (!baseOut || !sizeOut) return STATUS_INVALID_PARAMETER;
if (!src) src = "";
size_t len = strlen(src) + 1;
char* dst = (char*)MmAllocateUserMemory(len);
if (!dst) return STATUS_NO_MEMORY;
memcpy(dst, src, len);
*baseOut = (uintptr_t)dst;
*sizeOut = len;
return STATUS_SUCCESS;
}
static KSTATUS CopyEnvironmentStringFixed(const char* src, size_t capacity,
uintptr_t* baseOut, size_t* sizeOut) {
if (!baseOut || !sizeOut || capacity == 0) return STATUS_INVALID_PARAMETER;
if (!src) src = "";
char* dst = (char*)MmAllocateUserMemory(capacity);
if (!dst) return STATUS_NO_MEMORY;
memset(dst, 0, capacity);
size_t len = strlen(src);
if (len >= capacity) len = capacity - 1;
memcpy(dst, src, len);
dst[len] = '\0';
*baseOut = (uintptr_t)dst;
*sizeOut = capacity;
return STATUS_SUCCESS;
}
static void FreePartialProcessEnvironment(PROCESS* proc) {
if (!proc) return;
if (proc->UserPebBase && proc->UserPebSize) {
MmFreeUserMemory((void*)proc->UserPebBase, proc->UserPebSize);
proc->UserPebBase = 0;
proc->UserPebSize = 0;
}
if (proc->UserImagePathBase && proc->UserImagePathSize) {
MmFreeUserMemory((void*)proc->UserImagePathBase, proc->UserImagePathSize);
proc->UserImagePathBase = 0;
proc->UserImagePathSize = 0;
}
if (proc->UserCmdlineBase && proc->UserCmdlineSize) {
MmFreeUserMemory((void*)proc->UserCmdlineBase, proc->UserCmdlineSize);
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
}
if (proc->UserCwdBase && proc->UserCwdSize) {
MmFreeUserMemory((void*)proc->UserCwdBase, proc->UserCwdSize);
proc->UserCwdBase = 0;
proc->UserCwdSize = 0;
}
}
KSTATUS KeSetupUserProcessEnvironment(PROCESS* proc,
const char* imagePath,
const char* commandLine,
const char* currentDirectory) {
if (!proc || !imagePath) return STATUS_INVALID_PARAMETER;
const char* finalCommandLine = (commandLine && commandLine[0]) ? commandLine : imagePath;
const char* finalCwd = currentDirectory ? currentDirectory : proc->CurrentDirectory;
FreePartialProcessEnvironment(proc);
KSTATUS s = CopyEnvironmentString(imagePath, &proc->UserImagePathBase, &proc->UserImagePathSize);
if (!KSUCCESS(s)) goto fail;
s = CopyEnvironmentString(finalCommandLine, &proc->UserCmdlineBase, &proc->UserCmdlineSize);
if (!KSUCCESS(s)) goto fail;
s = CopyEnvironmentStringFixed(finalCwd, SYS_CWD_MAX, &proc->UserCwdBase, &proc->UserCwdSize);
if (!KSUCCESS(s)) goto fail;
SYS_PEB* peb = (SYS_PEB*)MmAllocateUserMemory(sizeof(SYS_PEB));
if (!peb) { s = STATUS_NO_MEMORY; goto fail; }
memset(peb, 0, sizeof(SYS_PEB));
peb->Magic = SYS_PEB_MAGIC;
peb->Version = SYS_ENV_VERSION;
peb->ProcessId = proc->ProcessId;
peb->ImagePath = (const char*)proc->UserImagePathBase;
peb->ImagePathLength = proc->UserImagePathSize ? proc->UserImagePathSize - 1 : 0;
peb->CommandLine = (const char*)proc->UserCmdlineBase;
peb->CommandLineLength = proc->UserCmdlineSize ? proc->UserCmdlineSize - 1 : 0;
peb->CurrentDirectory = (const char*)proc->UserCwdBase;
peb->CurrentDirectoryLength = strlen(finalCwd);
proc->UserPebBase = (uintptr_t)peb;
proc->UserPebSize = sizeof(SYS_PEB);
return STATUS_SUCCESS;
fail:
FreePartialProcessEnvironment(proc);
return s;
}
// ---------------------------------------------------------------------------
// KeExitProcess
// ---------------------------------------------------------------------------
void KeExitProcess(PROCESS* process, int exitCode) {
if (!process) return;
process->ExitCode = exitCode;
// Wake any threads waiting for this process to finish.
if (process->ExitEvent) KeSignalEvent(process->ExitEvent);
// The last thread exiting via KeExitThread will drop the process refcount.
// This function is a policy hook; actual cleanup is in ProcessDestroy.
ObDereferenceObject(&process->Header);
}
// ---------------------------------------------------------------------------
// KeGetProcessExitCode
// ---------------------------------------------------------------------------
KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut) {
if (!process || !exitCodeOut) return STATUS_INVALID_PARAMETER;
*exitCodeOut = process->ExitCode;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Handle table
// ---------------------------------------------------------------------------
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) {
if (!process || !object || !handleOut) return STATUS_INVALID_PARAMETER;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
for (int i = 1; i < HANDLE_TABLE_SIZE; i++) {
if (!process->Handles[i]) {
ObReferenceObject(object);
process->Handles[i] = object;
*handleOut = (uint32_t)i;
KeReleaseSpinlock(&process->HandleLock, flags);
return STATUS_SUCCESS;
}
}
KeReleaseSpinlock(&process->HandleLock, flags);
return STATUS_NO_MEMORY;
}
void KeCloseHandle(PROCESS* process, uint32_t handle) {
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
OBJECT* obj = process->Handles[handle];
if (obj) {
process->Handles[handle] = (OBJECT *)0;
KeReleaseSpinlock(&process->HandleLock, flags);
ObDereferenceObject(obj);
return;
}
KeReleaseSpinlock(&process->HandleLock, flags);
}
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle) {
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE)
return (OBJECT *)0;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
OBJECT* obj = process->Handles[handle];
if (obj) ObReferenceObject(obj);
KeReleaseSpinlock(&process->HandleLock, flags);
return obj;
}
PROCESS* KeGetProcessById(uint32_t pid) {
ProcessInit();
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
LIST_ENTRY* head = &gProcessList;
LIST_ENTRY* node = head->Fwd;
while (node != head) {
PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink);
if (p->ProcessId == pid) {
ObReferenceObject(&p->Header);
KeReleaseSpinlock(&gProcessListLock, flags);
return p;
}
node = node->Fwd;
}
KeReleaseSpinlock(&gProcessListLock, flags);
return (PROCESS*)0;
}
+86 -83
View File
@@ -1,83 +1,86 @@
#pragma once
// ke_process.h - process model
// A process owns a handle table and a list of threads.
// There is one shared address space - processes do not own one.
#include <ob_object.h>
#include <ke_list.h>
#include <ke_spinlock.h>
#include <ke_event.h>
#include <stddef.h>
#define PROCESS_NAME_MAX 64
#define HANDLE_TABLE_SIZE 256
#define PROCESS_MODULE_MAX 16
typedef struct PROCESS_MODULE {
char Name[32];
uintptr_t Base;
size_t Size;
} PROCESS_MODULE;
typedef struct PROCESS {
OBJECT Header; /* must be first */
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;
/* Thread list. */
LIST_ENTRY ThreadList; /* links THREAD.ProcessLink */
uint32_t ThreadCount;
/* Handle table.
Index 0 is always NULL (the invalid handle).
Entries are OBJECT pointers, each holding a reference. */
OBJECT* Handles[HANDLE_TABLE_SIZE];
SPINLOCK HandleLock;
int ExitCode;
/* Main executable image. Used for cleanup. */
uintptr_t ImageBase;
size_t ImageSize;
/* User command line buffer passed to the initial thread. */
uintptr_t UserCmdlineBase;
size_t UserCmdlineSize;
/* Per-process loaded modules (user processes only). */
PROCESS_MODULE Modules[PROCESS_MODULE_MAX];
uint32_t ModuleCount;
/* Signaled (manual-reset) when the process exits.
Waiters block on this via KeWaitForEvent. */
EVENT* ExitEvent;
LIST_ENTRY GlobalLink; /* node in the global process list */
} PROCESS;
KSTATUS KeCreateProcess(const char* name, PROCESS **out);
void KeExitProcess(PROCESS* process, int exitCode);
KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut);
/* Handle table operations. */
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut);
void KeCloseHandle(PROCESS* process, uint32_t handle);
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle);
/* Return the global process list head (kernel-internal). */
LIST_ENTRY* KeGetProcessList(void);
PROCESS* KeGetCurrentProcess(void);
/* Copy a snapshot of process IDs into caller-provided storage. */
size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount);
/* Find a live process by its ProcessId. Bumps refcount; caller must ObDereferenceObject. */
PROCESS* KeGetProcessById(uint32_t pid);
#pragma once
// ke_process.h - process model
// A process owns a handle table and a list of threads.
// There is one shared address space - processes do not own one.
#include <ob_object.h>
#include <ke_list.h>
#include <ke_spinlock.h>
#include <ke_event.h>
#include <stddef.h>
#include <stdint.h>
#define PROCESS_NAME_MAX 64
#define HANDLE_TABLE_SIZE 256
#define PROCESS_MODULE_MAX 16
typedef struct PROCESS_MODULE {
char Name[32];
uintptr_t Base;
size_t Size;
} PROCESS_MODULE;
typedef struct PROCESS {
OBJECT Header; /* must be first */
uint32_t ProcessId;
char Name[PROCESS_NAME_MAX]; /* UTF-8 */
char CurrentDirectory[512];
SPINLOCK CwdLock;
/* Thread list. */
LIST_ENTRY ThreadList; /* links THREAD.ProcessLink */
uint32_t ThreadCount;
/* Handle table.
Index 0 is always NULL (the invalid handle).
Entries are OBJECT pointers, each holding a reference. */
OBJECT* Handles[HANDLE_TABLE_SIZE];
SPINLOCK HandleLock;
int ExitCode;
/* Main executable image. Used for cleanup. */
uintptr_t ImageBase;
size_t ImageSize;
/* User-mode process environment. The initial thread sees this through GS->TEB->PEB. */
uintptr_t UserPebBase;
size_t UserPebSize;
uintptr_t UserImagePathBase;
size_t UserImagePathSize;
uintptr_t UserCmdlineBase;
size_t UserCmdlineSize;
uintptr_t UserCwdBase;
size_t UserCwdSize;
/* Per-process loaded modules (user processes only). */
PROCESS_MODULE Modules[PROCESS_MODULE_MAX];
uint32_t ModuleCount;
/* Signaled (manual-reset) when the process exits.
Waiters block on this via KeWaitForEvent. */
EVENT* ExitEvent;
LIST_ENTRY GlobalLink; /* node in the global process list */
} PROCESS;
KSTATUS KeCreateProcess(const char* name, PROCESS **out);
KSTATUS KeSetupUserProcessEnvironment(PROCESS* process, const char* imagePath, const char* commandLine, const char* currentDirectory);
void KeExitProcess(PROCESS* process, int exitCode);
KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut);
/* Handle table operations. */
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut);
void KeCloseHandle(PROCESS* process, uint32_t handle);
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle);
/* Return the global process list head (kernel-internal). */
LIST_ENTRY* KeGetProcessList(void);
PROCESS* KeGetCurrentProcess(void);
/* Copy a snapshot of process IDs into caller-provided storage. */
size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount);
/* Find a live process by its ProcessId. Bumps refcount; caller must ObDereferenceObject. */
PROCESS* KeGetProcessById(uint32_t pid);
+140 -138
View File
@@ -1,138 +1,140 @@
; 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
THREAD_FpuContext EQU 160
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
mov rax, [rcx + THREAD_FpuContext]
fxsave64 [rax]
; 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
mov rax, [rdx + THREAD_FpuContext]
fxrstor64 [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
; 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
; THREAD.Teb offset = 112
; THREAD.FpuContext offset = 168
; TSS64.RSP0 offset = 4
;
THREAD_SavedContext EQU 88
THREAD_TssRsp0 EQU 104
THREAD_Teb EQU 112
THREAD_FpuContext EQU 168
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
mov rax, [rcx + THREAD_FpuContext]
fxsave64 [rax]
; 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
mov rax, [rdx + THREAD_FpuContext]
fxrstor64 [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
+1331 -1331
View File
File diff suppressed because it is too large. Load diff
+281 -256
View File
@@ -1,256 +1,281 @@
// ke_thread.c - thread model
#include <string.h>
#include <intrin.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ke_event.h>
#include <mm_virt.h>
#include <ob_object.h>
#define FPU_FCW_DEFAULT 0x037Fu
#define FPU_MXCSR_DEFAULT 0x1F80u
static uint32_t gNextThreadId = 1;
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);
thread->Owner->ThreadCount--;
KeReleaseSpinlock(&thread->Owner->HandleLock, flags);
ObDereferenceObject(&thread->Owner->Header);
thread->Owner = (PROCESS *)0;
}
if (thread->KernelStackBase) {
MmFreePool(thread->KernelStackBase);
thread->KernelStackBase = (void *)0;
}
// Assumes UserStackBase was added to THREAD struct in ke_thread.h.
if (thread->UserStackBase) {
MmFreeUserMemory(thread->UserStackBase, USTACK_SIZE);
thread->UserStackBase = (void *)0;
}
}
__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
THREAD_PRIORITY priority, THREAD **out) {
if (!owner || !out) return STATUS_INVALID_PARAMETER;
if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER;
THREAD* thread = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
if (!thread) return STATUS_NO_MEMORY;
thread->Header.Destroy = ThreadDestroy;
thread->ThreadId = _InterlockedExchangeAdd((volatile long *)&gNextThreadId, 1);
thread->State = ThreadReady;
thread->Priority = priority;
thread->QuantaConsumed = 0;
thread->WaitObject = (OBJECT *)0;
thread->SchedLockFlags = 0x202ULL;
void* kstack = MmAllocatePool(KSTACK_SIZE);
if (!kstack) {
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
memset(kstack, 0, KSTACK_SIZE);
thread->KernelStackBase = kstack;
thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE;
thread->TssRsp0 = thread->KernelStackTop;
void* ustack = MmAllocateUserMemory(USTACK_SIZE);
if (!ustack) {
MmFreePool(kstack);
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
thread->UserStackBase = ustack;
uintptr_t userStackTop = (uintptr_t)ustack + USTACK_SIZE - 8; // i don't know why, i don't want to know why
// i shouldn't have to wonder why but the stack is off by 8 bytes unless we do this terribleness
// this magic number will hunt me in my dreams
// Initialize the dummy TEB
// offset 0x10 is StackLimit, which __chkstk checks.
uint64_t* teb = (uint64_t*)ustack;
teb[0] = 0; // ExceptionList (GS:0x00)
teb[1] = userStackTop; // StackBase
teb[2] = (uintptr_t)ustack; // StackLimit
thread->Teb = teb;
thread->UserEntry = entry;
thread->UserArg = arg;
uintptr_t* ksp = (uintptr_t *)thread->KernelStackTop;
*(--ksp) = userStackTop;
*(--ksp) = 0x23ULL;
*(--ksp) = userStackTop;
*(--ksp) = 0x202ULL;
*(--ksp) = 0x1BULL;
*(--ksp) = entry;
*(--ksp) = arg;
ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t);
KCONTEXT* ctx = (KCONTEXT *)ksp;
memset(ctx, 0, sizeof(KCONTEXT));
ctx->Rip = (uint64_t)KeThreadEntry;
thread->SavedContext = ctx;
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++;
KeReleaseSpinlock(&owner->HandleLock, flags);
KeInitThreadFpuContext(thread);
KeReadyThread(thread);
*out = thread;
return STATUS_SUCCESS;
}
__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();
}
// ---------------------------------------------------------------------------
// KeCreateKernelThread
// ---------------------------------------------------------------------------
// Kernel threads never go to ring 3: they have no user stack, no TEB, and
// their entry stub (KeKernelThreadEntry) calls the function directly then
// calls KeExitThread instead of IRETQ-ing.
//
// Stack layout built here (from KernelStackTop downward):
// [ksp - 8 ] arg <-- popped by KeKernelThreadEntry as r12
// [ksp - 16] fn <-- popped by KeKernelThreadEntry as rbx
// [ksp - 16 - sizeof(KCONTEXT)] KCONTEXT { .Rip = KeKernelThreadEntry }
// ---------------------------------------------------------------------------
extern void KeKernelThreadEntry(void);
__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg,
THREAD_PRIORITY priority, THREAD **out) {
// Kernel threads are owned by the idle process. We need a PROCESS* for
// the owner field; grab it from the idle/init thread that already exists.
THREAD* idleThread = KeGetCurrentThread(); // safe to call before scheduler starts
PROCESS* owner = idleThread ? idleThread->Owner : (PROCESS*)0;
if (!owner) return STATUS_INVALID_PARAMETER;
if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER;
THREAD* thread = (THREAD*)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
if (!thread) return STATUS_NO_MEMORY;
thread->Header.Destroy = ThreadDestroy;
thread->ThreadId = _InterlockedExchangeAdd((volatile long*)&gNextThreadId, 1);
thread->State = ThreadReady;
thread->Priority = priority;
thread->QuantaConsumed = 0;
thread->WaitObject = (OBJECT*)0;
thread->SchedLockFlags = 0x202ULL;
thread->UserStackBase = (void*)0; // no user stack
thread->Teb = (void*)0; // no TEB; GS_BASE will be 0 in kernel ctx
void* kstack = MmAllocatePool(KSTACK_SIZE);
if (!kstack) {
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
memset(kstack, 0, KSTACK_SIZE);
thread->KernelStackBase = kstack;
thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE;
thread->TssRsp0 = thread->KernelStackTop;
// Build the initial stack: arg, fn, then KCONTEXT.
uintptr_t* ksp = (uintptr_t*)thread->KernelStackTop;
*(--ksp) = arg; // popped second (-> r12)
*(--ksp) = fn; // popped first (-> rbx)
ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t);
KCONTEXT* ctx = (KCONTEXT*)ksp;
memset(ctx, 0, sizeof(KCONTEXT));
ctx->Rip = (uint64_t)KeKernelThreadEntry;
thread->SavedContext = ctx;
// Register with the owning process.
ObReferenceObject(&owner->Header);
thread->Owner = owner;
uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock);
ListInsertTail(&owner->ThreadList, &thread->ProcessLink);
owner->ThreadCount++;
KeReleaseSpinlock(&owner->HandleLock, flags);
if (out) *out = thread;
KeInitThreadFpuContext(thread);
KeReadyThread(thread);
return STATUS_SUCCESS;
}
void KeBlockThread(THREAD* thread, OBJECT* waitObject) {
thread->State = ThreadWaiting;
thread->WaitObject = waitObject;
KeYield();
}
void KeUnblockThread(THREAD* thread) {
thread->WaitObject = (OBJECT *)0;
thread->State = ThreadReady;
if (thread->Priority < PriorityHigh) {
thread->Priority = (THREAD_PRIORITY)(thread->Priority + 1);
thread->QuantaConsumed = 0;
}
KeReadyThread(thread);
}
void KeInitThreadFpuContext(THREAD* thread)
{
uintptr_t raw =
(uintptr_t)thread->FpuStorage;
uintptr_t aligned =
(raw + 15) & ~(uintptr_t)15;
thread->FpuContext = (void*)aligned;
memset(thread->FpuContext, 0, 512);
*(uint16_t*)((uint8_t*)thread->FpuContext + 0x00) = 0x037F;
*(uint32_t*)((uint8_t*)thread->FpuContext + 0x18) = 0x1F80;
}
// ke_thread.c - thread model
#include <string.h>
#include <intrin.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ke_event.h>
#include <ke_userenv.h>
#include <mm_virt.h>
#include <ob_object.h>
#define FPU_FCW_DEFAULT 0x037Fu
#define FPU_MXCSR_DEFAULT 0x1F80u
static uint32_t gNextThreadId = 1;
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);
thread->Owner->ThreadCount--;
KeReleaseSpinlock(&thread->Owner->HandleLock, flags);
ObDereferenceObject(&thread->Owner->Header);
thread->Owner = (PROCESS *)0;
}
if (thread->KernelStackBase) {
MmFreePool(thread->KernelStackBase);
thread->KernelStackBase = (void *)0;
}
if (thread->Teb && thread->TebSize) {
MmFreeUserMemory(thread->Teb, thread->TebSize);
thread->Teb = (void*)0;
thread->TebSize = 0;
}
if (thread->UserStackBase) {
MmFreeUserMemory(thread->UserStackBase, USTACK_SIZE);
thread->UserStackBase = (void *)0;
}
}
__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
THREAD_PRIORITY priority, THREAD** out) {
if (!owner || !out) return STATUS_INVALID_PARAMETER;
if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER;
THREAD* thread = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
if (!thread) return STATUS_NO_MEMORY;
thread->Header.Destroy = ThreadDestroy;
thread->ThreadId = _InterlockedExchangeAdd((volatile long *)&gNextThreadId, 1);
thread->State = ThreadReady;
thread->Priority = priority;
thread->QuantaConsumed = 0;
thread->WaitObject = (OBJECT *)0;
thread->SchedLockFlags = 0x202ULL;
void* kstack = MmAllocatePool(KSTACK_SIZE);
if (!kstack) {
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
memset(kstack, 0, KSTACK_SIZE);
thread->KernelStackBase = kstack;
thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE;
thread->TssRsp0 = thread->KernelStackTop;
void* ustack = MmAllocateUserMemory(USTACK_SIZE);
if (!ustack) {
MmFreePool(kstack);
thread->KernelStackBase = (void*)0;
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
thread->UserStackBase = ustack;
uintptr_t userStackTop = (uintptr_t)ustack + USTACK_SIZE - 8; // i don't know why, i don't want to know why
// i shouldn't have to wonder why but the stack is off by 8 bytes unless we do this terribleness
// this magic number will hunt me in my dreams
SYS_TEB* teb = (SYS_TEB*)MmAllocateUserMemory(sizeof(SYS_TEB));
if (!teb) {
MmFreeUserMemory(ustack, USTACK_SIZE);
thread->UserStackBase = (void*)0;
MmFreePool(kstack);
thread->KernelStackBase = (void*)0;
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
memset(teb, 0, sizeof(SYS_TEB));
teb->Peb = (SYS_PEB*)owner->UserPebBase;
teb->StackBase = (void*)userStackTop;
teb->StackLimit = ustack;
teb->Self = teb;
teb->ThreadId = thread->ThreadId;
teb->Magic = SYS_TEB_MAGIC;
thread->Teb = teb;
thread->TebSize = sizeof(SYS_TEB);
thread->UserEntry = entry;
thread->UserArg = arg;
uintptr_t* ksp = (uintptr_t *)thread->KernelStackTop;
*(--ksp) = userStackTop;
*(--ksp) = 0x23ULL;
*(--ksp) = userStackTop;
*(--ksp) = 0x202ULL;
*(--ksp) = 0x1BULL;
*(--ksp) = entry;
*(--ksp) = arg;
ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t);
KCONTEXT* ctx = (KCONTEXT *)ksp;
memset(ctx, 0, sizeof(KCONTEXT));
ctx->Rip = (uint64_t)KeThreadEntry;
thread->SavedContext = ctx;
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)) {
if (thread->Teb && thread->TebSize) MmFreeUserMemory(thread->Teb, thread->TebSize);
thread->Teb = (void*)0;
thread->TebSize = 0;
MmFreeUserMemory(ustack, USTACK_SIZE);
thread->UserStackBase = (void*)0;
MmFreePool(kstack);
thread->KernelStackBase = (void*)0;
ObDereferenceObject(&owner->Header);
thread->Owner = (PROCESS*)0;
ObDereferenceObject(&thread->Header);
return s;
}
thread->ExitEvent = exitEvent;
uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock);
ListInsertTail(&owner->ThreadList, &thread->ProcessLink);
owner->ThreadCount++;
KeReleaseSpinlock(&owner->HandleLock, flags);
KeInitThreadFpuContext(thread);
KeReadyThread(thread);
*out = thread;
return STATUS_SUCCESS;
}
__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();
}
// ---------------------------------------------------------------------------
// KeCreateKernelThread
// ---------------------------------------------------------------------------
// Kernel threads never go to ring 3: they have no user stack, no TEB, and
// their entry stub (KeKernelThreadEntry) calls the function directly then
// calls KeExitThread instead of IRETQ-ing.
//
// Stack layout built here (from KernelStackTop downward):
// [ksp - 8 ] arg <-- popped by KeKernelThreadEntry as r12
// [ksp - 16] fn <-- popped by KeKernelThreadEntry as rbx
// [ksp - 16 - sizeof(KCONTEXT)] KCONTEXT { .Rip = KeKernelThreadEntry }
// ---------------------------------------------------------------------------
extern void KeKernelThreadEntry(void);
__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg,
THREAD_PRIORITY priority, THREAD** out) {
// Kernel threads are owned by the idle process. We need a PROCESS* for
// the owner field; grab it from the idle/init thread that already exists.
THREAD* idleThread = KeGetCurrentThread(); // safe to call before scheduler starts
PROCESS* owner = idleThread ? idleThread->Owner : (PROCESS*)0;
if (!owner) return STATUS_INVALID_PARAMETER;
if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER;
THREAD* thread = (THREAD*)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
if (!thread) return STATUS_NO_MEMORY;
thread->Header.Destroy = ThreadDestroy;
thread->ThreadId = _InterlockedExchangeAdd((volatile long*)&gNextThreadId, 1);
thread->State = ThreadReady;
thread->Priority = priority;
thread->QuantaConsumed = 0;
thread->WaitObject = (OBJECT*)0;
thread->SchedLockFlags = 0x202ULL;
thread->UserStackBase = (void*)0; // no user stack
thread->Teb = (void*)0; // no TEB; GS_BASE will be 0 in kernel ctx
thread->TebSize = 0;
void* kstack = MmAllocatePool(KSTACK_SIZE);
if (!kstack) {
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
memset(kstack, 0, KSTACK_SIZE);
thread->KernelStackBase = kstack;
thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE;
thread->TssRsp0 = thread->KernelStackTop;
// Build the initial stack: arg, fn, then KCONTEXT.
uintptr_t* ksp = (uintptr_t*)thread->KernelStackTop;
*(--ksp) = arg; // popped second (-> r12)
*(--ksp) = fn; // popped first (-> rbx)
ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t);
KCONTEXT* ctx = (KCONTEXT*)ksp;
memset(ctx, 0, sizeof(KCONTEXT));
ctx->Rip = (uint64_t)KeKernelThreadEntry;
thread->SavedContext = ctx;
// Register with the owning process.
ObReferenceObject(&owner->Header);
thread->Owner = owner;
uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock);
ListInsertTail(&owner->ThreadList, &thread->ProcessLink);
owner->ThreadCount++;
KeReleaseSpinlock(&owner->HandleLock, flags);
if (out) *out = thread;
KeInitThreadFpuContext(thread);
KeReadyThread(thread);
return STATUS_SUCCESS;
}
void KeBlockThread(THREAD* thread, OBJECT* waitObject) {
thread->State = ThreadWaiting;
thread->WaitObject = waitObject;
KeYield();
}
void KeUnblockThread(THREAD* thread) {
thread->WaitObject = (OBJECT *)0;
thread->State = ThreadReady;
if (thread->Priority < PriorityHigh) {
thread->Priority = (THREAD_PRIORITY)(thread->Priority + 1);
thread->QuantaConsumed = 0;
}
KeReadyThread(thread);
}
void KeInitThreadFpuContext(THREAD* thread)
{
uintptr_t raw =
(uintptr_t)thread->FpuStorage;
uintptr_t aligned =
(raw + 15) & ~(uintptr_t)15;
thread->FpuContext = (void*)aligned;
memset(thread->FpuContext, 0, 512);
*(uint16_t*)((uint8_t*)thread->FpuContext + 0x00) = 0x037F;
*(uint32_t*)((uint8_t*)thread->FpuContext + 0x18) = 0x1F80;
}
+102 -101
View File
@@ -1,101 +1,102 @@
#pragma once
// ke_thread.h - thread model
// A THREAD is the unit of scheduling. Every thread belongs to exactly one process.
#include <stdint.h>
#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 */
typedef enum THREAD_PRIORITY {
PriorityLow = 0,
PriorityNormal = 1,
PriorityHigh = 2,
PriorityCount = 3,
} THREAD_PRIORITY;
typedef enum THREAD_STATE {
ThreadReady = 0,
ThreadRunning = 1,
ThreadWaiting = 2,
ThreadDead = 3,
} THREAD_STATE;
// Saved register context - matches the layout pushed by KeSwitchContext.
// The saved RSP points to this structure on the kernel stack.
typedef struct KCONTEXT {
uint64_t R15, R14, R13, R12;
uint64_t Rbp, Rbx;
uint64_t Rip; /* return address back into KeSwitchContext */
} KCONTEXT;
typedef struct THREAD {
OBJECT Header; /* must be first */
uint32_t ThreadId;
THREAD_STATE State;
THREAD_PRIORITY Priority;
// Quanta consumed without blocking - used by the MLFQ for demotion.
uint32_t QuantaConsumed;
PROCESS* Owner;
LIST_ENTRY ProcessLink; /* node in Owner->ThreadList */
LIST_ENTRY SchedulerLink; /* node in run queue or wait list */
// Kernel stack - never PAGE_USER, pointed to by TssRsp0.
void* KernelStackBase;
uintptr_t KernelStackTop;
// Saved context pointer - set by KeSwitchContext.
KCONTEXT* SavedContext;
// Non-NULL while State == ThreadWaiting.
OBJECT* WaitObject;
// Updated on every context switch so the CPU loads the right kernel stack.
uintptr_t TssRsp0;
// Thread Environment Block
void* Teb;
// Ring-3 entry point and argument for newly created threads.
uintptr_t UserEntry;
uintptr_t UserArg;
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;
void* FpuContext;
uint8_t FpuStorage[528];
} THREAD;
__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
THREAD_PRIORITY priority, THREAD **out);
// Create a kernel-mode-only thread. fn is called directly in ring 0 with
// arg; when fn returns the thread exits automatically. No user stack or TEB
// is allocated. The thread is owned by the idle process and runs at the
// given priority.
__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg,
THREAD_PRIORITY priority, THREAD **out);
// Mark a thread dead and deschedule it permanently.
__declspec(dllexport) void KeExitThread(THREAD* thread, int code);
// Block / unblock primitives used by sync objects.
void KeBlockThread(THREAD* thread, OBJECT* waitObject);
void KeUnblockThread(THREAD* thread);
// Assembly stub - implemented in ke_switch.asm.
void KeSwitchContext(THREAD* from, THREAD* to);
void KeInitThreadFpuContext(THREAD* thread);
#pragma once
// ke_thread.h - thread model
// A THREAD is the unit of scheduling. Every thread belongs to exactly one process.
#include <stdint.h>
#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 */
typedef enum THREAD_PRIORITY {
PriorityLow = 0,
PriorityNormal = 1,
PriorityHigh = 2,
PriorityCount = 3,
} THREAD_PRIORITY;
typedef enum THREAD_STATE {
ThreadReady = 0,
ThreadRunning = 1,
ThreadWaiting = 2,
ThreadDead = 3,
} THREAD_STATE;
// Saved register context - matches the layout pushed by KeSwitchContext.
// The saved RSP points to this structure on the kernel stack.
typedef struct KCONTEXT {
uint64_t R15, R14, R13, R12;
uint64_t Rbp, Rbx;
uint64_t Rip; /* return address back into KeSwitchContext */
} KCONTEXT;
typedef struct THREAD {
OBJECT Header; /* must be first */
uint32_t ThreadId;
THREAD_STATE State;
THREAD_PRIORITY Priority;
// Quanta consumed without blocking - used by the MLFQ for demotion.
uint32_t QuantaConsumed;
PROCESS* Owner;
LIST_ENTRY ProcessLink; /* node in Owner->ThreadList */
LIST_ENTRY SchedulerLink; /* node in run queue or wait list */
// Kernel stack - never PAGE_USER, pointed to by TssRsp0.
void* KernelStackBase;
uintptr_t KernelStackTop;
// Saved context pointer - set by KeSwitchContext.
KCONTEXT* SavedContext;
// Non-NULL while State == ThreadWaiting.
OBJECT* WaitObject;
// Updated on every context switch so the CPU loads the right kernel stack.
uintptr_t TssRsp0;
// Thread Environment Block. GS base is loaded from this pointer.
void* Teb;
size_t TebSize;
// Ring-3 entry point and argument for newly created threads.
uintptr_t UserEntry;
uintptr_t UserArg;
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;
void* FpuContext;
uint8_t FpuStorage[528];
} THREAD;
__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
THREAD_PRIORITY priority, THREAD** out);
// Create a kernel-mode-only thread. fn is called directly in ring 0 with
// arg; when fn returns the thread exits automatically. No user stack or TEB
// is allocated. The thread is owned by the idle process and runs at the
// given priority.
__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg,
THREAD_PRIORITY priority, THREAD** out);
// Mark a thread dead and deschedule it permanently.
__declspec(dllexport) void KeExitThread(THREAD* thread, int code);
// Block / unblock primitives used by sync objects.
void KeBlockThread(THREAD* thread, OBJECT* waitObject);
void KeUnblockThread(THREAD* thread);
// Assembly stub - implemented in ke_switch.asm.
void KeSwitchContext(THREAD* from, THREAD* to);
void KeInitThreadFpuContext(THREAD* thread);
+35
View File
@@ -0,0 +1,35 @@
#pragma once
// ke_userenv.h - kernel view of the user-mode process/thread environment.
// This is this OS' ABI, not a Windows compatibility structure.
#include <stdint.h>
#include <stddef.h>
#define SYS_PEB_MAGIC 0x30424550u /* "PEB0" */
#define SYS_TEB_MAGIC 0x30424554u /* "TEB0" */
#define SYS_ENV_VERSION 1u
#define SYS_CWD_MAX 512u
typedef struct SYS_PEB {
uint32_t Magic;
uint32_t Version;
uint32_t ProcessId;
const char* ImagePath;
size_t ImagePathLength;
const char* CommandLine;
size_t CommandLineLength;
const char* CurrentDirectory;
size_t CurrentDirectoryLength;
} SYS_PEB;
typedef struct SYS_TEB {
SYS_PEB* Peb;
void* StackBase;
void* StackLimit;
struct SYS_TEB* Self;
uint32_t ThreadId;
uint32_t Magic;
} SYS_TEB;
+17 -26
View File
@@ -250,8 +250,8 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) {
uint64_t size = IoGetFileSize(f);
DbgPrint("LaunchUserProcess: IoGetFileSize: %llu\n", size);
void* mem = MmAllocateUserMemory(size);
DbgPrint("LaunchUserProcess: MmAllocateUserMemory: 0x%llX\n", mem);
void* mem = MmAllocatePool(size);
DbgPrint("LaunchUserProcess: MmAllocatePool: 0x%llX\n", mem);
if (!mem) {
IoCloseFile(f);
return 0;
@@ -262,7 +262,7 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) {
DbgPrint("LaunchUserProcess: IoReadFile: %d\n", s);
IoCloseFile(f);
if (!KSUCCESS(s)) {
MmFreeUserMemory(mem, size);
MmFreePool(mem);
return 0;
}
@@ -273,35 +273,23 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) {
s = KeCreateProcess(filePath, &proc);
if (!KSUCCESS(s)) {
DbgPrint("Failed to create process. System halted.\n");
MmFreeUserMemory(mem, size);
MmFreePool(mem);
return 0;
}
DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, filePath);
DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, (args && args[0]) ? args : filePath);
if (args && args[0])
strncpy(proc->Cmdline, args, sizeof(proc->Cmdline) - 1);
else
strncpy(proc->Cmdline, filePath, sizeof(proc->Cmdline) - 1);
proc->Cmdline[sizeof(proc->Cmdline) - 1] = '\0';
// 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");
MmFreeUserMemory(mem, size);
s = KeSetupUserProcessEnvironment(proc, filePath, args, proc->CurrentDirectory);
if (!KSUCCESS(s)) {
DbgPrint("LaunchUserProcess: Failed to build user process environment. Status: %d\n", s);
MmFreePool(mem);
ObDereferenceObject(&proc->Header);
return 0;
}
memcpy(userCmdline, proc->Cmdline, cmdlineLen);
proc->UserCmdlineBase = (uintptr_t)userCmdline;
proc->UserCmdlineSize = cmdlineLen;
// Parse the raw PE file and map it into executable memory.
uintptr_t imageBase = 0;
s = LdrLoadImage(mem, size, &imageBase, true);
MmFreeUserMemory(mem, size);
MmFreePool(mem);
if (!KSUCCESS(s)) {
DbgPrint("LaunchUserProcess: LdrLoadImage failed with status: %d\n", s);
ObDereferenceObject(&proc->Header);
@@ -316,19 +304,22 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) {
return 0;
}
proc->ImageBase = imageBase;
proc->ImageSize = LdrGetImageSize(imageBase);
uintptr_t entryPoint = LdrGetEntryPoint(imageBase);
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, (uintptr_t)userCmdline, PriorityNormal, &thread);
// Create the main thread last. KeCreateThread queues it before returning.
s = KeCreateThread(proc, (uintptr_t)entryPoint, 0, PriorityNormal, &thread);
if (!KSUCCESS(s)) {
DbgPrint("LaunchUserProcess: Failed to create thread. Status: %d\n", s);
proc->ImageBase = 0;
proc->ImageSize = 0;
MmFreeUserMemory((void*)imageBase, LdrGetImageSize(imageBase));
ObDereferenceObject(&proc->Header);
return 0;
}
proc->ImageBase = imageBase;
proc->ImageSize = LdrGetImageSize(imageBase);
DbgPrint("LaunchUserProcess: Thread created and queued!\n");
// Caller owns the reference returned by KeCreateProcess.
+2
View File
@@ -323,6 +323,7 @@ static void* HeapAlloc(HEAP* h, size_t size) {
HdrToFtr(best)->Magic = BLOCK_MAGIC;
HdrToFtr(best)->Size = best->Size;
memset((char *)best + HDR_SIZE, 0, size);
KeReleaseSpinlock(&h->Lock, flags);
return (char *)best + HDR_SIZE;
}
@@ -409,6 +410,7 @@ static void* HeapRealloc(HEAP* h, void* ptr, size_t new_size) {
if (!new_ptr) {
return (void*)0; // Out of memory, original block is left untouched
}
memset(new_ptr, 0, aligned_size);
// Copy the old data to the new block.
// We only copy up to the old block's usable size.