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

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

No files matched your search

+185 -153
View File
@@ -1,43 +1,51 @@
// doom.c — NT-Lite port of PureDOOM
// Win32 personality carrying all the weight, just like the real ntoskrnl does.
// doom.c — PureDOOM port
// Native personality carrying all the weight.
// Drop doom1.wad (or doom.wad) next to this binary on your FAT32 volume.
#include <windows.h>
#define DOOM_IMPLEMENTATION
#include "PureDOOM.h"
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <intrin.h>
int _fltused;
#include <syscall.h>
// ---------------------------------------------------------------------------
// Globals
// ---------------------------------------------------------------------------
static HANDLE g_stdout;
#define DOOM_IMPLEMENTATION
#include "PureDOOM.h"
int _fltused;
// ---------------------------------------------------------------------------
// doom_print_fn
// ---------------------------------------------------------------------------
static void doom_print_wrapper(const char* msg)
{
UINT len = (UINT)strlen(msg);
WriteConsoleA(g_stdout, msg, len, 0, 0);
SysWriteConsole(msg, strlen(msg));
}
// ---------------------------------------------------------------------------
// doom_malloc_fn / doom_free_fn
// Prepend size tracking metadata to satisfy SysFree's size requirement.
// ---------------------------------------------------------------------------
static void* doom_malloc_wrapper(int size)
{
void* p = HeapAlloc(0, HEAP_ZERO_MEMORY, size);
if (!p) ExitProcess(GetLastError());
return p;
size_t total_size = size + sizeof(size_t);
void* p = SysAlloc(total_size);
if (!p) SysExitProcess(1);
// Store total allocated size at the start of the block
*(size_t*)p = total_size;
// Return pointer past the size prefix
return (void*)((size_t*)p + 1);
}
static void doom_free_wrapper(void* ptr)
{
HeapFree(0, 0, ptr);
if (!ptr) return;
size_t* real_p = (size_t*)ptr - 1;
size_t total_size = *real_p;
SysFree(real_p, total_size);
}
// ---------------------------------------------------------------------------
@@ -45,61 +53,71 @@ static void doom_free_wrapper(void* ptr)
// ---------------------------------------------------------------------------
static void* doom_open_wrapper(const char* path, const char* mode)
{
DWORD access = GENERIC_READ;
DWORD disp = OPEN_EXISTING;
uint32_t handle = 0;
uint32_t result;
if (mode[0] == 'w') {
access = GENERIC_WRITE;
disp = CREATE_ALWAYS;
} else if (mode[0] == 'r' && mode[1] == '+') {
access = GENERIC_READ | GENERIC_WRITE;
result = SysCreateFile(path, &handle);
} else {
result = SysOpenFile(path, &handle);
}
HANDLE h = CreateFileA(path, access, FILE_SHARE_READ, 0, disp, 0, 0);
return (h == INVALID_HANDLE_VALUE) ? 0 : h;
return (result == 0) ? (void*)(uintptr_t)handle : 0;
}
static void doom_close_wrapper(void* handle)
{
CloseHandle((HANDLE)handle);
SysCloseHandle((uint32_t)(uintptr_t)handle);
}
static int doom_read_wrapper(void* handle, void* buf, int count)
{
DWORD read = 0;
ReadFile((HANDLE)handle, buf, (DWORD)count, &read, 0);
size_t read = 0;
SysReadFile((uint32_t)(uintptr_t)handle, buf, (size_t)count, &read);
return (int)read;
}
static int doom_write_wrapper(void* handle, const void* buf, int count)
{
DWORD written = 0;
WriteFile((HANDLE)handle, buf, (DWORD)count, &written, 0);
size_t written = 0;
SysWriteFile((uint32_t)(uintptr_t)handle, buf, (size_t)count, &written);
return (int)written;
}
static int doom_seek_wrapper(void* handle, int offset, doom_seek_t origin)
{
DWORD method;
uint32_t h = (uint32_t)(uintptr_t)handle;
uint64_t absolute_offset = 0;
switch (origin) {
case DOOM_SEEK_SET: method = FILE_BEGIN; break;
case DOOM_SEEK_CUR: method = FILE_CURRENT; break;
case DOOM_SEEK_END: method = FILE_END; break;
default: method = FILE_BEGIN; break;
case DOOM_SEEK_SET:
absolute_offset = (uint64_t)offset;
break;
case DOOM_SEEK_CUR:
absolute_offset = SysGetFilePos(h) + offset;
break;
case DOOM_SEEK_END:
absolute_offset = SysGetFileSize(h) + offset;
break;
default:
absolute_offset = (uint64_t)offset;
break;
}
SetFilePointer((HANDLE)handle, (LONG)offset, 0, method);
SysSeekFile(h, absolute_offset);
return 0;
}
static int doom_tell_wrapper(void* handle)
{
return (int)SetFilePointer((HANDLE)handle, 0, 0, FILE_CURRENT);
return (int)SysGetFilePos((uint32_t)(uintptr_t)handle);
}
static int doom_eof_wrapper(void* handle)
{
DWORD size = GetFileSize((HANDLE)handle, 0);
DWORD pos = (DWORD)doom_tell_wrapper(handle);
uint32_t h = (uint32_t)(uintptr_t)handle;
uint64_t size = SysGetFileSize(h);
uint64_t pos = SysGetFilePos(h);
return (pos >= size) ? 1 : 0;
}
@@ -108,9 +126,9 @@ static int doom_eof_wrapper(void* handle)
// ---------------------------------------------------------------------------
static void doom_gettime_wrapper(int* sec, int* usec)
{
DWORD t = GetTickCount(); // milliseconds
*sec = (int)(t / 1000);
*usec = (int)((t % 1000) * 1000); // ms remainder → us
uint64_t t = SysGetTick();
*sec = (int)(t / 1000000); // 1,000,000 us = 1 second
*usec = (int)(t % 1000000); // Remainder in microseconds
}
// ---------------------------------------------------------------------------
@@ -118,16 +136,16 @@ static void doom_gettime_wrapper(int* sec, int* usec)
// ---------------------------------------------------------------------------
static void doom_exit_wrapper(int code)
{
ExitProcess((UINT)code);
SysExitProcess(code);
}
// ---------------------------------------------------------------------------
// doom_getenv_fn — we don't have environment variables
// doom_getenv_fn
// ---------------------------------------------------------------------------
static char* doom_getenv_wrapper(const char* name)
{
if (strcmp(name, "HOME") == 0) {
static char home[] = "c:";
static char home[] = ".";
doom_print_wrapper("getenv: Returning HOME="); doom_print_wrapper(home); doom_print_wrapper("\n");
return home;
}
@@ -136,102 +154,88 @@ static char* doom_getenv_wrapper(const char* name)
}
// ---------------------------------------------------------------------------
// Input — poll GetAsyncKeyState every frame
// Generates key_down / key_up edge events, not held-key spam.
// Input — Maps Scancode Set 1 values to PureDOOM
// ---------------------------------------------------------------------------
typedef struct { int vk; doom_key_t key; } KeyMap;
typedef struct { uint8_t scancode; bool extended; doom_key_t key; } KeyMap;
// NOTE: Win32 VK codes for letters are uppercase ASCII (e.g. 'W' = 0x57).
// DOOM_KEY_* for letters are lowercase ASCII (e.g. DOOM_KEY_W = 'w' = 0x77).
// These are different values — the VK column is what GetAsyncKeyState takes,
// the doom_key column is what PureDOOM wants. Don't collapse them.
static const KeyMap g_keymap[] = {
// Letters — VK is uppercase, DOOM key is lowercase
{ 'W', DOOM_KEY_W },
{ 'A', DOOM_KEY_A },
{ 'S', DOOM_KEY_S },
{ 'D', DOOM_KEY_D },
{ 'E', DOOM_KEY_E },
{ 'Q', DOOM_KEY_Q },
{ 'Y', DOOM_KEY_Y }, // yes/no menus
{ 'N', DOOM_KEY_N },
// Letters
{ 0x11, false, DOOM_KEY_W },
{ 0x1E, false, DOOM_KEY_A },
{ 0x1F, false, DOOM_KEY_S },
{ 0x20, false, DOOM_KEY_D },
{ 0x12, false, DOOM_KEY_E },
{ 0x10, false, DOOM_KEY_Q },
{ 0x15, false, DOOM_KEY_Y },
{ 0x31, false, DOOM_KEY_N },
// Arrow keys
{ VK_UP, DOOM_KEY_UP_ARROW },
{ VK_DOWN, DOOM_KEY_DOWN_ARROW },
{ VK_LEFT, DOOM_KEY_LEFT_ARROW },
{ VK_RIGHT, DOOM_KEY_RIGHT_ARROW },
{ 0x48, true, DOOM_KEY_UP_ARROW },
{ 0x50, true, DOOM_KEY_DOWN_ARROW },
{ 0x4B, true, DOOM_KEY_LEFT_ARROW },
{ 0x4D, true, DOOM_KEY_RIGHT_ARROW },
// Action keys — Ctrl=fire, Shift=run, Alt=strafe
{ VK_CONTROL, DOOM_KEY_CTRL },
{ VK_SPACE, DOOM_KEY_SPACE },
{ VK_SHIFT, DOOM_KEY_SHIFT },
{ VK_MENU, DOOM_KEY_ALT },
// Action keys
{ 0x1D, false, DOOM_KEY_CTRL },
{ 0x39, false, DOOM_KEY_SPACE },
{ 0x2A, false, DOOM_KEY_SHIFT },
{ 0x38, false, DOOM_KEY_ALT },
// Navigation
{ VK_ESCAPE, DOOM_KEY_ESCAPE },
{ VK_RETURN, DOOM_KEY_ENTER },
{ VK_TAB, DOOM_KEY_TAB },
{ VK_BACK, DOOM_KEY_BACKSPACE },
{ VK_PAUSE, DOOM_KEY_PAUSE },
{ 0x01, false, DOOM_KEY_ESCAPE },
{ 0x1C, false, DOOM_KEY_ENTER },
{ 0x0F, false, DOOM_KEY_TAB },
{ 0x0E, false, DOOM_KEY_BACKSPACE },
// Weapon slots
{ '1', DOOM_KEY_1 },
{ '2', DOOM_KEY_2 },
{ '3', DOOM_KEY_3 },
{ '4', DOOM_KEY_4 },
{ '5', DOOM_KEY_5 },
{ '6', DOOM_KEY_6 },
{ '7', DOOM_KEY_7 },
{ 0x02, false, DOOM_KEY_1 },
{ 0x03, false, DOOM_KEY_2 },
{ 0x04, false, DOOM_KEY_3 },
{ 0x05, false, DOOM_KEY_4 },
{ 0x06, false, DOOM_KEY_5 },
{ 0x07, false, DOOM_KEY_6 },
{ 0x08, false, DOOM_KEY_7 },
// Misc
{ '-', DOOM_KEY_MINUS },
{ '=', DOOM_KEY_EQUALS },
{ ',', DOOM_KEY_COMMA }, // default strafe left
{ '.', DOOM_KEY_PERIOD }, // default strafe right
{ 0x0C, false, DOOM_KEY_MINUS },
{ 0x0D, false, DOOM_KEY_EQUALS },
{ 0x33, false, DOOM_KEY_COMMA },
{ 0x34, false, DOOM_KEY_PERIOD },
// Function keys — save/load/volume/etc
{ VK_F1, DOOM_KEY_F1 },
{ VK_F2, DOOM_KEY_F2 },
{ VK_F3, DOOM_KEY_F3 },
{ VK_F4, DOOM_KEY_F4 },
{ VK_F5, DOOM_KEY_F5 },
{ VK_F6, DOOM_KEY_F6 },
{ VK_F7, DOOM_KEY_F7 },
{ VK_F8, DOOM_KEY_F8 },
{ VK_F9, DOOM_KEY_F9 },
{ VK_F10, DOOM_KEY_F10 },
{ VK_F11, DOOM_KEY_F11 },
{ VK_F12, DOOM_KEY_F12 },
// Function keys
{ 0x3B, false, DOOM_KEY_F1 },
{ 0x3C, false, DOOM_KEY_F2 },
{ 0x3D, false, DOOM_KEY_F3 },
{ 0x3E, false, DOOM_KEY_F4 },
{ 0x3F, false, DOOM_KEY_F5 },
{ 0x40, false, DOOM_KEY_F6 },
{ 0x41, false, DOOM_KEY_F7 },
{ 0x42, false, DOOM_KEY_F8 },
{ 0x43, false, DOOM_KEY_F9 },
{ 0x44, false, DOOM_KEY_F10 },
{ 0x57, false, DOOM_KEY_F11 },
{ 0x58, false, DOOM_KEY_F12 },
};
#define KEYMAP_COUNT (sizeof(g_keymap) / sizeof(g_keymap[0]))
static short g_prev_key[256];
static bool g_prev_key[KEYMAP_COUNT];
static void poll_input(void)
{
for (int i = 0; i < (int)KEYMAP_COUNT; i++) {
int vk = g_keymap[i].vk;
short cur = GetAsyncKeyState(vk);
int cur_down = (cur & 0x8000) != 0;
int prev_down = (g_prev_key[vk] & 0x8000) != 0;
bool cur_down = (SysKbIsKeyDown(g_keymap[i].scancode, g_keymap[i].extended) != 0);
bool prev_down = g_prev_key[i];
if (cur_down && !prev_down) doom_key_down(g_keymap[i].key);
if (!cur_down && prev_down) doom_key_up (g_keymap[i].key);
g_prev_key[vk] = cur;
g_prev_key[i] = cur_down;
}
}
// ---------------------------------------------------------------------------
// Render — StretchDIBits to screen, same pipeline as video.c.
//
// NOTE: PureDOOM's framebuffer is RGBA; Win32 StretchDIBits with BI_RGB/32bpp
// expects BGRA. Channels will be swapped (red ↔ blue) until you either:
// a) swap in the blit loop below, or
// b) tell PureDOOM to give you BGRA if a future version supports it.
// For a first boot, blue-tinted demons are still demons.
// Render — Performs lightweight software upscale and blits to the screen
// ---------------------------------------------------------------------------
#define DOOM_W 320
#define DOOM_H 200
@@ -239,55 +243,82 @@ static void poll_input(void)
#define DISP_W (DOOM_W * SCALE)
#define DISP_H (DOOM_H * SCALE)
static BITMAPINFO g_bmi;
static uint32_t g_blit_buf[DOOM_W * DOOM_H]; // BGRA-swapped copy
static uint32_t g_blit_buf[DISP_W * DISP_H];
static void render_frame(const uint8_t* rgba)
{
// Swap R <-> B so Win32 GDI sees correct colours
const uint32_t* src = (const uint32_t*)rgba;
for (int i = 0; i < DOOM_W * DOOM_H; i++) {
uint32_t p = src[i];
// RGBA -> BGRA: swap byte 0 (R) and byte 2 (B)
g_blit_buf[i] = (p & 0xFF00FF00u)
| ((p & 0x00FF0000u) >> 16)
| ((p & 0x000000FFu) << 16);
// Software upscale loop mapping 320x200 buffer directly to scaled window buffer
for (int y = 0; y < DOOM_H; y++) {
for (int x = 0; x < DOOM_W; x++) {
uint32_t p = src[y * DOOM_W + x];
// RGBA -> BGRA conversion (standard for linear x86/64 framebuffers)
uint32_t bgra = (p & 0xFF00FF00u)
| ((p & 0x00FF0000u) >> 16)
| ((p & 0x000000FFu) << 16);
// Populate SCALE * SCALE pixel block
for (int sy = 0; sy < SCALE; sy++) {
for (int sx = 0; sx < SCALE; sx++) {
g_blit_buf[(y * SCALE + sy) * DISP_W + (x * SCALE + sx)] = bgra;
}
}
}
}
PAINTSTRUCT ps;
HDC hdc = BeginPaint((HWND)0, &ps);
StretchDIBits(
hdc,
0, 0, DISP_W, DISP_H, // destination — scaled up
0, 0, DOOM_W, DOOM_H, // source — native 320×200
g_blit_buf,
&g_bmi,
DIB_RGB_COLORS,
SRCCOPY
);
EndPaint((HWND)0, &ps);
SysScreenBlit(0, 0, DISP_W, DISP_H, g_blit_buf);
}
static int build_argv_from_command_line(const char* cmdline, char* storage, size_t storageSize,
const char** argv, int maxArgv) {
if (!storage || storageSize == 0 || !argv || maxArgv <= 1) return 0;
if (!cmdline || cmdline[0] == '\0') cmdline = "doom";
size_t len = strlen(cmdline);
if (len >= storageSize) len = storageSize - 1;
memcpy(storage, cmdline, len);
storage[len] = '\0';
int argc = 0;
char* p = storage;
while (*p && argc < maxArgv - 1) {
while (*p == ' ' || *p == '\t') p++;
if (!*p) break;
if (*p == '"') {
p++;
argv[argc++] = p;
while (*p && *p != '"') p++;
if (*p) *p++ = '\0';
} else {
argv[argc++] = p;
while (*p && *p != ' ' && *p != '\t') p++;
if (*p) *p++ = '\0';
}
}
argv[argc] = 0;
if (argc == 0) {
argv[0] = "doom";
argv[1] = 0;
argc = 1;
}
return argc;
}
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main(const char* cmdline)
int main(void)
{
(void)cmdline;
const char* cmdline = SysGetCommandLine();
g_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsoleA(g_stdout, "doom: starting\n", 15, 0, 0);
SysWriteConsole("doom: starting\n", 15);
// Prepare the BMI header once (top-down, like video.c)
memset(&g_bmi, 0, sizeof(g_bmi));
g_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
g_bmi.bmiHeader.biWidth = DOOM_W;
g_bmi.bmiHeader.biHeight = -DOOM_H; // negative = top-down
g_bmi.bmiHeader.biPlanes = 1;
g_bmi.bmiHeader.biBitCount = 32;
g_bmi.bmiHeader.biCompression = BI_RGB;
// Wire up PureDOOM
// Wire up PureDOOM core mechanics
doom_set_print(doom_print_wrapper);
doom_set_malloc(doom_malloc_wrapper, doom_free_wrapper);
doom_set_file_io(
@@ -312,19 +343,20 @@ int main(const char* cmdline)
doom_set_default_int("key_use", DOOM_KEY_E);
doom_set_default_int("mouse_move", 0);
// No extra args — PureDOOM will scan for doom1.wad / doom.wad
const char* args[] = { "doom" };
doom_init(1, (char**)args, 0);
char argvStorage[512];
const char* argv[32];
int argc = build_argv_from_command_line(cmdline, argvStorage, sizeof(argvStorage), argv, 32);
doom_init(argc, argv, 0);
WriteConsoleA(g_stdout, "doom: entering game loop\n", 25, 0, 0);
SysWriteConsole("doom: entering game loop\n", 25);
// Game loop — identical shape to video.c's Bink loop
// Core execution loop
while (1) {
poll_input();
doom_update();
render_frame((const uint8_t*)doom_get_framebuffer(4 /* RGBA */));
}
// Unreachable: doom_exit() -> ExitProcess() handles clean shutdown
ExitProcess(0);
SysExitProcess(0);
return 0;
}
+1206 -1189
View File
File diff suppressed because it is too large. Load diff
+3 -2
View File
@@ -48,12 +48,13 @@ void itoa(int num, char* str) {
}
}
void _start(const char* args)
void _start(void)
{
const char* args = SysGetCommandLine();
uint32_t h = 0;
uint32_t s = 0;
for (;;) {
while (true) {
SysDebugPrint("[init] starting\n");
s = SysCreateProcess(args, &h, NULL);
if (s != STATUS_SUCCESS) {
+443 -424
View File
@@ -1,424 +1,443 @@
// io_file.c - VFS dispatch layer
#include <string.h>
#include <ke_list.h>
#include <ke_process.h>
#include <io_file.h>
#include <mm_virt.h>
#include <ob_object.h>
#define MAX_DRIVES 8
typedef struct {
IO_FILESYSTEM *Fs;
IO_VOLUME_PARAMS Vol;
} VOLUME_ENTRY;
static VOLUME_ENTRY gVolumes[MAX_DRIVES];
// ---------------------------------------------------------------------------
// Current Working Directory (drive 0 relative, no trailing backslash)
// e.g. "" = root, "apps" = root\apps, "apps\\data" = nested
// ---------------------------------------------------------------------------
#define CWD_MAX 512
__declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size) {
if (!buf || size == 0) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
size_t cwdLen = strlen(proc->CurrentDirectory);
if (cwdLen + 1 > size) {
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_BUFFER_TOO_SMALL;
}
memcpy(buf, proc->CurrentDirectory, cwdLen + 1);
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_SUCCESS;
}
// Resolve a possibly-relative path component against the CWD.
// Writes the fully-resolved, drive-stripped relative path into 'resolved'
// (caller supplies buf of at least CWD_MAX bytes).
// Returns the drive index.
static uint8_t ResolvePath(const char* path, char* resolved, size_t resolvedSize) {
// Strip explicit drive prefix "N:\"
uint8_t drive = 0;
bool fromDriveRoot = false;
if (path[0] >= '0' && path[0] <= '7' &&
path[1] == ':' && (path[2] == '\\' || path[2] == '/')) {
drive = (uint8_t)(path[0] - '0');
path = path + 3;
fromDriveRoot = true;
}
char work[CWD_MAX * 2];
work[0] = '\0';
// Initialize our working base
if (path[0] == '\\' || path[0] == '/' || fromDriveRoot) {
// Absolute path: start from the root (empty work buffer)
// The segment loop below will automatically skip the leading separators.
} else {
// Relative path: start from the current process's CWD
PROCESS* proc = KeGetCurrentProcess();
if (proc) {
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
size_t wlen = strlen(proc->CurrentDirectory);
if (wlen >= sizeof(work)) wlen = sizeof(work) - 1;
memcpy(work, proc->CurrentDirectory, wlen);
work[wlen] = '\0';
KeReleaseSpinlock(&proc->CwdLock, flags);
}
}
// Segment-by-segment canonicalization
const char* p = path;
while (*p) {
// Skip separators
while (*p == '\\' || *p == '/') p++;
if (!*p) break;
// Read next component
char comp[256];
int clen = 0;
while (*p && *p != '\\' && *p != '/' && clen < 255)
comp[clen++] = *p++;
comp[clen] = '\0';
if (clen == 0) continue;
if (strcmp(comp, ".") == 0) {
// Stay in place
} else if (strcmp(comp, "..") == 0) {
// Remove last component from work
int i = (int)strlen(work) - 1;
while (i >= 0 && work[i] != '\\' && work[i] != '/') i--;
if (i < 0) i = 0;
work[i] = '\0';
} else {
// Append separator + component
size_t wl = strlen(work);
if (wl > 0 && work[wl-1] != '\\' && work[wl-1] != '/') {
if (wl + 1 < sizeof(work)) { work[wl] = '\\'; work[wl+1] = '\0'; }
}
size_t wl2 = strlen(work);
if (wl2 + clen + 1 < sizeof(work)) {
memcpy(work + wl2, comp, clen);
work[wl2 + clen] = '\0';
}
}
}
// Output the final canonicalized, drive-stripped path
size_t finalLen = strlen(work);
if (finalLen + 1 > resolvedSize) finalLen = resolvedSize - 1;
memcpy(resolved, work, finalLen);
resolved[finalLen] = '\0';
return drive;
}
__declspec(dllexport) KSTATUS IoSetCwd(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
// Resolve the new path (this also validates ".." navigation).
// Note: ResolvePath already uses proc->CurrentDirectory internally!
char resolved[CWD_MAX];
uint8_t drive = ResolvePath(path, resolved, sizeof(resolved));
// Validate: try to open the directory to confirm it actually exists on disk
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->OpenDir) return STATUS_NOT_SUPPORTED;
void* dirCtx = NULL;
KSTATUS s = fs->OpenDir(fs, resolved, &dirCtx);
if (!KSUCCESS(s)) return s;
fs->CloseDir(fs, dirCtx);
// Commit the new CWD to the process block
size_t len = strlen(resolved);
if (len + 1 > sizeof(proc->CurrentDirectory)) return STATUS_BUFFER_TOO_SMALL;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
memcpy(proc->CurrentDirectory, resolved, len + 1);
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Registration
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t volumeIndex,
IO_FILESYSTEM *fs,
const IO_VOLUME_PARAMS *vol) {
if (volumeIndex >= MAX_DRIVES || !fs || !vol)
return STATUS_INVALID_PARAMETER;
if (gVolumes[volumeIndex].Fs)
return STATUS_ALREADY_EXISTS;
gVolumes[volumeIndex].Fs = fs;
gVolumes[volumeIndex].Vol = *vol;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Destructor
// ---------------------------------------------------------------------------
static void FileDestroy(OBJECT* obj) {
FILE_OBJECT* f = CONTAINING_RECORD(obj, FILE_OBJECT, Header);
if (f->Fs && f->FileCtx) f->Fs->Close(f->Fs, f->FileCtx);
}
// ---------------------------------------------------------------------------
// IoOpenFile
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) {
if (!path || !out) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
void* fileCtx;
KSTATUS s = fs->Open(fs, relPath, &fileCtx);
if (!KSUCCESS(s)) return s;
FILE_OBJECT* f = (FILE_OBJECT *)ObAllocateObject(ObjectTypeFile /* reuse slot */,
sizeof(FILE_OBJECT));
if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; }
f->Header.Destroy = FileDestroy;
f->Fs = fs;
f->FileCtx = fileCtx;
f->Size = fs->GetSize(fs, fileCtx);
f->Offset = 0;
*out = f;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// IoReadFile - sequential read at current offset
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t size, size_t* bytesRead) {
if (!file || !buf) return STATUS_INVALID_PARAMETER;
if (file->Offset >= file->Size) { *bytesRead = 0; return STATUS_SUCCESS; }
// Clamp to remaining bytes.
size_t remaining = (size_t)(file->Size - file->Offset);
if (size > remaining) size = remaining;
KSTATUS s = file->Fs->Read(file->Fs, file->FileCtx,
file->Offset, buf, size, bytesRead);
if (KSUCCESS(s)) file->Offset += *bytesRead;
return s;
}
// ---------------------------------------------------------------------------
// IoWriteFile - sequential write at current offset
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf,
size_t size, size_t* bytesWritten) {
if (!file || !buf || !bytesWritten) return STATUS_INVALID_PARAMETER;
if (!file->Fs->Write) return STATUS_NOT_SUPPORTED;
size_t written = 0;
KSTATUS s = file->Fs->Write(file->Fs, file->FileCtx,
file->Offset, buf, size, &written);
if (KSUCCESS(s)) {
file->Offset += written;
// Keep the cached size in sync if the file grew.
if (file->Offset > file->Size)
file->Size = file->Offset;
}
*bytesWritten = written;
return s;
}
// ---------------------------------------------------------------------------
// IoCreateFile - create a new file and return it open for writing
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoCreateFile(const char* path, FILE_OBJECT** out) {
if (!path || !out) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Create) return STATUS_NOT_SUPPORTED;
void* fileCtx;
KSTATUS s = fs->Create(fs, relPath, &fileCtx);
if (!KSUCCESS(s)) return s;
FILE_OBJECT* f = (FILE_OBJECT*)ObAllocateObject(ObjectTypeFile,
sizeof(FILE_OBJECT));
if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; }
f->Header.Destroy = FileDestroy;
f->Fs = fs;
f->FileCtx = fileCtx;
f->Size = 0;
f->Offset = 0;
*out = f;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// IoDeleteFile - delete a file by path (does not require an open handle)
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoDeleteFile(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Delete) return STATUS_NOT_SUPPORTED;
return fs->Delete(fs, relPath);
}
__declspec(dllexport) KSTATUS IoRenameFile(const char* oldPath, const char* newPath) {
if (!oldPath || !newPath) return STATUS_INVALID_PARAMETER;
char oldRel[CWD_MAX];
char newRel[CWD_MAX];
uint8_t oldDrive = ResolvePath(oldPath, oldRel, sizeof(oldRel));
uint8_t newDrive = ResolvePath(newPath, newRel, sizeof(newRel));
if (oldDrive != newDrive) return STATUS_NOT_SUPPORTED;
IO_FILESYSTEM* fs = gVolumes[oldDrive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Rename) return STATUS_NOT_SUPPORTED;
return fs->Rename(fs, oldRel, newRel);
}
__declspec(dllexport) KSTATUS IoCreateDir(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->CreateDir) return STATUS_NOT_SUPPORTED;
return fs->CreateDir(fs, relPath);
}
__declspec(dllexport) KSTATUS IoRemoveDir(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->RemoveDir) return STATUS_NOT_SUPPORTED;
return fs->RemoveDir(fs, relPath);
}
// ---------------------------------------------------------------------------
// IoTruncateFile - resize the file; updates the cached Size on success
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize) {
if (!file) return STATUS_INVALID_PARAMETER;
if (!file->Fs->Truncate) return STATUS_NOT_SUPPORTED;
KSTATUS s = file->Fs->Truncate(file->Fs, file->FileCtx, newSize);
if (KSUCCESS(s)) {
file->Size = newSize;
// Pull the file pointer back if it now sits past EOF.
if (file->Offset > file->Size)
file->Offset = file->Size;
}
return s;
}
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset) {
if (!file) return STATUS_INVALID_PARAMETER;
file->Offset = offset;
return STATUS_SUCCESS;
}
__declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file) {
return file ? file->Size : 0;
}
__declspec(dllexport) void IoCloseFile(FILE_OBJECT* file) {
if (file) ObDereferenceObject(&file->Header);
}
// ---------------------------------------------------------------------------
// Directory enumeration
// ---------------------------------------------------------------------------
static void DirDestroy(OBJECT* obj) {
DIR_OBJECT* d = (DIR_OBJECT*)obj; // Header is first member
if (d->Fs && d->DirCtx) d->Fs->CloseDir(d->Fs, d->DirCtx);
}
__declspec(dllexport) KSTATUS IoOpenDir(const char* dirPath, DIR_OBJECT** out) {
if (!out) return STATUS_INVALID_PARAMETER;
// NULL or empty path means the CWD itself
char relPath[CWD_MAX];
uint8_t drive;
if (!dirPath || dirPath[0] == '\0') {
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
memcpy(relPath, proc->CurrentDirectory, strlen(proc->CurrentDirectory) + 1);
KeReleaseSpinlock(&proc->CwdLock, flags);
// Note: For now, this assumes drive 0. If you add multi-drive support
// later, you should add a CurrentDrive field to the PROCESS struct.
drive = 0;
} else {
drive = ResolvePath(dirPath, relPath, sizeof(relPath));
}
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->OpenDir) return STATUS_NOT_SUPPORTED;
void* dirCtx = NULL;
KSTATUS s = fs->OpenDir(fs, relPath, &dirCtx);
if (!KSUCCESS(s)) return s;
DIR_OBJECT* d = (DIR_OBJECT*)ObAllocateObject(ObjectTypeDir, sizeof(DIR_OBJECT));
if (!d) { fs->CloseDir(fs, dirCtx); return STATUS_NO_MEMORY; }
d->Header.Destroy = DirDestroy;
d->Fs = fs;
d->DirCtx = dirCtx;
*out = d;
return STATUS_SUCCESS;
}
__declspec(dllexport) KSTATUS IoReadDir(DIR_OBJECT* dir, KDIR_ENTRY* entryOut) {
if (!dir || !entryOut) return STATUS_INVALID_PARAMETER;
if (!dir->Fs->ReadDir) return STATUS_NOT_SUPPORTED;
return dir->Fs->ReadDir(dir->Fs, dir->DirCtx, entryOut);
}
__declspec(dllexport) void IoCloseDir(DIR_OBJECT* dir) {
if (dir) ObDereferenceObject(&dir->Header);
}
// io_file.c - VFS dispatch layer
#include <string.h>
#include <ke_list.h>
#include <ke_process.h>
#include <io_file.h>
#include <mm_virt.h>
#include <ob_object.h>
#include <ke_userenv.h>
#define MAX_DRIVES 8
typedef struct {
IO_FILESYSTEM *Fs;
IO_VOLUME_PARAMS Vol;
} VOLUME_ENTRY;
static VOLUME_ENTRY gVolumes[MAX_DRIVES];
// ---------------------------------------------------------------------------
// Current Working Directory (drive 0 relative, no trailing backslash)
// e.g. "" = root, "apps" = root\apps, "apps\\data" = nested
// ---------------------------------------------------------------------------
#define CWD_MAX 512
__declspec(dllexport) KSTATUS IoGetCwd(char* buf, size_t size) {
if (!buf || size == 0) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
size_t cwdLen = strlen(proc->CurrentDirectory);
if (cwdLen + 1 > size) {
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_BUFFER_TOO_SMALL;
}
memcpy(buf, proc->CurrentDirectory, cwdLen + 1);
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_SUCCESS;
}
// Resolve a possibly-relative path component against the CWD.
// Writes the fully-resolved, drive-stripped relative path into 'resolved'
// (caller supplies buf of at least CWD_MAX bytes).
// Returns the drive index.
static uint8_t ResolvePath(const char* path, char* resolved, size_t resolvedSize) {
// Strip explicit drive prefix "N:\"
uint8_t drive = 0;
bool fromDriveRoot = false;
if (path[0] >= '0' && path[0] <= '7' &&
path[1] == ':' && (path[2] == '\\' || path[2] == '/')) {
drive = (uint8_t)(path[0] - '0');
path = path + 3;
fromDriveRoot = true;
}
char work[CWD_MAX * 2];
work[0] = '\0';
// Initialize our working base
if (path[0] == '\\' || path[0] == '/' || fromDriveRoot) {
// Absolute path: start from the root (empty work buffer)
// The segment loop below will automatically skip the leading separators.
} else {
// Relative path: start from the current process's CWD
PROCESS* proc = KeGetCurrentProcess();
if (proc) {
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
size_t wlen = strlen(proc->CurrentDirectory);
if (wlen >= sizeof(work)) wlen = sizeof(work) - 1;
memcpy(work, proc->CurrentDirectory, wlen);
work[wlen] = '\0';
KeReleaseSpinlock(&proc->CwdLock, flags);
}
}
// Segment-by-segment canonicalization
const char* p = path;
while (*p) {
// Skip separators
while (*p == '\\' || *p == '/') p++;
if (!*p) break;
// Read next component
char comp[256];
int clen = 0;
while (*p && *p != '\\' && *p != '/' && clen < 255)
comp[clen++] = *p++;
comp[clen] = '\0';
if (clen == 0) continue;
if (strcmp(comp, ".") == 0) {
// Stay in place
} else if (strcmp(comp, "..") == 0) {
// Remove last component from work
int i = (int)strlen(work) - 1;
while (i >= 0 && work[i] != '\\' && work[i] != '/') i--;
if (i < 0) i = 0;
work[i] = '\0';
} else {
// Append separator + component
size_t wl = strlen(work);
if (wl > 0 && work[wl-1] != '\\' && work[wl-1] != '/') {
if (wl + 1 < sizeof(work)) { work[wl] = '\\'; work[wl+1] = '\0'; }
}
size_t wl2 = strlen(work);
if (wl2 + clen + 1 < sizeof(work)) {
memcpy(work + wl2, comp, clen);
work[wl2 + clen] = '\0';
}
}
}
// Output the final canonicalized, drive-stripped path
size_t finalLen = strlen(work);
if (finalLen + 1 > resolvedSize) finalLen = resolvedSize - 1;
memcpy(resolved, work, finalLen);
resolved[finalLen] = '\0';
return drive;
}
__declspec(dllexport) KSTATUS IoSetCwd(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
// Resolve the new path (this also validates ".." navigation).
// Note: ResolvePath already uses proc->CurrentDirectory internally!
char resolved[CWD_MAX];
uint8_t drive = ResolvePath(path, resolved, sizeof(resolved));
// Validate: try to open the directory to confirm it actually exists on disk
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->OpenDir) return STATUS_NOT_SUPPORTED;
void* dirCtx = NULL;
KSTATUS s = fs->OpenDir(fs, resolved, &dirCtx);
if (!KSUCCESS(s)) return s;
fs->CloseDir(fs, dirCtx);
// Commit the new CWD to the process block
size_t len = strlen(resolved);
if (len + 1 > sizeof(proc->CurrentDirectory)) return STATUS_BUFFER_TOO_SMALL;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
memcpy(proc->CurrentDirectory, resolved, len + 1);
// Keep the user-visible PEB mirror in sync. The PEB's CWD buffer is a
// fixed SYS_CWD_MAX allocation made at process creation, so the pointer
// remains stable for personalities that cache SYS_PEB*.
if (proc->UserCwdBase && proc->UserCwdSize) {
size_t copyLen = len;
if (copyLen + 1 > proc->UserCwdSize) copyLen = proc->UserCwdSize - 1;
memcpy((void*)proc->UserCwdBase, resolved, copyLen);
((char*)proc->UserCwdBase)[copyLen] = '\0';
SYS_PEB* peb = (SYS_PEB*)proc->UserPebBase;
if (peb && peb->Magic == SYS_PEB_MAGIC) {
peb->CurrentDirectory = (const char*)proc->UserCwdBase;
peb->CurrentDirectoryLength = copyLen;
}
}
KeReleaseSpinlock(&proc->CwdLock, flags);
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Registration
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoRegisterFilesystem(uint8_t volumeIndex,
IO_FILESYSTEM *fs,
const IO_VOLUME_PARAMS *vol) {
if (volumeIndex >= MAX_DRIVES || !fs || !vol)
return STATUS_INVALID_PARAMETER;
if (gVolumes[volumeIndex].Fs)
return STATUS_ALREADY_EXISTS;
gVolumes[volumeIndex].Fs = fs;
gVolumes[volumeIndex].Vol = *vol;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Destructor
// ---------------------------------------------------------------------------
static void FileDestroy(OBJECT* obj) {
FILE_OBJECT* f = CONTAINING_RECORD(obj, FILE_OBJECT, Header);
if (f->Fs && f->FileCtx) f->Fs->Close(f->Fs, f->FileCtx);
}
// ---------------------------------------------------------------------------
// IoOpenFile
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoOpenFile(const char* path, FILE_OBJECT **out) {
if (!path || !out) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
void* fileCtx;
KSTATUS s = fs->Open(fs, relPath, &fileCtx);
if (!KSUCCESS(s)) return s;
FILE_OBJECT* f = (FILE_OBJECT *)ObAllocateObject(ObjectTypeFile /* reuse slot */,
sizeof(FILE_OBJECT));
if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; }
f->Header.Destroy = FileDestroy;
f->Fs = fs;
f->FileCtx = fileCtx;
f->Size = fs->GetSize(fs, fileCtx);
f->Offset = 0;
*out = f;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// IoReadFile - sequential read at current offset
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoReadFile(FILE_OBJECT* file, void* buf, size_t size, size_t* bytesRead) {
if (bytesRead) *bytesRead = 0;
if (!file || !buf || !bytesRead) return STATUS_INVALID_PARAMETER;
if (file->Offset >= file->Size || size == 0) return STATUS_SUCCESS;
// Clamp to remaining bytes.
size_t remaining = (size_t)(file->Size - file->Offset);
if (size > remaining) size = remaining;
KSTATUS s = file->Fs->Read(file->Fs, file->FileCtx,
file->Offset, buf, size, bytesRead);
if (KSUCCESS(s)) file->Offset += *bytesRead;
return s;
}
// ---------------------------------------------------------------------------
// IoWriteFile - sequential write at current offset
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoWriteFile(FILE_OBJECT* file, const void* buf,
size_t size, size_t* bytesWritten) {
if (!file || !buf || !bytesWritten) return STATUS_INVALID_PARAMETER;
if (!file->Fs->Write) return STATUS_NOT_SUPPORTED;
size_t written = 0;
KSTATUS s = file->Fs->Write(file->Fs, file->FileCtx,
file->Offset, buf, size, &written);
if (KSUCCESS(s)) {
file->Offset += written;
// Keep the cached size in sync if the file grew.
if (file->Offset > file->Size)
file->Size = file->Offset;
}
*bytesWritten = written;
return s;
}
// ---------------------------------------------------------------------------
// IoCreateFile - create a new file and return it open for writing
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoCreateFile(const char* path, FILE_OBJECT** out) {
if (!path || !out) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Create) return STATUS_NOT_SUPPORTED;
void* fileCtx;
KSTATUS s = fs->Create(fs, relPath, &fileCtx);
if (!KSUCCESS(s)) return s;
FILE_OBJECT* f = (FILE_OBJECT*)ObAllocateObject(ObjectTypeFile,
sizeof(FILE_OBJECT));
if (!f) { fs->Close(fs, fileCtx); return STATUS_NO_MEMORY; }
f->Header.Destroy = FileDestroy;
f->Fs = fs;
f->FileCtx = fileCtx;
f->Size = 0;
f->Offset = 0;
*out = f;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// IoDeleteFile - delete a file by path (does not require an open handle)
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoDeleteFile(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Delete) return STATUS_NOT_SUPPORTED;
return fs->Delete(fs, relPath);
}
__declspec(dllexport) KSTATUS IoRenameFile(const char* oldPath, const char* newPath) {
if (!oldPath || !newPath) return STATUS_INVALID_PARAMETER;
char oldRel[CWD_MAX];
char newRel[CWD_MAX];
uint8_t oldDrive = ResolvePath(oldPath, oldRel, sizeof(oldRel));
uint8_t newDrive = ResolvePath(newPath, newRel, sizeof(newRel));
if (oldDrive != newDrive) return STATUS_NOT_SUPPORTED;
IO_FILESYSTEM* fs = gVolumes[oldDrive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->Rename) return STATUS_NOT_SUPPORTED;
return fs->Rename(fs, oldRel, newRel);
}
__declspec(dllexport) KSTATUS IoCreateDir(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->CreateDir) return STATUS_NOT_SUPPORTED;
return fs->CreateDir(fs, relPath);
}
__declspec(dllexport) KSTATUS IoRemoveDir(const char* path) {
if (!path) return STATUS_INVALID_PARAMETER;
char relPath[CWD_MAX];
uint8_t drive = ResolvePath(path, relPath, sizeof(relPath));
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->RemoveDir) return STATUS_NOT_SUPPORTED;
return fs->RemoveDir(fs, relPath);
}
// ---------------------------------------------------------------------------
// IoTruncateFile - resize the file; updates the cached Size on success
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoTruncateFile(FILE_OBJECT* file, uint64_t newSize) {
if (!file) return STATUS_INVALID_PARAMETER;
if (!file->Fs->Truncate) return STATUS_NOT_SUPPORTED;
KSTATUS s = file->Fs->Truncate(file->Fs, file->FileCtx, newSize);
if (KSUCCESS(s)) {
file->Size = newSize;
// Pull the file pointer back if it now sits past EOF.
if (file->Offset > file->Size)
file->Offset = file->Size;
}
return s;
}
// ---------------------------------------------------------------------------
__declspec(dllexport) KSTATUS IoSeekFile(FILE_OBJECT* file, uint64_t offset) {
if (!file) return STATUS_INVALID_PARAMETER;
file->Offset = offset;
return STATUS_SUCCESS;
}
__declspec(dllexport) uint64_t IoGetFileSize(FILE_OBJECT* file) {
return file ? file->Size : 0;
}
__declspec(dllexport) void IoCloseFile(FILE_OBJECT* file) {
if (file) ObDereferenceObject(&file->Header);
}
// ---------------------------------------------------------------------------
// Directory enumeration
// ---------------------------------------------------------------------------
static void DirDestroy(OBJECT* obj) {
DIR_OBJECT* d = (DIR_OBJECT*)obj; // Header is first member
if (d->Fs && d->DirCtx) d->Fs->CloseDir(d->Fs, d->DirCtx);
}
__declspec(dllexport) KSTATUS IoOpenDir(const char* dirPath, DIR_OBJECT** out) {
if (!out) return STATUS_INVALID_PARAMETER;
// NULL or empty path means the CWD itself
char relPath[CWD_MAX];
uint8_t drive;
if (!dirPath || dirPath[0] == '\0') {
PROCESS* proc = KeGetCurrentProcess();
if (!proc) return STATUS_ACCESS_DENIED;
uintptr_t flags = KeAcquireSpinlock(&proc->CwdLock);
memcpy(relPath, proc->CurrentDirectory, strlen(proc->CurrentDirectory) + 1);
KeReleaseSpinlock(&proc->CwdLock, flags);
// Note: For now, this assumes drive 0. If you add multi-drive support
// later, you should add a CurrentDrive field to the PROCESS struct.
drive = 0;
} else {
drive = ResolvePath(dirPath, relPath, sizeof(relPath));
}
IO_FILESYSTEM* fs = gVolumes[drive].Fs;
if (!fs) return STATUS_NOT_FOUND;
if (!fs->OpenDir) return STATUS_NOT_SUPPORTED;
void* dirCtx = NULL;
KSTATUS s = fs->OpenDir(fs, relPath, &dirCtx);
if (!KSUCCESS(s)) return s;
DIR_OBJECT* d = (DIR_OBJECT*)ObAllocateObject(ObjectTypeDir, sizeof(DIR_OBJECT));
if (!d) { fs->CloseDir(fs, dirCtx); return STATUS_NO_MEMORY; }
d->Header.Destroy = DirDestroy;
d->Fs = fs;
d->DirCtx = dirCtx;
*out = d;
return STATUS_SUCCESS;
}
__declspec(dllexport) KSTATUS IoReadDir(DIR_OBJECT* dir, KDIR_ENTRY* entryOut) {
if (!dir || !entryOut) return STATUS_INVALID_PARAMETER;
if (!dir->Fs->ReadDir) return STATUS_NOT_SUPPORTED;
return dir->Fs->ReadDir(dir->Fs, dir->DirCtx, entryOut);
}
__declspec(dllexport) void IoCloseDir(DIR_OBJECT* dir) {
if (dir) ObDereferenceObject(&dir->Header);
}
+2 -2
View File
@@ -36,7 +36,7 @@ __declspec(dllexport) void DbgPrint(const char* fmt, ...) {
}
// KePanic
__declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...) {
__declspec(dllexport) _Noreturn void KePanic(const char* fmt, ...) {
// Silence! Stop the world!
_disable();
@@ -44,7 +44,7 @@ __declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...) {
// We call PrintChar directly so we can set a red foreground.
// (DbgPrint uses green; we want this visually distinct.)
static const char banner[] = "\n*** KERNEL PANIC ***\n";
for (const char *p = banner; *p; p++)
for (const char* p = banner; *p; p++)
PrintChar(*p, 0xFF2222, 0x000000);
// Now print the caller's message through the normal vprintf path.
+1 -1
View File
@@ -6,7 +6,7 @@
void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), int v);
__declspec(dllexport) void DbgPrint(const char* fmt, ...);
__declspec(dllexport) void KePrintConsole(const char* buf, size_t len);
__declspec(dllexport) _Noreturn void KePanic(const char *fmt, ...);
__declspec(dllexport) _Noreturn void KePanic(const char* fmt, ...);
// ---------------------------------------------------------------------------
// Fault dispatch - called from the three IDT fault stubs (ke_syscall.asm).
+369 -240
View File
@@ -1,240 +1,369 @@
// ke_process.c - process model
#include <string.h>
#include <intrin.h>
#include <ke_process.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ke_event.h>
#include <mm_virt.h>
#include <ob_object.h>
// ---------------------------------------------------------------------------
// Global process list
// ---------------------------------------------------------------------------
static LIST_ENTRY gProcessList;
static SPINLOCK gProcessListLock;
static uint32_t gNextProcessId = 1;
static void ProcessInit(void) {
static int inited = 0;
if (!inited) {
LIST_INIT(&gProcessList);
gProcessListLock = (SPINLOCK)SPINLOCK_INIT;
inited = 1;
}
}
LIST_ENTRY* KeGetProcessList(void) {
ProcessInit();
return &gProcessList;
}
PROCESS* KeGetCurrentProcess(void) {
THREAD* currentThread = KeGetCurrentThread();
if (currentThread) {
return currentThread->Owner;
}
return NULL;
}
size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount) {
ProcessInit();
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
size_t count = 0;
LIST_ENTRY* head = &gProcessList;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) {
PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink);
if (pidsOut && count < maxCount) pidsOut[count] = p->ProcessId;
count++;
}
KeReleaseSpinlock(&gProcessListLock, flags);
return count;
}
// ---------------------------------------------------------------------------
// Destructor (called by ObDereferenceObject when RefCount hits zero)
// ---------------------------------------------------------------------------
static void ProcessDestroy(OBJECT* obj) {
PROCESS* proc = CONTAINING_RECORD(obj, PROCESS, Header);
// Release all remaining handles.
for (int i = 1; i < HANDLE_TABLE_SIZE; i++) {
if (proc->Handles[i]) {
ObDereferenceObject(proc->Handles[i]);
proc->Handles[i] = (OBJECT *)0;
}
}
// Release the main executable image.
if (proc->ImageBase && proc->ImageSize) {
MmFreeUserMemory((void *)proc->ImageBase, proc->ImageSize);
proc->ImageBase = 0;
proc->ImageSize = 0;
}
// Release per-process modules.
for (uint32_t i = 0; i < proc->ModuleCount; i++) {
if (proc->Modules[i].Base && proc->Modules[i].Size) {
MmFreeUserMemory((void *)proc->Modules[i].Base, proc->Modules[i].Size);
}
proc->Modules[i].Base = 0;
proc->Modules[i].Size = 0;
proc->Modules[i].Name[0] = '\0';
}
proc->ModuleCount = 0;
// Release the user command line buffer.
if (proc->UserCmdlineBase && proc->UserCmdlineSize) {
MmFreeUserMemory((void *)proc->UserCmdlineBase, proc->UserCmdlineSize);
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
}
// Release the exit event.
if (proc->ExitEvent) {
ObDereferenceObject(&proc->ExitEvent->Header);
proc->ExitEvent = (EVENT *)0;
}
// Remove from global list.
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
ListRemove(&proc->GlobalLink);
KeReleaseSpinlock(&gProcessListLock, flags);
// Memory for the struct itself is freed by ObDereferenceObject after
// Destroy returns.
}
// ---------------------------------------------------------------------------
// KeCreateProcess
// ---------------------------------------------------------------------------
KSTATUS KeCreateProcess(const char* name, PROCESS **out) {
ProcessInit();
if (!name || !out) return STATUS_INVALID_PARAMETER;
PROCESS* proc = (PROCESS *)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS));
if (!proc) return STATUS_NO_MEMORY;
proc->Header.Destroy = ProcessDestroy;
proc->ProcessId = _InterlockedExchangeAdd((volatile long *)&gNextProcessId, 1);
strncpy(proc->Name, name, PROCESS_NAME_MAX - 1);
proc->Name[PROCESS_NAME_MAX - 1] = '\0';
LIST_INIT(&proc->ThreadList);
proc->ThreadCount = 0;
proc->HandleLock = (SPINLOCK)SPINLOCK_INIT;
proc->ExitCode = 0;
proc->ImageBase = 0;
proc->ImageSize = 0;
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
proc->ModuleCount = 0;
// Slot 0 is the invalid handle - leave it NULL.
memset(proc->Handles, 0, sizeof(proc->Handles));
LIST_INIT(&proc->GlobalLink);
// Create the manual-reset exit event (initially unsignaled).
if (!KSUCCESS(KeEventCreate(EventManualReset, 0, &proc->ExitEvent))) {
ObDereferenceObject(&proc->Header);
return STATUS_NO_MEMORY;
}
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
ListInsertTail(&gProcessList, &proc->GlobalLink);
KeReleaseSpinlock(&gProcessListLock, flags);
*out = proc;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// KeExitProcess
// ---------------------------------------------------------------------------
void KeExitProcess(PROCESS* process, int exitCode) {
if (!process) return;
process->ExitCode = exitCode;
// Wake any threads waiting for this process to finish.
if (process->ExitEvent) KeSignalEvent(process->ExitEvent);
// The last thread exiting via KeExitThread will drop the process refcount.
// This function is a policy hook; actual cleanup is in ProcessDestroy.
ObDereferenceObject(&process->Header);
}
// ---------------------------------------------------------------------------
// KeGetProcessExitCode
// ---------------------------------------------------------------------------
KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut) {
if (!process || !exitCodeOut) return STATUS_INVALID_PARAMETER;
*exitCodeOut = process->ExitCode;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Handle table
// ---------------------------------------------------------------------------
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) {
if (!process || !object || !handleOut) return STATUS_INVALID_PARAMETER;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
for (int i = 1; i < HANDLE_TABLE_SIZE; i++) {
if (!process->Handles[i]) {
ObReferenceObject(object);
process->Handles[i] = object;
*handleOut = (uint32_t)i;
KeReleaseSpinlock(&process->HandleLock, flags);
return STATUS_SUCCESS;
}
}
KeReleaseSpinlock(&process->HandleLock, flags);
return STATUS_NO_MEMORY;
}
void KeCloseHandle(PROCESS* process, uint32_t handle) {
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
OBJECT* obj = process->Handles[handle];
if (obj) {
process->Handles[handle] = (OBJECT *)0;
KeReleaseSpinlock(&process->HandleLock, flags);
ObDereferenceObject(obj);
return;
}
KeReleaseSpinlock(&process->HandleLock, flags);
}
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle) {
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE)
return (OBJECT *)0;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
OBJECT* obj = process->Handles[handle];
if (obj) ObReferenceObject(obj);
KeReleaseSpinlock(&process->HandleLock, flags);
return obj;
}
PROCESS* KeGetProcessById(uint32_t pid) {
ProcessInit();
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
LIST_ENTRY* head = &gProcessList;
LIST_ENTRY* node = head->Fwd;
while (node != head) {
PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink);
if (p->ProcessId == pid) {
ObReferenceObject(&p->Header);
KeReleaseSpinlock(&gProcessListLock, flags);
return p;
}
node = node->Fwd;
}
KeReleaseSpinlock(&gProcessListLock, flags);
return (PROCESS*)0;
}
// ke_process.c - process model
#include <string.h>
#include <intrin.h>
#include <ke_process.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ke_event.h>
#include <ke_userenv.h>
#include <mm_virt.h>
#include <ob_object.h>
// ---------------------------------------------------------------------------
// Global process list
// ---------------------------------------------------------------------------
static LIST_ENTRY gProcessList;
static SPINLOCK gProcessListLock;
static uint32_t gNextProcessId = 1;
static void ProcessInit(void) {
static int inited = 0;
if (!inited) {
LIST_INIT(&gProcessList);
gProcessListLock = (SPINLOCK)SPINLOCK_INIT;
inited = 1;
}
}
LIST_ENTRY* KeGetProcessList(void) {
ProcessInit();
return &gProcessList;
}
PROCESS* KeGetCurrentProcess(void) {
THREAD* currentThread = KeGetCurrentThread();
if (currentThread) {
return currentThread->Owner;
}
return NULL;
}
size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount) {
ProcessInit();
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
size_t count = 0;
LIST_ENTRY* head = &gProcessList;
for (LIST_ENTRY* node = head->Fwd; node != head; node = node->Fwd) {
PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink);
if (pidsOut && count < maxCount) pidsOut[count] = p->ProcessId;
count++;
}
KeReleaseSpinlock(&gProcessListLock, flags);
return count;
}
// ---------------------------------------------------------------------------
// Destructor (called by ObDereferenceObject when RefCount hits zero)
// ---------------------------------------------------------------------------
static void ProcessDestroy(OBJECT* obj) {
PROCESS* proc = CONTAINING_RECORD(obj, PROCESS, Header);
// Release all remaining handles.
for (int i = 1; i < HANDLE_TABLE_SIZE; i++) {
if (proc->Handles[i]) {
ObDereferenceObject(proc->Handles[i]);
proc->Handles[i] = (OBJECT *)0;
}
}
// Release the main executable image.
if (proc->ImageBase && proc->ImageSize) {
MmFreeUserMemory((void *)proc->ImageBase, proc->ImageSize);
proc->ImageBase = 0;
proc->ImageSize = 0;
}
// Release per-process modules.
for (uint32_t i = 0; i < proc->ModuleCount; i++) {
if (proc->Modules[i].Base && proc->Modules[i].Size) {
MmFreeUserMemory((void *)proc->Modules[i].Base, proc->Modules[i].Size);
}
proc->Modules[i].Base = 0;
proc->Modules[i].Size = 0;
proc->Modules[i].Name[0] = '\0';
}
proc->ModuleCount = 0;
// Release the user-mode process environment.
if (proc->UserPebBase && proc->UserPebSize) {
MmFreeUserMemory((void *)proc->UserPebBase, proc->UserPebSize);
proc->UserPebBase = 0;
proc->UserPebSize = 0;
}
if (proc->UserImagePathBase && proc->UserImagePathSize) {
MmFreeUserMemory((void *)proc->UserImagePathBase, proc->UserImagePathSize);
proc->UserImagePathBase = 0;
proc->UserImagePathSize = 0;
}
if (proc->UserCmdlineBase && proc->UserCmdlineSize) {
MmFreeUserMemory((void *)proc->UserCmdlineBase, proc->UserCmdlineSize);
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
}
if (proc->UserCwdBase && proc->UserCwdSize) {
MmFreeUserMemory((void *)proc->UserCwdBase, proc->UserCwdSize);
proc->UserCwdBase = 0;
proc->UserCwdSize = 0;
}
// Release the exit event.
if (proc->ExitEvent) {
ObDereferenceObject(&proc->ExitEvent->Header);
proc->ExitEvent = (EVENT *)0;
}
// Remove from global list.
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
ListRemove(&proc->GlobalLink);
KeReleaseSpinlock(&gProcessListLock, flags);
// Memory for the struct itself is freed by ObDereferenceObject after
// Destroy returns.
}
// ---------------------------------------------------------------------------
// KeCreateProcess
// ---------------------------------------------------------------------------
KSTATUS KeCreateProcess(const char* name, PROCESS **out) {
ProcessInit();
if (!name || !out) return STATUS_INVALID_PARAMETER;
PROCESS* proc = (PROCESS *)ObAllocateObject(ObjectTypeProcess, sizeof(PROCESS));
if (!proc) return STATUS_NO_MEMORY;
proc->Header.Destroy = ProcessDestroy;
proc->ProcessId = _InterlockedExchangeAdd((volatile long *)&gNextProcessId, 1);
strncpy(proc->Name, name, PROCESS_NAME_MAX - 1);
proc->Name[PROCESS_NAME_MAX - 1] = '\0';
LIST_INIT(&proc->ThreadList);
proc->ThreadCount = 0;
proc->HandleLock = (SPINLOCK)SPINLOCK_INIT;
proc->ExitCode = 0;
proc->ImageBase = 0;
proc->ImageSize = 0;
proc->UserPebBase = 0;
proc->UserPebSize = 0;
proc->UserImagePathBase = 0;
proc->UserImagePathSize = 0;
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
proc->UserCwdBase = 0;
proc->UserCwdSize = 0;
proc->ModuleCount = 0;
proc->CurrentDirectory[0] = '\0';
proc->CwdLock = (SPINLOCK)SPINLOCK_INIT;
// Slot 0 is the invalid handle - leave it NULL.
memset(proc->Handles, 0, sizeof(proc->Handles));
LIST_INIT(&proc->GlobalLink);
// Create the manual-reset exit event (initially unsignaled).
if (!KSUCCESS(KeEventCreate(EventManualReset, 0, &proc->ExitEvent))) {
ObDereferenceObject(&proc->Header);
return STATUS_NO_MEMORY;
}
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
ListInsertTail(&gProcessList, &proc->GlobalLink);
KeReleaseSpinlock(&gProcessListLock, flags);
*out = proc;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// KeSetupUserProcessEnvironment
// ---------------------------------------------------------------------------
static KSTATUS CopyEnvironmentString(const char* src, uintptr_t* baseOut, size_t* sizeOut) {
if (!baseOut || !sizeOut) return STATUS_INVALID_PARAMETER;
if (!src) src = "";
size_t len = strlen(src) + 1;
char* dst = (char*)MmAllocateUserMemory(len);
if (!dst) return STATUS_NO_MEMORY;
memcpy(dst, src, len);
*baseOut = (uintptr_t)dst;
*sizeOut = len;
return STATUS_SUCCESS;
}
static KSTATUS CopyEnvironmentStringFixed(const char* src, size_t capacity,
uintptr_t* baseOut, size_t* sizeOut) {
if (!baseOut || !sizeOut || capacity == 0) return STATUS_INVALID_PARAMETER;
if (!src) src = "";
char* dst = (char*)MmAllocateUserMemory(capacity);
if (!dst) return STATUS_NO_MEMORY;
memset(dst, 0, capacity);
size_t len = strlen(src);
if (len >= capacity) len = capacity - 1;
memcpy(dst, src, len);
dst[len] = '\0';
*baseOut = (uintptr_t)dst;
*sizeOut = capacity;
return STATUS_SUCCESS;
}
static void FreePartialProcessEnvironment(PROCESS* proc) {
if (!proc) return;
if (proc->UserPebBase && proc->UserPebSize) {
MmFreeUserMemory((void*)proc->UserPebBase, proc->UserPebSize);
proc->UserPebBase = 0;
proc->UserPebSize = 0;
}
if (proc->UserImagePathBase && proc->UserImagePathSize) {
MmFreeUserMemory((void*)proc->UserImagePathBase, proc->UserImagePathSize);
proc->UserImagePathBase = 0;
proc->UserImagePathSize = 0;
}
if (proc->UserCmdlineBase && proc->UserCmdlineSize) {
MmFreeUserMemory((void*)proc->UserCmdlineBase, proc->UserCmdlineSize);
proc->UserCmdlineBase = 0;
proc->UserCmdlineSize = 0;
}
if (proc->UserCwdBase && proc->UserCwdSize) {
MmFreeUserMemory((void*)proc->UserCwdBase, proc->UserCwdSize);
proc->UserCwdBase = 0;
proc->UserCwdSize = 0;
}
}
KSTATUS KeSetupUserProcessEnvironment(PROCESS* proc,
const char* imagePath,
const char* commandLine,
const char* currentDirectory) {
if (!proc || !imagePath) return STATUS_INVALID_PARAMETER;
const char* finalCommandLine = (commandLine && commandLine[0]) ? commandLine : imagePath;
const char* finalCwd = currentDirectory ? currentDirectory : proc->CurrentDirectory;
FreePartialProcessEnvironment(proc);
KSTATUS s = CopyEnvironmentString(imagePath, &proc->UserImagePathBase, &proc->UserImagePathSize);
if (!KSUCCESS(s)) goto fail;
s = CopyEnvironmentString(finalCommandLine, &proc->UserCmdlineBase, &proc->UserCmdlineSize);
if (!KSUCCESS(s)) goto fail;
s = CopyEnvironmentStringFixed(finalCwd, SYS_CWD_MAX, &proc->UserCwdBase, &proc->UserCwdSize);
if (!KSUCCESS(s)) goto fail;
SYS_PEB* peb = (SYS_PEB*)MmAllocateUserMemory(sizeof(SYS_PEB));
if (!peb) { s = STATUS_NO_MEMORY; goto fail; }
memset(peb, 0, sizeof(SYS_PEB));
peb->Magic = SYS_PEB_MAGIC;
peb->Version = SYS_ENV_VERSION;
peb->ProcessId = proc->ProcessId;
peb->ImagePath = (const char*)proc->UserImagePathBase;
peb->ImagePathLength = proc->UserImagePathSize ? proc->UserImagePathSize - 1 : 0;
peb->CommandLine = (const char*)proc->UserCmdlineBase;
peb->CommandLineLength = proc->UserCmdlineSize ? proc->UserCmdlineSize - 1 : 0;
peb->CurrentDirectory = (const char*)proc->UserCwdBase;
peb->CurrentDirectoryLength = strlen(finalCwd);
proc->UserPebBase = (uintptr_t)peb;
proc->UserPebSize = sizeof(SYS_PEB);
return STATUS_SUCCESS;
fail:
FreePartialProcessEnvironment(proc);
return s;
}
// ---------------------------------------------------------------------------
// KeExitProcess
// ---------------------------------------------------------------------------
void KeExitProcess(PROCESS* process, int exitCode) {
if (!process) return;
process->ExitCode = exitCode;
// Wake any threads waiting for this process to finish.
if (process->ExitEvent) KeSignalEvent(process->ExitEvent);
// The last thread exiting via KeExitThread will drop the process refcount.
// This function is a policy hook; actual cleanup is in ProcessDestroy.
ObDereferenceObject(&process->Header);
}
// ---------------------------------------------------------------------------
// KeGetProcessExitCode
// ---------------------------------------------------------------------------
KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut) {
if (!process || !exitCodeOut) return STATUS_INVALID_PARAMETER;
*exitCodeOut = process->ExitCode;
return STATUS_SUCCESS;
}
// ---------------------------------------------------------------------------
// Handle table
// ---------------------------------------------------------------------------
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut) {
if (!process || !object || !handleOut) return STATUS_INVALID_PARAMETER;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
for (int i = 1; i < HANDLE_TABLE_SIZE; i++) {
if (!process->Handles[i]) {
ObReferenceObject(object);
process->Handles[i] = object;
*handleOut = (uint32_t)i;
KeReleaseSpinlock(&process->HandleLock, flags);
return STATUS_SUCCESS;
}
}
KeReleaseSpinlock(&process->HandleLock, flags);
return STATUS_NO_MEMORY;
}
void KeCloseHandle(PROCESS* process, uint32_t handle) {
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE) return;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
OBJECT* obj = process->Handles[handle];
if (obj) {
process->Handles[handle] = (OBJECT *)0;
KeReleaseSpinlock(&process->HandleLock, flags);
ObDereferenceObject(obj);
return;
}
KeReleaseSpinlock(&process->HandleLock, flags);
}
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle) {
if (!process || handle == 0 || handle >= HANDLE_TABLE_SIZE)
return (OBJECT *)0;
uintptr_t flags = KeAcquireSpinlock(&process->HandleLock);
OBJECT* obj = process->Handles[handle];
if (obj) ObReferenceObject(obj);
KeReleaseSpinlock(&process->HandleLock, flags);
return obj;
}
PROCESS* KeGetProcessById(uint32_t pid) {
ProcessInit();
uintptr_t flags = KeAcquireSpinlock(&gProcessListLock);
LIST_ENTRY* head = &gProcessList;
LIST_ENTRY* node = head->Fwd;
while (node != head) {
PROCESS* p = CONTAINING_RECORD(node, PROCESS, GlobalLink);
if (p->ProcessId == pid) {
ObReferenceObject(&p->Header);
KeReleaseSpinlock(&gProcessListLock, flags);
return p;
}
node = node->Fwd;
}
KeReleaseSpinlock(&gProcessListLock, flags);
return (PROCESS*)0;
}
+86 -83
View File
@@ -1,83 +1,86 @@
#pragma once
// ke_process.h - process model
// A process owns a handle table and a list of threads.
// There is one shared address space - processes do not own one.
#include <ob_object.h>
#include <ke_list.h>
#include <ke_spinlock.h>
#include <ke_event.h>
#include <stddef.h>
#define PROCESS_NAME_MAX 64
#define HANDLE_TABLE_SIZE 256
#define PROCESS_MODULE_MAX 16
typedef struct PROCESS_MODULE {
char Name[32];
uintptr_t Base;
size_t Size;
} PROCESS_MODULE;
typedef struct PROCESS {
OBJECT Header; /* must be first */
uint32_t ProcessId;
char Name[PROCESS_NAME_MAX]; /* UTF-8 */
// Windows-style flat command line, stored at launch by SysCreateProcess.
// The initial thread receives this pointer as its UserArg so _start can
// parse it into argc/argv without an extra syscall.
char Cmdline[512];
char CurrentDirectory[512];
SPINLOCK CwdLock;
/* Thread list. */
LIST_ENTRY ThreadList; /* links THREAD.ProcessLink */
uint32_t ThreadCount;
/* Handle table.
Index 0 is always NULL (the invalid handle).
Entries are OBJECT pointers, each holding a reference. */
OBJECT* Handles[HANDLE_TABLE_SIZE];
SPINLOCK HandleLock;
int ExitCode;
/* Main executable image. Used for cleanup. */
uintptr_t ImageBase;
size_t ImageSize;
/* User command line buffer passed to the initial thread. */
uintptr_t UserCmdlineBase;
size_t UserCmdlineSize;
/* Per-process loaded modules (user processes only). */
PROCESS_MODULE Modules[PROCESS_MODULE_MAX];
uint32_t ModuleCount;
/* Signaled (manual-reset) when the process exits.
Waiters block on this via KeWaitForEvent. */
EVENT* ExitEvent;
LIST_ENTRY GlobalLink; /* node in the global process list */
} PROCESS;
KSTATUS KeCreateProcess(const char* name, PROCESS **out);
void KeExitProcess(PROCESS* process, int exitCode);
KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut);
/* Handle table operations. */
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut);
void KeCloseHandle(PROCESS* process, uint32_t handle);
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle);
/* Return the global process list head (kernel-internal). */
LIST_ENTRY* KeGetProcessList(void);
PROCESS* KeGetCurrentProcess(void);
/* Copy a snapshot of process IDs into caller-provided storage. */
size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount);
/* Find a live process by its ProcessId. Bumps refcount; caller must ObDereferenceObject. */
PROCESS* KeGetProcessById(uint32_t pid);
#pragma once
// ke_process.h - process model
// A process owns a handle table and a list of threads.
// There is one shared address space - processes do not own one.
#include <ob_object.h>
#include <ke_list.h>
#include <ke_spinlock.h>
#include <ke_event.h>
#include <stddef.h>
#include <stdint.h>
#define PROCESS_NAME_MAX 64
#define HANDLE_TABLE_SIZE 256
#define PROCESS_MODULE_MAX 16
typedef struct PROCESS_MODULE {
char Name[32];
uintptr_t Base;
size_t Size;
} PROCESS_MODULE;
typedef struct PROCESS {
OBJECT Header; /* must be first */
uint32_t ProcessId;
char Name[PROCESS_NAME_MAX]; /* UTF-8 */
char CurrentDirectory[512];
SPINLOCK CwdLock;
/* Thread list. */
LIST_ENTRY ThreadList; /* links THREAD.ProcessLink */
uint32_t ThreadCount;
/* Handle table.
Index 0 is always NULL (the invalid handle).
Entries are OBJECT pointers, each holding a reference. */
OBJECT* Handles[HANDLE_TABLE_SIZE];
SPINLOCK HandleLock;
int ExitCode;
/* Main executable image. Used for cleanup. */
uintptr_t ImageBase;
size_t ImageSize;
/* User-mode process environment. The initial thread sees this through GS->TEB->PEB. */
uintptr_t UserPebBase;
size_t UserPebSize;
uintptr_t UserImagePathBase;
size_t UserImagePathSize;
uintptr_t UserCmdlineBase;
size_t UserCmdlineSize;
uintptr_t UserCwdBase;
size_t UserCwdSize;
/* Per-process loaded modules (user processes only). */
PROCESS_MODULE Modules[PROCESS_MODULE_MAX];
uint32_t ModuleCount;
/* Signaled (manual-reset) when the process exits.
Waiters block on this via KeWaitForEvent. */
EVENT* ExitEvent;
LIST_ENTRY GlobalLink; /* node in the global process list */
} PROCESS;
KSTATUS KeCreateProcess(const char* name, PROCESS **out);
KSTATUS KeSetupUserProcessEnvironment(PROCESS* process, const char* imagePath, const char* commandLine, const char* currentDirectory);
void KeExitProcess(PROCESS* process, int exitCode);
KSTATUS KeGetProcessExitCode(PROCESS* process, int* exitCodeOut);
/* Handle table operations. */
KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut);
void KeCloseHandle(PROCESS* process, uint32_t handle);
OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle);
/* Return the global process list head (kernel-internal). */
LIST_ENTRY* KeGetProcessList(void);
PROCESS* KeGetCurrentProcess(void);
/* Copy a snapshot of process IDs into caller-provided storage. */
size_t KeCopyProcessIds(uint32_t* pidsOut, size_t maxCount);
/* Find a live process by its ProcessId. Bumps refcount; caller must ObDereferenceObject. */
PROCESS* KeGetProcessById(uint32_t pid);
+140 -138
View File
@@ -1,138 +1,140 @@
; ke_switch.asm - context switch and thread entry stubs (MASM64 / ML64)
;
; Struct offsets:
; sizeof(OBJECT) = 16 (type:4, refcount:4, destroy:8)
; THREAD.SavedContext offset = 88
; THREAD.TssRsp0 offset = 104
; TSS64.RSP0 offset = 4
;
THREAD_SavedContext EQU 88
THREAD_TssRsp0 EQU 104
THREAD_Teb EQU 112
THREAD_FpuContext EQU 160
TSS_RSP0_OFFSET EQU 4
EXTERN gTss:QWORD ; address of the single TSS, exported from ke_idt.c
.CODE
; -------------------------------------------------------------------------
; void KeSwitchContext(THREAD* from, THREAD* to)
; RCX = from, RDX = to (Microsoft x64)
; -------------------------------------------------------------------------
KeSwitchContext PROC
mov rax, [rcx + THREAD_FpuContext]
fxsave64 [rax]
; Save non-volatile registers onto the current (outgoing) kernel stack.
push rbx
push rbp
push r12
push r13
push r14
push r15
; from->SavedContext = rsp
mov [rcx + THREAD_SavedContext], rsp
; rsp = to->SavedContext
mov rsp, [rdx + THREAD_SavedContext]
; Update MSR_GS_BASE (0xC0000101) with to->Teb
mov rax, [rdx + THREAD_Teb] ; Fetch the incoming TEB pointer
mov ecx, 0C0000101h ; IA32_GS_BASE MSR
mov r8, rdx ; Save RDX ('to' thread) as WRMSR clobbers EDX
mov rdx, rax ; Copy TEB pointer to RDX
shr rdx, 32 ; EDX = High 32 bits of TEB
; EAX = Low 32 bits of TEB (upper 32 bits ignored by CPU)
wrmsr ; Write to MSR
mov rdx, r8 ; Restore RDX
; Update TSS.RSP0 so that the next interrupt/syscall uses the
; incoming thread's kernel stack top.
mov rax, [rdx + THREAD_TssRsp0]
mov r8, gTss
mov qword ptr [r8 + TSS_RSP0_OFFSET], rax
mov rax, [rdx + THREAD_FpuContext]
fxrstor64 [rax]
; Restore non-volatile registers of the incoming thread.
pop r15
pop r14
pop r13
pop r12
pop rbp
pop rbx
ret
KeSwitchContext ENDP
; -------------------------------------------------------------------------
; KeThreadEntry - landing pad for brand-new threads.
;
; Stack layout when we arrive here (set up by KeCreateThread):
; [rsp+00] = KCONTEXT (already popped by KeSwitchContext ret)
; After ret from KeSwitchContext RSP points here:
; [rsp+00] = user RCX arg
; [rsp+08] = user RIP
; [rsp+16] = user CS (0x1B)
; [rsp+24] = RFLAGS (0x202)
; [rsp+32] = user RSP
; [rsp+40] = user SS (0x23)
;
; We load RCX from the stack (first arg per MS x64), then IRETQ.
; -------------------------------------------------------------------------
EXTERN KeSchedulerPostSwitch : PROC
EXTERN KeGetCurrentThread : PROC
EXTERN KeExitThread : PROC
KeThreadEntry PROC
sub rsp, 32 ; Reserve shadow space
call KeSchedulerPostSwitch
add rsp, 32
pop rcx ; load user arg
iretq ; -> ring 3
KeThreadEntry ENDP
; -------------------------------------------------------------------------
; KeKernelThreadEntry - landing pad for kernel-mode threads created by
; KeCreateKernelThread. Never touches ring 3; no IRETQ.
;
; Stack layout when we arrive here (above the KCONTEXT already popped by
; KeSwitchContext's ret):
; [rsp+00] fn ptr (kernel function: void fn(void*))
; [rsp+08] arg ptr
;
; We pop both into non-volatile registers so KeSchedulerPostSwitch can
; clobber volatile ones freely, then call fn(arg). On return we call
; KeExitThread so the thread is cleanly destroyed.
; -------------------------------------------------------------------------
KeKernelThreadEntry PROC
pop rbx ; rbx = fn ptr (non-volatile, survives the call below)
pop r12 ; r12 = arg (non-volatile)
sub rsp, 32
call KeSchedulerPostSwitch ; release scheduler spinlock, reap dead threads
add rsp, 32
mov rcx, r12 ; first arg = arg
sub rsp, 32
call rbx ; fn(arg) -- still ring 0
add rsp, 32
; Thread function returned -- exit cleanly.
sub rsp, 32
call KeGetCurrentThread ; rax = current THREAD*
add rsp, 32
mov rcx, rax ; arg1 = thread*
xor edx, edx ; arg2 = exitCode 0
sub rsp, 32
call KeExitThread
add rsp, 32
; KeExitThread -> KeYield -> KeSwitchContext never returns here, but
; the assembler needs a defined end.
@@: jmp @B
KeKernelThreadEntry ENDP
END
; ke_switch.asm - context switch and thread entry stubs (MASM64 / ML64)
;
; Struct offsets:
; sizeof(OBJECT) = 16 (type:4, refcount:4, destroy:8)
; THREAD.SavedContext offset = 88
; THREAD.TssRsp0 offset = 104
; THREAD.Teb offset = 112
; THREAD.FpuContext offset = 168
; TSS64.RSP0 offset = 4
;
THREAD_SavedContext EQU 88
THREAD_TssRsp0 EQU 104
THREAD_Teb EQU 112
THREAD_FpuContext EQU 168
TSS_RSP0_OFFSET EQU 4
EXTERN gTss:QWORD ; address of the single TSS, exported from ke_idt.c
.CODE
; -------------------------------------------------------------------------
; void KeSwitchContext(THREAD* from, THREAD* to)
; RCX = from, RDX = to (Microsoft x64)
; -------------------------------------------------------------------------
KeSwitchContext PROC
mov rax, [rcx + THREAD_FpuContext]
fxsave64 [rax]
; Save non-volatile registers onto the current (outgoing) kernel stack.
push rbx
push rbp
push r12
push r13
push r14
push r15
; from->SavedContext = rsp
mov [rcx + THREAD_SavedContext], rsp
; rsp = to->SavedContext
mov rsp, [rdx + THREAD_SavedContext]
; Update MSR_GS_BASE (0xC0000101) with to->Teb
mov rax, [rdx + THREAD_Teb] ; Fetch the incoming TEB pointer
mov ecx, 0C0000101h ; IA32_GS_BASE MSR
mov r8, rdx ; Save RDX ('to' thread) as WRMSR clobbers EDX
mov rdx, rax ; Copy TEB pointer to RDX
shr rdx, 32 ; EDX = High 32 bits of TEB
; EAX = Low 32 bits of TEB (upper 32 bits ignored by CPU)
wrmsr ; Write to MSR
mov rdx, r8 ; Restore RDX
; Update TSS.RSP0 so that the next interrupt/syscall uses the
; incoming thread's kernel stack top.
mov rax, [rdx + THREAD_TssRsp0]
mov r8, gTss
mov qword ptr [r8 + TSS_RSP0_OFFSET], rax
mov rax, [rdx + THREAD_FpuContext]
fxrstor64 [rax]
; Restore non-volatile registers of the incoming thread.
pop r15
pop r14
pop r13
pop r12
pop rbp
pop rbx
ret
KeSwitchContext ENDP
; -------------------------------------------------------------------------
; KeThreadEntry - landing pad for brand-new threads.
;
; Stack layout when we arrive here (set up by KeCreateThread):
; [rsp+00] = KCONTEXT (already popped by KeSwitchContext ret)
; After ret from KeSwitchContext RSP points here:
; [rsp+00] = user RCX arg
; [rsp+08] = user RIP
; [rsp+16] = user CS (0x1B)
; [rsp+24] = RFLAGS (0x202)
; [rsp+32] = user RSP
; [rsp+40] = user SS (0x23)
;
; We load RCX from the stack (first arg per MS x64), then IRETQ.
; -------------------------------------------------------------------------
EXTERN KeSchedulerPostSwitch : PROC
EXTERN KeGetCurrentThread : PROC
EXTERN KeExitThread : PROC
KeThreadEntry PROC
sub rsp, 32 ; Reserve shadow space
call KeSchedulerPostSwitch
add rsp, 32
pop rcx ; load user arg
iretq ; -> ring 3
KeThreadEntry ENDP
; -------------------------------------------------------------------------
; KeKernelThreadEntry - landing pad for kernel-mode threads created by
; KeCreateKernelThread. Never touches ring 3; no IRETQ.
;
; Stack layout when we arrive here (above the KCONTEXT already popped by
; KeSwitchContext's ret):
; [rsp+00] fn ptr (kernel function: void fn(void*))
; [rsp+08] arg ptr
;
; We pop both into non-volatile registers so KeSchedulerPostSwitch can
; clobber volatile ones freely, then call fn(arg). On return we call
; KeExitThread so the thread is cleanly destroyed.
; -------------------------------------------------------------------------
KeKernelThreadEntry PROC
pop rbx ; rbx = fn ptr (non-volatile, survives the call below)
pop r12 ; r12 = arg (non-volatile)
sub rsp, 32
call KeSchedulerPostSwitch ; release scheduler spinlock, reap dead threads
add rsp, 32
mov rcx, r12 ; first arg = arg
sub rsp, 32
call rbx ; fn(arg) -- still ring 0
add rsp, 32
; Thread function returned -- exit cleanly.
sub rsp, 32
call KeGetCurrentThread ; rax = current THREAD*
add rsp, 32
mov rcx, rax ; arg1 = thread*
xor edx, edx ; arg2 = exitCode 0
sub rsp, 32
call KeExitThread
add rsp, 32
; KeExitThread -> KeYield -> KeSwitchContext never returns here, but
; the assembler needs a defined end.
@@: jmp @B
KeKernelThreadEntry ENDP
END
+1331 -1331
View File
File diff suppressed because it is too large. Load diff
+281 -256
View File
@@ -1,256 +1,281 @@
// ke_thread.c - thread model
#include <string.h>
#include <intrin.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ke_event.h>
#include <mm_virt.h>
#include <ob_object.h>
#define FPU_FCW_DEFAULT 0x037Fu
#define FPU_MXCSR_DEFAULT 0x1F80u
static uint32_t gNextThreadId = 1;
extern void KeThreadEntry(void);
static void ThreadDestroy(OBJECT* obj) {
THREAD* thread = CONTAINING_RECORD(obj, THREAD, Header);
if (thread->ExitEvent) {
ObDereferenceObject(&thread->ExitEvent->Header);
thread->ExitEvent = (EVENT*)0;
}
if (thread->Owner) {
uintptr_t flags = KeAcquireSpinlock(&thread->Owner->HandleLock);
ListRemove(&thread->ProcessLink);
thread->Owner->ThreadCount--;
KeReleaseSpinlock(&thread->Owner->HandleLock, flags);
ObDereferenceObject(&thread->Owner->Header);
thread->Owner = (PROCESS *)0;
}
if (thread->KernelStackBase) {
MmFreePool(thread->KernelStackBase);
thread->KernelStackBase = (void *)0;
}
// Assumes UserStackBase was added to THREAD struct in ke_thread.h.
if (thread->UserStackBase) {
MmFreeUserMemory(thread->UserStackBase, USTACK_SIZE);
thread->UserStackBase = (void *)0;
}
}
__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
THREAD_PRIORITY priority, THREAD **out) {
if (!owner || !out) return STATUS_INVALID_PARAMETER;
if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER;
THREAD* thread = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
if (!thread) return STATUS_NO_MEMORY;
thread->Header.Destroy = ThreadDestroy;
thread->ThreadId = _InterlockedExchangeAdd((volatile long *)&gNextThreadId, 1);
thread->State = ThreadReady;
thread->Priority = priority;
thread->QuantaConsumed = 0;
thread->WaitObject = (OBJECT *)0;
thread->SchedLockFlags = 0x202ULL;
void* kstack = MmAllocatePool(KSTACK_SIZE);
if (!kstack) {
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
memset(kstack, 0, KSTACK_SIZE);
thread->KernelStackBase = kstack;
thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE;
thread->TssRsp0 = thread->KernelStackTop;
void* ustack = MmAllocateUserMemory(USTACK_SIZE);
if (!ustack) {
MmFreePool(kstack);
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
thread->UserStackBase = ustack;
uintptr_t userStackTop = (uintptr_t)ustack + USTACK_SIZE - 8; // i don't know why, i don't want to know why
// i shouldn't have to wonder why but the stack is off by 8 bytes unless we do this terribleness
// this magic number will hunt me in my dreams
// Initialize the dummy TEB
// offset 0x10 is StackLimit, which __chkstk checks.
uint64_t* teb = (uint64_t*)ustack;
teb[0] = 0; // ExceptionList (GS:0x00)
teb[1] = userStackTop; // StackBase
teb[2] = (uintptr_t)ustack; // StackLimit
thread->Teb = teb;
thread->UserEntry = entry;
thread->UserArg = arg;
uintptr_t* ksp = (uintptr_t *)thread->KernelStackTop;
*(--ksp) = userStackTop;
*(--ksp) = 0x23ULL;
*(--ksp) = userStackTop;
*(--ksp) = 0x202ULL;
*(--ksp) = 0x1BULL;
*(--ksp) = entry;
*(--ksp) = arg;
ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t);
KCONTEXT* ctx = (KCONTEXT *)ksp;
memset(ctx, 0, sizeof(KCONTEXT));
ctx->Rip = (uint64_t)KeThreadEntry;
thread->SavedContext = ctx;
ObReferenceObject(&owner->Header);
thread->Owner = owner;
// Allocate the exit event before the thread becomes visible. Manual-reset
// so multiple joiners (if any) all wake up when the thread exits.
EVENT* exitEvent;
KSTATUS s = KeEventCreate(EventManualReset, 0, &exitEvent);
if (!KSUCCESS(s)) {
MmFreeUserMemory(ustack, USTACK_SIZE);
MmFreePool(kstack);
ObDereferenceObject(&owner->Header);
ObDereferenceObject(&thread->Header);
return s;
}
thread->ExitEvent = exitEvent;
uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock);
ListInsertTail(&owner->ThreadList, &thread->ProcessLink);
owner->ThreadCount++;
KeReleaseSpinlock(&owner->HandleLock, flags);
KeInitThreadFpuContext(thread);
KeReadyThread(thread);
*out = thread;
return STATUS_SUCCESS;
}
__declspec(dllexport) void KeExitThread(THREAD* thread, int code) {
(void)code;
if (!thread) return;
// Wake any pthread_join waiters before the thread is marked dead and
// potentially freed. Manual-reset means all concurrent waiters unblock.
if (thread->ExitEvent)
KeSignalEvent(thread->ExitEvent);
thread->State = ThreadDead;
KeYield();
}
// ---------------------------------------------------------------------------
// KeCreateKernelThread
// ---------------------------------------------------------------------------
// Kernel threads never go to ring 3: they have no user stack, no TEB, and
// their entry stub (KeKernelThreadEntry) calls the function directly then
// calls KeExitThread instead of IRETQ-ing.
//
// Stack layout built here (from KernelStackTop downward):
// [ksp - 8 ] arg <-- popped by KeKernelThreadEntry as r12
// [ksp - 16] fn <-- popped by KeKernelThreadEntry as rbx
// [ksp - 16 - sizeof(KCONTEXT)] KCONTEXT { .Rip = KeKernelThreadEntry }
// ---------------------------------------------------------------------------
extern void KeKernelThreadEntry(void);
__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg,
THREAD_PRIORITY priority, THREAD **out) {
// Kernel threads are owned by the idle process. We need a PROCESS* for
// the owner field; grab it from the idle/init thread that already exists.
THREAD* idleThread = KeGetCurrentThread(); // safe to call before scheduler starts
PROCESS* owner = idleThread ? idleThread->Owner : (PROCESS*)0;
if (!owner) return STATUS_INVALID_PARAMETER;
if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER;
THREAD* thread = (THREAD*)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
if (!thread) return STATUS_NO_MEMORY;
thread->Header.Destroy = ThreadDestroy;
thread->ThreadId = _InterlockedExchangeAdd((volatile long*)&gNextThreadId, 1);
thread->State = ThreadReady;
thread->Priority = priority;
thread->QuantaConsumed = 0;
thread->WaitObject = (OBJECT*)0;
thread->SchedLockFlags = 0x202ULL;
thread->UserStackBase = (void*)0; // no user stack
thread->Teb = (void*)0; // no TEB; GS_BASE will be 0 in kernel ctx
void* kstack = MmAllocatePool(KSTACK_SIZE);
if (!kstack) {
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
memset(kstack, 0, KSTACK_SIZE);
thread->KernelStackBase = kstack;
thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE;
thread->TssRsp0 = thread->KernelStackTop;
// Build the initial stack: arg, fn, then KCONTEXT.
uintptr_t* ksp = (uintptr_t*)thread->KernelStackTop;
*(--ksp) = arg; // popped second (-> r12)
*(--ksp) = fn; // popped first (-> rbx)
ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t);
KCONTEXT* ctx = (KCONTEXT*)ksp;
memset(ctx, 0, sizeof(KCONTEXT));
ctx->Rip = (uint64_t)KeKernelThreadEntry;
thread->SavedContext = ctx;
// Register with the owning process.
ObReferenceObject(&owner->Header);
thread->Owner = owner;
uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock);
ListInsertTail(&owner->ThreadList, &thread->ProcessLink);
owner->ThreadCount++;
KeReleaseSpinlock(&owner->HandleLock, flags);
if (out) *out = thread;
KeInitThreadFpuContext(thread);
KeReadyThread(thread);
return STATUS_SUCCESS;
}
void KeBlockThread(THREAD* thread, OBJECT* waitObject) {
thread->State = ThreadWaiting;
thread->WaitObject = waitObject;
KeYield();
}
void KeUnblockThread(THREAD* thread) {
thread->WaitObject = (OBJECT *)0;
thread->State = ThreadReady;
if (thread->Priority < PriorityHigh) {
thread->Priority = (THREAD_PRIORITY)(thread->Priority + 1);
thread->QuantaConsumed = 0;
}
KeReadyThread(thread);
}
void KeInitThreadFpuContext(THREAD* thread)
{
uintptr_t raw =
(uintptr_t)thread->FpuStorage;
uintptr_t aligned =
(raw + 15) & ~(uintptr_t)15;
thread->FpuContext = (void*)aligned;
memset(thread->FpuContext, 0, 512);
*(uint16_t*)((uint8_t*)thread->FpuContext + 0x00) = 0x037F;
*(uint32_t*)((uint8_t*)thread->FpuContext + 0x18) = 0x1F80;
}
// ke_thread.c - thread model
#include <string.h>
#include <intrin.h>
#include <ke_thread.h>
#include <ke_sched.h>
#include <ke_event.h>
#include <ke_userenv.h>
#include <mm_virt.h>
#include <ob_object.h>
#define FPU_FCW_DEFAULT 0x037Fu
#define FPU_MXCSR_DEFAULT 0x1F80u
static uint32_t gNextThreadId = 1;
extern void KeThreadEntry(void);
static void ThreadDestroy(OBJECT* obj) {
THREAD* thread = CONTAINING_RECORD(obj, THREAD, Header);
if (thread->ExitEvent) {
ObDereferenceObject(&thread->ExitEvent->Header);
thread->ExitEvent = (EVENT*)0;
}
if (thread->Owner) {
uintptr_t flags = KeAcquireSpinlock(&thread->Owner->HandleLock);
ListRemove(&thread->ProcessLink);
thread->Owner->ThreadCount--;
KeReleaseSpinlock(&thread->Owner->HandleLock, flags);
ObDereferenceObject(&thread->Owner->Header);
thread->Owner = (PROCESS *)0;
}
if (thread->KernelStackBase) {
MmFreePool(thread->KernelStackBase);
thread->KernelStackBase = (void *)0;
}
if (thread->Teb && thread->TebSize) {
MmFreeUserMemory(thread->Teb, thread->TebSize);
thread->Teb = (void*)0;
thread->TebSize = 0;
}
if (thread->UserStackBase) {
MmFreeUserMemory(thread->UserStackBase, USTACK_SIZE);
thread->UserStackBase = (void *)0;
}
}
__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
THREAD_PRIORITY priority, THREAD** out) {
if (!owner || !out) return STATUS_INVALID_PARAMETER;
if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER;
THREAD* thread = (THREAD *)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
if (!thread) return STATUS_NO_MEMORY;
thread->Header.Destroy = ThreadDestroy;
thread->ThreadId = _InterlockedExchangeAdd((volatile long *)&gNextThreadId, 1);
thread->State = ThreadReady;
thread->Priority = priority;
thread->QuantaConsumed = 0;
thread->WaitObject = (OBJECT *)0;
thread->SchedLockFlags = 0x202ULL;
void* kstack = MmAllocatePool(KSTACK_SIZE);
if (!kstack) {
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
memset(kstack, 0, KSTACK_SIZE);
thread->KernelStackBase = kstack;
thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE;
thread->TssRsp0 = thread->KernelStackTop;
void* ustack = MmAllocateUserMemory(USTACK_SIZE);
if (!ustack) {
MmFreePool(kstack);
thread->KernelStackBase = (void*)0;
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
thread->UserStackBase = ustack;
uintptr_t userStackTop = (uintptr_t)ustack + USTACK_SIZE - 8; // i don't know why, i don't want to know why
// i shouldn't have to wonder why but the stack is off by 8 bytes unless we do this terribleness
// this magic number will hunt me in my dreams
SYS_TEB* teb = (SYS_TEB*)MmAllocateUserMemory(sizeof(SYS_TEB));
if (!teb) {
MmFreeUserMemory(ustack, USTACK_SIZE);
thread->UserStackBase = (void*)0;
MmFreePool(kstack);
thread->KernelStackBase = (void*)0;
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
memset(teb, 0, sizeof(SYS_TEB));
teb->Peb = (SYS_PEB*)owner->UserPebBase;
teb->StackBase = (void*)userStackTop;
teb->StackLimit = ustack;
teb->Self = teb;
teb->ThreadId = thread->ThreadId;
teb->Magic = SYS_TEB_MAGIC;
thread->Teb = teb;
thread->TebSize = sizeof(SYS_TEB);
thread->UserEntry = entry;
thread->UserArg = arg;
uintptr_t* ksp = (uintptr_t *)thread->KernelStackTop;
*(--ksp) = userStackTop;
*(--ksp) = 0x23ULL;
*(--ksp) = userStackTop;
*(--ksp) = 0x202ULL;
*(--ksp) = 0x1BULL;
*(--ksp) = entry;
*(--ksp) = arg;
ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t);
KCONTEXT* ctx = (KCONTEXT *)ksp;
memset(ctx, 0, sizeof(KCONTEXT));
ctx->Rip = (uint64_t)KeThreadEntry;
thread->SavedContext = ctx;
ObReferenceObject(&owner->Header);
thread->Owner = owner;
// Allocate the exit event before the thread becomes visible. Manual-reset
// so multiple joiners (if any) all wake up when the thread exits.
EVENT* exitEvent;
KSTATUS s = KeEventCreate(EventManualReset, 0, &exitEvent);
if (!KSUCCESS(s)) {
if (thread->Teb && thread->TebSize) MmFreeUserMemory(thread->Teb, thread->TebSize);
thread->Teb = (void*)0;
thread->TebSize = 0;
MmFreeUserMemory(ustack, USTACK_SIZE);
thread->UserStackBase = (void*)0;
MmFreePool(kstack);
thread->KernelStackBase = (void*)0;
ObDereferenceObject(&owner->Header);
thread->Owner = (PROCESS*)0;
ObDereferenceObject(&thread->Header);
return s;
}
thread->ExitEvent = exitEvent;
uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock);
ListInsertTail(&owner->ThreadList, &thread->ProcessLink);
owner->ThreadCount++;
KeReleaseSpinlock(&owner->HandleLock, flags);
KeInitThreadFpuContext(thread);
KeReadyThread(thread);
*out = thread;
return STATUS_SUCCESS;
}
__declspec(dllexport) void KeExitThread(THREAD* thread, int code) {
(void)code;
if (!thread) return;
// Wake any pthread_join waiters before the thread is marked dead and
// potentially freed. Manual-reset means all concurrent waiters unblock.
if (thread->ExitEvent)
KeSignalEvent(thread->ExitEvent);
thread->State = ThreadDead;
KeYield();
}
// ---------------------------------------------------------------------------
// KeCreateKernelThread
// ---------------------------------------------------------------------------
// Kernel threads never go to ring 3: they have no user stack, no TEB, and
// their entry stub (KeKernelThreadEntry) calls the function directly then
// calls KeExitThread instead of IRETQ-ing.
//
// Stack layout built here (from KernelStackTop downward):
// [ksp - 8 ] arg <-- popped by KeKernelThreadEntry as r12
// [ksp - 16] fn <-- popped by KeKernelThreadEntry as rbx
// [ksp - 16 - sizeof(KCONTEXT)] KCONTEXT { .Rip = KeKernelThreadEntry }
// ---------------------------------------------------------------------------
extern void KeKernelThreadEntry(void);
__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg,
THREAD_PRIORITY priority, THREAD** out) {
// Kernel threads are owned by the idle process. We need a PROCESS* for
// the owner field; grab it from the idle/init thread that already exists.
THREAD* idleThread = KeGetCurrentThread(); // safe to call before scheduler starts
PROCESS* owner = idleThread ? idleThread->Owner : (PROCESS*)0;
if (!owner) return STATUS_INVALID_PARAMETER;
if (priority >= PriorityCount) return STATUS_INVALID_PARAMETER;
THREAD* thread = (THREAD*)ObAllocateObject(ObjectTypeThread, sizeof(THREAD));
if (!thread) return STATUS_NO_MEMORY;
thread->Header.Destroy = ThreadDestroy;
thread->ThreadId = _InterlockedExchangeAdd((volatile long*)&gNextThreadId, 1);
thread->State = ThreadReady;
thread->Priority = priority;
thread->QuantaConsumed = 0;
thread->WaitObject = (OBJECT*)0;
thread->SchedLockFlags = 0x202ULL;
thread->UserStackBase = (void*)0; // no user stack
thread->Teb = (void*)0; // no TEB; GS_BASE will be 0 in kernel ctx
thread->TebSize = 0;
void* kstack = MmAllocatePool(KSTACK_SIZE);
if (!kstack) {
ObDereferenceObject(&thread->Header);
return STATUS_NO_MEMORY;
}
memset(kstack, 0, KSTACK_SIZE);
thread->KernelStackBase = kstack;
thread->KernelStackTop = (uintptr_t)kstack + KSTACK_SIZE;
thread->TssRsp0 = thread->KernelStackTop;
// Build the initial stack: arg, fn, then KCONTEXT.
uintptr_t* ksp = (uintptr_t*)thread->KernelStackTop;
*(--ksp) = arg; // popped second (-> r12)
*(--ksp) = fn; // popped first (-> rbx)
ksp -= sizeof(KCONTEXT) / sizeof(uintptr_t);
KCONTEXT* ctx = (KCONTEXT*)ksp;
memset(ctx, 0, sizeof(KCONTEXT));
ctx->Rip = (uint64_t)KeKernelThreadEntry;
thread->SavedContext = ctx;
// Register with the owning process.
ObReferenceObject(&owner->Header);
thread->Owner = owner;
uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock);
ListInsertTail(&owner->ThreadList, &thread->ProcessLink);
owner->ThreadCount++;
KeReleaseSpinlock(&owner->HandleLock, flags);
if (out) *out = thread;
KeInitThreadFpuContext(thread);
KeReadyThread(thread);
return STATUS_SUCCESS;
}
void KeBlockThread(THREAD* thread, OBJECT* waitObject) {
thread->State = ThreadWaiting;
thread->WaitObject = waitObject;
KeYield();
}
void KeUnblockThread(THREAD* thread) {
thread->WaitObject = (OBJECT *)0;
thread->State = ThreadReady;
if (thread->Priority < PriorityHigh) {
thread->Priority = (THREAD_PRIORITY)(thread->Priority + 1);
thread->QuantaConsumed = 0;
}
KeReadyThread(thread);
}
void KeInitThreadFpuContext(THREAD* thread)
{
uintptr_t raw =
(uintptr_t)thread->FpuStorage;
uintptr_t aligned =
(raw + 15) & ~(uintptr_t)15;
thread->FpuContext = (void*)aligned;
memset(thread->FpuContext, 0, 512);
*(uint16_t*)((uint8_t*)thread->FpuContext + 0x00) = 0x037F;
*(uint32_t*)((uint8_t*)thread->FpuContext + 0x18) = 0x1F80;
}
+102 -101
View File
@@ -1,101 +1,102 @@
#pragma once
// ke_thread.h - thread model
// A THREAD is the unit of scheduling. Every thread belongs to exactly one process.
#include <stdint.h>
#include <ob_object.h>
#include <ke_list.h>
#include <ke_process.h>
#include <ke_event.h>
#define KSTACK_SIZE (64 * 1024) /* 64 KB kernel stack */
#define USTACK_SIZE (512 * 1024) /* 512 KB user stack */
typedef enum THREAD_PRIORITY {
PriorityLow = 0,
PriorityNormal = 1,
PriorityHigh = 2,
PriorityCount = 3,
} THREAD_PRIORITY;
typedef enum THREAD_STATE {
ThreadReady = 0,
ThreadRunning = 1,
ThreadWaiting = 2,
ThreadDead = 3,
} THREAD_STATE;
// Saved register context - matches the layout pushed by KeSwitchContext.
// The saved RSP points to this structure on the kernel stack.
typedef struct KCONTEXT {
uint64_t R15, R14, R13, R12;
uint64_t Rbp, Rbx;
uint64_t Rip; /* return address back into KeSwitchContext */
} KCONTEXT;
typedef struct THREAD {
OBJECT Header; /* must be first */
uint32_t ThreadId;
THREAD_STATE State;
THREAD_PRIORITY Priority;
// Quanta consumed without blocking - used by the MLFQ for demotion.
uint32_t QuantaConsumed;
PROCESS* Owner;
LIST_ENTRY ProcessLink; /* node in Owner->ThreadList */
LIST_ENTRY SchedulerLink; /* node in run queue or wait list */
// Kernel stack - never PAGE_USER, pointed to by TssRsp0.
void* KernelStackBase;
uintptr_t KernelStackTop;
// Saved context pointer - set by KeSwitchContext.
KCONTEXT* SavedContext;
// Non-NULL while State == ThreadWaiting.
OBJECT* WaitObject;
// Updated on every context switch so the CPU loads the right kernel stack.
uintptr_t TssRsp0;
// Thread Environment Block
void* Teb;
// Ring-3 entry point and argument for newly created threads.
uintptr_t UserEntry;
uintptr_t UserArg;
uintptr_t SchedLockFlags; // Stores RFLAGS while thread is context switched out
void* UserStackBase; // Tracks user stack allocation to prevent memory leaks
// Signaled (manual-reset) when the thread exits.
// Allocated in KeCreateThread, signaled in KeExitThread before teardown.
// Waiters block on this via SYS_WAIT_THREAD -> KeWaitForEvent.
EVENT* ExitEvent;
void* FpuContext;
uint8_t FpuStorage[528];
} THREAD;
__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
THREAD_PRIORITY priority, THREAD **out);
// Create a kernel-mode-only thread. fn is called directly in ring 0 with
// arg; when fn returns the thread exits automatically. No user stack or TEB
// is allocated. The thread is owned by the idle process and runs at the
// given priority.
__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg,
THREAD_PRIORITY priority, THREAD **out);
// Mark a thread dead and deschedule it permanently.
__declspec(dllexport) void KeExitThread(THREAD* thread, int code);
// Block / unblock primitives used by sync objects.
void KeBlockThread(THREAD* thread, OBJECT* waitObject);
void KeUnblockThread(THREAD* thread);
// Assembly stub - implemented in ke_switch.asm.
void KeSwitchContext(THREAD* from, THREAD* to);
void KeInitThreadFpuContext(THREAD* thread);
#pragma once
// ke_thread.h - thread model
// A THREAD is the unit of scheduling. Every thread belongs to exactly one process.
#include <stdint.h>
#include <ob_object.h>
#include <ke_list.h>
#include <ke_process.h>
#include <ke_event.h>
#define KSTACK_SIZE (64 * 1024) /* 64 KB kernel stack */
#define USTACK_SIZE (512 * 1024) /* 512 KB user stack */
typedef enum THREAD_PRIORITY {
PriorityLow = 0,
PriorityNormal = 1,
PriorityHigh = 2,
PriorityCount = 3,
} THREAD_PRIORITY;
typedef enum THREAD_STATE {
ThreadReady = 0,
ThreadRunning = 1,
ThreadWaiting = 2,
ThreadDead = 3,
} THREAD_STATE;
// Saved register context - matches the layout pushed by KeSwitchContext.
// The saved RSP points to this structure on the kernel stack.
typedef struct KCONTEXT {
uint64_t R15, R14, R13, R12;
uint64_t Rbp, Rbx;
uint64_t Rip; /* return address back into KeSwitchContext */
} KCONTEXT;
typedef struct THREAD {
OBJECT Header; /* must be first */
uint32_t ThreadId;
THREAD_STATE State;
THREAD_PRIORITY Priority;
// Quanta consumed without blocking - used by the MLFQ for demotion.
uint32_t QuantaConsumed;
PROCESS* Owner;
LIST_ENTRY ProcessLink; /* node in Owner->ThreadList */
LIST_ENTRY SchedulerLink; /* node in run queue or wait list */
// Kernel stack - never PAGE_USER, pointed to by TssRsp0.
void* KernelStackBase;
uintptr_t KernelStackTop;
// Saved context pointer - set by KeSwitchContext.
KCONTEXT* SavedContext;
// Non-NULL while State == ThreadWaiting.
OBJECT* WaitObject;
// Updated on every context switch so the CPU loads the right kernel stack.
uintptr_t TssRsp0;
// Thread Environment Block. GS base is loaded from this pointer.
void* Teb;
size_t TebSize;
// Ring-3 entry point and argument for newly created threads.
uintptr_t UserEntry;
uintptr_t UserArg;
uintptr_t SchedLockFlags; // Stores RFLAGS while thread is context switched out
void* UserStackBase; // Tracks user stack allocation to prevent memory leaks
// Signaled (manual-reset) when the thread exits.
// Allocated in KeCreateThread, signaled in KeExitThread before teardown.
// Waiters block on this via SYS_WAIT_THREAD -> KeWaitForEvent.
EVENT* ExitEvent;
void* FpuContext;
uint8_t FpuStorage[528];
} THREAD;
__declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
THREAD_PRIORITY priority, THREAD** out);
// Create a kernel-mode-only thread. fn is called directly in ring 0 with
// arg; when fn returns the thread exits automatically. No user stack or TEB
// is allocated. The thread is owned by the idle process and runs at the
// given priority.
__declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg,
THREAD_PRIORITY priority, THREAD** out);
// Mark a thread dead and deschedule it permanently.
__declspec(dllexport) void KeExitThread(THREAD* thread, int code);
// Block / unblock primitives used by sync objects.
void KeBlockThread(THREAD* thread, OBJECT* waitObject);
void KeUnblockThread(THREAD* thread);
// Assembly stub - implemented in ke_switch.asm.
void KeSwitchContext(THREAD* from, THREAD* to);
void KeInitThreadFpuContext(THREAD* thread);
+35
View File
@@ -0,0 +1,35 @@
#pragma once
// ke_userenv.h - kernel view of the user-mode process/thread environment.
// This is this OS' ABI, not a Windows compatibility structure.
#include <stdint.h>
#include <stddef.h>
#define SYS_PEB_MAGIC 0x30424550u /* "PEB0" */
#define SYS_TEB_MAGIC 0x30424554u /* "TEB0" */
#define SYS_ENV_VERSION 1u
#define SYS_CWD_MAX 512u
typedef struct SYS_PEB {
uint32_t Magic;
uint32_t Version;
uint32_t ProcessId;
const char* ImagePath;
size_t ImagePathLength;
const char* CommandLine;
size_t CommandLineLength;
const char* CurrentDirectory;
size_t CurrentDirectoryLength;
} SYS_PEB;
typedef struct SYS_TEB {
SYS_PEB* Peb;
void* StackBase;
void* StackLimit;
struct SYS_TEB* Self;
uint32_t ThreadId;
uint32_t Magic;
} SYS_TEB;
+17 -26
View File
@@ -250,8 +250,8 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) {
uint64_t size = IoGetFileSize(f);
DbgPrint("LaunchUserProcess: IoGetFileSize: %llu\n", size);
void* mem = MmAllocateUserMemory(size);
DbgPrint("LaunchUserProcess: MmAllocateUserMemory: 0x%llX\n", mem);
void* mem = MmAllocatePool(size);
DbgPrint("LaunchUserProcess: MmAllocatePool: 0x%llX\n", mem);
if (!mem) {
IoCloseFile(f);
return 0;
@@ -262,7 +262,7 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) {
DbgPrint("LaunchUserProcess: IoReadFile: %d\n", s);
IoCloseFile(f);
if (!KSUCCESS(s)) {
MmFreeUserMemory(mem, size);
MmFreePool(mem);
return 0;
}
@@ -273,35 +273,23 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) {
s = KeCreateProcess(filePath, &proc);
if (!KSUCCESS(s)) {
DbgPrint("Failed to create process. System halted.\n");
MmFreeUserMemory(mem, size);
MmFreePool(mem);
return 0;
}
DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, filePath);
DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, (args && args[0]) ? args : filePath);
if (args && args[0])
strncpy(proc->Cmdline, args, sizeof(proc->Cmdline) - 1);
else
strncpy(proc->Cmdline, filePath, sizeof(proc->Cmdline) - 1);
proc->Cmdline[sizeof(proc->Cmdline) - 1] = '\0';
// proc->Cmdline is kernel pool (ffffc000...) - not readable at CPL=3.
// Copy into user memory before handing the pointer to the initial thread.
size_t cmdlineLen = strlen(proc->Cmdline) + 1;
char* userCmdline = (char*)MmAllocateUserMemory(cmdlineLen);
if (!userCmdline) {
DbgPrint("LaunchUserProcess: Failed to allocate user cmdline buffer.\n");
MmFreeUserMemory(mem, size);
s = KeSetupUserProcessEnvironment(proc, filePath, args, proc->CurrentDirectory);
if (!KSUCCESS(s)) {
DbgPrint("LaunchUserProcess: Failed to build user process environment. Status: %d\n", s);
MmFreePool(mem);
ObDereferenceObject(&proc->Header);
return 0;
}
memcpy(userCmdline, proc->Cmdline, cmdlineLen);
proc->UserCmdlineBase = (uintptr_t)userCmdline;
proc->UserCmdlineSize = cmdlineLen;
// Parse the raw PE file and map it into executable memory.
uintptr_t imageBase = 0;
s = LdrLoadImage(mem, size, &imageBase, true);
MmFreeUserMemory(mem, size);
MmFreePool(mem);
if (!KSUCCESS(s)) {
DbgPrint("LaunchUserProcess: LdrLoadImage failed with status: %d\n", s);
ObDereferenceObject(&proc->Header);
@@ -316,19 +304,22 @@ static PROCESS* LaunchUserProcess(const char* filePath, const char* args) {
return 0;
}
proc->ImageBase = imageBase;
proc->ImageSize = LdrGetImageSize(imageBase);
uintptr_t entryPoint = LdrGetEntryPoint(imageBase);
DbgPrint("LaunchUserProcess: PE Image mapped! Base 0x%llx, Entry 0x%llX\n", imageBase, entryPoint);
// Create the main thread, pointing it to the PE Entry Point.
s = KeCreateThread(proc, (uintptr_t)entryPoint, (uintptr_t)userCmdline, PriorityNormal, &thread);
// Create the main thread last. KeCreateThread queues it before returning.
s = KeCreateThread(proc, (uintptr_t)entryPoint, 0, PriorityNormal, &thread);
if (!KSUCCESS(s)) {
DbgPrint("LaunchUserProcess: Failed to create thread. Status: %d\n", s);
proc->ImageBase = 0;
proc->ImageSize = 0;
MmFreeUserMemory((void*)imageBase, LdrGetImageSize(imageBase));
ObDereferenceObject(&proc->Header);
return 0;
}
proc->ImageBase = imageBase;
proc->ImageSize = LdrGetImageSize(imageBase);
DbgPrint("LaunchUserProcess: Thread created and queued!\n");
// Caller owns the reference returned by KeCreateProcess.
+2
View File
@@ -323,6 +323,7 @@ static void* HeapAlloc(HEAP* h, size_t size) {
HdrToFtr(best)->Magic = BLOCK_MAGIC;
HdrToFtr(best)->Size = best->Size;
memset((char *)best + HDR_SIZE, 0, size);
KeReleaseSpinlock(&h->Lock, flags);
return (char *)best + HDR_SIZE;
}
@@ -409,6 +410,7 @@ static void* HeapRealloc(HEAP* h, void* ptr, size_t new_size) {
if (!new_ptr) {
return (void*)0; // Out of memory, original block is left untouched
}
memset(new_ptr, 0, aligned_size);
// Copy the old data to the new block.
// We only copy up to the old block's usable size.
+2
View File
@@ -78,6 +78,8 @@ typedef void* pthread_mutexattr_t;
typedef void* posix_spawn_file_actions_t;
typedef void* posix_spawnattr_t;
__declspec(dllexport) const char* posix_get_command_line(void);
// ---------------------------------------------------------------------------
// dirent (used by posix_dir.c)
// ---------------------------------------------------------------------------
+28 -8
View File
@@ -22,9 +22,12 @@ static int KsToErrno(uint32_t s) {
}
}
__declspec(dllexport) int ClearConsoleScreenBuffer(void* hConsole) {
__declspec(dllexport) void clearscreen(void) {
SysScreenClear(0x00000000);
return 1;
}
__declspec(dllexport) void clearkeyboard(void) {
while (SysKbPoll()) (void)SysKbReadChar();
}
__declspec(dllexport) int open(const char* path, int flags, ...) {
@@ -154,14 +157,31 @@ __declspec(dllexport) off_t lseek(int fd, off_t offset, int whence) {
FD_ENTRY* e = FdGet(pid, fd);
if (!e || e->Type != FD_FILE) { errno = (e ? ESPIPE : EBADF); return -1; }
uint64_t target;
int64_t target = 0;
switch (whence) {
case SEEK_SET: target = (uint64_t)offset; break;
case SEEK_CUR: target = SysGetFilePos(e->KHandle) + (uint64_t)offset; break;
case SEEK_END: target = SysGetFileSize(e->KHandle) + (uint64_t)offset; break;
default: errno = EINVAL; return -1;
case SEEK_SET:
target = (int64_t)offset;
break;
case SEEK_CUR: {
uint64_t cur = SysGetFilePos(e->KHandle);
if (cur > 0x7FFFFFFFULL) { errno = EINVAL; return -1; }
target = (int64_t)cur + (int64_t)offset;
break;
}
case SEEK_END: {
uint64_t size = SysGetFileSize(e->KHandle);
if (size > 0x7FFFFFFFULL) { errno = EINVAL; return -1; }
target = (int64_t)size + (int64_t)offset;
break;
}
default:
errno = EINVAL;
return -1;
}
uint32_t s = SysSeekFile(e->KHandle, target);
if (target < 0 || target > 0x7FFFFFFFLL) { errno = EINVAL; return -1; }
uint32_t s = SysSeekFile(e->KHandle, (uint64_t)target);
if (s != 0) { errno = KsToErrno(s); return -1; }
return (off_t)target;
}
+14
View File
@@ -5,6 +5,20 @@
#include "posix.h"
#include "posix_path.h"
static SYS_PEB* gProcessPeb = NULL;
static SYS_PEB* PosixPeb(void) {
if (!gProcessPeb) gProcessPeb = SysGetPeb();
return gProcessPeb;
}
__declspec(dllexport) const char* posix_get_command_line(void) {
SYS_PEB* peb = PosixPeb();
if (!peb || peb->Magic != SYS_PEB_MAGIC || !peb->CommandLine) return "";
return peb->CommandLine;
}
// pid -> proc handle table (user-mode side, for waitpid)
#define PROC_TABLE_SIZE 32
typedef struct {
+12 -12
View File
@@ -41,7 +41,6 @@ typedef struct {
void* (*Fn)(void*);
void* Arg;
uint32_t Pid; // owner pid, for the retval table
uint32_t Tid; // filled in by pthread_create after KeCreateThread returns
} PTHREAD_TRAMP;
static void PthreadTrampoline(uintptr_t raw) {
@@ -49,14 +48,19 @@ static void PthreadTrampoline(uintptr_t raw) {
void* (*fn)(void*) = tr->Fn;
void* arg = tr->Arg;
uint32_t pid = tr->Pid;
uint32_t tid = tr->Tid;
uint32_t tid = SysGetCurrentTid();
// Create the join slot from inside the new thread. KeCreateThread makes the
// thread runnable before the creator regains control, so the old design
// raced on tr->Tid and could observe zero/stale memory.
PTHREAD_SLOT* slot = PthreadSlotFind(pid, tid);
if (!slot) slot = PthreadSlotAlloc(pid, tid);
SysFree(tr, sizeof(PTHREAD_TRAMP));
void* retval = fn(arg);
// Store retval for pthread_join. If the slot isn't there (join was called
// before the thread was even created, which shouldn't happen) just drop it.
PTHREAD_SLOT* slot = PthreadSlotFind(pid, tid);
// Store retval for pthread_join.
if (slot) slot->RetVal = retval;
SysExitThread(0);
@@ -77,7 +81,6 @@ __declspec(dllexport) int pthread_create(pthread_t* t, const pthread_attr_t* att
tr->Fn = fn;
tr->Arg = arg;
tr->Pid = pid;
tr->Tid = 0; // filled below
uint32_t tid = 0;
uint32_t s = SysCreateThread((uint64_t)PthreadTrampoline,
@@ -86,12 +89,9 @@ __declspec(dllexport) int pthread_create(pthread_t* t, const pthread_attr_t* att
SysFree(tr, sizeof(PTHREAD_TRAMP));
return EAGAIN;
}
tr->Tid = tid; // safe: trampoline reads Tid after SysCreateThread returns
// Reserve a retval slot before the trampoline can exit
PthreadSlotAlloc(pid, tid);
t->Tid = tid;
// The trampoline allocates the retval slot itself after it knows its real
// TID, avoiding the creator/new-thread race.
if (t) t->Tid = tid;
return 0;
}
+32 -3
View File
@@ -123,6 +123,27 @@ static void cmd_type(const char* filename) {
SysCloseHandle(hFile); print("\n");
}
static bool extractProgramPath(const char* cmdline, char* out, size_t outSize) {
if (!cmdline || !out || outSize == 0) return false;
while (*cmdline == ' ' || *cmdline == '\t') cmdline++;
if (*cmdline == '\0') return false;
size_t pos = 0;
if (*cmdline == '"') {
cmdline++;
while (*cmdline && *cmdline != '"' && pos + 1 < outSize)
out[pos++] = *cmdline++;
} else {
while (*cmdline && *cmdline != ' ' && *cmdline != '\t' && pos + 1 < outSize)
out[pos++] = *cmdline++;
}
out[pos] = '\0';
return pos != 0;
}
// ---------------------------------------------------------------------------
// Run one command
// ---------------------------------------------------------------------------
@@ -134,13 +155,16 @@ static int runCommand(const char* cmd) {
if (strcmp(cmd, "crash") == 0) { *(int*)0 = 42; return 0; }
if (strcmp(cmd, "dir") == 0) { cmd_dir("."); return 0; }
if (strncmp(cmd, "dir ", 4) == 0) { cmd_dir(cmd + 4); return 0; }
if (strncmp(cmd, "cd ", 3) == 0) { SysSetCwd(cmd + 3); return 0; }
if (strncmp(cmd, "cd ", 3) == 0) { if (SysSetCwd(cmd + 3) != 0) print("cd: failed\n"); return 0; }
if (strncmp(cmd, "del ", 4) == 0) { SysDeleteFile(cmd + 4); return 0; }
if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; }
if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; }
char program[MAX_CMD];
if (!extractProgramPath(cmd, program, sizeof(program))) return 0;
uint32_t hProc;
if (SysCreateProcess(cmd, &hProc, cmd) == 0) {
if (SysCreateProcess(program, &hProc, cmd) == 0) {
SysWaitForProcess(hProc);
SysCloseHandle(hProc);
while (SysKbPoll()) (void)SysKbReadChar();
@@ -148,8 +172,13 @@ static int runCommand(const char* cmd) {
return 0;
}
int main(int argc, char** argv) {
int main(void) {
const char* cmdline = SysGetCommandLine();
char lineBuf[MAX_CMD];
print("shell: command line: ");
print(cmdline);
print("\n");
print("shell: ready\n");
while (true) {
print("> ");
+48 -6
View File
@@ -2,6 +2,7 @@
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <syscall.h>
#define MAX_CMD 256
#define NAME_MAX 259
@@ -21,7 +22,8 @@ extern int unlink(const char *pathname);
extern int posix_spawn(int *pid, const char *path, const void *act, const void *attr, char *const argv[], char *const envp[]);
extern int waitpid(int pid, int *status, int options);
extern void exit(int status);
extern int ClearConsoleScreenBuffer(void* h);
extern void clearscreen(void);
extern void clearkeyboard(void);
extern void PosixFdInit(void);
static int hIn = 0; static int hOut = 1;
@@ -52,6 +54,31 @@ static void readLine(char* buf, int len) {
buf[pos] = '\0';
}
static int splitCommandLine(char* line, char** argv, int maxArgv) {
int argc = 0;
char* p = line;
while (*p && argc < maxArgv - 1) {
while (*p == ' ' || *p == '\t') p++;
if (!*p) break;
if (*p == '"') {
p++;
argv[argc++] = p;
while (*p && *p != '"') p++;
if (*p) *p++ = '\0';
} else {
argv[argc++] = p;
while (*p && *p != ' ' && *p != '\t') p++;
if (*p) *p++ = '\0';
}
}
argv[argc] = NULL;
return argc;
}
static char* trim(char* s) {
while (*s == ' ') s++;
int len = (int)strlen(s);
@@ -111,25 +138,40 @@ static int runCommand(const char* cmd) {
if (!cmd || *cmd == '\0') return 0;
if (strcmp(cmd, "exit") == 0) return 1;
if (strcmp(cmd, "help") == 0) { cmd_help(); return 0; }
if (strcmp(cmd, "clear") == 0) { ClearConsoleScreenBuffer(0); return 0; }
if (strcmp(cmd, "clear") == 0) { clearscreen(); return 0; }
if (strcmp(cmd, "crash") == 0) { *(int*)0 = 42; return 0; }
if (strcmp(cmd, "dir") == 0) { cmd_dir("."); return 0; }
if (strncmp(cmd, "dir ", 4) == 0) { cmd_dir(cmd + 4); return 0; }
if (strncmp(cmd, "cd ", 3) == 0) { chdir(cmd + 3); return 0; }
if (strncmp(cmd, "cd ", 3) == 0) { if (chdir(cmd + 3) != 0) print("cd: failed\n"); return 0; }
if (strncmp(cmd, "del ", 4) == 0) { unlink(cmd + 4); return 0; }
if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; }
if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; }
int pid; char *argv[] = {(char*)cmd, NULL};
if (posix_spawn(&pid, cmd, NULL, NULL, argv, NULL) == 0) {
char cmdBuf[MAX_CMD];
strncpy(cmdBuf, cmd, MAX_CMD - 1);
cmdBuf[MAX_CMD - 1] = '\0';
char* argv[32];
int argc = splitCommandLine(cmdBuf, argv, 32);
if (argc == 0) return 0;
int pid;
if (posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL) == 0) {
int status; waitpid(pid, &status, 0);
clearkeyboard();
} else { print("shell: launch failed\n"); }
return 0;
}
int main() {
int main(void) {
PosixFdInit();
const char* cmdline = SysGetCommandLine();
char lineBuf[MAX_CMD];
print("shell: command line: ");
print(cmdline);
print("\n");
print("shell: ready\n");
while (true) {
print("> ");
+168 -159
View File
@@ -1,159 +1,168 @@
#include <windows.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#define MAX_CMD 256
static HANDLE hIn = INVALID_HANDLE_VALUE;
static HANDLE hOut = INVALID_HANDLE_VALUE;
// External/Custom API
BOOL ClearConsoleScreenBuffer(HANDLE hConsole);
// --- Output Helpers ---
static void print(const char* s) {
DWORD written;
if (hOut != INVALID_HANDLE_VALUE) WriteConsoleA(hOut, s, (DWORD)strlen(s), &written, 0);
else OutputDebugStringA(s);
}
static void printUInt(uint32_t n) {
char buf[12]; int i = 0;
if (n == 0) { print("0"); return; }
while (n > 0) { buf[i++] = (char)('0' + n % 10); n /= 10; }
while (i > 0) { char c[2] = { buf[--i], '\0' }; print(c); }
}
static void printInt(int32_t n) {
if (n < 0) { print("-"); n = -n; }
printUInt((uint32_t)n);
}
// --- Line Editor ---
static void readLine(char* buf, int len) {
int pos = 0;
while (true) {
char c; DWORD nRead;
ReadConsoleA(hIn, &c, 1, &nRead, 0);
if (!nRead) continue;
if (c == '\r' || c == '\n') { print("\n"); break; }
if ((c == '\b' || c == 127) && pos > 0) { pos--; print("\b \b"); continue; }
if (c < 0x20 || c == 127) continue;
if (pos < len - 1) {
buf[pos++] = c;
char echo[2] = { c, '\0' };
print(echo);
}
}
buf[pos] = '\0';
}
static char* trim(char* s) {
while (*s == ' ') s++;
int len = (int)strlen(s);
while (len > 0 && s[len - 1] == ' ') len--;
s[len] = '\0';
return s;
}
// --- Built-in Commands ---
static void cmd_help() {
print("Available commands:\n"
" help - Show this list\n"
" dir [path] - List directory contents\n"
" cd <path> - Change directory\n"
" type <file> - Display file contents\n"
" del <file> - Delete a file\n"
" write <file> <tx> - Create a file with text\n"
" clear - Clear the screen\n"
" exit - Close the shell\n");
}
static void cmd_write(const char* args) {
char filename[MAX_PATH]; int i = 0;
while (args[i] != ' ' && args[i] != '\0' && i < MAX_PATH - 1) {
filename[i] = args[i]; i++;
}
filename[i] = '\0';
if (args[i] == '\0' || args[i+1] == '\0') {
print("write: usage: write <filename> <text>\n");
return;
}
HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
if (hFile == INVALID_HANDLE_VALUE) { print("write: could not create file\n"); return; }
DWORD written;
WriteFile(hFile, args + i + 1, (DWORD)strlen(args + i + 1), &written, 0);
CloseHandle(hFile);
print("File written successfully.\n");
}
static void cmd_dir(const char* path) {
WIN32_FIND_DATAA fd;
char search_path[MAX_PATH];
const char* target = (path && *path != '\0') ? path : ".";
size_t len = strlen(target);
if (len + 3 > MAX_PATH) { print("dir: path too long\n"); return; }
strcpy(search_path, target);
strcat(search_path, "\\*");
HANDLE hFind = FindFirstFileA(search_path, &fd);
if (hFind == INVALID_HANDLE_VALUE) { print("dir: failed to read directory\n"); return; }
do {
if (fd.cFileName[0] == '.') continue; // Skip . and ..
print(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? "<DIR> " : " ");
print(fd.cFileName); print("\n");
} while (FindNextFileA(hFind, &fd));
FindClose(hFind);
}
static void cmd_type(const char* filename) {
HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
if (hFile == INVALID_HANDLE_VALUE) { print("type: cannot open file\n"); return; }
char buffer[512]; DWORD bytesRead;
while (ReadFile(hFile, buffer, sizeof(buffer) - 1, &bytesRead, 0) && bytesRead > 0) {
buffer[bytesRead] = '\0'; print(buffer);
}
CloseHandle(hFile); print("\n");
}
static int runCommand(const char* cmd) {
if (!cmd || *cmd == '\0') return 0;
if (strcmp(cmd, "exit") == 0) return 1;
if (strcmp(cmd, "help") == 0) { cmd_help(); return 0; }
if (strcmp(cmd, "clear") == 0) { ClearConsoleScreenBuffer(hOut); return 0; }
if (strcmp(cmd, "crash") == 0) { *(int*)0 = 42; return 0; }
if (strcmp(cmd, "dir") == 0) { cmd_dir("."); return 0; }
if (strncmp(cmd, "dir ", 4) == 0) { cmd_dir(cmd + 4); return 0; }
if (strncmp(cmd, "cd ", 3) == 0) { SetCurrentDirectoryA(cmd + 3); return 0; }
if (strncmp(cmd, "del ", 4) == 0) { DeleteFileA(cmd + 4); return 0; }
if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; }
if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; }
PROCESS_INFORMATION pi;
char cmdBuf[MAX_CMD];
strncpy(cmdBuf, cmd, MAX_CMD - 1);
cmdBuf[MAX_CMD - 1] = '\0';
if (CreateProcessA(NULL, cmdBuf, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi)) {
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
} else {
print("shell: launch failed\n");
}
return 0;
}
int main() {
hIn = GetStdHandle(STD_INPUT_HANDLE);
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
char lineBuf[MAX_CMD];
print("shell: ready\n");
while (true) {
print("> ");
readLine(lineBuf, MAX_CMD);
if (runCommand(trim(lineBuf))) break;
}
print("shell: goodbye\n");
ExitProcess(0);
}
#include <windows.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <syscall.h>
#define MAX_CMD 256
static HANDLE hIn = INVALID_HANDLE_VALUE;
static HANDLE hOut = INVALID_HANDLE_VALUE;
// External/Custom API
BOOL ClearConsoleScreenBuffer(HANDLE hConsole);
// --- Output Helpers ---
static void print(const char* s) {
DWORD written;
if (hOut != INVALID_HANDLE_VALUE) WriteConsoleA(hOut, s, (DWORD)strlen(s), &written, 0);
else OutputDebugStringA(s);
}
static void printUInt(uint32_t n) {
char buf[12]; int i = 0;
if (n == 0) { print("0"); return; }
while (n > 0) { buf[i++] = (char)('0' + n % 10); n /= 10; }
while (i > 0) { char c[2] = { buf[--i], '\0' }; print(c); }
}
static void printInt(int32_t n) {
if (n < 0) { print("-"); n = -n; }
printUInt((uint32_t)n);
}
// --- Line Editor ---
static void readLine(char* buf, int len) {
int pos = 0;
while (true) {
char c; DWORD nRead;
ReadConsoleA(hIn, &c, 1, &nRead, 0);
if (!nRead) continue;
if (c == '\r' || c == '\n') { print("\n"); break; }
if ((c == '\b' || c == 127) && pos > 0) { pos--; print("\b \b"); continue; }
if (c < 0x20 || c == 127) continue;
if (pos < len - 1) {
buf[pos++] = c;
char echo[2] = { c, '\0' };
print(echo);
}
}
buf[pos] = '\0';
}
static char* trim(char* s) {
while (*s == ' ') s++;
int len = (int)strlen(s);
while (len > 0 && s[len - 1] == ' ') len--;
s[len] = '\0';
return s;
}
// --- Built-in Commands ---
static void cmd_help() {
print("Available commands:\n"
" help - Show this list\n"
" dir [path] - List directory contents\n"
" cd <path> - Change directory\n"
" type <file> - Display file contents\n"
" del <file> - Delete a file\n"
" write <file> <tx> - Create a file with text\n"
" clear - Clear the screen\n"
" exit - Close the shell\n");
}
static void cmd_write(const char* args) {
char filename[MAX_PATH]; int i = 0;
while (args[i] != ' ' && args[i] != '\0' && i < MAX_PATH - 1) {
filename[i] = args[i]; i++;
}
filename[i] = '\0';
if (args[i] == '\0' || args[i+1] == '\0') {
print("write: usage: write <filename> <text>\n");
return;
}
HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
if (hFile == INVALID_HANDLE_VALUE) { print("write: could not create file\n"); return; }
DWORD written;
WriteFile(hFile, args + i + 1, (DWORD)strlen(args + i + 1), &written, 0);
CloseHandle(hFile);
print("File written successfully.\n");
}
static void cmd_dir(const char* path) {
WIN32_FIND_DATAA fd;
char search_path[MAX_PATH];
const char* target = (path && *path != '\0') ? path : ".";
size_t len = strlen(target);
if (len + 3 > MAX_PATH) { print("dir: path too long\n"); return; }
strcpy(search_path, target);
strcat(search_path, "\\*");
HANDLE hFind = FindFirstFileA(search_path, &fd);
if (hFind == INVALID_HANDLE_VALUE) { print("dir: failed to read directory\n"); return; }
do {
if (fd.cFileName[0] == '.') continue; // Skip . and ..
print(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? "<DIR> " : " ");
print(fd.cFileName); print("\n");
} while (FindNextFileA(hFind, &fd));
FindClose(hFind);
}
static void cmd_type(const char* filename) {
HANDLE hFile = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
if (hFile == INVALID_HANDLE_VALUE) { print("type: cannot open file\n"); return; }
char buffer[512]; DWORD bytesRead;
while (ReadFile(hFile, buffer, sizeof(buffer) - 1, &bytesRead, 0) && bytesRead > 0) {
buffer[bytesRead] = '\0'; print(buffer);
}
CloseHandle(hFile); print("\n");
}
static int runCommand(const char* cmd) {
if (!cmd || *cmd == '\0') return 0;
if (strcmp(cmd, "exit") == 0) return 1;
if (strcmp(cmd, "help") == 0) { cmd_help(); return 0; }
if (strcmp(cmd, "clear") == 0) { ClearConsoleScreenBuffer(hOut); return 0; }
if (strcmp(cmd, "crash") == 0) { *(int*)0 = 42; return 0; }
if (strcmp(cmd, "dir") == 0) { cmd_dir("."); return 0; }
if (strncmp(cmd, "dir ", 4) == 0) { cmd_dir(cmd + 4); return 0; }
if (strncmp(cmd, "cd ", 3) == 0) { if (!SetCurrentDirectoryA(cmd + 3)) print("cd: failed\n"); return 0; }
if (strncmp(cmd, "del ", 4) == 0) { DeleteFileA(cmd + 4); return 0; }
if (strncmp(cmd, "type ", 5) == 0) { cmd_type(cmd + 5); return 0; }
if (strncmp(cmd, "write ", 6) == 0) { cmd_write(cmd + 6); return 0; }
PROCESS_INFORMATION pi;
char cmdBuf[MAX_CMD];
strncpy(cmdBuf, cmd, MAX_CMD - 1);
cmdBuf[MAX_CMD - 1] = '\0';
if (CreateProcessA(NULL, cmdBuf, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi)) {
WaitForSingleObject(pi.hProcess, INFINITE);
if (pi.hThread && pi.hThread != INVALID_HANDLE_VALUE)
CloseHandle(pi.hThread);
FlushConsoleInputBuffer(hIn);
} else {
print("shell: launch failed\n");
}
return 0;
}
int main(void) {
hIn = GetStdHandle(STD_INPUT_HANDLE);
hOut = GetStdHandle(STD_OUTPUT_HANDLE);
const char* cmdline = SysGetCommandLine();
char lineBuf[MAX_CMD];
print("shell: command line: ");
print(cmdline);
print("\n");
print("shell: ready\n");
while (true) {
print("> ");
readLine(lineBuf, MAX_CMD);
if (runCommand(trim(lineBuf))) break;
}
print("shell: goodbye\n");
ExitProcess(0);
}
+50
View File
@@ -4,6 +4,56 @@
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <intrin.h>
// ---------------------------------------------------------------------------
// User process/thread environment
// ---------------------------------------------------------------------------
// GS base points at SYS_TEB. GS:[0] is the process PEB pointer.
// This is this OS' ABI, not a Windows compatibility layout.
#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;
static inline SYS_PEB* SysGetPeb(void) {
return (SYS_PEB*)(uintptr_t)__readgsqword(0);
}
static inline SYS_TEB* SysGetTeb(void) {
return (SYS_TEB*)(uintptr_t)__readgsqword(offsetof(SYS_TEB, Self));
}
static inline const char* SysGetCommandLine(void) {
SYS_PEB* peb = SysGetPeb();
if (!peb || peb->Magic != SYS_PEB_MAGIC) return "";
return peb->CommandLine ? peb->CommandLine : "";
}
// ---------------------------------------------------------------------------
// Thread & Process Management (0-7)
+40 -4
View File
@@ -5,16 +5,52 @@
#include <intrin.h>
#include "bink.h"
int main(const char* cmdline) {
static const char* commandLineSecondToken(const char* cmdline, char* out, size_t outSize) {
if (!out || outSize == 0) return "video.bk2";
strcpy(out, "video.bk2");
if (!cmdline) return out;
while (*cmdline == ' ' || *cmdline == '\t') cmdline++;
if (*cmdline == '\0') return out;
if (*cmdline == '"') {
cmdline++;
while (*cmdline && *cmdline != '"') cmdline++;
if (*cmdline == '"') cmdline++;
} else {
while (*cmdline && *cmdline != ' ' && *cmdline != '\t') cmdline++;
}
while (*cmdline == ' ' || *cmdline == '\t') cmdline++;
if (*cmdline == '\0') return out;
size_t pos = 0;
if (*cmdline == '"') {
cmdline++;
while (*cmdline && *cmdline != '"' && pos + 1 < outSize)
out[pos++] = *cmdline++;
} else {
while (*cmdline && *cmdline != ' ' && *cmdline != '\t' && pos + 1 < outSize)
out[pos++] = *cmdline++;
}
out[pos] = '\0';
return out;
}
int main(void) {
const char* cmdline = GetCommandLineA();
char videoPath[260];
const char* path = commandLineSecondToken(cmdline, videoPath, sizeof(videoPath));
void* h = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsoleA(h, "video: starting\n", 16, 0, 0);
WriteConsoleA(h, cmdline, (unsigned int)strlen(cmdline), 0, 0);
WriteConsoleA(h, "\n", 1, 0, 0);
void* f = CreateFileA("video.bk2", 0, 0, 0, 3, 0, 0);
void* f = CreateFileA(path, 0, 0, 0, 3, 0, 0);
if (!f || f == (void*)(unsigned long long)0xFFFFFFFF) { // fuckass
WriteConsoleA(h, "video: failed to open video.bk2\n", 32, 0, 0);
WriteConsoleA(h, "video: failed to open input file\n", 33, 0, 0);
ExitProcess(1);
}
@@ -34,7 +70,7 @@ int main(const char* cmdline) {
WriteConsoleA(h, "video: ReadFile failed\n", 23, 0, 0);
ExitProcess(1);
}
WriteConsoleA(h, "video: video.bk2 loaded\n", 24, 0, 0);
WriteConsoleA(h, "video: input file loaded\n", 25, 0, 0);
CloseHandle(h);
+1723 -1577
View File
File diff suppressed because it is too large. Load diff