diff --git a/OVMF/OVMF_VARS_4M.fd b/OVMF/OVMF_VARS_4M.fd index 256d01a..71d485c 100644 Binary files a/OVMF/OVMF_VARS_4M.fd and b/OVMF/OVMF_VARS_4M.fd differ diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index c143d6e..cc7f78a 100644 Binary files a/OVMF/OVMF_VARS_4M_d.fd and b/OVMF/OVMF_VARS_4M_d.fd differ diff --git a/build.bat b/build.bat index ab74cad..25f3020 100644 --- a/build.bat +++ b/build.bat @@ -13,6 +13,7 @@ mkdir "obj\kernel" 2>nul mkdir "obj\hal" 2>nul mkdir "obj\syscall" 2>nul mkdir "obj\win32" 2>nul +mkdir "obj\posix" 2>nul mkdir "obj\drivers" 2>nul mkdir "obj\shell" 2>nul mkdir "obj\video" 2>nul @@ -80,6 +81,14 @@ for %%f in (%SRC%\win32\*.c) do ( link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL ^ /OUT:"bin\win32.dll" bin\syscall.lib obj\libc.obj obj\win32\*.obj || exit /b 1 +:: ---- Compile posix.dll ---- +echo Compiling posix.dll... +for %%f in (%SRC%\posix\*.c) do ( + cl %CFLAGS% /c /Fo"obj\posix\%%~nf.obj" "%%f" || exit /b 1 +) +link /nologo /SUBSYSTEM:NATIVE /NODEFAULTLIB /DLL ^ + /OUT:"bin\posix.dll" bin\syscall.lib obj\libc.obj obj\posix\*.obj || exit /b 1 + :: ---- Compile drivers ---- echo Compiling drivers... cl %CFLAGS% /I"%SRC%\hal" /c /Fo"obj\drivers\ide.obj" "%SRC%\drivers\ide\ide.c" || exit /b 1 @@ -129,6 +138,7 @@ copy /y "bin\kernel.exe" "%DEST%\colonel.exe" copy /y "bin\hal.dll" "%DEST%\" copy /y "bin\syscall.dll" "%DEST%\" copy /y "bin\win32.dll" "%DEST%\" +copy /y "bin\posix.dll" "%DEST%\" copy /y "bin\ide.sys" "%DEST%\drivers\" copy /y "bin\ahci.sys" "%DEST%\drivers\" copy /y "bin\fat32.sys" "%DEST%\drivers\" diff --git a/code/hal/hal_screen.c b/code/hal/hal_screen.c index 9dcd609..2a44be6 100644 --- a/code/hal/hal_screen.c +++ b/code/hal/hal_screen.c @@ -149,29 +149,37 @@ static void ScreenPrintChar(char c, uint32_t fg, uint32_t bg) { // 0 = transparent 1 = black outline 2 = white fill // The hotspot is at (0,0) (top-left pixel = tip of the arrow). // --------------------------------------------------------------------------- -#define CURSOR_W 12 -#define CURSOR_H 19 +#define CURSOR_W 16 +#define CURSOR_H 25 -static const uint8_t gCursorBitmap[CURSOR_H][CURSOR_W] = { - {1,0,0,0,0,0,0,0,0,0,0,0}, - {1,1,0,0,0,0,0,0,0,0,0,0}, - {1,2,1,0,0,0,0,0,0,0,0,0}, - {1,2,2,1,0,0,0,0,0,0,0,0}, - {1,2,2,2,1,0,0,0,0,0,0,0}, - {1,2,2,2,2,1,0,0,0,0,0,0}, - {1,2,2,2,2,2,1,0,0,0,0,0}, - {1,2,2,2,2,2,2,1,0,0,0,0}, - {1,2,2,2,2,2,2,2,1,0,0,0}, - {1,2,2,2,2,2,2,2,2,1,0,0}, - {1,2,2,2,2,2,2,2,2,2,1,0}, - {1,2,2,2,2,2,1,1,1,1,1,1}, - {1,2,2,2,1,2,2,1,0,0,0,0}, - {1,2,2,1,0,1,2,2,1,0,0,0}, - {1,2,1,0,0,0,1,2,2,1,0,0}, - {1,1,0,0,0,0,0,1,2,2,1,0}, - {1,0,0,0,0,0,0,0,1,2,1,0}, - {0,0,0,0,0,0,0,0,0,1,1,0}, - {0,0,0,0,0,0,0,0,0,0,0,0}, +// Each entry is 0xAARRGGBB stored as a uint32_t. +// Pixels with brightness < 24 are fully transparent. +static const unsigned int gCursorPixels[CURSOR_H][CURSOR_W] = { + { 0x7F575757, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFAFAFA, 0x855D5D5D, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFEFEFE, 0xFFFFFFFF, 0x865E5E5E, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFDFDFD, 0xFFFDFDFD, 0xFFFFFFFF, 0x8A626262, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFDFDFD, 0xFFFFFFFF, 0x8D656565, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFDFDFD, 0xFFFFFFFF, 0x8E666666, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFCFCFC, 0xFFFFFFFF, 0x91696969, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFDFDFD, 0xFFFFFFFF, 0x946C6C6C, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFCFCFC, 0xFFFFFFFF, 0x956D6D6D, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFCFCFC, 0xFFFFFFFF, 0x99717171, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFCFCFC, 0xFFFFFFFF, 0x9B737373, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFCFCFC, 0xFFFFFFFF, 0x9D757575, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFCFCFC, 0xFFFFFFFF, 0xA0787878, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFDFDFD, 0xFFFDFDFD, 0xFFFCFCFC, 0xFFF9F9F9, 0xFFFFFFFF, 0xA1797979, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFEFEFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xAE868686, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFFFFFF, 0xFDD5D5D5, 0xF4CCCCCC, 0xF4CCCCCC, 0xF4CCCCCC, 0xF3CBCBCB, 0xF1C9C9C9, 0xFAD2D2D2, 0x7E565656 }, + { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFEFEFE, 0xFFFCFCFC, 0xFFEFEFEF, 0xFFFCFCFC, 0xFFFFFFFF, 0x9A727272, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0xFFFEFEFE, 0xFFFDFDFD, 0xFFFFFFFF, 0x7F575757, 0x8F676767, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE0E0E0, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFEFEFE, 0xFFFDFDFD, 0xFFFFFFFF, 0x90686868, 0x00000000, 0x00000000, 0xFFEBEBEB, 0xFFFFFFFF, 0xFFFFFFFF, 0x81595959, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFDFDFD, 0xFFFFFFFF, 0x8C646464, 0x00000000, 0x00000000, 0x00000000, 0xB78F8F8F, 0xFFFFFFFF, 0xFFFEFEFE, 0xECC4C4C4, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0xFFFFFFFF, 0x90686868, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x50282828, 0xFFF9F9F9, 0xFFFFFFFF, 0xFFFFFFFF, 0x623A3A3A, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0x865E5E5E, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xD8B0B0B0, 0xFFFFFFFF, 0xFFFFFFFF, 0xCDA5A5A5, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x6C444444, 0xFFFFFFFF, 0xFFFCFCFC, 0xFFF4F4F4, 0x4D252525, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF2CACACA, 0xFFFFFFFF, 0xFFFFFFFF, 0x99717171, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, + { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x8E666666, 0xEFC7C7C7, 0x7D555555, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }, }; // Saved framebuffer pixels beneath the current cursor position. @@ -205,6 +213,28 @@ static void CursorRestoreBg(int x, int y) { } } +// Alpha-blend a cursor pixel over a framebuffer pixel. +// Framebuffer format: 0x00RRGGBB (high byte ignored / always 0). +// Cursor pixel format: 0xAARRGGBB. +static inline uint32_t BlendPixel(uint32_t dst, uint32_t src) { + uint8_t a = (uint8_t)(src >> 24); + if (a == 0) return dst; + if (a == 255) return src & 0x00FFFFFFu; + + uint8_t sr = (uint8_t)(src >> 16); + uint8_t sg = (uint8_t)(src >> 8); + uint8_t sb = (uint8_t)(src ); + uint8_t dr = (uint8_t)(dst >> 16); + uint8_t dg = (uint8_t)(dst >> 8); + uint8_t db = (uint8_t)(dst ); + + uint8_t na = 255 - a; + uint8_t or_ = (uint8_t)((a * sr + na * dr) >> 8); + uint8_t og = (uint8_t)((a * sg + na * dg) >> 8); + uint8_t ob = (uint8_t)((a * sb + na * db) >> 8); + return ((uint32_t)or_ << 16) | ((uint32_t)og << 8) | ob; +} + // Draw the arrow sprite at (x,y), saving the background first. // Called from hal_mouse.c via extern declaration. void HalScreenDrawCursor(int x, int y) { @@ -214,12 +244,12 @@ void HalScreenDrawCursor(int x, int y) { for (int row = 0; row < CURSOR_H; row++) { int fy = y + row; for (int col = 0; col < CURSOR_W; col++) { - uint8_t p = gCursorBitmap[row][col]; - if (!p) continue; + uint32_t src = gCursorPixels[row][col]; + if ((src >> 24) == 0) continue; // fully transparent, skip int fx = x + col; if ((unsigned)fx < (unsigned)gWidth && (unsigned)fy < (unsigned)gHeight) { - uint32_t color = (p == 1) ? 0x000000u : 0xFFFFFFu; - gFb[fy * gStride + fx] = color; + uint32_t* dst = &gFb[fy * gStride + fx]; + *dst = BlendPixel(*dst, src); } } } diff --git a/code/kernel/ke_process.h b/code/kernel/ke_process.h index cfb58c4..4ddfc70 100644 --- a/code/kernel/ke_process.h +++ b/code/kernel/ke_process.h @@ -16,6 +16,11 @@ typedef struct PROCESS { uint32_t ProcessId; char Name[PROCESS_NAME_MAX]; /* UTF-8 */ + // Windows-style flat command line, stored at launch by SysCreateProcess. + // The initial thread receives this pointer as its UserArg so _start can + // parse it into argc/argv without an extra syscall. + char Cmdline[512]; + char CurrentDirectory[512]; SPINLOCK CwdLock; diff --git a/code/kernel/ke_syscall.asm b/code/kernel/ke_syscall.asm index f128492..7a9ccb5 100644 --- a/code/kernel/ke_syscall.asm +++ b/code/kernel/ke_syscall.asm @@ -37,35 +37,92 @@ KeSyscallEntry PROC KeSyscallEntry ENDP ; --------------------------------------------------------------------------- -; Fault stubs - print a message and halt. A real kernel would do a bugcheck. +; Fault stubs - print a message and halt. ; --------------------------------------------------------------------------- EXTERN KePanic:PROC -FaultMsg DB "KERNEL FAULT: vector %d at RIP %016llx", 0Ah, 0 +.data +; We define separate strings to maximize the info we can print +StrKernel DB "Kernel Mode", 0 +StrUser DB "User Mode", 0 +MsgFaultDE DB "PANIC: Divide Error (#0) | RIP: %016llx | Mode: %s", 0Ah, 0 +MsgFaultGP DB "PANIC: General Protection (#13) | RIP: %016llx | Code: %04x | Mode: %s", 0Ah, 0 +MsgFaultPF DB "PANIC: Page Fault (#14) | RIP: %016llx | Code: %04x | CR2: %016llx | %s", 0Ah, 0 + +.code + +; --------------------------------------------------------- +; Divide Error (#0) - NO Error Code pushed by CPU +; Stack: [rsp] = RIP, [rsp+8] = CS, [rsp+16] = RFLAGS +; --------------------------------------------------------- KeFaultDE PROC - sub rsp, 28h - lea rcx, FaultMsg - mov rdx, 0 - pop r8 ; RIP from the fault frame + mov rdx, [rsp] ; Arg 2: RIP + + ; Determine User/Kernel mode from CS + mov rax, [rsp + 8] ; Read CS + and rax, 3 ; Isolate the bottom 2 bits (RPL) + lea r8, StrKernel + lea r9, StrUser + test rax, rax ; If 0, it's Kernel. If >0, it's User. + cmovnz r8, r9 ; Arg 3: Mode String (moves r9 into r8 if Not Zero) + + lea rcx, MsgFaultDE ; Arg 1: Format string + + ; Safely align stack and allocate shadow space for the call + and rsp, -16 ; Force 16-byte ABI stack alignment + sub rsp, 20h ; Allocate 32 bytes of shadow space call KePanic KeFaultDE ENDP +; --------------------------------------------------------- +; General Protection Fault (#13) - Error Code PUSHED +; Stack: [rsp] = ErrCode, [rsp+8] = RIP, [rsp+16] = CS +; --------------------------------------------------------- KeFaultGP PROC - sub rsp, 28h - lea rcx, FaultMsg - mov rdx, 13 - pop rax ; error code - pop r8 ; RIP + mov r8, [rsp] ; Arg 3: Error Code + mov rdx, [rsp + 8] ; Arg 2: RIP + + ; Determine User/Kernel mode from CS + mov rax, [rsp + 16] ; Read CS + and rax, 3 + lea r9, StrKernel + lea r10, StrUser + test rax, rax + cmovnz r9, r10 ; Arg 4: Mode String + + lea rcx, MsgFaultGP ; Arg 1: Format string + + and rsp, -16 ; Align stack + sub rsp, 20h ; 32 bytes shadow space call KePanic KeFaultGP ENDP +; --------------------------------------------------------- +; Page Fault (#14) - Error Code PUSHED +; Stack: [rsp] = ErrCode, [rsp+8] = RIP, [rsp+16] = CS +; --------------------------------------------------------- KeFaultPF PROC - sub rsp, 28h - lea rcx, FaultMsg - mov rdx, 14 - pop rax ; error code - pop r8 ; RIP + mov r8, [rsp] ; Arg 3: Error Code + mov rdx, [rsp + 8] ; Arg 2: RIP + mov r9, cr2 ; Arg 4: CR2 (The faulting virtual address) + + ; Determine User/Kernel mode from CS + mov rax, [rsp + 16] ; Read CS + and rax, 3 + lea r10, StrKernel + lea r11, StrUser + test rax, rax + cmovnz r10, r11 ; r10 now holds the Mode String + + lea rcx, MsgFaultPF ; Arg 1: Format string + + ; We have 5 arguments for PF! (rcx, rdx, r8, r9, and the stack) + ; x64 ABI dictates the 5th argument goes on the stack just above the shadow space. + and rsp, -16 ; Align stack + sub rsp, 30h ; 32 bytes shadow space + 16 bytes for 5th arg and padding + mov [rsp + 20h], r10 ; Arg 5: Mode String (placed exactly after shadow space) + call KePanic KeFaultPF ENDP diff --git a/code/kernel/ke_syscall.c b/code/kernel/ke_syscall.c index e0f7f7c..18ef615 100644 --- a/code/kernel/ke_syscall.c +++ b/code/kernel/ke_syscall.c @@ -683,14 +683,15 @@ static uint64_t SyscallDebugPrint(uint64_t a0, uint64_t a1, uint64_t a2, } // --------------------------------------------------------------------------- -// SYS_CREATE_PROCESS (40) a0=*path a1=*handleOut +// SYS_CREATE_PROCESS (40) a0=*path a1=*handleOut a2=*cmdline (optional) // --------------------------------------------------------------------------- static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5) { - (void)a2; (void)a3; (void)a4; (void)a5; + (void)a3; (void)a4; (void)a5; - const char* path = (const char*)(uintptr_t)a0; + const char* path = (const char*)(uintptr_t)a0; uint32_t* handleOut = (uint32_t*)(uintptr_t)a1; + const char* cmdline = (const char*)(uintptr_t)a2; // NULL = no args if (!path || !handleOut) return STATUS_INVALID_PARAMETER; // Read the executable from disk. @@ -712,16 +713,33 @@ static uint64_t SyscallCreateProcess(uint64_t a0, uint64_t a1, uint64_t a2, s = KeCreateProcess(path, &proc); if (!KSUCCESS(s)) { MmFreeUserMemory(mem, size); return s; } + // Store the command line in the process so _start can pick it up. + // If no cmdline was supplied default to just the image path. + if (cmdline && cmdline[0]) + strncpy(proc->Cmdline, cmdline, sizeof(proc->Cmdline) - 1); + else + strncpy(proc->Cmdline, path, sizeof(proc->Cmdline) - 1); + proc->Cmdline[sizeof(proc->Cmdline) - 1] = '\0'; + + // proc->Cmdline is kernel pool - CPL=3 code can't read it. + // Copy into user memory so the initial thread can access it directly. + size_t cmdlineLen = strlen(proc->Cmdline) + 1; + char* userCmdline = (char*)MmAllocateUserMemory(cmdlineLen); + if (!userCmdline) { ObDereferenceObject(&proc->Header); return STATUS_NO_MEMORY; } + memcpy(userCmdline, proc->Cmdline, cmdlineLen); + // Map the PE image into memory. uintptr_t imageBase = 0; s = LdrLoadImage(mem, size, &imageBase, true); MmFreeUserMemory(mem, size); if (!KSUCCESS(s)) { ObDereferenceObject(&proc->Header); return s; } - // Spin up the initial thread at the PE entry point. + // Spin up the initial thread. Pass userCmdline as UserArg so _start + // receives it directly in RCX without an extra syscall. THREAD* thread; uintptr_t entryPoint = LdrGetEntryPoint(imageBase); - s = KeCreateThread(proc, entryPoint, 0, PriorityNormal, &thread); + s = KeCreateThread(proc, entryPoint, (uintptr_t)userCmdline, + PriorityNormal, &thread); if (!KSUCCESS(s)) { ObDereferenceObject(&proc->Header); return s; } // Give the caller a handle to the new process so they can wait on it. @@ -850,6 +868,69 @@ static uint64_t SyscallGetCwd(uint64_t a0, uint64_t a1, uint64_t a2, return IoGetCwd(buf, size); } +// --------------------------------------------------------------------------- +// SYS_WAIT_THREAD (53) a0=tid +// Blocks the caller until the thread with the given TID exits. +// Searches the current process's thread list; cross-process join is not +// supported in v1 (POSIX pthread_join is always intra-process). +// Returns STATUS_NOT_FOUND if the TID is unknown (already reaped or never +// existed) - treat the same as a successful join at the POSIX layer. +// --------------------------------------------------------------------------- +static uint64_t SyscallWaitThread(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; + uint32_t targetTid = (uint32_t)a0; + + PROCESS* proc = KeGetCurrentProcess(); + if (!proc) return STATUS_FAILURE; + + // Walk the thread list under the spinlock to find the target, then bump + // its ExitEvent refcount so the event survives even if the thread exits + // and is destroyed while we wait. + EVENT* exitEvent = (EVENT*)0; + uintptr_t flags = KeAcquireSpinlock(&proc->HandleLock); + LIST_ENTRY* entry = proc->ThreadList.Fwd; + while (entry != &proc->ThreadList) { + THREAD* t = CONTAINING_RECORD(entry, THREAD, ProcessLink); + if (t->ThreadId == targetTid && t->ExitEvent) { + exitEvent = t->ExitEvent; + ObReferenceObject(&exitEvent->Header); // keep event alive + break; + } + entry = entry->Fwd; + } + KeReleaseSpinlock(&proc->HandleLock, flags); + + if (!exitEvent) { + // TID not found - either already exited and reaped, or invalid. + // Treat as success at the POSIX layer (join on an already-dead thread). + return STATUS_NOT_FOUND; + } + + KSTATUS s = KeWaitForEvent(exitEvent); + ObDereferenceObject(&exitEvent->Header); + return s; +} + +// --------------------------------------------------------------------------- +// SYS_GET_PROCESS_PID (54) a0=procHandle a1=*pidOut +// Returns the numeric PID of a process identified by handle. +// Used by posix_spawn to map the kernel handle back to a pid_t for waitpid. +// --------------------------------------------------------------------------- +static uint64_t SyscallGetProcessPid(uint64_t a0, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + (void)a2; (void)a3; (void)a4; (void)a5; + + PROCESS* proc = (PROCESS*)HandleToObject((uint32_t)a0, ObjectTypeProcess); + uint32_t* pidOut = (uint32_t*)(uintptr_t)a1; + if (!proc) return STATUS_INVALID_HANDLE; + if (!pidOut) { ObDereferenceObject(&proc->Header); return STATUS_INVALID_PARAMETER; } + + *pidOut = proc->ProcessId; + ObDereferenceObject(&proc->Header); + return STATUS_SUCCESS; +} + // =========================================================================== // Dispatch table - index must match SYS_* in syscall_nr.h exactly. // =========================================================================== @@ -910,6 +991,8 @@ static const SYSCALL_HANDLER gSyscallTable[SYS_MAX] = { /* 50 SYS_READ_DIR */ SyscallReadDir, /* 51 SYS_SET_CWD */ SyscallSetCwd, /* 52 SYS_GET_CWD */ SyscallGetCwd, + /* 53 SYS_WAIT_THREAD */ SyscallWaitThread, + /* 54 SYS_GET_PROCESS_PID */ SyscallGetProcessPid, }; // --------------------------------------------------------------------------- diff --git a/code/kernel/ke_thread.c b/code/kernel/ke_thread.c index 3bd8ef8..661e251 100644 --- a/code/kernel/ke_thread.c +++ b/code/kernel/ke_thread.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -12,6 +13,11 @@ extern void KeThreadEntry(void); static void ThreadDestroy(OBJECT* obj) { THREAD* thread = CONTAINING_RECORD(obj, THREAD, Header); + if (thread->ExitEvent) { + ObDereferenceObject(&thread->ExitEvent->Header); + thread->ExitEvent = (EVENT*)0; + } + if (thread->Owner) { uintptr_t flags = KeAcquireSpinlock(&thread->Owner->HandleLock); ListRemove(&thread->ProcessLink); @@ -100,6 +106,19 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui ObReferenceObject(&owner->Header); thread->Owner = owner; + // Allocate the exit event before the thread becomes visible. Manual-reset + // so multiple joiners (if any) all wake up when the thread exits. + EVENT* exitEvent; + KSTATUS s = KeEventCreate(EventManualReset, 0, &exitEvent); + if (!KSUCCESS(s)) { + MmFreeUserMemory(ustack, USTACK_SIZE); + MmFreePool(kstack); + ObDereferenceObject(&owner->Header); + ObDereferenceObject(&thread->Header); + return s; + } + thread->ExitEvent = exitEvent; + uintptr_t flags = KeAcquireSpinlock(&owner->HandleLock); ListInsertTail(&owner->ThreadList, &thread->ProcessLink); owner->ThreadCount++; @@ -114,6 +133,11 @@ __declspec(dllexport) void KeExitThread(THREAD* thread, int code) { (void)code; if (!thread) return; + // Wake any pthread_join waiters before the thread is marked dead and + // potentially freed. Manual-reset means all concurrent waiters unblock. + if (thread->ExitEvent) + KeSignalEvent(thread->ExitEvent); + thread->State = ThreadDead; KeYield(); diff --git a/code/kernel/ke_thread.h b/code/kernel/ke_thread.h index 2d05661..75eb0dd 100644 --- a/code/kernel/ke_thread.h +++ b/code/kernel/ke_thread.h @@ -5,6 +5,7 @@ #include #include #include +#include #define KSTACK_SIZE (64 * 1024) /* 64 KB kernel stack */ #define USTACK_SIZE (512 * 1024) /* 512 KB user stack */ @@ -67,6 +68,11 @@ typedef struct THREAD { uintptr_t SchedLockFlags; // Stores RFLAGS while thread is context switched out void* UserStackBase; // Tracks user stack allocation to prevent memory leaks + + // Signaled (manual-reset) when the thread exits. + // Allocated in KeCreateThread, signaled in KeExitThread before teardown. + // Waiters block on this via SYS_WAIT_THREAD -> KeWaitForEvent. + EVENT* ExitEvent; } THREAD; __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg, diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index c5b2774..b845b43 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -238,6 +238,18 @@ static PROCESS* LaunchUserProcess(const char* filePath) { } DbgPrint("Created process (PID %u) with command line: %s\n", proc->ProcessId, filePath); + strncpy(proc->Cmdline, filePath, sizeof(proc->Cmdline) - 1); + + // proc->Cmdline is kernel pool (ffffc000...) - not readable at CPL=3. + // Copy into user memory before handing the pointer to the initial thread. + size_t cmdlineLen = strlen(proc->Cmdline) + 1; + char* userCmdline = (char*)MmAllocateUserMemory(cmdlineLen); + if (!userCmdline) { + DbgPrint("LaunchUserProcess: Failed to allocate user cmdline buffer.\n"); + return (PROCESS*)0; + } + memcpy(userCmdline, proc->Cmdline, cmdlineLen); + // Parse the raw PE file and map it into executable memory uintptr_t imageBase = 0; s = LdrLoadImage(mem, size, &imageBase, true); @@ -248,7 +260,7 @@ static PROCESS* LaunchUserProcess(const char* filePath) { DbgPrint("LaunchUserProcess: PE Image mapped! Base 0x%llx, Entry 0x%llX\n", imageBase, entryPoint); // Create the main thread, pointing it to the PE Entry Point - s = KeCreateThread(proc, (uintptr_t)entryPoint, 0, PriorityNormal, &thread); + s = KeCreateThread(proc, (uintptr_t)entryPoint, (uintptr_t)userCmdline, PriorityNormal, &thread); if (!KSUCCESS(s)) { DbgPrint("LaunchUserProcess: Failed to create thread. Status: %d\n", s); } diff --git a/code/kernel/syscall_nr.h b/code/kernel/syscall_nr.h index 586fe00..0a7b78e 100644 --- a/code/kernel/syscall_nr.h +++ b/code/kernel/syscall_nr.h @@ -130,7 +130,19 @@ #define SYS_SET_CWD 51 // (const char* path) -> KSTATUS #define SYS_GET_CWD 52 // (char* buf, size_t size) -> KSTATUS +// --------------------------------------------------------------------------- +// Thread join +// --------------------------------------------------------------------------- +#define SYS_WAIT_THREAD 53 // (uint32_t tid) -> KSTATUS + // Blocks until the thread with that TID exits. + // Returns STATUS_NOT_FOUND if TID unknown. + +// --------------------------------------------------------------------------- +// Process PID query (needed by posix_spawn -> waitpid) +// --------------------------------------------------------------------------- +#define SYS_GET_PROCESS_PID 54 // (uint32_t procHandle, uint32_t* pidOut) -> KSTATUS + // --------------------------------------------------------------------------- // Sentinel // --------------------------------------------------------------------------- -#define SYS_MAX 53 +#define SYS_MAX 55 diff --git a/code/posix/posix.h b/code/posix/posix.h new file mode 100644 index 0000000..6203acf --- /dev/null +++ b/code/posix/posix.h @@ -0,0 +1,98 @@ +#pragma once +// posix.h - POSIX type and constant definitions for posix.dll +// Covers just what open/read/write/close/lseek/spawn/pthread/chdir need. + +#include +#include +#include + +// --------------------------------------------------------------------------- +// errno +// --------------------------------------------------------------------------- +#ifdef errno +#undef errno +#endif +#ifdef EAGAIN +#undef EAGAIN +#endif +#ifdef ENOSYS +#undef ENOSYS +#endif +extern int errno; // defined in posix_fd.c; single global in v1 + +#define EPERM 1 +#define ENOENT 2 +#define ESRCH 3 +#define EINTR 4 +#define EIO 5 +#define EBADF 9 +#define ENOMEM 12 +#define EACCES 13 +#define EEXIST 17 +#define ENODEV 19 +#define ENOTDIR 20 +#define EINVAL 22 +#define EMFILE 24 +#define ENOSPC 28 +#define ERANGE 34 +#define EAGAIN 35 +#define ENOSYS 38 + +// --------------------------------------------------------------------------- +// open() flags +// --------------------------------------------------------------------------- +#define O_RDONLY 0x0000 +#define O_WRONLY 0x0001 +#define O_RDWR 0x0002 +#define O_CREAT 0x0040 +#define O_TRUNC 0x0200 +#define O_APPEND 0x0400 + +// --------------------------------------------------------------------------- +// lseek() whence +// --------------------------------------------------------------------------- +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +// --------------------------------------------------------------------------- +// Scalar types +// --------------------------------------------------------------------------- +typedef int32_t pid_t; +typedef int32_t off_t; +typedef uint32_t mode_t; +typedef int64_t ssize_t; + +// --------------------------------------------------------------------------- +// pthread types +// --------------------------------------------------------------------------- +typedef struct { uint32_t Tid; } pthread_t; +typedef struct { uint32_t Handle; } pthread_mutex_t; +typedef void* pthread_attr_t; // unused in v1 +typedef void* pthread_mutexattr_t; + +// --------------------------------------------------------------------------- +// posix_spawn types (file_actions and attrp both ignored in v1) +// --------------------------------------------------------------------------- +typedef void* posix_spawn_file_actions_t; +typedef void* posix_spawnattr_t; + +// --------------------------------------------------------------------------- +// dirent (used by posix_dir.c) +// --------------------------------------------------------------------------- +#define NAME_MAX 259 + +typedef struct dirent { + char d_name[NAME_MAX + 1]; + unsigned char d_type; // DT_REG or DT_DIR +} dirent; + +#define DT_UNKNOWN 0 +#define DT_REG 8 +#define DT_DIR 4 + +typedef struct DIR { + uint32_t KHandle; // kernel DIR_OBJECT handle + dirent Current; + bool Valid; +} DIR; diff --git a/code/posix/posix_dir.c b/code/posix/posix_dir.c new file mode 100644 index 0000000..8f1a590 --- /dev/null +++ b/code/posix/posix_dir.c @@ -0,0 +1,78 @@ +// posix_dir.c - chdir, getcwd, opendir, readdir, closedir +#include +#include "posix.h" +#include "posix_path.h" + +extern uint32_t SysGetCurrentPid(void); +extern uint32_t SysSetCwd(const char* path); +extern uint32_t SysGetCwd(char* buf, size_t size); +extern uint32_t SysOpenDir(const char* path, uint32_t* handleOut); +extern uint32_t SysReadDir(uint32_t handle, void* entryOut); +extern void SysCloseHandle(uint32_t handle); +extern void* SysAlloc(size_t); +extern void SysFree(void*, size_t); + +// KDIR_ENTRY layout mirrors io_file.h - duplicated here to avoid +// pulling kernel headers into posix.dll +#define KDIR_NAME_MAX 260 +typedef struct { + char Name[KDIR_NAME_MAX]; + uint8_t Attr; + uint32_t FileSize; +} KDIR_ENTRY; + +#define KATTR_DIR 0x10 + +int chdir(const char* path) { + char native[512]; + PosixTranslatePath(path, native, sizeof(native)); + uint32_t s = SysSetCwd(native); + if (s != 0) { errno = ENOENT; return -1; } + return 0; +} + +char* getcwd(char* buf, size_t size) { + if (!buf || size == 0) { errno = EINVAL; return (char*)0; } + char native[512]; + uint32_t s = SysGetCwd(native, sizeof(native)); + if (s != 0) { errno = ERANGE; return (char*)0; } + NativeToPosix(native, buf, size); + return buf; +} + +DIR* opendir(const char* path) { + char native[512]; + PosixTranslatePath(path, native, sizeof(native)); + uint32_t kh; + uint32_t s = SysOpenDir(native, &kh); + if (s != 0) { errno = ENOENT; return (DIR*)0; } + + // Allocate DIR on the kernel heap (SysAlloc) so it outlives the stack. + DIR* d = (DIR*)SysAlloc(sizeof(DIR)); + if (!d) { SysCloseHandle(kh); errno = ENOMEM; return (DIR*)0; } + d->KHandle = kh; + d->Valid = false; + return d; +} + +struct dirent* readdir(DIR* d) { + if (!d) { errno = EBADF; return (struct dirent*)0; } + KDIR_ENTRY ke; + uint32_t s = SysReadDir(d->KHandle, &ke); + if (s != 0) return (struct dirent*)0; // end of directory or error + + size_t len = strlen(ke.Name); + if (len > NAME_MAX) len = NAME_MAX; + memcpy(d->Current.d_name, ke.Name, len); + d->Current.d_name[len] = '\0'; + d->Current.d_type = (ke.Attr & KATTR_DIR) ? DT_DIR : DT_REG; + d->Valid = true; + return &d->Current; +} + +int closedir(DIR* d) { + if (!d) { errno = EBADF; return -1; } + SysCloseHandle(d->KHandle); + SysFree(d, sizeof(DIR)); + return 0; +} diff --git a/code/posix/posix_fd.c b/code/posix/posix_fd.c new file mode 100644 index 0000000..0059dc0 --- /dev/null +++ b/code/posix/posix_fd.c @@ -0,0 +1,55 @@ +// posix_fd.c - file descriptor table +#include "posix.h" +#include "posix_fd.h" + +// yeah +#ifdef errno +#undef errno +#endif + +int errno = 0; + +static FD_ENTRY gFdTable[FD_TABLE_PIDS][FD_MAX]; + +// Syscall stub declarations (from syscall.dll) +extern uint32_t SysGetCurrentPid(void); + +void PosixFdInit(void) { + uint32_t pid = SysGetCurrentPid(); + uint32_t slot = pid % FD_TABLE_PIDS; + + // Clear this process's slice (handles inherited state from a previous + // process that happened to land on the same slot - unlikely but safe). + for (int i = 0; i < FD_MAX; i++) + gFdTable[slot][i] = (FD_ENTRY){ FD_FREE, 0, false }; + + gFdTable[slot][0] = (FD_ENTRY){ FD_CONSOLE_R, 0, true }; + gFdTable[slot][1] = (FD_ENTRY){ FD_CONSOLE_W, 0, true }; + gFdTable[slot][2] = (FD_ENTRY){ FD_CONSOLE_W, 0, true }; +} + +int FdAlloc(uint32_t pid, FD_TYPE type, uint32_t khandle) { + uint32_t slot = pid % FD_TABLE_PIDS; + for (int fd = 3; fd < FD_MAX; fd++) { + if (!gFdTable[slot][fd].Used) { + gFdTable[slot][fd] = (FD_ENTRY){ type, khandle, true }; + return fd; + } + } + errno = EMFILE; + return -1; +} + +FD_ENTRY* FdGet(uint32_t pid, int fd) { + if (fd < 0 || fd >= FD_MAX) { errno = EBADF; return (FD_ENTRY*)0; } + uint32_t slot = pid % FD_TABLE_PIDS; + FD_ENTRY* e = &gFdTable[slot][fd]; + if (!e->Used) { errno = EBADF; return (FD_ENTRY*)0; } + return e; +} + +void FdFree(uint32_t pid, int fd) { + if (fd < 0 || fd >= FD_MAX) return; + uint32_t slot = pid % FD_TABLE_PIDS; + gFdTable[slot][fd] = (FD_ENTRY){ FD_FREE, 0, false }; +} diff --git a/code/posix/posix_fd.h b/code/posix/posix_fd.h new file mode 100644 index 0000000..0310089 --- /dev/null +++ b/code/posix/posix_fd.h @@ -0,0 +1,34 @@ +#pragma once +// posix_fd.h - file descriptor table (internal to posix.dll) +#include +#include + +typedef enum { + FD_FREE, + FD_FILE, + FD_CONSOLE_R, // fd 0 - reads from keyboard via SysKbReadChar + FD_CONSOLE_W, // fd 1, 2 - writes via SysWriteConsole + FD_DEVNULL, // /dev/null + FD_DEVZERO, // /dev/zero +} FD_TYPE; + +typedef struct { + FD_TYPE Type; + uint32_t KHandle; // kernel handle; 0 for console/devnull/devzero + bool Used; +} FD_ENTRY; + +#define FD_MAX 64 +#define FD_TABLE_PIDS 32 // circular, indexed by pid % FD_TABLE_PIDS + +// Called from _start before main(); sets up fds 0/1/2 for this process. +void PosixFdInit(void); + +// Allocate the lowest free fd >= 3. Returns -1 and sets errno=EMFILE on failure. +int FdAlloc(uint32_t pid, FD_TYPE type, uint32_t khandle); + +// Look up an fd. Returns NULL and sets errno=EBADF if invalid. +FD_ENTRY* FdGet(uint32_t pid, int fd); + +// Release an fd slot (does NOT close the kernel handle). +void FdFree(uint32_t pid, int fd); diff --git a/code/posix/posix_io.c b/code/posix/posix_io.c new file mode 100644 index 0000000..84bd5c4 --- /dev/null +++ b/code/posix/posix_io.c @@ -0,0 +1,158 @@ +// posix_io.c - open, read, write, close, lseek +#include +#include +#include "posix.h" +#include "posix_fd.h" +#include "posix_path.h" + +extern uint32_t SysGetCurrentPid(void); +extern uint32_t SysOpenFile (const char* path, uint32_t* handleOut); +extern uint32_t SysCreateFile (const char* path, uint32_t* handleOut); +extern uint32_t SysTruncateFile(uint32_t handle, uint64_t newSize); +extern uint32_t SysReadFile (uint32_t handle, void* buf, size_t size, size_t* bytesRead); +extern uint32_t SysWriteFile (uint32_t handle, const void* buf, size_t size, size_t* written); +extern uint32_t SysSeekFile (uint32_t handle, uint64_t offset); +extern uint64_t SysGetFilePos (uint32_t handle); +extern uint64_t SysGetFileSize(uint32_t handle); +extern void SysCloseHandle(uint32_t handle); +extern void SysWriteConsole(const char* buf, size_t len); +extern char SysKbReadChar(void); + +// KSTATUS -> errno +static int KsToErrno(uint32_t s) { + switch (s) { + case 0x0000: return 0; // STATUS_SUCCESS + case 0x0022: return ENOENT; // STATUS_NOT_FOUND + case 0x0021: return EACCES; // STATUS_ACCESS_DENIED + case 0x0020: return ENOMEM; // STATUS_NO_MEMORY + case 0x0023: return EEXIST; // STATUS_ALREADY_EXISTS + case 0x0010: return EINVAL; // STATUS_INVALID_PARAMETER + case 0x0012: return ERANGE; // STATUS_BUFFER_TOO_SMALL + case 0x0002: return ENOSYS; // STATUS_NOT_SUPPORTED + case 0x0031: return EAGAIN; // STATUS_QUEUE_FULL + default: return EIO; + } +} + +int open(const char* path, int flags, ...) { + char native[512]; + const char* p = PosixTranslatePath(path, native, sizeof(native)); + + uint32_t pid = SysGetCurrentPid(); + FD_TYPE type = FD_FILE; + + // Synthetic devices + if (strcmp(p, SYNTH_DEVNULL) == 0) { + int fd = FdAlloc(pid, FD_DEVNULL, 0); + return fd; + } + + if (strcmp(p, SYNTH_DEVZERO) == 0) { + int fd = FdAlloc(pid, FD_DEVZERO, 0); + return fd; + } + + uint32_t kh = 0; + uint32_t s; + if (flags & O_WRONLY) { + s = SysCreateFile(p, &kh); + } else { + s = SysOpenFile(p, &kh); + } + if (s != 0) { errno = KsToErrno(s); return -1; } + + if ((flags & O_TRUNC) && (flags & O_WRONLY)) + SysTruncateFile(kh, 0); + + int fd = FdAlloc(pid, type, kh); + if (fd < 0) { + SysCloseHandle(kh); // FdAlloc already set errno + return -1; + } + return fd; +} + +ssize_t read(int fd, void* buf, size_t count) { + if (count == 0) return 0; + uint32_t pid = SysGetCurrentPid(); + FD_ENTRY* e = FdGet(pid, fd); + if (!e) return -1; + + switch (e->Type) { + case FD_CONSOLE_R: { + // Read up to count chars from keyboard, blocking per char. + char* dst = (char*)buf; + for (size_t i = 0; i < count; i++) { + dst[i] = SysKbReadChar(); + if (dst[i] == '\n' || dst[i] == '\r') return (ssize_t)(i + 1); + } + return (ssize_t)count; + } + case FD_DEVNULL: + return 0; + case FD_DEVZERO: + memset(buf, 0, count); + return (ssize_t)count; + case FD_FILE: { + size_t got = 0; + uint32_t s = SysReadFile(e->KHandle, buf, count, &got); + if (s != 0 && got == 0) { errno = KsToErrno(s); return -1; } + return (ssize_t)got; + } + default: + errno = EBADF; + return -1; + } +} + +ssize_t write(int fd, const void* buf, size_t count) { + if (count == 0) return 0; + uint32_t pid = SysGetCurrentPid(); + FD_ENTRY* e = FdGet(pid, fd); + if (!e) return -1; + + switch (e->Type) { + case FD_CONSOLE_W: + SysWriteConsole((const char*)buf, count); + return (ssize_t)count; + case FD_DEVNULL: + case FD_DEVZERO: + return (ssize_t)count; + case FD_FILE: { + size_t written = 0; + uint32_t s = SysWriteFile(e->KHandle, buf, count, &written); + if (s != 0 && written == 0) { errno = KsToErrno(s); return -1; } + return (ssize_t)written; + } + default: + errno = EBADF; + return -1; + } +} + +int close(int fd) { + uint32_t pid = SysGetCurrentPid(); + FD_ENTRY* e = FdGet(pid, fd); + if (!e) return -1; + if (e->Type == FD_FILE) + SysCloseHandle(e->KHandle); + FdFree(pid, fd); + return 0; +} + +off_t lseek(int fd, off_t offset, int whence) { + uint32_t pid = SysGetCurrentPid(); + FD_ENTRY* e = FdGet(pid, fd); + if (!e || e->Type != FD_FILE) { errno = (e ? ESPIPE : EBADF); return -1; } + + uint64_t target; + 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; + } + uint32_t s = SysSeekFile(e->KHandle, target); + if (s != 0) { errno = KsToErrno(s); return -1; } + return (off_t)target; +} diff --git a/code/posix/posix_path.c b/code/posix/posix_path.c new file mode 100644 index 0000000..79db61a --- /dev/null +++ b/code/posix/posix_path.c @@ -0,0 +1,62 @@ +// posix_path.c - POSIX <-> native path translation +#include +#include +#include "posix_path.h" + +// The drive letter used as the POSIX filesystem root. +// A future SYS_GET_BOOT_DRIVE could populate this; hardcoded for v1. +static const char gRootDrive = 'C'; + +static void FlipSlashes(char* s) { + for (; *s; s++) if (*s == '/') *s = '\\'; +} + +const char* PosixTranslatePath(const char* in, char* out, size_t outSize) { + if (!in || !out || outSize < 4) return out; + + // Synthetic devices + if (strcmp(in, "/dev/null") == 0) return SYNTH_DEVNULL; + if (strcmp(in, "/dev/zero") == 0) return SYNTH_DEVZERO; + + // /proc/self/* - read-only synthesized files; flag with a sentinel so + // posix_io.c can intercept them without opening a real file. + // (In v1 only /proc/self/pid is handled; others fall through to ENOENT.) + + if (in[0] == '/') { + // Absolute POSIX path -> C:\... + // outSize must fit "C:\" + rest + out[0] = gRootDrive; + out[1] = ':'; + out[2] = '\\'; + size_t rest = strlen(in + 1); + if (rest + 4 > outSize) rest = outSize - 4; + memcpy(out + 3, in + 1, rest); + out[3 + rest] = '\0'; + } else { + // Relative - pass through with slash flip only + size_t len = strlen(in); + if (len + 1 > outSize) len = outSize - 1; + memcpy(out, in, len); + out[len] = '\0'; + } + + FlipSlashes(out); + return out; +} + +void NativeToPosix(const char* in, char* out, size_t outSize) { + if (!in || !out || outSize == 0) return; + + // Strip "X:\" prefix if present + const char* src = in; + if (src[0] != '\0' && src[1] == ':' && (src[2] == '\\' || src[2] == '/')) + src += 2; // skip "C:", leave the leading backslash + + size_t len = strlen(src); + if (len + 1 > outSize) len = outSize - 1; + memcpy(out, src, len); + out[len] = '\0'; + + // Backslash -> forward slash + for (char* p = out; *p; p++) if (*p == '\\') *p = '/'; +} diff --git a/code/posix/posix_path.h b/code/posix/posix_path.h new file mode 100644 index 0000000..7ebc58f --- /dev/null +++ b/code/posix/posix_path.h @@ -0,0 +1,16 @@ +#pragma once +// posix_path.h - POSIX <-> native path translation + +// SYNTHETIC sentinel returned from PosixTranslatePath for /dev/null and +// /dev/zero; the caller must handle these before passing to any syscall. +#define SYNTH_DEVNULL "\x01" +#define SYNTH_DEVZERO "\x01" + +// Translate a POSIX path to a native kernel path. +// Returns a pointer to 'out' on success, or SYNTH_DEVNULL / SYNTH_DEVZERO +// for those synthetic devices. 'out' must be at least 512 bytes. +const char* PosixTranslatePath(const char* in, char* out, size_t outSize); + +// Translate a native kernel path back to a POSIX-style path. +// Strips the drive letter prefix and flips backslashes. +void NativeToPosix(const char* in, char* out, size_t outSize); diff --git a/code/posix/posix_proc.c b/code/posix/posix_proc.c new file mode 100644 index 0000000..bb84c80 --- /dev/null +++ b/code/posix/posix_proc.c @@ -0,0 +1,126 @@ +// posix_proc.c - posix_spawn, waitpid, getpid +#include +#include +#include "posix.h" +#include "posix_path.h" + +extern uint32_t SysGetCurrentPid(void); +extern uint32_t SysCreateProcess(const char* path, uint32_t* handleOut, + const char* cmdline); +extern uint32_t SysWaitForProcess(uint32_t handle); +extern uint32_t SysGetProcessExitCode(uint32_t handle, int* exitCodeOut); +extern uint32_t SysGetProcessPid(uint32_t handle, uint32_t* pidOut); +extern void SysCloseHandle(uint32_t handle); + +// pid -> proc handle table (user-mode side, for waitpid) +#define PROC_TABLE_SIZE 32 +typedef struct { + pid_t pid; + uint32_t handle; + bool used; +} ProcEntry; + +static ProcEntry gProcTable[PROC_TABLE_SIZE]; + +static void ProcTableInsert(pid_t pid, uint32_t handle) { + for (int i = 0; i < PROC_TABLE_SIZE; i++) { + if (!gProcTable[i].used) { + gProcTable[i] = (ProcEntry){ pid, handle, true }; + return; + } + } + SysCloseHandle(handle); +} + +static uint32_t ProcTableFind(pid_t pid) { + for (int i = 0; i < PROC_TABLE_SIZE; i++) + if (gProcTable[i].used && gProcTable[i].pid == pid) + return gProcTable[i].handle; + return 0; +} + +static void ProcTableRemove(pid_t pid) { + for (int i = 0; i < PROC_TABLE_SIZE; i++) + if (gProcTable[i].used && gProcTable[i].pid == pid) + gProcTable[i].used = false; +} + +// Build a flat Windows-style cmdline from a NULL-terminated argv array. +// Each token that contains a space is wrapped in double-quotes. +// argv[0] is included (it becomes the executable token visible to _start). +static void BuildCmdline(char* const argv[], char* out, size_t outSize) { + size_t pos = 0; + for (int i = 0; argv && argv[i]; i++) { + if (i > 0 && pos < outSize - 1) out[pos++] = ' '; + const char* tok = argv[i]; + bool needQuote = false; + for (const char* c = tok; *c; c++) if (*c == ' ' || *c == '\t') { needQuote = true; break; } + if (needQuote && pos < outSize - 1) out[pos++] = '"'; + for (; *tok && pos < outSize - 2; tok++) { + if (*tok == '"') { out[pos++] = '\\'; } + out[pos++] = *tok; + } + if (needQuote && pos < outSize - 1) out[pos++] = '"'; + } + if (pos < outSize) out[pos] = '\0'; +} + +// --------------------------------------------------------------------------- +// POSIX API +// --------------------------------------------------------------------------- + +pid_t getpid(void) { + return (pid_t)SysGetCurrentPid(); +} + +int posix_spawn(pid_t* pidOut, + const char* path, + const posix_spawn_file_actions_t* file_actions, // ignored v1 + const posix_spawnattr_t* attrp, // ignored v1 + char* const argv[], + char* const envp[]) // ignored v1 +{ + (void)file_actions; (void)attrp; (void)envp; + + char native[512]; + PosixTranslatePath(path, native, sizeof(native)); + + char cmdline[512]; + BuildCmdline(argv, cmdline, sizeof(cmdline)); + // If argv was NULL or empty, fall back to just the path so _start still + // gets a meaningful argv[0]. + if (cmdline[0] == '\0') { + size_t len = strlen(path); + if (len >= sizeof(cmdline)) len = sizeof(cmdline) - 1; + memcpy(cmdline, path, len); + cmdline[len] = '\0'; + } + + uint32_t procHandle; + uint32_t s = SysCreateProcess(native, &procHandle, cmdline); + if (s != 0) { errno = ENOENT; return -1; } + + uint32_t kpid = 0; + SysGetProcessPid(procHandle, &kpid); + ProcTableInsert((pid_t)kpid, procHandle); + if (pidOut) *pidOut = (pid_t)kpid; + return 0; +} + +// options: WNOHANG not supported in v1 (always blocks). +pid_t waitpid(pid_t pid, int* status, int options) { + (void)options; + + uint32_t handle = ProcTableFind(pid); + if (!handle) { errno = ESRCH; return -1; } + + SysWaitForProcess(handle); + + int exitCode = 0; + SysGetProcessExitCode(handle, &exitCode); + if (status) *status = exitCode << 8; // WEXITSTATUS convention + + SysCloseHandle(handle); + ProcTableRemove(pid); + return pid; +} diff --git a/code/posix/posix_thread.c b/code/posix/posix_thread.c new file mode 100644 index 0000000..ae578ec --- /dev/null +++ b/code/posix/posix_thread.c @@ -0,0 +1,160 @@ +// posix_thread.c - pthread_create, pthread_join, pthread_exit, pthread_mutex_* +#include +#include "posix.h" + +extern uint32_t SysGetCurrentPid(void); +extern uint32_t SysGetCurrentTid(void); +extern uint32_t SysCreateThread(uint64_t entry, uint64_t arg, + uint32_t priority, uint32_t* tidOut); +extern uint32_t SysWaitThread(uint32_t tid); +extern void SysExitThread(int code); +extern void* SysAlloc(size_t size); +extern void SysFree(void* ptr, size_t size); +extern uint32_t SysMutexCreate(uint32_t* handleOut); +extern uint32_t SysMutexAcquire(uint32_t handle); +extern uint32_t SysMutexTryAcquire(uint32_t handle); +extern uint32_t SysMutexRelease(uint32_t handle); +extern void SysCloseHandle(uint32_t handle); + +// Thread return-value table (PID-keyed like the fd table) +#define PTHREAD_TABLE_PIDS 32 +#define PTHREAD_TABLE_SIZE 32 + +typedef struct { + uint32_t Tid; + void* RetVal; + bool Used; +} PTHREAD_SLOT; + +static PTHREAD_SLOT gPthreadTable[PTHREAD_TABLE_PIDS][PTHREAD_TABLE_SIZE]; + +static PTHREAD_SLOT* PthreadSlotAlloc(uint32_t pid, uint32_t tid) { + PTHREAD_SLOT* table = gPthreadTable[pid % PTHREAD_TABLE_PIDS]; + for (int i = 0; i < PTHREAD_TABLE_SIZE; i++) { + if (!table[i].Used) { + table[i] = (PTHREAD_SLOT){ tid, (void*)0, true }; + return &table[i]; + } + } + return (PTHREAD_SLOT*)0; +} + +static PTHREAD_SLOT* PthreadSlotFind(uint32_t pid, uint32_t tid) { + PTHREAD_SLOT* table = gPthreadTable[pid % PTHREAD_TABLE_PIDS]; + for (int i = 0; i < PTHREAD_TABLE_SIZE; i++) + if (table[i].Used && table[i].Tid == tid) + return &table[i]; + return (PTHREAD_SLOT*)0; +} + +// --------------------------------------------------------------------------- +// Trampoline - this is what actually runs as the new thread +// --------------------------------------------------------------------------- +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) { + PTHREAD_TRAMP* tr = (PTHREAD_TRAMP*)raw; + void* (*fn)(void*) = tr->Fn; + void* arg = tr->Arg; + uint32_t pid = tr->Pid; + uint32_t tid = tr->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); + if (slot) slot->RetVal = retval; + + SysExitThread(0); +} + +// --------------------------------------------------------------------------- +// pthread API +// --------------------------------------------------------------------------- + +int pthread_create(pthread_t* t, const pthread_attr_t* attr, + void* (*fn)(void*), void* arg) { + (void)attr; + + uint32_t pid = SysGetCurrentPid(); + + PTHREAD_TRAMP* tr = (PTHREAD_TRAMP*)SysAlloc(sizeof(PTHREAD_TRAMP)); + if (!tr) return ENOMEM; + 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, + (uint64_t)tr, 1 /*PriorityNormal*/, &tid); + if (s != 0) { + 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; + return 0; +} + +int pthread_join(pthread_t t, void** retval) { + uint32_t pid = SysGetCurrentPid(); + uint32_t s = SysWaitThread(t.Tid); + // STATUS_NOT_FOUND (0x22) means already dead - treat as success + if (s != 0 && s != 0x0022) return ESRCH; + + PTHREAD_SLOT* slot = PthreadSlotFind(pid, t.Tid); + if (retval) *retval = slot ? slot->RetVal : (void*)0; + if (slot) slot->Used = false; + return 0; +} + +void pthread_exit(void* retval) { + uint32_t pid = SysGetCurrentPid(); + uint32_t tid = SysGetCurrentTid(); + PTHREAD_SLOT* slot = PthreadSlotFind(pid, tid); + if (slot) slot->RetVal = retval; + SysExitThread(0); + while (1); // unreachable +} + +int pthread_mutex_init(pthread_mutex_t* m, const pthread_mutexattr_t* attr) { + (void)attr; + uint32_t h; + uint32_t s = SysMutexCreate(&h); + if (s != 0) return ENOMEM; + m->Handle = h; + return 0; +} + +int pthread_mutex_destroy(pthread_mutex_t* m) { + SysCloseHandle(m->Handle); + m->Handle = 0; + return 0; +} + +int pthread_mutex_lock(pthread_mutex_t* m) { + uint32_t s = SysMutexAcquire(m->Handle); + return s == 0 ? 0 : EINVAL; +} + +int pthread_mutex_trylock(pthread_mutex_t* m) { + uint32_t s = SysMutexTryAcquire(m->Handle); + return s == 0 ? 0 : EAGAIN; +} + +int pthread_mutex_unlock(pthread_mutex_t* m) { + uint32_t s = SysMutexRelease(m->Handle); + return s == 0 ? 0 : EINVAL; +} diff --git a/code/shell/shell.c b/code/shell/shell.c index 617a6a1..033383f 100644 --- a/code/shell/shell.c +++ b/code/shell/shell.c @@ -357,12 +357,14 @@ static int runCommand(const char* cmd) { // --------------------------------------------------------------------------- // Entry point // --------------------------------------------------------------------------- -int main() { +int main(const char* cmdline) { hIn = GetStdHandle(STD_INPUT_HANDLE); hOut = GetStdHandle(STD_OUTPUT_HANDLE); char lineBuf[MAX_CMD]; print("shell: ready\n"); + print(cmdline); + print("\n"); while (true) { print("> "); diff --git a/code/syscall/syscall_nr.inc b/code/syscall/syscall_nr.inc index 1bb8ad3..f163db5 100644 --- a/code/syscall/syscall_nr.inc +++ b/code/syscall/syscall_nr.inc @@ -86,5 +86,11 @@ SYS_READ_DIR EQU 50 SYS_SET_CWD EQU 51 SYS_GET_CWD EQU 52 +; Thread join +SYS_WAIT_THREAD EQU 53 ; a0=tid -> KSTATUS + +; Process PID query +SYS_GET_PROCESS_PID EQU 54 ; a0=procHandle a1=*pidOut -> KSTATUS + ; Sentinel -SYS_MAX EQU 53 +SYS_MAX EQU 55 diff --git a/code/syscall/syscall_stubs.asm b/code/syscall/syscall_stubs.asm index a5a16b6..0092531 100644 --- a/code/syscall/syscall_stubs.asm +++ b/code/syscall/syscall_stubs.asm @@ -166,4 +166,14 @@ SYSCALL_STUB2 ReadDir, SYS_READ_DIR SYSCALL_STUB2 SetCwd, SYS_SET_CWD SYSCALL_STUB2 GetCwd, SYS_GET_CWD +; --------------------------------------------------------------------------- +; Thread join (used by pthread_join in posix.dll) +; --------------------------------------------------------------------------- +SYSCALL_STUB2 WaitThread, SYS_WAIT_THREAD ; a0=tid -> KSTATUS + +; --------------------------------------------------------------------------- +; Process PID query (used by posix_spawn -> waitpid in posix.dll) +; --------------------------------------------------------------------------- +SYSCALL_STUB2 GetProcessPid, SYS_GET_PROCESS_PID ; a0=procHandle a1=*pidOut -> KSTATUS + END diff --git a/code/video/video.c b/code/video/video.c index 091f24f..ecf415e 100644 --- a/code/video/video.c +++ b/code/video/video.c @@ -76,10 +76,13 @@ int StretchDIBits(HDC hdc, int xDest, int yDest, int DestWidth, int DestHeight, UINT iUsage, DWORD rop); -int main() { +int main(const char* cmdline) { 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); if (!f) { WriteConsoleA(h, "video: failed to open video.bk2\n", 32, 0, 0); diff --git a/code/win32/kernel32.c b/code/win32/kernel32.c index 745c38c..ab8b9e0 100644 --- a/code/win32/kernel32.c +++ b/code/win32/kernel32.c @@ -50,7 +50,7 @@ extern uint32_t SysCreateThread(uint64_t entry, uint64_t arg, uint32_t priority, uint32_t* tidOut); extern uint32_t SysGetCurrentTid(void); extern uint32_t SysGetCurrentPid(void); -extern uint32_t SysCreateProcess(const char* path, uint32_t* handleOut); +extern uint32_t SysCreateProcess(const char* path, uint32_t* handleOut, const char* cmdline); extern uint32_t SysWaitForProcess(uint32_t handle); extern uint32_t SysGetProcessExitCode(uint32_t handle, int* exitCodeOut); extern char SysKbReadChar(void); @@ -608,9 +608,12 @@ __declspec(dllexport) BOOL CreateProcessA( return FALSE; } + // Build the cmdline: prefer lpCommandLine if given, otherwise fall back to path + const char* cmdline = lpCommandLine ? lpCommandLine : targetPath; + uint32_t kh = 0; // Call the syscall.dll stub - uint32_t s = SysCreateProcess(targetPath, &kh); + uint32_t s = SysCreateProcess(targetPath, &kh, cmdline); if (s != 0 || kh == 0) { gLastError = 2; // ERROR_FILE_NOT_FOUND return FALSE; diff --git a/readme.md b/readme.md index 2fde100..c78efda 100644 --- a/readme.md +++ b/readme.md @@ -20,8 +20,9 @@ It boots into a shell. - VFS with IRPs - drive-letter paths (`0:\apps\shell.exe`) - INI-based boot and driver configuration -- partial Win32 layer (kernel32 and user32, capable of running simple windows apps) - PE import resolution and dynamic driver loading +- Win32 personality (partial) +- POSIX personality (partial) ## how 2 build @@ -85,7 +86,7 @@ currently shipping: AHCI (interrupts), IDE (PIO/DMA) and FAT32. "2:\data\config.ini" -> drive 2 ``` -drives 0-7. a drive only works if a filesystem registered on it. +Drives 0-7. A drive only works if a filesystem registered on it. ## screenshots