fix the return value of LdrLoadImage, make use of __declspec(dllexport), speed up blitting, add dummy TEB, redesign the syscall interface, fix stack alignent so movaps doesn't crash, implement a shitload of win32, implement a simple shell, add bink as video.exe 😎, minor kmain.c cleanup, HAL now supports power-off events, implemented \b in hal_screen.c, fix many deadlocks, add wait for exit event for processes, completely new error codes, small readme changes.

This commit is contained in:
maksw committed 2026-05-03 13:43:29 +02:00
1 parent c13dd99150
commit 7f4052f41c
33 files changed
+6396 -735

No files matched your search

Binary file not shown.
Binary file not shown.
+17 -21
View File
@@ -14,7 +14,8 @@ mkdir "obj\hal" 2>nul
mkdir "obj\syscall" 2>nul
mkdir "obj\win32" 2>nul
mkdir "obj\drivers" 2>nul
mkdir "obj\shell" 2>nul
mkdir "obj\shell" 2>nul
mkdir "obj\video" 2>nul
mkdir "bin" 2>nul
set CFLAGS=/nologo /W4 /WX /Od /GS- /Gs999999 /Gz /Gy /EHsc- /GR- /Oi /Zi /std:c17 /D_CRT_SECURE_NO_WARNINGS /I"%SRC%\kernel" /I"%SRC%\hal"
@@ -77,16 +78,7 @@ link %LFLAGS% /SUBSYSTEM:NATIVE /ENTRY:KernelMain /OUT:"bin\kernel.exe" ^
:: ---- Compile syscall.dll ----
echo Compiling syscall.dll...
link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL ^
/OUT:"bin\syscall.dll" ^
/EXPORT:SysYield /EXPORT:SysAlloc /EXPORT:SysFree ^
/EXPORT:SysCreateThread /EXPORT:SysCloseHandle ^
/EXPORT:SysMutexCreate /EXPORT:SysMutexAcquire /EXPORT:SysMutexRelease ^
/EXPORT:SysSemCreate /EXPORT:SysSemWait /EXPORT:SysSemSignal ^
/EXPORT:SysEventCreate /EXPORT:SysEventSet /EXPORT:SysEventWait ^
/EXPORT:SysQueueCreate /EXPORT:SysQueueSend /EXPORT:SysQueueRecv ^
/EXPORT:SysScreenBlit /EXPORT:SysGetTick /EXPORT:SysSleep ^
/EXPORT:SysExitThread /EXPORT:SysExitProcess /EXPORT:SysDbgPrint ^
obj\syscall\stubs.obj || exit /b 1
/OUT:"bin\syscall.dll" obj\syscall\stubs.obj || exit /b 1
:: ---- Compile win32.dll ----
echo Compiling win32.dll...
@@ -94,15 +86,7 @@ for %%f in (%SRC%\win32\*.c) do (
cl %CFLAGS% /c /Fo"obj\win32\%%~nf.obj" "%%f" || exit /b 1
)
link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL ^
/OUT:"bin\win32.dll" ^
/EXPORT:HeapAlloc /EXPORT:HeapFree /EXPORT:HeapReAlloc ^
/EXPORT:CreateThread /EXPORT:ExitProcess /EXPORT:ExitThread ^
/EXPORT:Sleep /EXPORT:SwitchToThread ^
/EXPORT:CreateMutex /EXPORT:ReleaseMutex /EXPORT:WaitForSingleObject ^
/EXPORT:CreateEvent /EXPORT:SetEvent /EXPORT:CloseHandle ^
/EXPORT:GetTickCount /EXPORT:GetTickCount64 ^
/EXPORT:BitBlt /EXPORT:GetMessage /EXPORT:DispatchMessage /EXPORT:OutputDebugStringA ^
bin\syscall.lib obj\win32\*.obj || exit /b 1
/OUT:"bin\win32.dll" bin\syscall.lib obj\libc.obj obj\win32\*.obj || exit /b 1
:: ---- Compile drivers ----
echo Compiling drivers...
@@ -137,6 +121,17 @@ echo Linking shell.exe...
link %LFLAGS% /SUBSYSTEM:NATIVE /ENTRY:main ^
/OUT:"bin\shell.exe" /PDB:"bin\shell.pdb" bin\win32.lib obj\libc.obj obj\shell\*.obj || exit /b 1
:: ---- Compile video player ----
echo Compiling video...
for %%f in (%SRC%\video\*.c) do (
cl %CFLAGS% /c /Fo"obj\video\%%~nf.obj" "%%f" || exit /b 1
)
:: ---- Link video.exe ----
echo Linking video.exe...
link %LFLAGS% /SUBSYSTEM:NATIVE /ENTRY:main ^
/OUT:"bin\video.exe" /PDB:"bin\video.pdb" bin\win32.lib obj\libc.obj %SRC%\video\bink2w64.lib obj\video\*.obj || exit /b 1
:: ---- Copy to ESP layout ----
echo Assembling ESP...
mkdir "%DEST%\drivers" 2>nul
@@ -150,7 +145,8 @@ copy /y "bin\ide.sys" "%DEST%\drivers\"
copy /y "bin\ahci.sys" "%DEST%\drivers\"
copy /y "bin\fat32.sys" "%DEST%\drivers\"
copy /y "bin\usb.sys" "%DEST%\drivers\"
copy /y "bin\shell.exe" "%DEST%\"
copy /y "bin\shell.exe" "%DEST%\"
copy /y "bin\video.exe" "%DEST%\"
copy /y "%SRC%\boot.ini" "%DEST%\"
copy /y "%SRC%\drivers\drivers.ini" "%DEST%\drivers\"
+1 -1
View File
@@ -697,7 +697,7 @@ EFI_STATUS EFIAPI UefiMain(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable
UINT64 start = desc->PhysicalStart;
UINT64 end = start + (desc->NumberOfPages * PAGE_SIZE);
// CRITICAL FIX: Add 2 pages (8KB) of padding to the end of the region.
// Add 2 pages (8KB) of padding to the end of the region.
// This catches ACPI tables that cross page boundaries into unmapped
// memory because of buggy UEFI size reporting.
end += 2 * PAGE_SIZE;
+7
View File
@@ -60,6 +60,12 @@ typedef struct HAL_INTC {
void (*SendEoi)(void);
} HAL_INTC;
typedef struct HAL_POWER {
// ACPI S5 soft-off. Disables interrupts, writes SLP_EN to PM1a/b_CNT,
// and halts if firmware doesn't respond. Does not return.
void (*Shutdown)(void);
} HAL_POWER;
typedef struct HAL_API {
uint32_t Version;
void *(*PhysToVirt)(uint64_t physAddr);
@@ -69,6 +75,7 @@ typedef struct HAL_API {
HAL_TIMER Timer;
HAL_PCI Pci;
HAL_INTC Intc;
HAL_POWER Power;
} HAL_API;
typedef struct HAL_INIT_PARAMS {
+1 -1
View File
@@ -1,6 +1,7 @@
// hal_keyboard.c - PS/2 keyboard, interrupt-driven, 256-entry ring buffer
#include "hal_api.h"
#include "hal_apic.h"
#include "hal_debug.h"
#include <stdint.h>
#include <stdbool.h>
#include <intrin.h>
@@ -48,7 +49,6 @@ static uint8_t KbdReadScancode(void) {
}
static char KbdReadChar(void) {
if (gHead == gTail) return 0;
uint8_t sc = KbdReadScancode();
if (sc < 128) return gScanToAscii[sc];
return 0;
+127 -10
View File
@@ -81,6 +81,12 @@ static uintptr_t gLapicPhys = 0xFEE00000;
static uintptr_t gIoapicPhys = 0xFEC00000;
static uintptr_t gHpetPhys = 0xFED00000;
// ACPI power-off state (filled in by ParseFadt / ScanDsdtForS5)
static uint16_t gPm1aCntBlk = 0;
static uint16_t gPm1bCntBlk = 0;
static uint8_t gSlpTypA = 5; // S5 SLP_TYP for PM1a; 5 is correct for QEMU/most VMs
static uint8_t gSlpTypB = 5; // S5 SLP_TYP for PM1b
int debugEnabled = 0;
int _fltused; // fuckass
static void hal_putchar(char c) {
@@ -99,6 +105,101 @@ static void *HalPhysToVirt(uint64_t phys) {
return (void *)(phys + gPhysBase);
}
// FADT (Fixed ACPI Description Table) - partial layout up to PM1b_CNT_BLK.
// We only need offsets 40 (DSDT ptr), 64 (PM1a_CNT_BLK), 68 (PM1b_CNT_BLK).
// The full table is ~276 bytes (ACPI 2.0+); we never access beyond offset 68.
#pragma pack(push, 1)
typedef struct {
ACPISDTHeader Header; // 0 (36 bytes)
uint32_t FirmwareCtrl; // 36
uint32_t Dsdt; // 40 - physical addr of DSDT
uint8_t Reserved1; // 44
uint8_t PreferredPmProfile; // 45
uint16_t SciInt; // 46
uint32_t SmiCmd; // 48
uint8_t AcpiEnable; // 52
uint8_t AcpiDisable; // 53
uint8_t S4BiosReq; // 54
uint8_t PstateCnt; // 55
uint32_t Pm1aEvtBlk; // 56
uint32_t Pm1bEvtBlk; // 60
uint32_t Pm1aCntBlk; // 64 - I/O port for PM1a control register
uint32_t Pm1bCntBlk; // 68 - I/O port for PM1b control register
} FADT;
#pragma pack(pop)
// Scan DSDT AML for the _S5_ Name object and extract SLP_TYP values.
// The encoding looks like:
// 0x08 '_' 'S' '5' '_' ; NameOp + "_S5_"
// 0x12 <PkgLen> ; PackageOp + encoded length
// <NumElements> ; usually 0x04
// 0x0A <slp_typ_a> ; BytePrefix + value (or 0x00 for zero)
// 0x0A <slp_typ_b>
static void ScanDsdtForS5(const uint8_t* aml, uint32_t len) {
for (uint32_t i = 0; i + 9 < len; i++) {
// Match NameOp + "_S5_" + PackageOp
if (aml[i] != 0x08) continue;
if (aml[i+1] != 0x5F || aml[i+2] != 0x53 ||
aml[i+3] != 0x35 || aml[i+4] != 0x5F) continue;
if (aml[i+5] != 0x12) continue;
// PkgLength encoding: low 6 bits = byte count (0 → only this byte);
// bits [7:6] = number of following bytes in the length field.
uint8_t pkgByte = aml[i+6];
uint32_t pkgExtra = pkgByte >> 6; // 0..3 extra bytes
uint32_t j = i + 7 + pkgExtra; // skip PkgLen field
if (j + 2 >= len) break;
j++; // skip NumElements byte
// SLP_TYP_A
if (aml[j] == 0x0A && j + 1 < len) {
gSlpTypA = aml[j + 1]; j += 2;
} else if (aml[j] == 0x00) {
gSlpTypA = 0; j++;
} else {
break; // unexpected encoding - keep defaults
}
// SLP_TYP_B
if (j < len && aml[j] == 0x0A && j + 1 < len)
gSlpTypB = aml[j + 1];
else if (j < len && aml[j] == 0x00)
gSlpTypB = 0;
HalDbgPrint("HAL: _S5_ SLP_TYP_A=0x%02x SLP_TYP_B=0x%02x\n",
gSlpTypA, gSlpTypB);
return;
}
HalDbgPrint("HAL: _S5_ not found in DSDT, using defaults (A=0x%02x B=0x%02x)\n",
gSlpTypA, gSlpTypB);
}
static int validate_checksum(void* table, uint32_t length) {
uint8_t sum = 0;
uint8_t* bytes = (uint8_t*)table;
for (uint32_t i = 0; i < length; i++) {
sum += bytes[i];
}
return sum == 0;
}
static void ParseFadt(const FADT* fadt) {
gPm1aCntBlk = (uint16_t)fadt->Pm1aCntBlk;
gPm1bCntBlk = (uint16_t)fadt->Pm1bCntBlk;
HalDbgPrint("HAL: FADT PM1a_CNT=0x%04x PM1b_CNT=0x%04x DSDT=0x%08x\n",
gPm1aCntBlk, gPm1bCntBlk, fadt->Dsdt);
if (fadt->Dsdt) {
const ACPISDTHeader* dsdt =
(const ACPISDTHeader*)((uintptr_t)fadt->Dsdt + gPhysBase);
if (memcmp(dsdt->Signature, "DSDT", 4) == 0 &&
validate_checksum((void*)dsdt, dsdt->Length)) {
const uint8_t* aml = (const uint8_t*)(dsdt + 1);
ScanDsdtForS5(aml, dsdt->Length - (uint32_t)sizeof(ACPISDTHeader));
}
}
}
static void ParseMadt(MADT* madt) {
gLapicPhys = madt->LocalAPICAddress;
uint8_t* p = (uint8_t *)(madt + 1);
@@ -112,15 +213,6 @@ static void ParseMadt(MADT* madt) {
}
}
static int validate_checksum(void* table, uint32_t length) {
uint8_t sum = 0;
uint8_t* bytes = (uint8_t*)table;
for (uint32_t i = 0; i < length; i++) {
sum += bytes[i];
}
return sum == 0;
}
static void WalkAcpi(uintptr_t rsdpPhys) {
if (!rsdpPhys) return;
@@ -188,7 +280,11 @@ static void WalkAcpi(uintptr_t rsdpPhys) {
// Parse MADT
if (memcmp(header->Signature, "APIC", 4) == 0) {
ParseMadt((MADT*)header);
}
}
// Parse FADT (signature "FACP") for PM1 control ports and DSDT pointer
else if (memcmp(header->Signature, "FACP", 4) == 0) {
ParseFadt((const FADT*)header);
}
// Parse HPET
else if (memcmp(header->Signature, "HPET", 4) == 0) {
// HPET base address block is located at offset 44
@@ -198,6 +294,25 @@ static void WalkAcpi(uintptr_t rsdpPhys) {
}
}
// ---------------------------------------------------------------------------
// HalShutdown - ACPI S5 soft power-off. Does not return.
// ---------------------------------------------------------------------------
#include <intrin.h>
static void HalShutdown(void) {
_disable();
// SLP_EN (bit 13) | SLP_TYPx (bits 12:10)
uint16_t valA = (uint16_t)((gSlpTypA << 10) | (1 << 13));
uint16_t valB = (uint16_t)((gSlpTypB << 10) | (1 << 13));
if (gPm1aCntBlk) __outword(gPm1aCntBlk, valA);
if (gPm1bCntBlk) __outword(gPm1bCntBlk, valB);
// Firmware should have powered off by now. If it didn't, halt.
for (;;) __halt();
}
// ---------------------------------------------------------------------------
KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) {
if (gInitialised) return STATUS_ALREADY_EXISTS;
@@ -236,6 +351,8 @@ KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) {
gApi.Intc.UnmaskIrq = HalApicUnmaskIrq;
gApi.Intc.SendEoi = HalApicSendEoi;
gApi.Power.Shutdown = HalShutdown;
gApi.Version = HAL_API_VERSION;
gInitialised = 1;
+50 -4
View File
@@ -45,9 +45,47 @@ static void ScreenPutPixel(int x, int y, uint32_t color) {
}
static void ScreenBlit(int x, int y, int w, int h, const uint32_t* pixels) {
for (int row = 0; row < h; row++)
for (int col = 0; col < w; col++)
PutPixelRaw(x + col, y + row, pixels[row * w + col]);
// Calculate clipping bounds
int startX = (x < 0) ? 0 : x;
int startY = (y < 0) ? 0 : y;
int endX = (x + w > gWidth) ? gWidth : (x + w);
int endY = (y + h > gHeight) ? gHeight : (y + h);
int drawW = endX - startX;
int drawH = endY - startY;
// If the image is entirely off-screen, do nothing
if (drawW <= 0 || drawH <= 0) {
return;
}
// Adjust the source pointer if the image was clipped on the top or left
int srcOffsetX = startX - x;
int srcOffsetY = startY - y;
// Calculate bytes to copy per row
size_t bytesPerRow = (size_t)drawW * sizeof(uint32_t);
// If the width we are drawing exactly matches the physical screen stride,
// AND it matches the original source image width, memory is perfectly contiguous.
if (drawW == gStride && drawW == w) {
uint32_t* dst = gFb + (startY * gStride) + startX;
const uint32_t* src = pixels + (srcOffsetY * w) + srcOffsetX;
memcpy(dst, src, bytesPerRow * drawH);
return;
}
// Copy row-by-row
for (int row = 0; row < drawH; row++) {
// Destination in the actual framebuffer (accounting for stride)
uint32_t* dst = gFb + ((startY + row) * gStride) + startX;
// Source from the provided pixel buffer
const uint32_t* src = pixels + ((srcOffsetY + row) * w) + srcOffsetX;
memcpy(dst, src, bytesPerRow);
}
}
static void ScreenGetSize(int* w, int* h) { *w = gWidth; *h = gHeight; }
@@ -68,7 +106,8 @@ static void ScreenPutChar(char c, int x, int y, uint32_t fg, uint32_t bg) {
for (int row = 0; row < 16; row++) {
uint8_t bits = glyph[row];
for (int col = 0; col < 8; col++) {
uint32_t color = (bits & (1 << col)) ? fg : bg; // i don't know why, i don't want to know why, i shouldn't have to wonder why but the font is backwards unless we do this terriblness
uint32_t color = (bits & (1 << col)) ? fg : bg; // i don't know why, i don't want to know why
// i shouldn't have to wonder why but the font is backwards unless we do this terriblness
PutPixelRaw(x + col, y + row, color);
}
}
@@ -88,6 +127,13 @@ static void ScreenPrintChar(char c, uint32_t fg, uint32_t bg) {
gCursorY++;
} else if (c == '\r') {
gCursorX = 0;
} else if (c == '\b') {
if (gCursorX > 0) {
gCursorX--;
} else if (gCursorY > 0) {
gCursorY--;
gCursorX = cols - 1;
}
} else {
ScreenPutChar(c, gCursorX * FONT_W, gCursorY * FONT_H, fg, bg);
if (++gCursorX >= cols) { gCursorX = 0; gCursorY++; }
+24
View File
@@ -1,6 +1,7 @@
// ke_process.c - process model
#include "ke_process.h"
#include "ke_thread.h"
#include "ke_event.h"
#include "mm_virt.h"
#include "ob_object.h"
#include <string.h>
@@ -40,6 +41,12 @@ static void ProcessDestroy(OBJECT* obj) {
}
}
// 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);
@@ -76,6 +83,12 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) {
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);
@@ -90,11 +103,22 @@ KSTATUS KeCreateProcess(const char* name, PROCESS **out) {
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
// ---------------------------------------------------------------------------
+7
View File
@@ -5,6 +5,7 @@
#include "ob_object.h"
#include "ke_list.h"
#include "ke_spinlock.h"
#include "ke_event.h"
#define PROCESS_NAME_MAX 64
#define HANDLE_TABLE_SIZE 256
@@ -26,11 +27,17 @@ typedef struct PROCESS {
SPINLOCK HandleLock;
int ExitCode;
/* 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);
+11 -4
View File
@@ -158,11 +158,11 @@ void KeYield(void) {
uintptr_t flags = KeAcquireSpinlock(&gSchedLock);
cur->SchedLockFlags = flags;
// Issue 4 Fix: Deleted misleading voluntary promotion from here.
THREAD* next = PickNext();
if (next == cur) {
if (cur != gIdleThread)
// Only re-queue if still running normally; a ThreadWaiting/ThreadDead caller
// has already placed SchedulerLink elsewhere (or is discarding the thread).
if (cur != gIdleThread && cur->State == ThreadRunning)
ListInsertTail(&gRunQueue[cur->Priority], &cur->SchedulerLink);
KeReleaseSpinlock(&gSchedLock, flags);
return;
@@ -171,8 +171,15 @@ void KeYield(void) {
next->State = ThreadRunning;
gCurrentThread = next;
if (cur != gIdleThread && cur->State == ThreadReady)
// Only re-queue the outgoing thread if it is still in the running state.
// If KeBlockThread set State = ThreadWaiting, SchedulerLink was already
// inserted into the sync object's wait list — inserting it here too would
// corrupt both lists simultaneously (same intrusive node in two lists).
// If KeExitThread set State = ThreadDead, the thread must not be re-queued.
if (cur != gIdleThread && cur->State == ThreadRunning) {
cur->State = ThreadReady;
ListInsertTail(&gRunQueue[cur->Priority], &cur->SchedulerLink);
}
gOutgoingThread = cur;
KeSwitchContext(cur, next);
+25 -15
View File
@@ -6,20 +6,30 @@
typedef uint32_t KSTATUS;
#define STATUS_SUCCESS ((KSTATUS)0x00000000)
#define STATUS_FAILURE ((KSTATUS)0x00000001)
#define STATUS_INVALID_PARAMETER ((KSTATUS)0x00000002)
#define STATUS_NO_MEMORY ((KSTATUS)0x00000003)
#define STATUS_NOT_FOUND ((KSTATUS)0x00000004)
#define STATUS_ACCESS_DENIED ((KSTATUS)0x00000005)
#define STATUS_ALREADY_EXISTS ((KSTATUS)0x00000006)
#define STATUS_NOT_SUPPORTED ((KSTATUS)0x00000007)
#define STATUS_TIMEOUT ((KSTATUS)0x00000008)
#define STATUS_BUFFER_TOO_SMALL ((KSTATUS)0x00000009)
#define STATUS_DEVICE_ERROR ((KSTATUS)0x0000000A)
#define STATUS_OBJECT_DEAD ((KSTATUS)0x0000000B)
#define STATUS_QUEUE_FULL ((KSTATUS)0x0000000C)
#define STATUS_QUEUE_EMPTY ((KSTATUS)0x0000000D)
#define STATUS_CHECKSUM_MISMATCH ((KSTATUS)0x0000000E)
// --- General & Control (0x0000) ---
#define STATUS_SUCCESS ((KSTATUS)0x0000)
#define STATUS_FAILURE ((KSTATUS)0x0001)
#define STATUS_NOT_SUPPORTED ((KSTATUS)0x0002)
#define STATUS_TIMEOUT ((KSTATUS)0x0003)
// --- Validation & Parameters (0x0010) ---
#define STATUS_INVALID_PARAMETER ((KSTATUS)0x0010)
#define STATUS_INVALID_HANDLE ((KSTATUS)0x0011)
#define STATUS_BUFFER_TOO_SMALL ((KSTATUS)0x0012)
#define STATUS_CHECKSUM_MISMATCH ((KSTATUS)0x0013)
// --- Resource & Access (0x0020) ---
#define STATUS_NO_MEMORY ((KSTATUS)0x0020)
#define STATUS_ACCESS_DENIED ((KSTATUS)0x0021)
#define STATUS_NOT_FOUND ((KSTATUS)0x0022)
#define STATUS_ALREADY_EXISTS ((KSTATUS)0x0023)
// --- State & Lifecycle (0x0030) ---
#define STATUS_OBJECT_DEAD ((KSTATUS)0x0030)
#define STATUS_QUEUE_FULL ((KSTATUS)0x0031)
#define STATUS_QUEUE_EMPTY ((KSTATUS)0x0032)
// --- Hardware & External (0x0040) ---
#define STATUS_DEVICE_ERROR ((KSTATUS)0x0040)
#define KSUCCESS(s) ((s) == STATUS_SUCCESS)
+55 -1
View File
@@ -1,6 +1,6 @@
; ke_switch.asm - context switch and thread entry stubs (MASM64 / ML64)
;
; Generated struct offsets (regenerate with gen_offsets.exe if structs change):
; Struct offsets:
; sizeof(OBJECT) = 16 (type:4, refcount:4, destroy:8)
; THREAD.SavedContext offset = 88
; THREAD.TssRsp0 offset = 104
@@ -8,6 +8,7 @@
;
THREAD_SavedContext EQU 88
THREAD_TssRsp0 EQU 104
THREAD_Teb EQU 112
TSS_RSP0_OFFSET EQU 4
EXTERN gTss:QWORD ; address of the single TSS, exported from ke_idt.c
@@ -33,6 +34,16 @@ KeSwitchContext PROC
; 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]
@@ -65,6 +76,8 @@ KeSwitchContext ENDP
; We load RCX from the stack (first arg per MS x64), then IRETQ.
; -------------------------------------------------------------------------
EXTERN KeSchedulerPostSwitch : PROC
EXTERN KeCurrentThread : PROC
EXTERN KeExitThread : PROC
KeThreadEntry PROC
sub rsp, 32 ; Reserve shadow space
@@ -74,4 +87,45 @@ KeThreadEntry PROC
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 KeCurrentThread ; 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
+1 -1
View File
@@ -1,4 +1,4 @@
; ke_syscall.asm - INT 0x2E kernel entry (MASM64)
; ke_syscall.asm - INT 0x40 kernel entry (MASM64)
;
; On entry the CPU has pushed (ring-3 -> ring-0 transition):
; SS, RSP, RFLAGS, CS, RIP (on the kernel stack via TSS.RSP0)
+665 -276
View File
File diff suppressed because it is too large. Load diff
+84 -1
View File
@@ -66,7 +66,18 @@ KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg,
return STATUS_NO_MEMORY;
}
thread->UserStackBase = ustack;
uintptr_t userStackTop = (uintptr_t)ustack + USTACK_SIZE;
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] = 0; // StackBase (GS:0x08)
teb[2] = 0; // StackLimit (GS:0x10) - __chkstk will see 0 and safely exit
thread->Teb = teb;
thread->UserEntry = entry;
thread->UserArg = arg;
@@ -108,6 +119,78 @@ void KeExitThread(THREAD* thread, int code) {
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);
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 = KeCurrentThread(); // 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;
KeReadyThread(thread);
return STATUS_SUCCESS;
}
void KeBlockThread(THREAD* thread, OBJECT* waitObject) {
thread->State = ThreadWaiting;
thread->WaitObject = waitObject;
+13 -3
View File
@@ -46,7 +46,7 @@ typedef struct THREAD {
LIST_ENTRY SchedulerLink; /* node in run queue or wait list */
// Kernel stack - never PAGE_USER, pointed to by TssRsp0.
void* KernelStackBase;
void* KernelStackBase;
uintptr_t KernelStackTop;
// Saved context pointer - set by KeSwitchContext.
@@ -58,17 +58,27 @@ typedef struct THREAD {
// 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
uintptr_t SchedLockFlags; // Stores RFLAGS while thread is context switched out
void* UserStackBase; // Tracks user stack allocation to prevent memory leaks
} THREAD;
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.
KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg,
THREAD_PRIORITY priority, THREAD **out);
// Mark a thread dead and deschedule it permanently.
void KeExitThread(THREAD* thread, int code);
+64 -32
View File
@@ -74,6 +74,10 @@ typedef struct {
typedef KSTATUS (*DRIVER_ENTRY_FN)(const HAL_API *);
// HAL pointer cached at boot so kernel threads can reach it without
// threading a parameter through every call chain.
static const HAL_API* gKmainHal;
// ---------------------------------------------------------------------------
// Phase Helper: Resolve Imports for all registered modules
// ---------------------------------------------------------------------------
@@ -210,7 +214,7 @@ static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) {
}
// ---------------------------------------------------------------------------
// Phase Helper: Load a DLL from disk and register it as a system module
// Phase Helper: Load a DLL from disk and register it
// ---------------------------------------------------------------------------
static void LoadAndRegisterDll(const char* filename) {
KSTATUS s;
@@ -219,8 +223,8 @@ static void LoadAndRegisterDll(const char* filename) {
// Read the DLL from disk
s = IoOpenFile(filename, &f);
if (s != STATUS_SUCCESS) {
DbgPrint("KernelMain: Failed to open critical system DLL: %s\n", filename);
_disable(); while(1) __halt();
DbgPrint("KernelMain: Failed to open DLL: %s\n", filename);
return;
}
uint64_t size = IoGetFileSize(f);
@@ -229,7 +233,7 @@ static void LoadAndRegisterDll(const char* filename) {
if (!KSUCCESS(IoReadFile(f, mem, size, &got))) {
DbgPrint("KernelMain: Failed to read %s\n", filename);
IoCloseFile(f);
_disable(); while(1) __halt();
return;
}
IoCloseFile(f);
@@ -239,7 +243,7 @@ static void LoadAndRegisterDll(const char* filename) {
s = LdrLoadImage(mem, size, &mappedBase, true);
if (!KSUCCESS(s)) {
DbgPrint("KernelMain: Failed to map %s\n", filename);
_disable(); while(1) __halt();
return;
}
MmFreePool(mem); // Free the raw file buffer, we only need the mapped image now
@@ -248,18 +252,20 @@ static void LoadAndRegisterDll(const char* filename) {
}
// ---------------------------------------------------------------------------
// Phase Helper: Load and launch the init process from the filesystem
// Phase Helper: Load and launch a process from the filesystem.
// Returns the new PROCESS* with one reference owned by the caller.
// Returns NULL on failure.
// ---------------------------------------------------------------------------
static void LaunchInitProcess(BOOT_INFO* bootInfo) {
static PROCESS* LaunchUserProcess(const char* filePath) {
KSTATUS s;
FILE_OBJECT* f;
// Read the executable file into a raw buffer
s = IoOpenFile(bootInfo->CommandLine, &f);
s = IoOpenFile(filePath, &f);
DbgPrint("KernelMain: IoOpenFile: %d\n", s);
if (s != STATUS_SUCCESS) {
DbgPrint("Failed to load file: \"%s\"!\n", bootInfo->CommandLine);
_disable(); while(1) __halt();
DbgPrint("Failed to load file: \"%s\"!\n", filePath);
return (PROCESS*)0;
}
uint64_t size = IoGetFileSize(f);
@@ -278,29 +284,46 @@ static void LaunchInitProcess(BOOT_INFO* bootInfo) {
PROCESS* proc;
THREAD* thread;
s = KeCreateProcess("init", &proc);
s = KeCreateProcess(filePath, &proc);
if (!KSUCCESS(s)) {
DbgPrint("Failed to create init process. System halted.\n");
_disable(); while(1) __halt();
DbgPrint("Failed to create process. System halted.\n");
return (PROCESS*)0;
}
DbgPrint("Created init process (PID %u) with command line: %s\n", proc->ProcessId, bootInfo->CommandLine);
DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, filePath);
// Parse the raw PE file and map it into executable memory
uintptr_t entryPoint = 0;
s = LdrLoadImage(mem, size, &entryPoint, true);
uintptr_t imageBase = 0;
s = LdrLoadImage(mem, size, &imageBase, true);
if (!KSUCCESS(s)) {
DbgPrint("KernelMain: LdrLoadImage failed with status: %d\n", s);
_disable(); while(1) __halt();
}
DbgPrint("KernelMain: PE Image mapped! Entry point at 0x%llX\n", (void*)entryPoint);
uintptr_t entryPoint = LdrGetEntryPoint(imageBase);
DbgPrint("KernelMain: PE Image mapped! Base 0x%llx, Entry 0x%llX\n", imageBase, entryPoint);
// Create the main thread, pointing it to the PE Entry Point
s = KeCreateThread(proc, (uintptr_t)entryPoint, 0, PriorityNormal, &thread);
if (!KSUCCESS(s)) {
DbgPrint("KernelMain: Failed to create init thread. Status: %d\n", s);
_disable(); while(1) __halt();
DbgPrint("KernelMain: Failed to create thread. Status: %d\n", s);
}
DbgPrint("KernelMain: Thread created and queued!\n");
// Caller owns the reference returned by KeCreateProcess.
return proc;
}
// ---------------------------------------------------------------------------
// Kernel thread: wait for a process to exit, then log a message and shut down.
// Takes ownership of the PROCESS* reference passed as arg; releases it on exit.
// ---------------------------------------------------------------------------
static void ProcessWatcherThread(void* arg) {
PROCESS* proc = (PROCESS*)arg;
KeWaitForEvent(proc->ExitEvent);
DbgPrint("KernelMain: Process \"%s\" (PID %u) exited with code %d.\n",
proc->Name, proc->ProcessId, proc->ExitCode);
ObDereferenceObject(&proc->Header);
DbgPrint("KernelMain: Shutting down.\n");
gKmainHal->Power.Shutdown(); // does not return
}
static void PrintProcessList() {
@@ -360,6 +383,7 @@ extern void KernelMain(BOOT_INFO* bootInfo) {
KSTATUS s = HalInitialize(&halParams);
if (!KSUCCESS(s)) for (;;) __halt(); // there's nothing we can do.
const HAL_API* hal = HalGetApi();
gKmainHal = hal;
KeInitDebug(hal->Screen.PrintChar, bootInfo->DebugEnabled); // Enable debug
@@ -382,11 +406,12 @@ extern void KernelMain(BOOT_INFO* bootInfo) {
// Scheduler Prep & Status Update
//
KeInitializeScheduler();
// hal->Screen.Clear(0x000000);
hal->Screen.Clear(0x000000);
DbgPrint("KernelMain: Loading system DLLs...\n");
LoadAndRegisterDll("syscall.dll");
LoadAndRegisterDll("win32.dll");
LoadAndRegisterDll("bink2w64.dll");
// Now that they are in memory and registered, we link them together
// (e.g., win32.dll resolving its imports against syscall.dll)
@@ -395,7 +420,20 @@ extern void KernelMain(BOOT_INFO* bootInfo) {
//
// User-Mode Launch
//
LaunchInitProcess(bootInfo);
PROCESS* shellProc = LaunchUserProcess(bootInfo->CommandLine);
// Spawn a kernel thread that blocks on the process exit event and prints
// when it fires. The watcher takes ownership of the LaunchUserProcess
// reference so we do not hold a dangling pointer in KernelMain.
if (shellProc) {
KSTATUS ws = KeCreateKernelThread((uintptr_t)ProcessWatcherThread,
(uintptr_t)shellProc,
PriorityNormal, (THREAD**)0);
if (!KSUCCESS(ws)) {
DbgPrint("KernelMain: Failed to create watcher thread (%d) -- leaking proc ref.\n", ws);
}
// Ownership of shellProc transferred to watcher; do not use it below.
}
//
// Ignite the System
@@ -403,18 +441,12 @@ extern void KernelMain(BOOT_INFO* bootInfo) {
DbgPrint("KernelMain: Starting scheduler... Handing over control.\n");
_enable();
float freeMB = (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f);
float kernelMB = (float)MmGetKernelPoolUsage() / (1024.0f * 1024.0f);
float userMB = (float)MmGetUserHeapUsage() / (1024.0f * 1024.0f);
DbgPrint("Memory Status:\n");
DbgPrint(" -> Free (PMM): %.2f MB\n", freeMB);
DbgPrint(" -> Kernel Pool: %.2f MB\n", kernelMB);
DbgPrint(" -> User Heap: %.2f MB\n", userMB);
DbgPrint(" -> Free (PMM): %.2f MB\n", (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f));
DbgPrint(" -> Kernel Pool: %.2f MB\n", (float)MmGetKernelPoolUsage() / (1024.0f * 1024.0f));
DbgPrint(" -> User Heap: %.2f MB\n", (float)MmGetUserHeapUsage() / (1024.0f * 1024.0f));
while (1) {
//DbgPrint("KernelMain: Idle loop. Free memory: %.2f MB.\n",
//(float)(MmFreeFrameCount() * PAGE_SIZE) / (1024.0f * 1024.0f));
__halt(); // nothing to do
KeYield(); // nothing to do
}
}
+1 -1
View File
@@ -299,6 +299,6 @@ KSTATUS LdrLoadImage(const void* buffer, size_t size, uintptr_t* baseOut, bool u
KSTATUS status = LdrResolveImports((uintptr_t)mem);
if (status != STATUS_SUCCESS) return status;
*baseOut = (uintptr_t)mem + opt->AddressOfEntryPoint;
*baseOut = (uintptr_t)mem;
return STATUS_SUCCESS;
}
+108 -32
View File
@@ -1,37 +1,113 @@
#pragma once
// syscall_nr.h - system call numbers
// Shared between kernel (ke_syscall.c) and user mode (syscall.dll).
// Keep in sync with the dispatch table in ke_syscall.c.
// Keep in sync with gSyscallTable in ke_syscall.c and SYS_MAX below.
//
// Calling convention (INT 0x40, MS x64):
// RAX = syscall number
// RCX, RDX, R8, R9 = args 0-3
// R10, R11 = args 4-5 (loaded by SYSCALL_STUB6 in syscall_stubs.asm)
// Return value in RAX (KSTATUS or data).
//
// Handle semantics:
// Every kernel object (file, mutex, semaphore, event, queue) is reference-
// counted. SYS_CLOSE_HANDLE is the ONE way to close any handle regardless
// of the object type behind it. KeCloseHandle calls ObDereferenceObject,
// which fires the type-specific Destroy callback (e.g. FileDestroy)
// automatically when the refcount reaches zero. There is no separate
// SYS_CLOSE_FILE - that was redundant.
#define SYS_EXIT_THREAD 0
#define SYS_EXIT_PROCESS 1
#define SYS_SPAWN 2
#define SYS_SLEEP 3
#define SYS_YIELD 4
#define SYS_ALLOC 5
#define SYS_FREE 6
#define SYS_CREATE_THREAD 7
#define SYS_OPEN_HANDLE 8
#define SYS_CLOSE_HANDLE 9
#define SYS_DBG_PRINT 10
#define SYS_MUTEX_CREATE 11
#define SYS_MUTEX_ACQUIRE 12
#define SYS_MUTEX_RELEASE 13
#define SYS_SEM_CREATE 14
#define SYS_SEM_WAIT 15
#define SYS_SEM_SIGNAL 16
#define SYS_EVENT_CREATE 17
#define SYS_EVENT_SET 18
#define SYS_EVENT_WAIT 19
#define SYS_QUEUE_CREATE 20
#define SYS_QUEUE_SEND 21
#define SYS_QUEUE_RECV 22
#define SYS_SCREEN_BLIT 23
#define SYS_GET_TICK 24
#define SYS_OPEN 25
#define SYS_READ 26
#define SYS_FCLOSE 27
#define SYS_FSEEK 28
#define SYS_FSIZE 29
// ---------------------------------------------------------------------------
// Process & Thread
// ---------------------------------------------------------------------------
#define SYS_EXIT_PROCESS 0 // (int exitCode) -> void
#define SYS_EXIT_THREAD 1 // (int exitCode) -> void
#define SYS_CREATE_THREAD 2 // (uint64_t entry, uint64_t arg, uint32_t priority, uint32_t* tidOut) -> KSTATUS
#define SYS_GET_CURRENT_TID 3 // () -> uint32_t
#define SYS_GET_CURRENT_PID 4 // () -> uint32_t
#define SYS_YIELD 5 // () -> void
#define SYS_SLEEP 6 // (uint32_t ms) -> void
#define SYS_SET_THREAD_PRIORITY 7 // (uint32_t priority) -> KSTATUS
#define SYS_MAX 30
// ---------------------------------------------------------------------------
// Memory
// ---------------------------------------------------------------------------
#define SYS_ALLOC 8 // (size_t size) -> void* (NULL on failure)
#define SYS_FREE 9 // (void* ptr, size_t size) -> void
#define SYS_REALLOC 10 // (void* ptr, size_t newSize) -> void*
// ---------------------------------------------------------------------------
// Synchronisation Objects
// ---------------------------------------------------------------------------
#define SYS_MUTEX_CREATE 11 // (uint32_t* handleOut) -> KSTATUS
#define SYS_MUTEX_ACQUIRE 12 // (uint32_t handle) -> KSTATUS
#define SYS_MUTEX_TRY_ACQUIRE 13 // (uint32_t handle) -> KSTATUS
#define SYS_MUTEX_RELEASE 14 // (uint32_t handle) -> KSTATUS
#define SYS_SEM_CREATE 15 // (int32_t initial, int32_t max, uint32_t* handleOut) -> KSTATUS
#define SYS_SEM_WAIT 16 // (uint32_t handle) -> KSTATUS
#define SYS_SEM_TRY_WAIT 17 // (uint32_t handle) -> KSTATUS
#define SYS_SEM_SIGNAL 18 // (uint32_t handle) -> void
#define SYS_EVENT_CREATE 19 // (uint32_t type, uint32_t initialState, uint32_t* handleOut) -> KSTATUS
#define SYS_EVENT_SIGNAL 20 // (uint32_t handle) -> void
#define SYS_EVENT_CLEAR 21 // (uint32_t handle) -> void
#define SYS_EVENT_WAIT 22 // (uint32_t handle) -> KSTATUS
// ---------------------------------------------------------------------------
// Message Queues
// ---------------------------------------------------------------------------
#define SYS_QUEUE_CREATE 23 // (uint32_t* handleOut) -> KSTATUS
#define SYS_QUEUE_SEND 24 // (uint32_t handle, const QUEUE_MESSAGE* msg, uint32_t blocking) -> KSTATUS
#define SYS_QUEUE_RECV 25 // (uint32_t handle, QUEUE_MESSAGE* msg, uint32_t blocking) -> KSTATUS
// ---------------------------------------------------------------------------
// Handle Management
// ---------------------------------------------------------------------------
#define SYS_CLOSE_HANDLE 26 // (uint32_t handle) -> void
// ---------------------------------------------------------------------------
// File I/O
// (Close a file handle via SYS_CLOSE_HANDLE, same as any other object.)
// ---------------------------------------------------------------------------
#define SYS_OPEN_FILE 27 // (const char* path, uint32_t* handleOut) -> KSTATUS
#define SYS_READ_FILE 28 // (uint32_t handle, void* buf, size_t size, size_t* bytesReadOut) -> KSTATUS
#define SYS_SEEK_FILE 29 // (uint32_t handle, uint64_t offset) -> KSTATUS
#define SYS_GET_FILE_SIZE 30 // (uint32_t handle) -> uint64_t
// ---------------------------------------------------------------------------
// Screen / Graphics
// ---------------------------------------------------------------------------
#define SYS_SCREEN_BLIT 31 // (int x, int y, int w, int h, const uint32_t* pixels) -> void
#define SYS_SCREEN_CLEAR 32 // (uint32_t color) -> void
#define SYS_SCREEN_GET_SIZE 33 // (int* widthOut, int* heightOut) -> void
#define SYS_SCREEN_PUT_CHAR 34 // (char c, int x, int y, uint32_t fg, uint32_t bg) -> void
// ---------------------------------------------------------------------------
// Keyboard
// ---------------------------------------------------------------------------
#define SYS_KB_POLL 35 // () -> uint32_t (1 if key available, 0 otherwise)
#define SYS_KB_READ_CHAR 36 // () -> char (blocks, yields while waiting)
#define SYS_KB_READ_SCANCODE 37 // () -> uint8_t (blocks, yields while waiting)
// ---------------------------------------------------------------------------
// Timer
// ---------------------------------------------------------------------------
#define SYS_GET_TICK 38 // () -> uint64_t (milliseconds since boot)
// ---------------------------------------------------------------------------
// Debug
// ---------------------------------------------------------------------------
#define SYS_DEBUG_PRINT 39 // (const char* str) -> void
// ---------------------------------------------------------------------------
// Process launch & lifecycle
// ---------------------------------------------------------------------------
#define SYS_CREATE_PROCESS 40 // (const char* path, uint32_t* handleOut) -> KSTATUS
#define SYS_WAIT_FOR_PROCESS 41 // (uint32_t handle) -> KSTATUS
#define SYS_GET_PROCESS_EXIT_CODE 42 // (uint32_t handle, int* exitCodeOut) -> KSTATUS
// ---------------------------------------------------------------------------
// Sentinel
// ---------------------------------------------------------------------------
#define SYS_MAX 43
+159 -29
View File
@@ -1,47 +1,177 @@
#include <stdint.h>
#include <stddef.h>
#include <string.h>
void BitBlt(int x, int y, int w, int h, const uint32_t* pixels);
void* HeapAlloc(void* heap, unsigned long flags, size_t size);
int HeapFree(void* heap, unsigned long flags, void* mem);
// ---------------------------------------------------------------------------
// Win32 imports
// ---------------------------------------------------------------------------
_Noreturn void ExitProcess(unsigned int code);
_Noreturn void ExitThread(unsigned long code);
void OutputDebugStringA(const char* s);
int main() {
OutputDebugStringA("Hello from the shell!\n");
int width = 300;
int height = 300;
typedef void* HANDLE;
typedef unsigned long DWORD;
typedef int BOOL;
#define INVALID_HANDLE_VALUE ((HANDLE)0)
#define INFINITE 0xFFFFFFFFUL
#define STD_INPUT_HANDLE ((DWORD)-10)
// Allocate space for uint32_t (4 bytes per pixel)
uint32_t* image = (uint32_t *)HeapAlloc(NULL, 0, width * height * sizeof(uint32_t));
int tileSize = 8;
HANDLE GetStdHandle(DWORD nStdHandle);
BOOL ReadConsoleA(HANDLE hConsoleInput, void* lpBuffer,
DWORD nNumberOfCharsToRead, DWORD* lpNumberOfCharsRead,
void* pInputControl);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint32_t color1 = 0xFFFFFFFF; // White
uint32_t color2 = 0xFF000000; // Black
typedef struct {
DWORD cb;
char* lpReserved;
char* lpDesktop;
char* lpTitle;
DWORD dwX, dwY, dwXSize, dwYSize;
DWORD dwXCountChars, dwYCountChars;
DWORD dwFillAttribute, dwFlags;
unsigned short wShowWindow, cbReserved2;
unsigned char* lpReserved2;
HANDLE hStdInput, hStdOutput, hStdError;
} STARTUPINFOA;
int tileX = x / tileSize;
int tileY = y / tileSize;
typedef struct {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
if ((tileX + tileY) % 2 == 0) {
image[y * width + x] = color1;
} else {
image[y * width + x] = color2;
}
BOOL CreateProcessA(const char* lpApp, char* lpCmdLine,
void* lpPA, void* lpTA, BOOL bInherit,
DWORD dwFlags, void* lpEnv, const char* lpDir,
STARTUPINFOA* lpSI, PROCESS_INFORMATION* lpPI);
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMs);
BOOL GetExitCodeProcess(HANDLE hProcess, DWORD* lpExitCode);
BOOL CloseHandle(HANDLE hObject);
// ---------------------------------------------------------------------------
// Output helpers
// ---------------------------------------------------------------------------
static void print(const char* s) {
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; }
char rev[12];
int j = 0;
while (i > 0) rev[j++] = buf[--i];
rev[j] = '\0';
print(rev);
}
// ---------------------------------------------------------------------------
// Line editor
// Reads one character at a time via ReadConsoleA, echoes it, handles
// backspace, returns on Enter.
// ---------------------------------------------------------------------------
#define MAX_CMD 256
static void readLine(HANDLE hIn, char* buf, int len) {
int pos = 0;
while (1) {
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';
}
// Pass the 32-bit buffer directly to BitBlt
BitBlt(600, 400, width, height, image);
// ---------------------------------------------------------------------------
// Trim leading/trailing spaces in-place; returns pointer into buf.
// ---------------------------------------------------------------------------
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;
}
// for (size_t i = 0; i < 1000000000; i++) {
// // burn!
// }
// ---------------------------------------------------------------------------
// Run one command. Returns 1 if the shell should exit.
// ---------------------------------------------------------------------------
static int runCommand(const char* cmd) {
if (!cmd || *cmd == '\0')
return 0;
OutputDebugStringA("Goodbye from the shell!\n");
if (strcmp(cmd, "exit") == 0)
return 1;
STARTUPINFOA si;
PROCESS_INFORMATION pi;
unsigned char* p = (unsigned char*)&si;
for (size_t i = 0; i < sizeof(si); i++) p[i] = 0;
si.cb = (DWORD)sizeof(si);
if (!CreateProcessA(cmd, 0, 0, 0, 0, 0, 0, 0, &si, &pi)) {
print("shell: could not launch '");
print(cmd);
print("'\n");
return 0;
}
WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exitCode = 0;
GetExitCodeProcess(pi.hProcess, &exitCode);
CloseHandle(pi.hProcess);
if (exitCode != 0) {
print("shell: '");
print(cmd);
print("' exited with code ");
printUInt(exitCode);
print("\n");
}
return 0;
}
// ---------------------------------------------------------------------------
// Entry point
// ---------------------------------------------------------------------------
int main() {
HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
char lineBuf[MAX_CMD];
print("shell: ready\n");
while (1) {
print("> ");
readLine(hIn, lineBuf, MAX_CMD);
if (runCommand(trim(lineBuf)))
break;
}
print("shell: goodbye\n");
ExitProcess(0);
}
+73 -31
View File
@@ -1,32 +1,74 @@
; syscall_nr.inc - assembled from syscall_nr.h for use in .asm files
SYS_EXIT_THREAD EQU 0
SYS_EXIT_PROCESS EQU 1
SYS_SPAWN EQU 2
SYS_SLEEP EQU 3
SYS_YIELD EQU 4
SYS_ALLOC EQU 5
SYS_FREE EQU 6
SYS_CREATE_THREAD EQU 7
SYS_OPEN_HANDLE EQU 8
SYS_CLOSE_HANDLE EQU 9
SYS_DBG_PRINT EQU 10
SYS_MUTEX_CREATE EQU 11
SYS_MUTEX_ACQUIRE EQU 12
SYS_MUTEX_RELEASE EQU 13
SYS_SEM_CREATE EQU 14
SYS_SEM_WAIT EQU 15
SYS_SEM_SIGNAL EQU 16
SYS_EVENT_CREATE EQU 17
SYS_EVENT_SET EQU 18
SYS_EVENT_WAIT EQU 19
SYS_QUEUE_CREATE EQU 20
SYS_QUEUE_SEND EQU 21
SYS_QUEUE_RECV EQU 22
SYS_SCREEN_BLIT EQU 23
SYS_GET_TICK EQU 24
SYS_OPEN EQU 25
SYS_READ EQU 26
SYS_FCLOSE EQU 27
SYS_FSEEK EQU 28
SYS_FSIZE EQU 29
SYS_MAX EQU 30
; Keep in sync with syscall_nr.h and ke_syscall.c.
; Process & Thread
SYS_EXIT_PROCESS EQU 0
SYS_EXIT_THREAD EQU 1
SYS_CREATE_THREAD EQU 2
SYS_GET_CURRENT_TID EQU 3
SYS_GET_CURRENT_PID EQU 4
SYS_YIELD EQU 5
SYS_SLEEP EQU 6
SYS_SET_THREAD_PRIORITY EQU 7
; Memory
SYS_ALLOC EQU 8
SYS_FREE EQU 9
SYS_REALLOC EQU 10
; Mutex
SYS_MUTEX_CREATE EQU 11
SYS_MUTEX_ACQUIRE EQU 12
SYS_MUTEX_TRY_ACQUIRE EQU 13
SYS_MUTEX_RELEASE EQU 14
; Semaphore
SYS_SEM_CREATE EQU 15
SYS_SEM_WAIT EQU 16
SYS_SEM_TRY_WAIT EQU 17
SYS_SEM_SIGNAL EQU 18
; Event
SYS_EVENT_CREATE EQU 19
SYS_EVENT_SIGNAL EQU 20
SYS_EVENT_CLEAR EQU 21
SYS_EVENT_WAIT EQU 22
; Queue
SYS_QUEUE_CREATE EQU 23
SYS_QUEUE_SEND EQU 24
SYS_QUEUE_RECV EQU 25
; Handle (unified - one close for every object type)
SYS_CLOSE_HANDLE EQU 26
; File I/O
SYS_OPEN_FILE EQU 27
SYS_READ_FILE EQU 28
SYS_SEEK_FILE EQU 29
SYS_GET_FILE_SIZE EQU 30
; Screen
SYS_SCREEN_BLIT EQU 31
SYS_SCREEN_CLEAR EQU 32
SYS_SCREEN_GET_SIZE EQU 33
SYS_SCREEN_PUT_CHAR EQU 34
; Keyboard
SYS_KB_POLL EQU 35
SYS_KB_READ_CHAR EQU 36
SYS_KB_READ_SCANCODE EQU 37
; Timer
SYS_GET_TICK EQU 38
; Debug
SYS_DEBUG_PRINT EQU 39
; Process launch & lifecycle
SYS_CREATE_PROCESS EQU 40
SYS_WAIT_FOR_PROCESS EQU 41
SYS_GET_PROCESS_EXIT_CODE EQU 42
; Sentinel
SYS_MAX EQU 43
+125 -158
View File
@@ -1,180 +1,147 @@
; syscall_stubs.asm - user-mode INT 0x2E stubs exported by syscall.dll (MASM64)
; Microsoft x64 ABI: args in RCX, RDX, R8, R9; args 5+6 in R10, R11.
; syscall_stubs.asm - user-mode INT 0x40 stubs exported by syscall.dll (MASM64)
;
; Calling convention (kernel side):
; RAX = syscall number
; RCX = arg 0
; RDX = arg 1
; R8 = arg 2
; R9 = arg 3
; R10 = arg 4 (our extension - loaded from the stack by SYSCALL_STUB6)
; R11 = arg 5 (our extension - loaded from the stack by SYSCALL_STUB6)
;
; User-mode callers follow standard MS x64: args 1-4 in RCX/RDX/R8/R9,
; args 5+ on the stack above the home area ([rsp+28h], [rsp+30h], ...).
;
; SYSCALL_STUB2 - 4 or fewer args: pure register passing, no stack read.
; SYSCALL_STUB6 - 5 or 6 args: loads [rsp+28h] -> R10 and [rsp+30h] -> R11.
;
; Handle lifecycle:
; SysCloseHandle is the single close stub for every object type.
; There is no SysCloseFile - files are closed through the unified path,
; exactly like mutexes, semaphores, events, and queues.
INCLUDE syscall_nr.inc ; SYS_* constants
INCLUDE syscall_nr.inc
.CODE
SysExitThread PROC
mov rax, SYS_EXIT_THREAD
int 40h
; ---------------------------------------------------------------------------
SYSCALL_STUB2 MACRO name, nr
PUBLIC Sys&name
Sys&name PROC EXPORT
mov rax, nr
int 40h
ret
SysExitThread ENDP
Sys&name ENDP
ENDM
SysExitProcess PROC
mov rax, SYS_EXIT_PROCESS
int 40h
; ---------------------------------------------------------------------------
SYSCALL_STUB6 MACRO name, nr
PUBLIC Sys&name
Sys&name PROC EXPORT
mov r10, [rsp+28h] ; 5th argument
mov r11, [rsp+30h] ; 6th argument
mov rax, nr
int 40h
ret
SysExitProcess ENDP
Sys&name ENDP
ENDM
SysYield PROC
mov rax, SYS_YIELD
int 40h
ret
SysYield ENDP
; ---------------------------------------------------------------------------
; Process & Thread
; ---------------------------------------------------------------------------
SYSCALL_STUB2 ExitProcess, SYS_EXIT_PROCESS
SYSCALL_STUB2 ExitThread, SYS_EXIT_THREAD
SYSCALL_STUB2 CreateThread, SYS_CREATE_THREAD
SYSCALL_STUB2 GetCurrentTid, SYS_GET_CURRENT_TID
SYSCALL_STUB2 GetCurrentPid, SYS_GET_CURRENT_PID
SYSCALL_STUB2 Yield, SYS_YIELD
SYSCALL_STUB2 Sleep, SYS_SLEEP
SYSCALL_STUB2 SetThreadPriority, SYS_SET_THREAD_PRIORITY
SysSleep PROC ; RCX = milliseconds
mov rax, SYS_SLEEP
int 40h
ret
SysSleep ENDP
; ---------------------------------------------------------------------------
; Memory
; ---------------------------------------------------------------------------
SYSCALL_STUB2 Alloc, SYS_ALLOC
SYSCALL_STUB2 Free, SYS_FREE
SYSCALL_STUB2 Realloc, SYS_REALLOC
SysAlloc PROC ; RCX = size -> RAX = ptr
mov rax, SYS_ALLOC
int 40h
ret
SysAlloc ENDP
; ---------------------------------------------------------------------------
; Mutex
; ---------------------------------------------------------------------------
SYSCALL_STUB2 MutexCreate, SYS_MUTEX_CREATE
SYSCALL_STUB2 MutexAcquire, SYS_MUTEX_ACQUIRE
SYSCALL_STUB2 MutexTryAcquire, SYS_MUTEX_TRY_ACQUIRE
SYSCALL_STUB2 MutexRelease, SYS_MUTEX_RELEASE
SysFree PROC ; RCX = ptr, RDX = size
mov rax, SYS_FREE
int 40h
ret
SysFree ENDP
; ---------------------------------------------------------------------------
; Semaphore
; ---------------------------------------------------------------------------
SYSCALL_STUB2 SemCreate, SYS_SEM_CREATE
SYSCALL_STUB2 SemWait, SYS_SEM_WAIT
SYSCALL_STUB2 SemTryWait, SYS_SEM_TRY_WAIT
SYSCALL_STUB2 SemSignal, SYS_SEM_SIGNAL
SysCreateThread PROC ; RCX=entry, RDX=arg, R8=priority -> RAX=handle
mov rax, SYS_CREATE_THREAD
int 40h
ret
SysCreateThread ENDP
; ---------------------------------------------------------------------------
; Event
; ---------------------------------------------------------------------------
SYSCALL_STUB2 EventCreate, SYS_EVENT_CREATE
SYSCALL_STUB2 EventSignal, SYS_EVENT_SIGNAL
SYSCALL_STUB2 EventClear, SYS_EVENT_CLEAR
SYSCALL_STUB2 EventWait, SYS_EVENT_WAIT
SysCloseHandle PROC ; RCX = handle
mov rax, SYS_CLOSE_HANDLE
int 40h
ret
SysCloseHandle ENDP
; ---------------------------------------------------------------------------
; Queue
; ---------------------------------------------------------------------------
SYSCALL_STUB2 QueueCreate, SYS_QUEUE_CREATE
SYSCALL_STUB2 QueueSend, SYS_QUEUE_SEND
SYSCALL_STUB2 QueueRecv, SYS_QUEUE_RECV
SysDbgPrint PROC ; RCX = string ptr
mov rax, SYS_DBG_PRINT
int 40h
ret
SysDbgPrint ENDP
; ---------------------------------------------------------------------------
; Handle (unified - use for files, mutexes, semaphores, events, queues)
; ---------------------------------------------------------------------------
SYSCALL_STUB2 CloseHandle, SYS_CLOSE_HANDLE
SysMutexCreate PROC ; -> RAX = handle
mov rax, SYS_MUTEX_CREATE
int 40h
ret
SysMutexCreate ENDP
; ---------------------------------------------------------------------------
; File I/O
; (To close: SysCloseHandle(handle) - no dedicated SysCloseFile)
; ---------------------------------------------------------------------------
SYSCALL_STUB2 OpenFile, SYS_OPEN_FILE
SYSCALL_STUB2 ReadFile, SYS_READ_FILE
SYSCALL_STUB2 SeekFile, SYS_SEEK_FILE
SYSCALL_STUB2 GetFileSize, SYS_GET_FILE_SIZE
SysMutexAcquire PROC ; RCX = handle
mov rax, SYS_MUTEX_ACQUIRE
int 40h
ret
SysMutexAcquire ENDP
; ---------------------------------------------------------------------------
; Screen
; SysScreenBlit has 5 args (x, y, w, h, pixels) - needs SYSCALL_STUB6.
; SysScreenPutChar has 5 args (c, x, y, fg, bg) - needs SYSCALL_STUB6.
; ---------------------------------------------------------------------------
SYSCALL_STUB6 ScreenBlit, SYS_SCREEN_BLIT
SYSCALL_STUB2 ScreenClear, SYS_SCREEN_CLEAR
SYSCALL_STUB2 ScreenGetSize, SYS_SCREEN_GET_SIZE
SYSCALL_STUB6 ScreenPutChar, SYS_SCREEN_PUT_CHAR
SysMutexRelease PROC ; RCX = handle
mov rax, SYS_MUTEX_RELEASE
int 40h
ret
SysMutexRelease ENDP
; ---------------------------------------------------------------------------
; Keyboard
; ---------------------------------------------------------------------------
SYSCALL_STUB2 KbPoll, SYS_KB_POLL
SYSCALL_STUB2 KbReadChar, SYS_KB_READ_CHAR
SYSCALL_STUB2 KbReadScancode, SYS_KB_READ_SCANCODE
SysSemCreate PROC ; RCX=initial, RDX=max -> RAX=handle
mov rax, SYS_SEM_CREATE
int 40h
ret
SysSemCreate ENDP
; ---------------------------------------------------------------------------
; Timer
; ---------------------------------------------------------------------------
SYSCALL_STUB2 GetTick, SYS_GET_TICK
SysSemWait PROC ; RCX = handle
mov rax, SYS_SEM_WAIT
int 40h
ret
SysSemWait ENDP
; ---------------------------------------------------------------------------
; Debug
; ---------------------------------------------------------------------------
SYSCALL_STUB2 DebugPrint, SYS_DEBUG_PRINT
SysSemSignal PROC ; RCX = handle
mov rax, SYS_SEM_SIGNAL
int 40h
ret
SysSemSignal ENDP
SysEventCreate PROC ; RCX=manualReset, RDX=initialState -> RAX=handle
mov rax, SYS_EVENT_CREATE
int 40h
ret
SysEventCreate ENDP
SysEventSet PROC ; RCX = handle
mov rax, SYS_EVENT_SET
int 40h
ret
SysEventSet ENDP
SysEventWait PROC ; RCX = handle
mov rax, SYS_EVENT_WAIT
int 40h
ret
SysEventWait ENDP
SysQueueCreate PROC ; -> RAX = handle
mov rax, SYS_QUEUE_CREATE
int 40h
ret
SysQueueCreate ENDP
SysQueueSend PROC ; RCX=handle, RDX=*msg, R8=blocking
mov rax, SYS_QUEUE_SEND
int 40h
ret
SysQueueSend ENDP
SysQueueRecv PROC ; RCX=handle, RDX=*msg, R8=blocking
mov rax, SYS_QUEUE_RECV
int 40h
ret
SysQueueRecv ENDP
SysScreenBlit PROC
; RCX=x, RDX=y, R8=w, R9=h
; The 5th arg (pixels) is on the stack above the 32-byte shadow space and return address.
mov r10, [rsp + 40]
mov rax, SYS_SCREEN_BLIT
int 40h
ret
SysScreenBlit ENDP
SysGetTick PROC ; -> RAX = milliseconds
mov rax, SYS_GET_TICK
int 40h
ret
SysGetTick ENDP
SysOpen PROC ; RCX = *path -> RAX = handle (0 = failure)
mov rax, SYS_OPEN
int 40h
ret
SysOpen ENDP
SysRead PROC ; RCX = handle, RDX = *buf, R8 = size -> RAX = bytes read
mov rax, SYS_READ
int 40h
ret
SysRead ENDP
SysFClose PROC ; RCX = handle
mov rax, SYS_FCLOSE
int 40h
ret
SysFClose ENDP
SysFSeek PROC ; RCX = handle, RDX = offset
mov rax, SYS_FSEEK
int 40h
ret
SysFSeek ENDP
SysFSize PROC ; RCX = handle -> RAX = size in bytes
mov rax, SYS_FSIZE
int 40h
ret
SysFSize ENDP
; ---------------------------------------------------------------------------
; Process launch & lifecycle
; ---------------------------------------------------------------------------
SYSCALL_STUB2 CreateProcess, SYS_CREATE_PROCESS
SYSCALL_STUB2 WaitForProcess, SYS_WAIT_FOR_PROCESS
SYSCALL_STUB2 GetProcessExitCode, SYS_GET_PROCESS_EXIT_CODE
END
+1075
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.
+2322
View File
File diff suppressed because it is too large. Load diff
+86
View File
@@ -0,0 +1,86 @@
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <intrin.h>
#include "bink.h"
void OutputDebugStringA(const char* s);
_Noreturn void ExitProcess(unsigned int code);
void* HeapAlloc(void* heap, unsigned long flags, size_t size);
void BitBlt(int x, int y, int w, int h, const uint32_t* pixels);
void* CreateFileA(const char* name, unsigned long access,
unsigned long share, void* sa,
unsigned long disp, unsigned long flags,
void* templ);
unsigned long GetFileSize(void* handle, unsigned long* high);
int ReadFile(void* handle, void* buf, unsigned long toRead,
unsigned long* read, void* overlapped);
int main() {
OutputDebugStringA("video: starting\n");
// void* f = CreateFileA("video.bk2", 0, 0, 0, 3, 0, 0);
// if (!f) {
// OutputDebugStringA("video: failed to open video.bk2\n");
// ExitProcess(1);
// }
// unsigned long s = GetFileSize(f, 0);
// void* buf = HeapAlloc(0, 0, s);
// if (!buf) {
// OutputDebugStringA("video: HeapAlloc failed\n");
// ExitProcess(1);
// }
// int r = ReadFile(f, buf, s, 0, 0);
// if (!r) {
// OutputDebugStringA("video: ReadFile failed\n");
// ExitProcess(1);
// }
// OutputDebugStringA("video: video.bk2 loaded\n");
// HBINK bink = BinkOpen(buf, BINKFROMMEMORY);
// if (!bink) {
// OutputDebugStringA("video: BinkOpen failed: ");
// OutputDebugStringA(BinkGetError());
// OutputDebugStringA("\n");
// ExitProcess(1);
// }
// uint32_t width = bink->Width;
// uint32_t height = bink->Height;
// uint32_t* fb = HeapAlloc(0, 0, width * height * sizeof(uint32_t));
// if (!fb) {
// OutputDebugStringA("video: framebuffer alloc failed\n");
// ExitProcess(1);
// }
// while (1) {
// if (BinkWait(bink)) {
// _mm_pause();
// continue;
// }
// BinkDoFrame(bink);
// if (BinkShouldSkip(bink)) {
// BinkNextFrame(bink);
// BinkDoFrame(bink);
// continue;
// }
// BinkCopyToBuffer(bink, fb, width * 4, height, 0, 0, BINKSURFACE32);
// BitBlt(0, 0, width, height, fb);
// if (bink->FrameNum == bink->Frames)
// break;
// BinkNextFrame(bink);
// }
// BinkClose(bink);
for (int i = 0; i < 10000000; i++) _mm_pause();
OutputDebugStringA("video: done\n");
ExitProcess(0);
}
+992 -91
View File
File diff suppressed because it is too large. Load diff
+81 -2
View File
@@ -1,2 +1,81 @@
// ole32.c - stub; no COM at this stage.
static void stub(void) { }
// ole32.c - Win32 COM / OLE stubs
// No COM infrastructure at this stage; all functions fail gracefully.
#include <stdint.h>
#include <stddef.h>
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
typedef long HRESULT;
typedef void* IUnknown;
#define S_OK ((HRESULT)0x00000000L)
#define E_NOTIMPL ((HRESULT)0x80004001L)
#define E_OUTOFMEMORY ((HRESULT)0x8007000EL)
#define COINIT_MULTITHREADED 0x0
// GUID - 16-byte COM interface identifier.
typedef struct {
unsigned int Data1;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[8];
} GUID;
// PROPVARIANT - variant type used by shell / media APIs.
typedef struct {
unsigned short vt;
unsigned short wReserved1, wReserved2, wReserved3;
union {
long long hVal;
double dblVal;
void* punkVal;
unsigned char bVal;
unsigned int ulVal;
// ... (only common fields; full union is ~16 bytes)
unsigned char padding[16];
};
} PROPVARIANT;
extern void SysFree(void* ptr, size_t size);
// ---------------------------------------------------------------------------
// COM initialisation
// ---------------------------------------------------------------------------
__declspec(dllexport) HRESULT CoInitializeEx(void* reserved, unsigned long coInit) {
(void)reserved; (void)coInit;
return S_OK; // Pretend success; no actual apartment is created.
}
__declspec(dllexport) void CoUninitialize(void) {
// No-op.
}
// ---------------------------------------------------------------------------
// Object creation
// ---------------------------------------------------------------------------
__declspec(dllexport) HRESULT CoCreateInstance(const GUID* clsid, IUnknown* outer,
unsigned long ctx, const GUID* iid,
void** ppv) {
(void)clsid; (void)outer; (void)ctx; (void)iid;
if (ppv) *ppv = (void*)0;
return E_NOTIMPL;
}
// ---------------------------------------------------------------------------
// Memory
// ---------------------------------------------------------------------------
__declspec(dllexport) void CoTaskMemFree(void* pv) {
if (pv) SysFree(pv, 0);
}
// ---------------------------------------------------------------------------
// PROPVARIANT
// ---------------------------------------------------------------------------
__declspec(dllexport) HRESULT PropVariantClear(PROPVARIANT* pvar) {
if (!pvar) return E_NOTIMPL;
// Zero the entire structure; no deep release needed for stub types.
unsigned char* p = (unsigned char*)pvar;
for (size_t i = 0; i < sizeof(*pvar); i++) p[i] = 0;
return S_OK;
}
+79 -8
View File
@@ -1,20 +1,91 @@
// user32.c - minimal user32 stubs (no window manager yet)
// user32.c - Win32 user interface stubs (no window manager yet)
#include <stdint.h>
#include <stddef.h>
// Screen blit is the only graphical primitive available at this stage.
// ---------------------------------------------------------------------------
// Syscall stubs (from syscall.dll)
// ---------------------------------------------------------------------------
extern void SysScreenBlit(int x, int y, int w, int h, const uint32_t* pixels);
// syscall.dll
extern void SysYield(void);
extern void SysDebugPrint(const char* str);
extern uint32_t SysKbPoll(void);
void BitBlt(int x, int y, int w, int h, const uint32_t* pixels) {
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
typedef void* HWND;
#define IDOK 1
#define IDCANCEL 2
// ---------------------------------------------------------------------------
// Keyboard
// ---------------------------------------------------------------------------
// GetAsyncKeyState: returns non-zero if a key is available in the input
// buffer. We have no per-key state, so the vKey argument is ignored and
// the high bit (key-down) is set whenever SysKbPoll reports data waiting.
__declspec(dllexport) short GetAsyncKeyState(int vKey) {
(void)vKey;
// High bit (0x8000 / INT16_MIN) = key is down.
// Low bit = key pressed since last call; we have no per-key history to
// track, so it is left clear.
return SysKbPoll() ? (short)(-32768) : 0;
}
// ---------------------------------------------------------------------------
// Screen output
// ---------------------------------------------------------------------------
// BitBlt is the only graphical primitive available at this stage.
__declspec(dllexport) void BitBlt(int x, int y, int w, int h, const uint32_t* pixels) {
SysScreenBlit(x, y, w, h, pixels);
}
// Message loop stubs - always return 0.
int GetMessage(void* msg, void* hwnd, unsigned int min, unsigned int max) {
// ---------------------------------------------------------------------------
// Message loop (stubs - always return 0 / no messages)
// ---------------------------------------------------------------------------
__declspec(dllexport) int GetMessage(void* msg, HWND hwnd,
unsigned int min, unsigned int max) {
(void)msg; (void)hwnd; (void)min; (void)max;
SysYield();
return 0;
}
int DispatchMessage(const void* msg) { (void)msg; return 0; }
__declspec(dllexport) int DispatchMessage(const void* msg) { (void)msg; return 0; }
// ---------------------------------------------------------------------------
// Window queries (stubs - no WM exists yet, all return NULL / 0)
// ---------------------------------------------------------------------------
__declspec(dllexport) HWND GetDesktopWindow(void) { return (HWND)0; }
__declspec(dllexport) HWND GetActiveWindow(void) { return (HWND)0; }
__declspec(dllexport) HWND GetTopWindow(HWND hwnd){ (void)hwnd; return (HWND)0; }
__declspec(dllexport) HWND GetWindow(HWND hwnd, unsigned int cmd) {
(void)hwnd; (void)cmd; return (HWND)0;
}
__declspec(dllexport) long GetWindowLongA(HWND hwnd, int index) {
(void)hwnd; (void)index; return 0;
}
__declspec(dllexport) intptr_t GetWindowLongPtrA(HWND hwnd, int index) {
(void)hwnd; (void)index; return 0;
}
__declspec(dllexport) unsigned long GetWindowThreadProcessId(HWND hwnd, unsigned long* pid) {
(void)hwnd;
if (pid) *pid = 1;
return 1; // Thread ID
}
// ---------------------------------------------------------------------------
// Dialogs
// ---------------------------------------------------------------------------
__declspec(dllexport) int MessageBoxA(HWND hwnd, const char* text,
const char* caption, unsigned int type) {
(void)hwnd; (void)text; (void)caption; (void)type;
// No UI available, console
SysDebugPrint(text);
return IDOK;
}
+128 -2
View File
@@ -1,2 +1,128 @@
// winmm.c - stub; no multimedia at this stage.
static void stub(void) { }
// winmm.c - Win32 multimedia: waveform audio, timer
// Stubs only; no audio hardware access at this stage.
#include <stdint.h>
#include <stddef.h>
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
typedef unsigned int MMRESULT;
typedef void* HWAVEOUT;
#define MMSYSERR_NOERROR 0
#define MMSYSERR_NOTSUPPORTED 8
#define WAVERR_STILLPLAYING 33
// WAVEFORMATEX - standard PCM audio format descriptor.
typedef struct {
unsigned short wFormatTag;
unsigned short nChannels;
unsigned int nSamplesPerSec;
unsigned int nAvgBytesPerSec;
unsigned short nBlockAlign;
unsigned short wBitsPerSample;
unsigned short cbSize;
} WAVEFORMATEX;
// WAVEHDR - buffer header submitted to waveOutWrite.
typedef struct _WAVEHDR {
char* lpData;
unsigned int dwBufferLength;
unsigned int dwBytesRecorded;
uintptr_t dwUser;
unsigned int dwFlags;
unsigned int dwLoops;
struct _WAVEHDR* lpNext;
uintptr_t reserved;
} WAVEHDR;
// MMTIME - used by waveOutGetPosition.
typedef struct {
unsigned int wType;
union {
unsigned int ms;
unsigned int sample;
unsigned int cb;
unsigned int ticks;
} u;
} MMTIME;
// ---------------------------------------------------------------------------
// Timer
// ---------------------------------------------------------------------------
// timeGetTime is the lightweight alternative to GetTickCount; both return
// milliseconds since system start. We forward to the kernel tick counter.
extern uint64_t SysGetTick(void);
__declspec(dllexport) unsigned long timeGetTime(void) {
return (unsigned long)SysGetTick();
}
// ---------------------------------------------------------------------------
// Waveform audio (stubs - no audio hardware at this stage)
// ---------------------------------------------------------------------------
__declspec(dllexport) MMRESULT waveOutOpen(HWAVEOUT* phwo, unsigned int deviceId,
const WAVEFORMATEX* fmt, uintptr_t callback,
uintptr_t callbackInstance, unsigned long flags) {
(void)deviceId; (void)fmt; (void)callback; (void)callbackInstance; (void)flags;
if (phwo) *phwo = (void*)0;
return MMSYSERR_NOTSUPPORTED;
}
__declspec(dllexport) MMRESULT waveOutClose(HWAVEOUT hwo) {
(void)hwo; return MMSYSERR_NOERROR;
}
__declspec(dllexport) MMRESULT waveOutPrepareHeader(HWAVEOUT hwo, WAVEHDR* hdr,
unsigned int size) {
(void)hwo; (void)hdr; (void)size; return MMSYSERR_NOERROR;
}
__declspec(dllexport) MMRESULT waveOutUnprepareHeader(HWAVEOUT hwo, WAVEHDR* hdr,
unsigned int size) {
(void)hwo; (void)hdr; (void)size; return MMSYSERR_NOERROR;
}
__declspec(dllexport) MMRESULT waveOutWrite(HWAVEOUT hwo, WAVEHDR* hdr, unsigned int size) {
(void)hwo; (void)hdr; (void)size; return MMSYSERR_NOTSUPPORTED;
}
__declspec(dllexport) MMRESULT waveOutPause(HWAVEOUT hwo) {
(void)hwo; return MMSYSERR_NOERROR;
}
__declspec(dllexport) MMRESULT waveOutRestart(HWAVEOUT hwo) {
(void)hwo; return MMSYSERR_NOERROR;
}
__declspec(dllexport) MMRESULT waveOutReset(HWAVEOUT hwo) {
(void)hwo; return MMSYSERR_NOERROR;
}
__declspec(dllexport) MMRESULT waveOutSetVolume(HWAVEOUT hwo, unsigned long volume) {
(void)hwo; (void)volume; return MMSYSERR_NOTSUPPORTED;
}
__declspec(dllexport) MMRESULT waveOutGetPosition(HWAVEOUT hwo, MMTIME* mmt,
unsigned int size) {
(void)hwo; (void)size;
if (mmt) { mmt->wType = 0; mmt->u.ms = 0; }
return MMSYSERR_NOERROR;
}
// ---------------------------------------------------------------------------
// Timer resolution control
// No hardware timer reprogramming at this stage; pretend success so callers
// that wrap their audio/game loop in timeBeginPeriod(1) don't bail out.
// ---------------------------------------------------------------------------
__declspec(dllexport) MMRESULT timeBeginPeriod(unsigned int uPeriod) {
(void)uPeriod;
return MMSYSERR_NOERROR;
}
__declspec(dllexport) MMRESULT timeEndPeriod(unsigned int uPeriod) {
(void)uPeriod;
return MMSYSERR_NOERROR;
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

+15 -11
View File
@@ -1,26 +1,26 @@
# an x86-64 kernel
# NT-Lite -- an x86-64 kernel
boots. runs processes. doesn't crash (usually).
Boots. Runs processes. Mimics Windows to some extent.
## what is it?
a 64-bit operating system kernel written from scratch.
A 64-bit operating system kernel written from scratch.
UEFI bootloader, physical and virtual memory managers, preemptive scheduler, HAL, VFS, PE loader, partial Win32 layer.
it boots into a shell.
~20k lines of code.
It boots into a shell.
~20k lines of C and assembly.
## features
- UEFI bootloader with PE loading and base relocation
- physical memory manager (frame stack, O(1) alloc/free)
- UEFI bootloader
- physical memory manager
- virtual memory manager with kernel pool
- HAL (screen, keyboard, timer, disk, PCI, APIC)
- preemptive scheduler with priority levels
- processes and threads
- VFS with FAT32 driver (read-only)
- VFS
- drive-letter paths (`0:\apps\shell.exe`)
- INI-based boot and driver configuration
- partial Win32 layer (kernel32 and user32, far from finished)
- partial Win32 layer (kernel32 and user32, far from finished but capable of running simple apps)
- PE import resolution and dynamic driver loading
## how 2 build
@@ -62,10 +62,14 @@ a filesystem driver fills in an `IO_FILESYSTEM` vtable and calls `IoRegisterFile
drives 0–7. a drive only works if a filesystem registered on it.
## screenshots
![shell.exe playing a video with bink 2](image.png)
## faq
Q: Why?
A: Why not.
A: Why not?
Q: Does it run Doom?
A: Not yet.
@@ -74,7 +78,7 @@ Q: Is the Win32 layer real?
A: Real enough to load binaries that expect it. Not real enough to run Notepad.
Q: Is the scheduler preemptive?
A: Yes.
A: As much as it will ever be.
Q: Will it corrupt my data?
A: How would it? It's read-only.