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:
1 parent
ddb5d5f5d8
commit
f5fd7bdd75
28 files changed
+6334
-5722
No files matched your search
+443
-424
@@ -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);
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+281
-256
@@ -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
@@ -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);
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user