add page cache with read-ahead and dirty writeback, file-backed and anonymous mmap with CoW, VAD-based virtual memory replacing the old user region model, PFN database with eviction and reclaim, file identity for cross-open section sharing, FAT32 path cache and per-file cluster chain cache, serial.sys for COM1 keyboard input, xHCI MSI with INTx and polled fallback, PciEnableMsiAuto, waitable input queue, KeWaitForAnyEvent, per-process thread info syscall, expanded kernel stats (PFN/PTE/VAD/file-cache/disk), pool allocation tags, async I/O user buffer locking, file flush on close, add docs.md.
This commit is contained in:
1 parent
61ec2442eb
commit
2c4a68a75f
114 files changed
+18069
-2620
No files matched your search
+47
-11
@@ -88,7 +88,8 @@ static int doom_read_wrapper(void* handle, void* buf, int count)
|
||||
if (!handle || count <= 0) return 0;
|
||||
DOOM_FILE_HANDLE* f = (DOOM_FILE_HANDLE*)handle;
|
||||
size_t read = 0;
|
||||
SysReadFile(f->Handle, buf, (size_t)count, &read);
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(f->Handle, buf, (size_t)count, &read);
|
||||
SysReadFile(&req);
|
||||
f->Pos += read;
|
||||
if (f->Pos > f->Size) f->Size = f->Pos;
|
||||
return (int)read;
|
||||
@@ -99,7 +100,8 @@ static int doom_write_wrapper(void* handle, const void* buf, int count)
|
||||
if (!handle || count <= 0) return 0;
|
||||
DOOM_FILE_HANDLE* f = (DOOM_FILE_HANDLE*)handle;
|
||||
size_t written = 0;
|
||||
SysWriteFile(f->Handle, buf, (size_t)count, &written);
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(f->Handle, buf, (size_t)count, &written);
|
||||
SysWriteFile(&req);
|
||||
f->Pos += written;
|
||||
if (f->Pos > f->Size) f->Size = f->Pos;
|
||||
return (int)written;
|
||||
@@ -262,7 +264,7 @@ static void poll_input(void)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Render — Performs lightweight software upscale and blits to the screen
|
||||
// Render — writes directly into a mapped kernel surface and presents it.
|
||||
// ---------------------------------------------------------------------------
|
||||
#define DOOM_W 320
|
||||
#define DOOM_H 200
|
||||
@@ -270,32 +272,65 @@ static void poll_input(void)
|
||||
#define DISP_W (DOOM_W * SCALE)
|
||||
#define DISP_H (DOOM_H * SCALE)
|
||||
|
||||
static uint32_t g_blit_buf[DISP_W * DISP_H];
|
||||
static uint32_t g_surface_handle;
|
||||
static uint32_t* g_surface_pixels;
|
||||
static uint32_t g_surface_pitch_pixels;
|
||||
|
||||
static void init_surface(void)
|
||||
{
|
||||
uint32_t s = SysCreateSurface(DISP_W, DISP_H, SYS_SURFACE_FORMAT_XRGB8888,
|
||||
SYS_SURFACE_FLAG_NONE, &g_surface_handle);
|
||||
if (s != SYS_STATUS_SUCCESS || !g_surface_handle) {
|
||||
doom_print_wrapper("doom: SysCreateSurface failed\n");
|
||||
SysExitProcess(1);
|
||||
}
|
||||
|
||||
void* base = 0;
|
||||
uint32_t pitchBytes = 0;
|
||||
s = SysMapSurface(g_surface_handle, &base, &pitchBytes);
|
||||
if (s != SYS_STATUS_SUCCESS || !base || pitchBytes < DISP_W * sizeof(uint32_t)) {
|
||||
doom_print_wrapper("doom: SysMapSurface failed\n");
|
||||
SysExitProcess(1);
|
||||
}
|
||||
|
||||
g_surface_pixels = (uint32_t*)base;
|
||||
g_surface_pitch_pixels = pitchBytes / sizeof(uint32_t);
|
||||
}
|
||||
|
||||
static void render_frame(const uint8_t* rgba)
|
||||
{
|
||||
const uint32_t* src = (const uint32_t*)rgba;
|
||||
|
||||
// Software upscale loop mapping 320x200 buffer directly to scaled window buffer
|
||||
uint32_t* dstBase = g_surface_pixels;
|
||||
uint32_t dstPitch = g_surface_pitch_pixels;
|
||||
|
||||
if (!dstBase || dstPitch < DISP_W) {
|
||||
doom_print_wrapper("doom: surface not initialized\n");
|
||||
SysExitProcess(1);
|
||||
}
|
||||
|
||||
for (int y = 0; y < DOOM_H; y++) {
|
||||
for (int x = 0; x < DOOM_W; x++) {
|
||||
uint32_t p = src[y * DOOM_W + x];
|
||||
|
||||
// RGBA -> BGRA conversion (standard for linear x86/64 framebuffers)
|
||||
|
||||
// RGBA -> BGRA/XRGB8888 conversion for the current display path.
|
||||
uint32_t bgra = (p & 0xFF00FF00u)
|
||||
| ((p & 0x00FF0000u) >> 16)
|
||||
| ((p & 0x000000FFu) << 16);
|
||||
|
||||
// Populate SCALE * SCALE pixel block
|
||||
uint32_t* block = dstBase + (size_t)(y * SCALE) * dstPitch + (size_t)(x * SCALE);
|
||||
for (int sy = 0; sy < SCALE; sy++) {
|
||||
for (int sx = 0; sx < SCALE; sx++) {
|
||||
g_blit_buf[(y * SCALE + sy) * DISP_W + (x * SCALE + sx)] = bgra;
|
||||
block[(size_t)sy * dstPitch + sx] = bgra;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SysBlitScreen(0, 0, DISP_W, DISP_H, g_blit_buf);
|
||||
DISPLAY_RECT rect = { 0, 0, DISP_W, DISP_H };
|
||||
uint32_t code = SysPresentSurface(g_surface_handle, &rect, 1);
|
||||
if (code != SYS_STATUS_SUCCESS) {
|
||||
SysExitProcess(code);
|
||||
}
|
||||
}
|
||||
|
||||
static int build_argv_from_command_line(const char* cmdline, char* storage, size_t storageSize,
|
||||
@@ -373,6 +408,7 @@ int main(void)
|
||||
char argvStorage[512];
|
||||
const char* argv[32];
|
||||
int argc = build_argv_from_command_line(cmdline, argvStorage, sizeof(argvStorage), argv, 32);
|
||||
init_surface();
|
||||
doom_init(argc, argv, 0);
|
||||
|
||||
do { SYS_WRITE_CONSOLE_REQUEST req = { SYS_SV_LITERAL("doom: entering game loop\n") }; SysWriteConsole(&req); } while (0);
|
||||
|
||||
@@ -380,12 +380,14 @@ static void save_file(void) {
|
||||
for (int i = 0; i < gLineCount; i++) {
|
||||
size_t written = 0;
|
||||
if (gLines[i].len) {
|
||||
st = SysWriteFile(h, gLines[i].text, (size_t)gLines[i].len, &written);
|
||||
SYS_FILE_IO_REQUEST writeReq = SysMakeFileIoRequest(h, gLines[i].text, (size_t)gLines[i].len, &written);
|
||||
st = SysWriteFile(&writeReq);
|
||||
if (st != 0) { SysCloseHandle(h); msg_set("save failed: write error"); return; }
|
||||
}
|
||||
if (i + 1 < gLineCount) {
|
||||
const char nl = '\n';
|
||||
st = SysWriteFile(h, &nl, 1, &written);
|
||||
SYS_FILE_IO_REQUEST newlineReq = SysMakeFileIoRequest(h, &nl, 1, &written);
|
||||
st = SysWriteFile(&newlineReq);
|
||||
if (st != 0) { SysCloseHandle(h); msg_set("save failed: write error"); return; }
|
||||
}
|
||||
}
|
||||
@@ -418,7 +420,9 @@ static void load_file(void) {
|
||||
char buf[512];
|
||||
size_t got = 0;
|
||||
bool truncated = false;
|
||||
while (SysReadFile(h, buf, sizeof(buf), &got) == 0 && got > 0) {
|
||||
for (;;) {
|
||||
SYS_FILE_IO_REQUEST readReq = SysMakeFileIoRequest(h, buf, sizeof(buf), &got);
|
||||
if (SysReadFile(&readReq) != 0 || got == 0) break;
|
||||
for (size_t i = 0; i < got; i++) {
|
||||
char c = buf[i];
|
||||
if (c == '\r') continue;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
static int u64_to_string(uint64_t n, char* buf) {
|
||||
int i = 0;
|
||||
if (n == 0) {
|
||||
buf[i++] = '0';
|
||||
buf[i] = '\0';
|
||||
return i;
|
||||
}
|
||||
char rev[24];
|
||||
int r_idx = 0;
|
||||
while (n > 0) {
|
||||
rev[r_idx++] = (char)('0' + (n % 10));
|
||||
n /= 10;
|
||||
}
|
||||
while (r_idx > 0) {
|
||||
buf[i++] = rev[--r_idx];
|
||||
}
|
||||
buf[i] = '\0';
|
||||
return i;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
SYS_CONSOLE_INFO console;
|
||||
SYS_MEMORY_STATUS mem;
|
||||
char metricsBuf[128];
|
||||
|
||||
while (true) {
|
||||
if (SysGetConsoleInfo(&console) == SYS_STATUS_SUCCESS &&
|
||||
SysGetMemoryStatus(&mem) == SYS_STATUS_SUCCESS) {
|
||||
|
||||
// If the system isn't reporting columns correctly, default to a safe standard width
|
||||
int columns = (console.Columns > 0) ? (int)console.Columns : 80;
|
||||
|
||||
char freeStr[24], poolStr[24], heapStr[24];
|
||||
u64_to_string(mem.FreePhysicalBytes / 1024u, freeStr);
|
||||
u64_to_string(mem.KernelPoolBytes / 1024u, poolStr);
|
||||
u64_to_string(mem.UserHeapBytes / 1024u, heapStr);
|
||||
|
||||
metricsBuf[0] = '\0';
|
||||
strcat(metricsBuf, "[ Free: ");
|
||||
strcat(metricsBuf, freeStr);
|
||||
strcat(metricsBuf, "K | Pool: ");
|
||||
strcat(metricsBuf, poolStr);
|
||||
strcat(metricsBuf, "K | Heap: ");
|
||||
strcat(metricsBuf, heapStr);
|
||||
strcat(metricsBuf, "K ]");
|
||||
|
||||
size_t textLen = strlen(metricsBuf);
|
||||
int targetX = columns - (int)textLen;
|
||||
int targetY = 0;
|
||||
|
||||
if (targetX >= 0) {
|
||||
size_t written = 0;
|
||||
|
||||
// Loop through the string and use the console's dedicated text filling API
|
||||
// This utilizes the console's native font configuration instead of drawing raw screen bits
|
||||
for (size_t i = 0; i < textLen; i++) {
|
||||
SysFillConsoleCharacter(metricsBuf[i], targetX + (int)i, targetY, 1, &written);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SysSleep(500);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -147,6 +147,8 @@ static int runCommand(const char* cmd) {
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
if (pi.hThread && pi.hThread != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(pi.hThread);
|
||||
if (pi.hProcess && pi.hProcess != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(pi.hProcess);
|
||||
FlushConsoleInputBuffer(hIn);
|
||||
} else {
|
||||
print("shell: launch failed\n");
|
||||
|
||||
+209
-95
@@ -1,110 +1,231 @@
|
||||
// User application entry points for video.
|
||||
// Native NTLite Bink video player.
|
||||
//
|
||||
// This application intentionally uses the native syscall ABI directly. Do not
|
||||
// include <windows.h> here; Win32 compatibility belongs in the Win32 personality
|
||||
// layer, not in native apps.
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
#include <ntlite/syscall.h>
|
||||
#include "bink.h"
|
||||
|
||||
// Implements the command Line Second Token operation.
|
||||
static const char* commandLineSecondToken(const char* cmdline, char* out, size_t outSize) {
|
||||
if (!out || outSize == 0) return "video.bk2";
|
||||
strcpy(out, "video.bk2");
|
||||
if (!cmdline) return out;
|
||||
#define VIDEO_DEFAULT_PATH "video.bk2"
|
||||
|
||||
while (*cmdline == ' ' || *cmdline == '\t') cmdline++;
|
||||
if (*cmdline == '\0') return out;
|
||||
static void print_span(const char* s, size_t len) {
|
||||
if (!s || !len) return;
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(s, len) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
if (*cmdline == '"') {
|
||||
cmdline++;
|
||||
while (*cmdline && *cmdline != '"') cmdline++;
|
||||
if (*cmdline == '"') cmdline++;
|
||||
static void print(const char* s) {
|
||||
if (!s) return;
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) {
|
||||
print("0");
|
||||
return;
|
||||
}
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i) {
|
||||
char c = buf[--i];
|
||||
print_span(&c, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("video: ");
|
||||
print(what);
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
for (;;) SysExitProcess(1);
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static SYS_STRING_VIEW command_line_view(void) {
|
||||
SYS_PEB* peb = SysGetPeb();
|
||||
if (!peb || peb->Magic != SYS_PEB_MAGIC)
|
||||
return SYS_SV_NULL;
|
||||
return peb->CommandLine;
|
||||
}
|
||||
|
||||
static int sv_is_space(char c) {
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
static SYS_STRING_VIEW skip_one_token(SYS_STRING_VIEW s) {
|
||||
size_t i = 0;
|
||||
while (i < s.Length && sv_is_space(s.Data[i]))
|
||||
i++;
|
||||
|
||||
if (i < s.Length && s.Data[i] == '"') {
|
||||
i++;
|
||||
while (i < s.Length && s.Data[i] != '"')
|
||||
i++;
|
||||
if (i < s.Length && s.Data[i] == '"')
|
||||
i++;
|
||||
} else {
|
||||
while (*cmdline && *cmdline != ' ' && *cmdline != '\t') cmdline++;
|
||||
while (i < s.Length && !sv_is_space(s.Data[i]))
|
||||
i++;
|
||||
}
|
||||
|
||||
while (*cmdline == ' ' || *cmdline == '\t') cmdline++;
|
||||
if (*cmdline == '\0') return out;
|
||||
while (i < s.Length && sv_is_space(s.Data[i]))
|
||||
i++;
|
||||
|
||||
size_t pos = 0;
|
||||
if (*cmdline == '"') {
|
||||
cmdline++;
|
||||
while (*cmdline && *cmdline != '"' && pos + 1 < outSize)
|
||||
out[pos++] = *cmdline++;
|
||||
return SysSvFromParts(s.Data + i, s.Length - i);
|
||||
}
|
||||
|
||||
static SYS_STRING_VIEW command_line_second_token(SYS_STRING_VIEW cmdline) {
|
||||
SYS_STRING_VIEW rest = skip_one_token(cmdline);
|
||||
if (!rest.Data || rest.Length == 0)
|
||||
return SYS_SV_LITERAL(VIDEO_DEFAULT_PATH);
|
||||
|
||||
size_t begin = 0;
|
||||
size_t end = 0;
|
||||
if (rest.Data[0] == '"') {
|
||||
begin = 1;
|
||||
end = begin;
|
||||
while (end < rest.Length && rest.Data[end] != '"')
|
||||
end++;
|
||||
} else {
|
||||
while (*cmdline && *cmdline != ' ' && *cmdline != '\t' && pos + 1 < outSize)
|
||||
out[pos++] = *cmdline++;
|
||||
begin = 0;
|
||||
end = 0;
|
||||
while (end < rest.Length && !sv_is_space(rest.Data[end]))
|
||||
end++;
|
||||
}
|
||||
out[pos] = '\0';
|
||||
|
||||
if (end <= begin)
|
||||
return SYS_SV_LITERAL(VIDEO_DEFAULT_PATH);
|
||||
|
||||
return SysSvFromParts(rest.Data + begin, end - begin);
|
||||
}
|
||||
|
||||
static const char* path_to_cstr(SYS_STRING_VIEW path, char* out, size_t outSize) {
|
||||
if (!out || outSize == 0)
|
||||
return VIDEO_DEFAULT_PATH;
|
||||
|
||||
if (!path.Data || path.Length == 0)
|
||||
path = SYS_SV_LITERAL(VIDEO_DEFAULT_PATH);
|
||||
|
||||
size_t n = path.Length;
|
||||
if (n + 1 > outSize)
|
||||
n = outSize - 1;
|
||||
|
||||
memcpy(out, path.Data, n);
|
||||
out[n] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
// Runs the requested operation.
|
||||
static uint32_t open_file_view(SYS_STRING_VIEW path, uint32_t* handleOut) {
|
||||
SYS_PATH_HANDLE_REQUEST req = { path, handleOut };
|
||||
return SysOpenFile(&req);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
const char* cmdline = GetCommandLineA();
|
||||
char videoPath[260];
|
||||
const char* path = commandLineSecondToken(cmdline, videoPath, sizeof(videoPath));
|
||||
SYS_STRING_VIEW cmdline = command_line_view();
|
||||
SYS_STRING_VIEW pathView = command_line_second_token(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(path, 0, 0, 0, 3, 0, 0);
|
||||
if (!f || f == (void*)(unsigned long long)0xFFFFFFFF) { // fuckass
|
||||
WriteConsoleA(h, "video: failed to open input file\n", 33, 0, 0);
|
||||
ExitProcess(1);
|
||||
print("video: starting\n");
|
||||
if (cmdline.Data && cmdline.Length) {
|
||||
print_span(cmdline.Data, cmdline.Length);
|
||||
print("\n");
|
||||
}
|
||||
|
||||
unsigned long s = GetFileSize(f, 0);
|
||||
if (!s || s == 0xFFFFFFFF) {
|
||||
WriteConsoleA(h, "video: GetFileSize failed!\n", 27, 0, 0);
|
||||
ExitProcess(1);
|
||||
}
|
||||
void* buf = HeapAlloc(0, 0, s);
|
||||
if (!buf) {
|
||||
WriteConsoleA(h, "video: HeapAlloc failed\n", 24, 0, 0);
|
||||
ExitProcess(1);
|
||||
uint32_t file = 0;
|
||||
uint32_t status = open_file_view(pathView, &file);
|
||||
if (status != SYS_STATUS_SUCCESS) {
|
||||
char pathBuf[260];
|
||||
print("video: failed to open input file: ");
|
||||
print(path_to_cstr(pathView, pathBuf, sizeof(pathBuf)));
|
||||
print("\n");
|
||||
fail_status("SysOpenFile", status);
|
||||
}
|
||||
|
||||
int r = ReadFile(f, buf, s, 0, 0);
|
||||
if (!r) {
|
||||
WriteConsoleA(h, "video: ReadFile failed\n", 23, 0, 0);
|
||||
ExitProcess(1);
|
||||
uint64_t fileSize = SysGetFileSize(file);
|
||||
if (fileSize == 0) {
|
||||
SysCloseHandle(file);
|
||||
print("video: input file is empty\n");
|
||||
exit_now(1);
|
||||
}
|
||||
WriteConsoleA(h, "video: input file loaded\n", 25, 0, 0);
|
||||
|
||||
CloseHandle(h);
|
||||
|
||||
HBINK b = BinkOpen(buf, BINKFROMMEMORY);
|
||||
uint32_t mapping = 0;
|
||||
status = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, fileSize, &mapping);
|
||||
if (status != SYS_STATUS_SUCCESS || !mapping) {
|
||||
SysCloseHandle(file);
|
||||
fail_status("SysCreateFileMapping", status);
|
||||
}
|
||||
|
||||
void* buf = 0;
|
||||
status = SysMapFile(mapping, 0, (size_t)fileSize,
|
||||
SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &buf);
|
||||
if (status != SYS_STATUS_SUCCESS || !buf) {
|
||||
SysCloseHandle(mapping);
|
||||
SysCloseHandle(file);
|
||||
fail_status("SysMapFile", status);
|
||||
}
|
||||
|
||||
// The mapped VAD owns the file reference now; keep only the mapped pointer
|
||||
// alive while Bink is open. This avoids a second whole-file heap allocation
|
||||
// and lets the VM fault file pages in on demand.
|
||||
SysCloseHandle(mapping);
|
||||
SysCloseHandle(file);
|
||||
|
||||
print("video: input file mapped\n");
|
||||
|
||||
HBINK b = BinkOpen((const char*)buf, BINKFROMMEMORY);
|
||||
if (!b) {
|
||||
WriteConsoleA(h, "video: BinkOpen failed: ", 24, 0, 0);
|
||||
print("video: BinkOpen failed: ");
|
||||
const char* err = BinkGetError();
|
||||
WriteConsoleA(h, err, (unsigned int)strlen(err), 0, 0);
|
||||
WriteConsoleA(h, "\n", 1, 0, 0);
|
||||
ExitProcess(1);
|
||||
print(err ? err : "unknown");
|
||||
print("\n");
|
||||
SysFreeVm(buf);
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
uint32_t width = b->Width;
|
||||
uint32_t width = b->Width;
|
||||
uint32_t height = b->Height;
|
||||
|
||||
uint32_t* fb = HeapAlloc(0, 0, width * height * sizeof(uint32_t));
|
||||
if (!fb) {
|
||||
WriteConsoleA(h, "video: framebuffer alloc failed\n", 32, 0, 0);
|
||||
ExitProcess(1);
|
||||
if (!width || !height) {
|
||||
print("video: invalid video dimensions\n");
|
||||
BinkClose(b);
|
||||
SysFreeVm(buf);
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
// Prepare the Win32 BITMAPINFO structure.
|
||||
BITMAPINFO bmi;
|
||||
memset(&bmi, 0, sizeof(bmi));
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = width;
|
||||
bmi.bmiHeader.biHeight = -(LONG)height; // Negative means top-down drawing!
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
uint32_t surface = 0;
|
||||
status = SysCreateSurface(width, height, SYS_SURFACE_FORMAT_XRGB8888,
|
||||
SYS_SURFACE_FLAG_NONE, &surface);
|
||||
if (status != SYS_STATUS_SUCCESS || !surface) {
|
||||
BinkClose(b);
|
||||
SysFreeVm(buf);
|
||||
fail_status("SysCreateSurface", status);
|
||||
}
|
||||
|
||||
void* surfaceBase = 0;
|
||||
uint32_t pitchBytes = 0;
|
||||
status = SysMapSurface(surface, &surfaceBase, &pitchBytes);
|
||||
if (status != SYS_STATUS_SUCCESS || !surfaceBase || pitchBytes < width * sizeof(uint32_t)) {
|
||||
SysCloseHandle(surface);
|
||||
BinkClose(b);
|
||||
SysFreeVm(buf);
|
||||
fail_status("SysMapSurface", status);
|
||||
}
|
||||
|
||||
print("video: native surface ready\n");
|
||||
|
||||
DISPLAY_RECT fullFrame = { 0, 0, (int32_t)width, (int32_t)height };
|
||||
|
||||
while (1) {
|
||||
if (BinkWait(b)) {
|
||||
@@ -120,33 +241,26 @@ int main(void) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BinkCopyToBuffer(b, fb, width * 4, height, 0, 0,
|
||||
BINKSURFACE32);
|
||||
BinkCopyToBuffer(b, surfaceBase, pitchBytes, height, 0, 0, BINKSURFACE32);
|
||||
|
||||
// Welcome to the GDI pipeline!
|
||||
PAINTSTRUCT ps;
|
||||
// Using NULL/0 as the HWND since we don't have a real window handle yet.
|
||||
HDC hdc = BeginPaint((HWND)0, &ps);
|
||||
|
||||
StretchDIBits(
|
||||
hdc,
|
||||
0, 0, width, height, // Destination rect
|
||||
0, 0, width, height, // Source rect
|
||||
fb,
|
||||
&bmi,
|
||||
DIB_RGB_COLORS,
|
||||
SRCCOPY
|
||||
);
|
||||
|
||||
EndPaint((HWND)0, &ps);
|
||||
status = SysPresentSurface(surface, &fullFrame, 1);
|
||||
if (status != SYS_STATUS_SUCCESS) {
|
||||
SysCloseHandle(surface);
|
||||
BinkClose(b);
|
||||
SysFreeVm(buf);
|
||||
fail_status("SysPresentSurface", status);
|
||||
}
|
||||
|
||||
if (b->FrameNum == b->Frames)
|
||||
break;
|
||||
|
||||
BinkNextFrame(b);
|
||||
}
|
||||
|
||||
WriteConsoleA(h, "video: goodbye\n", 15, 0, 0);
|
||||
|
||||
ExitProcess(0);
|
||||
SysCloseHandle(surface);
|
||||
BinkClose(b);
|
||||
SysFreeVm(buf);
|
||||
|
||||
print("video: goodbye\n");
|
||||
exit_now(0);
|
||||
}
|
||||
@@ -72,12 +72,27 @@ extern int errno; // defined in posix_fd.c; single global in v1
|
||||
#define SEEK_CUR 1
|
||||
#define SEEK_END 2
|
||||
|
||||
// mmap() protection and mapping flags.
|
||||
#define PROT_NONE 0x0
|
||||
#define PROT_READ 0x1
|
||||
#define PROT_WRITE 0x2
|
||||
#define PROT_EXEC 0x4
|
||||
#define MAP_SHARED 0x01
|
||||
#define MAP_PRIVATE 0x02
|
||||
#define MAP_FAILED ((void*)(intptr_t)-1)
|
||||
#define MS_SYNC 0x04
|
||||
#define MS_ASYNC 0x01
|
||||
#define MS_INVALIDATE 0x02
|
||||
|
||||
// Scalar types.
|
||||
typedef int32_t pid_t;
|
||||
typedef int32_t off_t;
|
||||
typedef uint32_t mode_t;
|
||||
typedef int64_t ssize_t;
|
||||
|
||||
__declspec(dllexport) void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
|
||||
__declspec(dllexport) int munmap(void* addr, size_t length);
|
||||
__declspec(dllexport) int msync(void* addr, size_t length, int flags);
|
||||
|
||||
// Sockets.
|
||||
typedef uint16_t sa_family_t;
|
||||
@@ -188,7 +203,7 @@ __declspec(dllexport) int getaddrinfo(const char* node, const char* service, con
|
||||
__declspec(dllexport) void freeaddrinfo(struct addrinfo* res);
|
||||
|
||||
// pthread types.
|
||||
typedef struct { uint32_t Tid; } pthread_t;
|
||||
typedef struct { uint32_t Handle; 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;
|
||||
|
||||
@@ -81,7 +81,8 @@ __declspec(dllexport) ssize_t read(int fd, void* buf, size_t count) {
|
||||
return (ssize_t)count;
|
||||
case FD_FILE: {
|
||||
size_t got = 0;
|
||||
uint32_t s = SysReadFile(e->KHandle, buf, count, &got);
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(e->KHandle, buf, count, &got);
|
||||
uint32_t s = SysReadFile(&req);
|
||||
if (s != 0 && got == 0) { errno = PosixStatusToErrno(s); return -1; }
|
||||
return (ssize_t)got;
|
||||
}
|
||||
@@ -115,7 +116,8 @@ __declspec(dllexport) ssize_t write(int fd, const void* buf, size_t count) {
|
||||
return (ssize_t)count;
|
||||
case FD_FILE: {
|
||||
size_t written = 0;
|
||||
uint32_t s = SysWriteFile(e->KHandle, buf, count, &written);
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(e->KHandle, buf, count, &written);
|
||||
uint32_t s = SysWriteFile(&req);
|
||||
if (s != 0 && written == 0) { errno = PosixStatusToErrno(s); return -1; }
|
||||
return (ssize_t)written;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// POSIX compatibility support for mmap-style file mappings.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <ntlite/syscall.h>
|
||||
#include "posix.h"
|
||||
#include "posix_fd.h"
|
||||
|
||||
static uint32_t PosixProtToSysMapAccess(int prot, int flags) {
|
||||
uint32_t access = 0;
|
||||
if (prot & PROT_EXEC) access |= SYS_MAP_ACCESS_EXECUTE;
|
||||
if (prot & PROT_WRITE) {
|
||||
access |= (flags & MAP_SHARED) ? SYS_MAP_ACCESS_WRITE : SYS_MAP_ACCESS_COPY;
|
||||
} else if (flags & MAP_PRIVATE) {
|
||||
access |= SYS_MAP_ACCESS_COPY;
|
||||
}
|
||||
if (prot & PROT_READ) access |= SYS_MAP_ACCESS_READ;
|
||||
if (!access) access = SYS_MAP_ACCESS_READ;
|
||||
return access;
|
||||
}
|
||||
|
||||
__declspec(dllexport) void* mmap(void* addr, size_t length, int prot,
|
||||
int flags, int fd, off_t offset) {
|
||||
if (addr || length == 0 || offset < 0 || ((uint64_t)offset & 0xFFFu)) {
|
||||
errno = EINVAL;
|
||||
return MAP_FAILED;
|
||||
}
|
||||
|
||||
if (!(flags & MAP_PRIVATE) && !(flags & MAP_SHARED)) {
|
||||
errno = EINVAL;
|
||||
return MAP_FAILED;
|
||||
}
|
||||
|
||||
uint32_t pid = SysGetCurrentPid();
|
||||
FD_ENTRY* e = FdGet(pid, fd);
|
||||
if (!e) return MAP_FAILED;
|
||||
if (e->Type != FD_FILE) {
|
||||
errno = EBADF;
|
||||
return MAP_FAILED;
|
||||
}
|
||||
|
||||
uint64_t maxSize = (uint64_t)offset + (uint64_t)length;
|
||||
if (maxSize < (uint64_t)offset) {
|
||||
errno = EINVAL;
|
||||
return MAP_FAILED;
|
||||
}
|
||||
|
||||
uint32_t protect = (prot & PROT_WRITE) ? SYS_VM_READWRITE : SYS_VM_READ;
|
||||
if (prot & PROT_EXEC)
|
||||
protect = (prot & PROT_WRITE) ? SYS_VM_EXECUTE_READWRITE : SYS_VM_EXECUTE_READ;
|
||||
|
||||
uint32_t mapping = 0;
|
||||
uint32_t s = SysCreateFileMapping(e->KHandle, protect, SYS_MAP_FLAG_NONE,
|
||||
maxSize, &mapping);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
errno = PosixStatusToErrno(s);
|
||||
return MAP_FAILED;
|
||||
}
|
||||
|
||||
void* base = (void*)0;
|
||||
s = SysMapFile(mapping, (uint64_t)offset, length,
|
||||
PosixProtToSysMapAccess(prot, flags),
|
||||
SYS_MAP_FLAG_NONE, &base);
|
||||
SysCloseHandle(mapping);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
errno = PosixStatusToErrno(s);
|
||||
return MAP_FAILED;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int munmap(void* addr, size_t length) {
|
||||
(void)length;
|
||||
if (!addr) { errno = EINVAL; return -1; }
|
||||
uint32_t s = SysFreeVm(addr);
|
||||
if (s != SYS_STATUS_SUCCESS) { errno = PosixStatusToErrno(s); return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
__declspec(dllexport) int msync(void* addr, size_t length, int flags) {
|
||||
(void)addr;
|
||||
(void)length;
|
||||
(void)flags;
|
||||
return 0;
|
||||
}
|
||||
@@ -126,7 +126,7 @@ __declspec(dllexport) pid_t waitpid(pid_t pid, int* status, int options) {
|
||||
uint32_t handle = ProcTableFind(pid);
|
||||
if (!handle) { errno = ESRCH; return -1; }
|
||||
|
||||
SysWaitForProcess(handle);
|
||||
SysWait(handle, SYS_WAIT_FLAG_NONE);
|
||||
|
||||
int exitCode = 0;
|
||||
SysGetProcessExitCode(handle, &exitCode);
|
||||
|
||||
@@ -84,28 +84,41 @@ __declspec(dllexport) int pthread_create(pthread_t* t, const pthread_attr_t* att
|
||||
tr->Pid = pid;
|
||||
|
||||
uint32_t tid = 0;
|
||||
uint32_t s = SysCreateThread((uint64_t)PthreadTrampoline,
|
||||
(uint64_t)tr, 1, &tid);
|
||||
uint32_t handle = 0;
|
||||
SYS_CREATE_THREAD_REQUEST req = {
|
||||
(uintptr_t)PthreadTrampoline,
|
||||
(uintptr_t)tr,
|
||||
1,
|
||||
0,
|
||||
&handle,
|
||||
&tid
|
||||
};
|
||||
uint32_t s = SysCreateThread(&req);
|
||||
if (s != 0) {
|
||||
free(tr);
|
||||
return EAGAIN;
|
||||
}
|
||||
// The trampoline allocates the retval slot itself after it knows its real
|
||||
// TID, avoiding the creator/new-thread race.
|
||||
if (t) t->Tid = tid;
|
||||
if (t) {
|
||||
t->Handle = handle;
|
||||
t->Tid = tid;
|
||||
} else {
|
||||
SysCloseHandle(handle);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Implements the pthread join operation.
|
||||
__declspec(dllexport) 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;
|
||||
uint32_t s = SysWait(t.Handle, SYS_WAIT_FLAG_NONE);
|
||||
if (s != 0) return ESRCH;
|
||||
|
||||
PTHREAD_SLOT* slot = PthreadSlotFind(pid, t.Tid);
|
||||
if (retval) *retval = slot ? slot->RetVal : (void*)0;
|
||||
if (slot) slot->Used = false;
|
||||
SysCloseHandle(t.Handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -138,7 +151,7 @@ __declspec(dllexport) int pthread_mutex_destroy(pthread_mutex_t* m) {
|
||||
|
||||
// Implements the pthread mutex lock operation.
|
||||
__declspec(dllexport) int pthread_mutex_lock(pthread_mutex_t* m) {
|
||||
uint32_t s = SysAcquireMutex(m->Handle);
|
||||
uint32_t s = SysWait(m->Handle, SYS_WAIT_FLAG_NONE);
|
||||
return s == 0 ? 0 : EINVAL;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,101 +63,16 @@ static int* srcX_map = NULL;
|
||||
static int currentDestCapacity = 0;
|
||||
static int currentXMapCapacity = 0;
|
||||
|
||||
__declspec(dllexport) int StretchDIBits(
|
||||
HDC hdc,
|
||||
int xDest, int yDest, int DestWidth, int DestHeight,
|
||||
__declspec(dllexport) int StretchDIBits(HDC hdc, int xDest, int yDest, int DestWidth, int DestHeight,
|
||||
int xSrc, int ySrc, int SrcWidth, int SrcHeight,
|
||||
const void *lpBits, const BITMAPINFO *lpbmi,
|
||||
UINT iUsage, DWORD rop)
|
||||
{
|
||||
(void)hdc; (void)lpbmi; (void)iUsage; (void)rop;
|
||||
(void)hdc; (void)xDest; (void)yDest; (void)DestWidth; (void)DestHeight;
|
||||
(void)xSrc; (void)ySrc; (void)SrcWidth; (void)SrcHeight;
|
||||
(void)lpBits; (void)lpbmi; (void)iUsage; (void)rop;
|
||||
|
||||
// Safety check to prevent division by zero.
|
||||
if (DestWidth <= 0 || DestHeight <= 0 || SrcWidth <= 0 || SrcHeight <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Handle trivial case (1:1 copy) to bypass allocation and scaling overhead.
|
||||
if (DestWidth == SrcWidth && DestHeight == SrcHeight && xSrc == 0 && ySrc == 0) {
|
||||
SysBlitScreen(xDest, yDest, DestWidth, DestHeight, (const uint32_t*)lpBits);
|
||||
return DestHeight;
|
||||
}
|
||||
|
||||
const uint32_t* srcPixels = (const uint32_t*)lpBits;
|
||||
|
||||
// Only reallocate if the new size is LARGER than what we already have.
|
||||
int requiredPixels = DestWidth * DestHeight;
|
||||
if (requiredPixels > currentDestCapacity) {
|
||||
free(destBuffer);
|
||||
destBuffer = (uint32_t*)malloc(requiredPixels * sizeof(uint32_t));
|
||||
currentDestCapacity = requiredPixels;
|
||||
}
|
||||
|
||||
if (DestWidth > currentXMapCapacity) {
|
||||
free(srcX_map);
|
||||
srcX_map = (int*)malloc(DestWidth * sizeof(int));
|
||||
currentXMapCapacity = DestWidth;
|
||||
}
|
||||
|
||||
for (int dx = 0; dx < DestWidth; ++dx) {
|
||||
// Nearest neighbor coordinate mapping.
|
||||
srcX_map[dx] = xSrc + (dx * SrcWidth) / DestWidth;
|
||||
|
||||
// Clamp to safe bounds.
|
||||
if (srcX_map[dx] < 0) srcX_map[dx] = 0;
|
||||
if (srcX_map[dx] >= SrcWidth) srcX_map[dx] = SrcWidth - 1;
|
||||
}
|
||||
|
||||
// Process the image row by row.
|
||||
for (int dy = 0; dy < DestHeight; ++dy) {
|
||||
// Map destination Y to source Y.
|
||||
int sy = ySrc + (dy * SrcHeight) / DestHeight;
|
||||
if (sy < 0) sy = 0;
|
||||
if (sy >= SrcHeight) sy = SrcHeight - 1;
|
||||
|
||||
// Assumes stride == SrcWidth (tightly packed 32bpp).
|
||||
const uint32_t* srcRow = srcPixels + (sy * SrcWidth);
|
||||
uint32_t* dstRow = destBuffer + (dy * DestWidth);
|
||||
|
||||
int dx = 0;
|
||||
|
||||
#if defined(__SSE2__) || defined(_M_X64)
|
||||
// Process 4 pixels (16 bytes) per loop iteration using SSE2.
|
||||
for (; dx <= DestWidth - 4; dx += 4) {
|
||||
// Gather 4 source pixels using our precomputed X map.
|
||||
// Note: _mm_set_epi32 takes arguments in reverse order (3, 2, 1, 0).
|
||||
__m128i pixels = _mm_set_epi32(
|
||||
srcRow[srcX_map[dx + 3]],
|
||||
srcRow[srcX_map[dx + 2]],
|
||||
srcRow[srcX_map[dx + 1]],
|
||||
srcRow[srcX_map[dx + 0]]
|
||||
);
|
||||
// Issue a single 128-bit unaligned store.
|
||||
_mm_storeu_si128((__m128i*)&dstRow[dx], pixels);
|
||||
}
|
||||
#elif defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(_M_ARM64)
|
||||
// Process 4 pixels per loop iteration using ARM NEON.
|
||||
for (; dx <= DestWidth - 4; dx += 4) {
|
||||
// Initialize a vector and populate lanes (avoids stack-memory hop).
|
||||
uint32x4_t pixels = vdupq_n_u32(0);
|
||||
pixels = vsetq_lane_u32(srcRow[srcX_map[dx + 0]], pixels, 0);
|
||||
pixels = vsetq_lane_u32(srcRow[srcX_map[dx + 1]], pixels, 1);
|
||||
pixels = vsetq_lane_u32(srcRow[srcX_map[dx + 2]], pixels, 2);
|
||||
pixels = vsetq_lane_u32(srcRow[srcX_map[dx + 3]], pixels, 3);
|
||||
|
||||
// Issue a single 128-bit store.
|
||||
vst1q_u32(&dstRow[dx], pixels);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Epilogue: Handle remaining pixels if DestWidth isn't a multiple of 4.
|
||||
for (; dx < DestWidth; ++dx) {
|
||||
dstRow[dx] = srcRow[srcX_map[dx]];
|
||||
}
|
||||
}
|
||||
|
||||
// Direct mapping to kernel screen blit using the stretched buffer.
|
||||
SysBlitScreen(xDest, yDest, DestWidth, DestHeight, destBuffer);
|
||||
|
||||
return DestHeight;
|
||||
// There is no implicit display blit ABI. Native graphics clients own a
|
||||
// surface and present it explicitly.
|
||||
return 0;
|
||||
}
|
||||
@@ -27,6 +27,8 @@
|
||||
#include <intrin.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
extern size_t _malloc_usable_size(const void* ptr);
|
||||
|
||||
// Types.
|
||||
typedef void* HANDLE;
|
||||
typedef unsigned long DWORD;
|
||||
@@ -58,6 +60,7 @@ typedef struct _CONSOLE_SCREEN_BUFFER_INFO {
|
||||
#define MEM_RELEASE 0x00008000UL
|
||||
#define MEM_FREE 0x00010000UL
|
||||
#define MEM_PRIVATE 0x00020000UL
|
||||
#define MEM_MAPPED 0x00040000UL
|
||||
#define PAGE_NOACCESS 0x01UL
|
||||
#define PAGE_READONLY 0x02UL
|
||||
#define PAGE_READWRITE 0x04UL
|
||||
@@ -65,6 +68,13 @@ typedef struct _CONSOLE_SCREEN_BUFFER_INFO {
|
||||
#define PAGE_EXECUTE 0x10UL
|
||||
#define PAGE_EXECUTE_READ 0x20UL
|
||||
#define PAGE_EXECUTE_READWRITE 0x40UL
|
||||
#define SEC_COMMIT 0x08000000UL
|
||||
|
||||
#define FILE_MAP_COPY 0x00000001UL
|
||||
#define FILE_MAP_WRITE 0x00000002UL
|
||||
#define FILE_MAP_READ 0x00000004UL
|
||||
#define FILE_MAP_EXECUTE 0x00000020UL
|
||||
#define FILE_MAP_ALL_ACCESS 0x000F001FUL
|
||||
|
||||
typedef struct _MEMORY_BASIC_INFORMATION {
|
||||
void* BaseAddress;
|
||||
@@ -90,6 +100,26 @@ static uint32_t WinProtectToSysVm(DWORD protect) {
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t WinFileMappingProtectToSysVm(DWORD protect) {
|
||||
// Strip section attributes; the low byte carries PAGE_* protection.
|
||||
DWORD pageProtect = protect & 0xFFu;
|
||||
if (pageProtect == PAGE_WRITECOPY)
|
||||
return SYS_VM_READ; // MapViewOfFile(FILE_MAP_COPY) asks for the private writable view.
|
||||
return WinProtectToSysVm(pageProtect ? pageProtect : PAGE_READONLY);
|
||||
}
|
||||
|
||||
static uint32_t WinFileMapAccessToSys(DWORD desiredAccess) {
|
||||
uint32_t access = 0;
|
||||
if (desiredAccess & FILE_MAP_EXECUTE) access |= SYS_MAP_ACCESS_EXECUTE;
|
||||
if (desiredAccess & FILE_MAP_COPY) access |= SYS_MAP_ACCESS_COPY;
|
||||
if (desiredAccess & FILE_MAP_WRITE) access |= SYS_MAP_ACCESS_WRITE;
|
||||
if (desiredAccess & FILE_MAP_READ) access |= SYS_MAP_ACCESS_READ;
|
||||
if ((desiredAccess & FILE_MAP_ALL_ACCESS) == FILE_MAP_ALL_ACCESS)
|
||||
access |= SYS_MAP_ACCESS_READ | SYS_MAP_ACCESS_COPY;
|
||||
if (!access) access = SYS_MAP_ACCESS_READ;
|
||||
return access;
|
||||
}
|
||||
|
||||
// Implements the protect Vm To Win operation.
|
||||
static DWORD SysProtectVmToWin(uint32_t protect) {
|
||||
switch (protect) {
|
||||
@@ -153,24 +183,9 @@ typedef struct _WIN32_FIND_DATAA {
|
||||
static inline HANDLE KH_TO_HANDLE(uint32_t kh) { return (HANDLE)(uintptr_t)kh; }
|
||||
static inline uint32_t HANDLE_TO_KH(HANDLE h) { return (uint32_t)(uintptr_t)h; }
|
||||
|
||||
// Thread IDs are not kernel handles. Keep Win32 thread pseudo-handles out of
|
||||
// the low numeric handle namespace so CloseHandle/WaitForSingleObject cannot
|
||||
// accidentally operate on an unrelated file/process handle with the same value.
|
||||
#define WIN32_THREAD_HANDLE_TAG ((uintptr_t)1ULL << 63)
|
||||
#define WIN32_MUTEX_SENTINEL ((uintptr_t)0x4D555458UL) // 'MUTX'
|
||||
#define WIN32_SEMA_SENTINEL ((uintptr_t)0x53454D41UL) // 'SEMA'
|
||||
static inline HANDLE TID_TO_THREAD_HANDLE(uint32_t tid) {
|
||||
return (HANDLE)(WIN32_THREAD_HANDLE_TAG | (uintptr_t)tid);
|
||||
}
|
||||
static inline BOOL IS_THREAD_HANDLE(HANDLE h) {
|
||||
return h && h != INVALID_HANDLE_VALUE && (((uintptr_t)h & WIN32_THREAD_HANDLE_TAG) != 0);
|
||||
}
|
||||
static inline uint32_t THREAD_HANDLE_TO_TID(HANDLE h) {
|
||||
return (uint32_t)((uintptr_t)h & 0xFFFFFFFFu);
|
||||
}
|
||||
static inline BOOL IS_NOOP_SYNC_HANDLE(HANDLE h) {
|
||||
static inline BOOL IS_VALID_KERNEL_HANDLE(HANDLE h) {
|
||||
uintptr_t v = (uintptr_t)h;
|
||||
return v == WIN32_MUTEX_SENTINEL || v == WIN32_SEMA_SENTINEL;
|
||||
return v != 0 && v != (uintptr_t)INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
// Path translator.
|
||||
@@ -268,11 +283,14 @@ static void K32DebugPrintA(const char* s) {
|
||||
}
|
||||
|
||||
#define WIN32_TLS_SLOTS 64
|
||||
#define FLS_MAX_SLOTS 64
|
||||
#define FLS_OUT_OF_INDEXES 0xFFFFFFFFUL
|
||||
|
||||
typedef struct WIN32_THREAD_ENV {
|
||||
DWORD LastError;
|
||||
DWORD Reserved0;
|
||||
void* TlsSlots[WIN32_TLS_SLOTS];
|
||||
void* FlsSlots[FLS_MAX_SLOTS];
|
||||
} WIN32_THREAD_ENV;
|
||||
|
||||
static WIN32_THREAD_ENV gFallbackThreadEnv;
|
||||
@@ -418,11 +436,27 @@ __declspec(dllexport) uint64_t GetTickCount64(void) {
|
||||
}
|
||||
|
||||
// Memory (heap argument is ignored - single user-mode heap).
|
||||
#define HEAP_ZERO_MEMORY 0x00000008UL
|
||||
#define HEAP_REALLOC_IN_PLACE_ONLY 0x00000010UL
|
||||
|
||||
__declspec(dllexport) void* HeapAlloc(void* heap, DWORD flags, size_t size) {
|
||||
(void)heap; (void)flags;
|
||||
(void)heap;
|
||||
if (!size) return NULL;
|
||||
|
||||
void* ptr = malloc(size);
|
||||
if (!ptr) gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY
|
||||
if (!ptr) {
|
||||
gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Bink and the Microsoft CRT routinely allocate internal state with
|
||||
// HEAP_ZERO_MEMORY. Ignoring this flag only works accidentally while the
|
||||
// heap keeps receiving fresh demand-zero pages; under low memory / swap the
|
||||
// allocator reuses dirty freed blocks and stale pitch/pointer fields can
|
||||
// corrupt the decoder.
|
||||
if (flags & HEAP_ZERO_MEMORY)
|
||||
memset(ptr, 0, size);
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -436,9 +470,28 @@ __declspec(dllexport) BOOL HeapFree(void* heap, DWORD flags, void* ptr) {
|
||||
|
||||
__declspec(dllexport) void* HeapReAlloc(void* heap, DWORD flags,
|
||||
void* ptr, size_t size) {
|
||||
(void)heap; (void)flags;
|
||||
(void)heap;
|
||||
|
||||
if (flags & HEAP_REALLOC_IN_PLACE_ONLY) {
|
||||
// The user heap does not expose an in-place grow primitive. Preserve
|
||||
// Windows semantics by failing instead of silently moving the block.
|
||||
size_t oldSize = _malloc_usable_size(ptr);
|
||||
if (oldSize != (size_t)-1 && oldSize >= size)
|
||||
return ptr;
|
||||
gLastError = 8; // ERROR_NOT_ENOUGH_MEMORY
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t oldSize = ptr ? _malloc_usable_size(ptr) : 0;
|
||||
void* newptr = realloc(ptr, size);
|
||||
if (!newptr && size) gLastError = 8;
|
||||
if (!newptr && size) {
|
||||
gLastError = 8;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (newptr && (flags & HEAP_ZERO_MEMORY) && oldSize != (size_t)-1 && size > oldSize)
|
||||
memset((unsigned char*)newptr + oldSize, 0, size - oldSize);
|
||||
|
||||
return newptr;
|
||||
}
|
||||
|
||||
@@ -508,10 +561,88 @@ __declspec(dllexport) size_t VirtualQuery(const void* lpAddress,
|
||||
lpBuffer->RegionSize = info.PageCount * 4096u;
|
||||
lpBuffer->State = MEM_COMMIT;
|
||||
lpBuffer->Protect = protect;
|
||||
lpBuffer->Type = MEM_PRIVATE;
|
||||
lpBuffer->Type = (info.Type == 7u) ? MEM_MAPPED : MEM_PRIVATE; // 7 == MmVadMappedFile
|
||||
return sizeof(MEMORY_BASIC_INFORMATION);
|
||||
}
|
||||
|
||||
__declspec(dllexport) HANDLE CreateFileMappingA(HANDLE hFile,
|
||||
void* lpFileMappingAttributes,
|
||||
DWORD flProtect,
|
||||
DWORD dwMaximumSizeHigh,
|
||||
DWORD dwMaximumSizeLow,
|
||||
const char* lpName) {
|
||||
(void)lpFileMappingAttributes;
|
||||
(void)lpName;
|
||||
if (!hFile || hFile == INVALID_HANDLE_VALUE) {
|
||||
gLastError = 6; // ERROR_INVALID_HANDLE
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t maximumSize = ((uint64_t)dwMaximumSizeHigh << 32) | (uint64_t)dwMaximumSizeLow;
|
||||
uint32_t kh = 0;
|
||||
uint32_t s = SysCreateFileMapping(HANDLE_TO_KH(hFile),
|
||||
WinFileMappingProtectToSysVm(flProtect),
|
||||
SYS_MAP_FLAG_NONE,
|
||||
maximumSize,
|
||||
&kh);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
gLastError = (s == SYS_STATUS_NOT_SUPPORTED) ? 120 : ((s == SYS_STATUS_NO_MEMORY) ? 8 : 87);
|
||||
return NULL;
|
||||
}
|
||||
return KH_TO_HANDLE(kh);
|
||||
}
|
||||
|
||||
__declspec(dllexport) HANDLE CreateFileMappingW(HANDLE hFile,
|
||||
void* lpFileMappingAttributes,
|
||||
DWORD flProtect,
|
||||
DWORD dwMaximumSizeHigh,
|
||||
DWORD dwMaximumSizeLow,
|
||||
const unsigned short* lpName) {
|
||||
(void)lpName;
|
||||
return CreateFileMappingA(hFile, lpFileMappingAttributes, flProtect,
|
||||
dwMaximumSizeHigh, dwMaximumSizeLow, NULL);
|
||||
}
|
||||
|
||||
__declspec(dllexport) void* MapViewOfFile(HANDLE hFileMappingObject,
|
||||
DWORD dwDesiredAccess,
|
||||
DWORD dwFileOffsetHigh,
|
||||
DWORD dwFileOffsetLow,
|
||||
size_t dwNumberOfBytesToMap) {
|
||||
if (!hFileMappingObject || hFileMappingObject == INVALID_HANDLE_VALUE) {
|
||||
gLastError = 6;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t offset = ((uint64_t)dwFileOffsetHigh << 32) | (uint64_t)dwFileOffsetLow;
|
||||
void* base = NULL;
|
||||
uint32_t s = SysMapFile(HANDLE_TO_KH(hFileMappingObject),
|
||||
offset,
|
||||
dwNumberOfBytesToMap,
|
||||
WinFileMapAccessToSys(dwDesiredAccess),
|
||||
SYS_MAP_FLAG_NONE,
|
||||
&base);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
gLastError = (s == SYS_STATUS_NOT_SUPPORTED) ? 120 : ((s == SYS_STATUS_NO_MEMORY) ? 8 : 87);
|
||||
return NULL;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
__declspec(dllexport) BOOL UnmapViewOfFile(const void* lpBaseAddress) {
|
||||
if (!lpBaseAddress) { gLastError = 87; return FALSE; }
|
||||
uint32_t s = SysFreeVm((void*)lpBaseAddress);
|
||||
if (s != SYS_STATUS_SUCCESS) { gLastError = 87; return FALSE; }
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
__declspec(dllexport) BOOL FlushViewOfFile(const void* lpBaseAddress, size_t dwNumberOfBytesToFlush) {
|
||||
(void)dwNumberOfBytesToFlush;
|
||||
if (!lpBaseAddress) { gLastError = 87; return FALSE; }
|
||||
// Native flush is file-handle based for now; unmap/close also harvests dirty
|
||||
// PTEs, so FlushViewOfFile remains a successful cache-coherency hint.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// --- Win32 API ---.
|
||||
|
||||
// GetAsyncKeyState implementation over the canonical input key namespace.
|
||||
@@ -720,7 +851,7 @@ __declspec(dllexport) HANDLE CreateFileA(const char* name,
|
||||
// GetFileSize - fills optional high-DWORD pointer, returns low DWORD.
|
||||
// Returns 0xFFFFFFFF on error, matching Win32 behaviour.
|
||||
__declspec(dllexport) DWORD GetFileSize(HANDLE hFile, DWORD* fileSizeHigh) {
|
||||
if (!hFile || hFile == INVALID_HANDLE_VALUE || IS_THREAD_HANDLE(hFile)) { gLastError = 6; return 0xFFFFFFFFUL; }
|
||||
if (!hFile || hFile == INVALID_HANDLE_VALUE) { gLastError = 6; return 0xFFFFFFFFUL; }
|
||||
uint64_t sz = SysGetFileSize(HANDLE_TO_KH(hFile));
|
||||
if (fileSizeHigh) *fileSizeHigh = (DWORD)(sz >> 32);
|
||||
return (DWORD)(sz & 0xFFFFFFFFULL);
|
||||
@@ -731,25 +862,27 @@ __declspec(dllexport) BOOL ReadFile(HANDLE hFile, void* buf, DWORD toRead,
|
||||
DWORD* bytesRead, void* overlapped) {
|
||||
(void)overlapped;
|
||||
if (bytesRead) *bytesRead = 0;
|
||||
if (!hFile || hFile == INVALID_HANDLE_VALUE || IS_THREAD_HANDLE(hFile) || !buf) {
|
||||
if (!hFile || hFile == INVALID_HANDLE_VALUE || !buf) {
|
||||
gLastError = 6;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
size_t got = 0;
|
||||
uint32_t s = SysReadFile(HANDLE_TO_KH(hFile), buf, (size_t)toRead, &got);
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(HANDLE_TO_KH(hFile), buf, (size_t)toRead, &got);
|
||||
uint32_t s = SysReadFile(&req);
|
||||
if (bytesRead) *bytesRead = (DWORD)got;
|
||||
|
||||
// Harden callers that accidentally look past *bytesRead. EOF/short reads
|
||||
// are legal for files, but consuming stale stack/heap bytes after them makes
|
||||
// the failure mode look like random game-data corruption.
|
||||
if (got < (size_t)toRead)
|
||||
memset((unsigned char*)buf + got, 0, (size_t)toRead - got);
|
||||
|
||||
if (s != 0) {
|
||||
gLastError = 1;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Harden callers that accidentally look past *bytesRead. EOF/short reads
|
||||
// are legal for files, but do not touch the caller's remaining buffer after
|
||||
// a failed read because that can fault in megabytes under low memory.
|
||||
if (got < (size_t)toRead)
|
||||
memset((unsigned char*)buf + got, 0, (size_t)toRead - got);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -758,9 +891,10 @@ __declspec(dllexport) BOOL ReadFile(HANDLE hFile, void* buf, DWORD toRead,
|
||||
__declspec(dllexport) BOOL WriteFile(HANDLE hFile, const void* buf, DWORD toWrite,
|
||||
DWORD* bytesWritten, void* overlapped) {
|
||||
(void)overlapped;
|
||||
if (!hFile || hFile == INVALID_HANDLE_VALUE || IS_THREAD_HANDLE(hFile) || !buf) { gLastError = 6; return FALSE; }
|
||||
if (!hFile || hFile == INVALID_HANDLE_VALUE || !buf) { gLastError = 6; return FALSE; }
|
||||
size_t written = 0;
|
||||
uint32_t s = SysWriteFile(HANDLE_TO_KH(hFile), buf, (size_t)toWrite, &written);
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(HANDLE_TO_KH(hFile), buf, (size_t)toWrite, &written);
|
||||
uint32_t s = SysWriteFile(&req);
|
||||
if (bytesWritten) *bytesWritten = (DWORD)written;
|
||||
if (s != 0) { gLastError = 29; return FALSE; } // ERROR_WRITE_FAULT
|
||||
return TRUE;
|
||||
@@ -770,7 +904,7 @@ __declspec(dllexport) BOOL WriteFile(HANDLE hFile, const void* buf, DWORD toWrit
|
||||
// Returns new position low DWORD, or 0xFFFFFFFF on error.
|
||||
__declspec(dllexport) DWORD SetFilePointer(HANDLE hFile, long dist,
|
||||
long* distHigh, DWORD method) {
|
||||
if (!hFile || hFile == INVALID_HANDLE_VALUE || IS_THREAD_HANDLE(hFile)) {
|
||||
if (!hFile || hFile == INVALID_HANDLE_VALUE) {
|
||||
gLastError = 6;
|
||||
return 0xFFFFFFFFUL;
|
||||
}
|
||||
@@ -813,7 +947,7 @@ __declspec(dllexport) DWORD SetFilePointer(HANDLE hFile, long dist,
|
||||
|
||||
// SetEndOfFile - truncate or extend the file to the current seek position.
|
||||
__declspec(dllexport) BOOL SetEndOfFile(HANDLE hFile) {
|
||||
if (!hFile || hFile == INVALID_HANDLE_VALUE || IS_THREAD_HANDLE(hFile)) { gLastError = 6; return FALSE; }
|
||||
if (!hFile || hFile == INVALID_HANDLE_VALUE) { gLastError = 6; return FALSE; }
|
||||
uint32_t kh = HANDLE_TO_KH(hFile);
|
||||
uint64_t pos = SysGetFilePos(kh);
|
||||
if (pos == (uint64_t)-1) { gLastError = 6; return FALSE; }
|
||||
@@ -834,14 +968,10 @@ __declspec(dllexport) BOOL DeleteFileA(const char* lpFileName) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// CloseHandle - unified close for files, mutexes, semaphores, events, queues.
|
||||
// CloseHandle - unified close for files, threads, mutexes, semaphores, events, queues, and processes.
|
||||
__declspec(dllexport) BOOL CloseHandle(HANDLE hObject) {
|
||||
if (!hObject || hObject == INVALID_HANDLE_VALUE) { gLastError = 6; return FALSE; }
|
||||
|
||||
// Our thread/sentinel sync handles are not entries in the kernel handle table.
|
||||
// There is nothing to close; the thread object is reaped by the scheduler.
|
||||
if (IS_THREAD_HANDLE(hObject) || IS_NOOP_SYNC_HANDLE(hObject)) return TRUE;
|
||||
|
||||
SysCloseHandle(HANDLE_TO_KH(hObject));
|
||||
return TRUE;
|
||||
}
|
||||
@@ -870,8 +1000,11 @@ __declspec(dllexport) BOOL HeapDestroy(HANDLE hHeap) {
|
||||
|
||||
// Implements the heap Size operation.
|
||||
__declspec(dllexport) size_t HeapSize(HANDLE heap, DWORD flags, const void* ptr) {
|
||||
(void)heap; (void)flags; (void)ptr;
|
||||
return (size_t)-1; // Win32 returns (SIZE_T)-1 on error / unsupported
|
||||
(void)heap; (void)flags;
|
||||
size_t size = _malloc_usable_size(ptr);
|
||||
if (size == (size_t)-1)
|
||||
gLastError = 87; // ERROR_INVALID_PARAMETER
|
||||
return size;
|
||||
}
|
||||
|
||||
__declspec(dllexport) BOOL HeapSetInformation(HANDLE HeapHandle,
|
||||
@@ -1050,9 +1183,29 @@ __declspec(dllexport) DWORD GetModuleFileNameA(HANDLE hModule,
|
||||
|
||||
// Implements the terminate Process operation.
|
||||
__declspec(dllexport) BOOL TerminateProcess(HANDLE hProcess, unsigned int uExitCode) {
|
||||
(void)hProcess;
|
||||
SysExitProcess((int)uExitCode);
|
||||
while (true) ;
|
||||
if (hProcess == GetCurrentProcess()) {
|
||||
SysExitProcess((int)uExitCode);
|
||||
while (true) ;
|
||||
}
|
||||
|
||||
if (!hProcess) {
|
||||
gLastError = 6; // ERROR_INVALID_HANDLE
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
uint32_t pid = 0;
|
||||
uint32_t s = SysGetProcessPid(HANDLE_TO_KH(hProcess), &pid);
|
||||
if (s != 0 || pid == 0) {
|
||||
gLastError = 6; // ERROR_INVALID_HANDLE
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
s = SysKillProcess(pid, (int)uExitCode);
|
||||
if (s != 0) {
|
||||
gLastError = 5; // ERROR_ACCESS_DENIED / generic failure for now
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// STARTUPINFOA - only cb matters for our stub.
|
||||
@@ -1099,35 +1252,42 @@ __declspec(dllexport) HANDLE CreateThread(void* lpThreadAttributes,
|
||||
DWORD* lpThreadId) {
|
||||
(void)lpThreadAttributes; (void)dwStackSize; (void)dwCreationFlags;
|
||||
uint32_t tid = 0;
|
||||
uint32_t s = SysCreateThread((uint64_t)(uintptr_t)lpStartAddress,
|
||||
(uint64_t)(uintptr_t)lpParameter,
|
||||
0, &tid);
|
||||
uint32_t kh = 0;
|
||||
SYS_CREATE_THREAD_REQUEST req = {
|
||||
(uintptr_t)lpStartAddress,
|
||||
(uintptr_t)lpParameter,
|
||||
0,
|
||||
0,
|
||||
&kh,
|
||||
&tid
|
||||
};
|
||||
uint32_t s = SysCreateThread(&req);
|
||||
if (s != 0) { gLastError = 11; return INVALID_HANDLE_VALUE; }
|
||||
if (lpThreadId) *lpThreadId = (DWORD)tid;
|
||||
return TID_TO_THREAD_HANDLE(tid);
|
||||
return KH_TO_HANDLE(kh);
|
||||
}
|
||||
|
||||
// WaitForSingleObject - blocks on process handles via SysWaitForProcess;
|
||||
// thread and other handle types are not yet waitable so return immediately.
|
||||
// WaitForSingleObject - blocks on native waitable handles. A zero timeout is
|
||||
// only a probe for mutexes/semaphores until native timeout waits exist.
|
||||
__declspec(dllexport) DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) {
|
||||
(void)dwMilliseconds;
|
||||
if (!hHandle || hHandle == INVALID_HANDLE_VALUE) { gLastError = 6; return 0xFFFFFFFFUL; }
|
||||
|
||||
if (IS_THREAD_HANDLE(hHandle)) {
|
||||
uint32_t tid = THREAD_HANDLE_TO_TID(hHandle);
|
||||
uint32_t s = SysWaitThread(tid);
|
||||
if (s != 0 && s != 0x0022) { gLastError = 6; return 0xFFFFFFFFUL; }
|
||||
return WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
if (IS_NOOP_SYNC_HANDLE(hHandle)) return WAIT_OBJECT_0;
|
||||
if (!IS_VALID_KERNEL_HANDLE(hHandle)) { gLastError = 6; return 0xFFFFFFFFUL; }
|
||||
|
||||
uint32_t kh = HANDLE_TO_KH(hHandle);
|
||||
if (kh) {
|
||||
uint32_t s = SysWaitForProcess(kh);
|
||||
if (s != 0) { gLastError = 6; return 0xFFFFFFFFUL; }
|
||||
if (dwMilliseconds == 0) {
|
||||
uint32_t s = SysTryAcquireMutex(kh);
|
||||
if (s == 0) return WAIT_OBJECT_0;
|
||||
|
||||
s = SysTryWaitSem(kh);
|
||||
if (s == 0) return WAIT_OBJECT_0;
|
||||
|
||||
return WAIT_TIMEOUT;
|
||||
}
|
||||
return WAIT_OBJECT_0;
|
||||
|
||||
uint32_t s = SysWait(kh, SYS_WAIT_FLAG_NONE);
|
||||
if (s == 0) return WAIT_OBJECT_0;
|
||||
|
||||
gLastError = 6;
|
||||
return 0xFFFFFFFFUL;
|
||||
}
|
||||
|
||||
|
||||
@@ -1250,7 +1410,7 @@ __declspec(dllexport) BOOL CreateProcessA(
|
||||
__declspec(dllexport) BOOL GetExitCodeProcess(HANDLE hProcess, DWORD* lpExitCode) {
|
||||
if (!lpExitCode) { gLastError = 87; return FALSE; }
|
||||
uint32_t kh = HANDLE_TO_KH(hProcess);
|
||||
if (!kh || IS_THREAD_HANDLE(hProcess)) { gLastError = 6; return FALSE; } // ERROR_INVALID_HANDLE
|
||||
if (!kh) { gLastError = 6; return FALSE; } // ERROR_INVALID_HANDLE
|
||||
int code = 0;
|
||||
uint32_t s = SysGetProcessExitCode(kh, &code);
|
||||
if (s != 0) { gLastError = 6; return FALSE; }
|
||||
@@ -1791,57 +1951,123 @@ __declspec(dllexport) DWORD GetCurrentDirectoryA(DWORD nBufferLength, char* lpBu
|
||||
|
||||
__declspec(dllexport) HANDLE CreateMutexA(void* lpMutexAttributes, BOOL bInitialOwner,
|
||||
const char* lpName) {
|
||||
(void)lpMutexAttributes; (void)bInitialOwner; (void)lpName;
|
||||
return (HANDLE)WIN32_MUTEX_SENTINEL;
|
||||
(void)lpMutexAttributes; (void)lpName;
|
||||
uint32_t kh = 0;
|
||||
uint32_t s = SysCreateMutex(&kh);
|
||||
if (s != 0 || !kh) { gLastError = 8; return NULL; }
|
||||
if (bInitialOwner) {
|
||||
s = SysWait(kh, SYS_WAIT_FLAG_NONE);
|
||||
if (s != 0) {
|
||||
SysCloseHandle(kh);
|
||||
gLastError = 1;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return KH_TO_HANDLE(kh);
|
||||
}
|
||||
|
||||
// Releases mutex.
|
||||
__declspec(dllexport) BOOL ReleaseMutex(HANDLE hMutex) {
|
||||
(void)hMutex;
|
||||
if (!IS_VALID_KERNEL_HANDLE(hMutex)) { gLastError = 6; return FALSE; }
|
||||
uint32_t s = SysReleaseMutex(HANDLE_TO_KH(hMutex));
|
||||
if (s != 0) { gLastError = 6; return FALSE; }
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
__declspec(dllexport) HANDLE CreateSemaphoreA(void* lpSemaphoreAttributes,
|
||||
long lInitialCount, long lMaximumCount,
|
||||
const char* lpName) {
|
||||
(void)lpSemaphoreAttributes; (void)lInitialCount;
|
||||
(void)lMaximumCount; (void)lpName;
|
||||
return (HANDLE)WIN32_SEMA_SENTINEL;
|
||||
(void)lpSemaphoreAttributes; (void)lpName;
|
||||
uint32_t kh = 0;
|
||||
uint32_t s = SysCreateSem((int32_t)lInitialCount, (int32_t)lMaximumCount, &kh);
|
||||
if (s != 0 || !kh) { gLastError = 8; return NULL; }
|
||||
return KH_TO_HANDLE(kh);
|
||||
}
|
||||
|
||||
__declspec(dllexport) BOOL ReleaseSemaphore(HANDLE hSemaphore, long lReleaseCount,
|
||||
long* lpPreviousCount) {
|
||||
(void)hSemaphore; (void)lReleaseCount;
|
||||
if (!IS_VALID_KERNEL_HANDLE(hSemaphore) || lReleaseCount < 0) { gLastError = 6; return FALSE; }
|
||||
if (lpPreviousCount) *lpPreviousCount = 0;
|
||||
for (long i = 0; i < lReleaseCount; ++i) {
|
||||
uint32_t s = SysSignalSem(HANDLE_TO_KH(hSemaphore));
|
||||
if (s != 0) { gLastError = 6; return FALSE; }
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Critical Sections & Condition Variables
|
||||
// Single-threaded: all locks are no-ops.
|
||||
|
||||
// Minimal opaque types; callers allocate these from their Windows headers -
|
||||
// we just need the pointer to be accepted by our stubs.
|
||||
// Critical Sections & Condition Variables.
|
||||
// Match the size of Win64 RTL_CRITICAL_SECTION (5 pointer-sized fields) while
|
||||
// using the storage as a small recursive spin lock. Bink uses these from its
|
||||
// worker/copy paths, so the old no-op implementation allowed internal state
|
||||
// races as soon as CreateThread started working.
|
||||
typedef struct { uintptr_t _opaque[5]; } CRITICAL_SECTION;
|
||||
typedef struct { void* _ptr; } CONDITION_VARIABLE;
|
||||
|
||||
#define K32_CS_LOCK 0
|
||||
#define K32_CS_OWNER 1
|
||||
#define K32_CS_RECURSION 2
|
||||
|
||||
static volatile long* K32CsLockWord(CRITICAL_SECTION* cs) {
|
||||
return (volatile long*)&cs->_opaque[K32_CS_LOCK];
|
||||
}
|
||||
|
||||
static DWORD K32CurrentTidOrOne(void) {
|
||||
DWORD tid = (DWORD)SysGetCurrentTid();
|
||||
return tid ? tid : 1;
|
||||
}
|
||||
|
||||
// Initializes critical Section.
|
||||
__declspec(dllexport) void InitializeCriticalSection(CRITICAL_SECTION* lpCS) {
|
||||
(void)lpCS;
|
||||
if (!lpCS) return;
|
||||
memset(lpCS, 0, sizeof(*lpCS));
|
||||
}
|
||||
__declspec(dllexport) BOOL InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION* lpCS,
|
||||
DWORD dwSpinCount) {
|
||||
(void)lpCS; (void)dwSpinCount;
|
||||
(void)dwSpinCount;
|
||||
InitializeCriticalSection(lpCS);
|
||||
return TRUE;
|
||||
}
|
||||
// Releases critical Section.
|
||||
__declspec(dllexport) void DeleteCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; }
|
||||
// Implements the enter Critical Section operation.
|
||||
__declspec(dllexport) void EnterCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; }
|
||||
__declspec(dllexport) void EnterCriticalSection(CRITICAL_SECTION* lpCS) {
|
||||
if (!lpCS) return;
|
||||
DWORD tid = K32CurrentTidOrOne();
|
||||
if ((DWORD)lpCS->_opaque[K32_CS_OWNER] == tid) {
|
||||
lpCS->_opaque[K32_CS_RECURSION]++;
|
||||
return;
|
||||
}
|
||||
while (_InterlockedCompareExchange(K32CsLockWord(lpCS), 1, 0) != 0)
|
||||
_mm_pause();
|
||||
lpCS->_opaque[K32_CS_OWNER] = tid;
|
||||
lpCS->_opaque[K32_CS_RECURSION] = 1;
|
||||
}
|
||||
// Implements the leave Critical Section operation.
|
||||
__declspec(dllexport) void LeaveCriticalSection(CRITICAL_SECTION* lpCS) { (void)lpCS; }
|
||||
__declspec(dllexport) void LeaveCriticalSection(CRITICAL_SECTION* lpCS) {
|
||||
if (!lpCS) return;
|
||||
DWORD tid = K32CurrentTidOrOne();
|
||||
if ((DWORD)lpCS->_opaque[K32_CS_OWNER] != tid || lpCS->_opaque[K32_CS_RECURSION] == 0)
|
||||
return;
|
||||
lpCS->_opaque[K32_CS_RECURSION]--;
|
||||
if (lpCS->_opaque[K32_CS_RECURSION] == 0) {
|
||||
lpCS->_opaque[K32_CS_OWNER] = 0;
|
||||
_ReadWriteBarrier();
|
||||
_InterlockedExchange(K32CsLockWord(lpCS), 0);
|
||||
}
|
||||
}
|
||||
// Implements the try Enter Critical Section operation.
|
||||
__declspec(dllexport) BOOL TryEnterCriticalSection(CRITICAL_SECTION* lpCS) {
|
||||
(void)lpCS; return TRUE;
|
||||
if (!lpCS) return FALSE;
|
||||
DWORD tid = K32CurrentTidOrOne();
|
||||
if ((DWORD)lpCS->_opaque[K32_CS_OWNER] == tid) {
|
||||
lpCS->_opaque[K32_CS_RECURSION]++;
|
||||
return TRUE;
|
||||
}
|
||||
if (_InterlockedCompareExchange(K32CsLockWord(lpCS), 1, 0) != 0)
|
||||
return FALSE;
|
||||
lpCS->_opaque[K32_CS_OWNER] = tid;
|
||||
lpCS->_opaque[K32_CS_RECURSION] = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Initializes condition Variable.
|
||||
@@ -2357,14 +2583,12 @@ __declspec(dllexport) int GetLocaleInfoA(unsigned int Locale, DWORD LCType,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fiber Local Storage (FLS)
|
||||
// We implement a minimal 64-slot static table so callers that use FLS for
|
||||
// thread-local-ish state don't immediately fail.
|
||||
// Fiber Local Storage (FLS).
|
||||
// Allocation state is process-global, but the values themselves are per-thread,
|
||||
// matching the contract that Bink and the CRT expect when worker threads are
|
||||
// enabled. The old single global value array let unrelated threads overwrite
|
||||
// each other's decoder-local state.
|
||||
|
||||
#define FLS_MAX_SLOTS 64
|
||||
#define FLS_OUT_OF_INDEXES 0xFFFFFFFFUL
|
||||
|
||||
static void* gFlsSlots[FLS_MAX_SLOTS];
|
||||
static int gFlsUsed[FLS_MAX_SLOTS]; // 0 = free, 1 = allocated
|
||||
|
||||
// Implements the fls Alloc operation.
|
||||
@@ -2372,8 +2596,8 @@ __declspec(dllexport) DWORD FlsAlloc(void* lpCallback) {
|
||||
(void)lpCallback;
|
||||
for (int i = 0; i < FLS_MAX_SLOTS; i++) {
|
||||
if (!gFlsUsed[i]) {
|
||||
gFlsUsed[i] = 1;
|
||||
gFlsSlots[i] = NULL;
|
||||
gFlsUsed[i] = 1;
|
||||
K32ThreadEnv()->FlsSlots[i] = NULL;
|
||||
return (DWORD)i;
|
||||
}
|
||||
}
|
||||
@@ -2387,7 +2611,7 @@ __declspec(dllexport) void* FlsGetValue(DWORD dwFlsIndex) {
|
||||
gLastError = 87; // ERROR_INVALID_PARAMETER
|
||||
return NULL;
|
||||
}
|
||||
return gFlsSlots[dwFlsIndex];
|
||||
return K32ThreadEnv()->FlsSlots[dwFlsIndex];
|
||||
}
|
||||
|
||||
// Implements the fls Set Value operation.
|
||||
@@ -2396,7 +2620,7 @@ __declspec(dllexport) BOOL FlsSetValue(DWORD dwFlsIndex, void* lpFlsData) {
|
||||
gLastError = 87;
|
||||
return FALSE;
|
||||
}
|
||||
gFlsSlots[dwFlsIndex] = lpFlsData;
|
||||
K32ThreadEnv()->FlsSlots[dwFlsIndex] = lpFlsData;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -2406,8 +2630,8 @@ __declspec(dllexport) BOOL FlsFree(DWORD dwFlsIndex) {
|
||||
gLastError = 87;
|
||||
return FALSE;
|
||||
}
|
||||
gFlsUsed[dwFlsIndex] = 0;
|
||||
gFlsSlots[dwFlsIndex] = NULL;
|
||||
gFlsUsed[dwFlsIndex] = 0;
|
||||
K32ThreadEnv()->FlsSlots[dwFlsIndex] = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +210,29 @@ void* _calloc(size_t count, size_t size) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
|
||||
// Returns the usable payload size of an allocation made by this heap.
|
||||
size_t _malloc_usable_size(const void* ptr) {
|
||||
if (!ptr) return (size_t)-1;
|
||||
|
||||
HeapLock();
|
||||
HEAP_SEGMENT* seg = SegmentForPointer((void*)ptr);
|
||||
if (!seg) {
|
||||
HeapUnlock();
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
HEAP_BLOCK* block = PayloadBlock((void*)ptr);
|
||||
if (block->Magic != USER_HEAP_MAGIC || block->Free) {
|
||||
HeapUnlock();
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
size_t size = block->Size;
|
||||
HeapUnlock();
|
||||
return size;
|
||||
}
|
||||
|
||||
// Implements the realloc operation.
|
||||
void* _realloc(void* ptr, size_t newSize) {
|
||||
if (!ptr) return _malloc(newSize);
|
||||
|
||||
@@ -12,6 +12,7 @@ void* _malloc(size_t size);
|
||||
void _free(void* ptr);
|
||||
void* _calloc(size_t count, size_t size);
|
||||
void* _realloc(void* ptr, size_t size);
|
||||
size_t _malloc_usable_size(const void* ptr);
|
||||
|
||||
HEAP_EXPORT void* malloc(size_t size) {
|
||||
return _malloc(size);
|
||||
@@ -28,3 +29,7 @@ HEAP_EXPORT void* calloc(size_t count, size_t size) {
|
||||
HEAP_EXPORT void* realloc(void* ptr, size_t size) {
|
||||
return _realloc(ptr, size);
|
||||
}
|
||||
|
||||
HEAP_EXPORT size_t malloc_usable_size(const void* ptr) {
|
||||
return _malloc_usable_size(ptr);
|
||||
}
|
||||
@@ -3,13 +3,13 @@
|
||||
SYS_EXIT_PROCESS EQU 0
|
||||
SYS_EXIT_THREAD EQU 1
|
||||
SYS_CREATE_PROCESS EQU 2
|
||||
SYS_WAIT_FOR_PROCESS EQU 3
|
||||
SYS_WAIT_SINGLE EQU 3
|
||||
SYS_GET_PROCESS_EXIT_CODE EQU 4
|
||||
SYS_KILL_PROCESS EQU 5
|
||||
SYS_GET_PROCESS_LIST EQU 6
|
||||
SYS_GET_PROCESS_PID EQU 7
|
||||
SYS_CREATE_THREAD EQU 8
|
||||
SYS_WAIT_THREAD EQU 9
|
||||
SYS_RETIRED_WAIT_THREAD EQU 9
|
||||
SYS_GET_CURRENT_TID EQU 10
|
||||
SYS_GET_CURRENT_PID EQU 11
|
||||
SYS_SET_THREAD_PRIORITY EQU 12
|
||||
@@ -23,17 +23,17 @@ SYS_PROTECT_VM EQU 19
|
||||
SYS_QUERY_VM EQU 20
|
||||
SYS_CLOSE_HANDLE EQU 21
|
||||
SYS_CREATE_MUTEX EQU 22
|
||||
SYS_ACQUIRE_MUTEX EQU 23
|
||||
SYS_RETIRED_ACQUIRE_MUTEX EQU 23
|
||||
SYS_TRY_ACQUIRE_MUTEX EQU 24
|
||||
SYS_RELEASE_MUTEX EQU 25
|
||||
SYS_CREATE_SEM EQU 26
|
||||
SYS_WAIT_SEM EQU 27
|
||||
SYS_RETIRED_WAIT_SEM EQU 27
|
||||
SYS_TRY_WAIT_SEM EQU 28
|
||||
SYS_SIGNAL_SEM EQU 29
|
||||
SYS_CREATE_EVENT EQU 30
|
||||
SYS_SIGNAL_EVENT EQU 31
|
||||
SYS_CLEAR_EVENT EQU 32
|
||||
SYS_WAIT_EVENT EQU 33
|
||||
SYS_RETIRED_WAIT_EVENT EQU 33
|
||||
SYS_CREATE_QUEUE EQU 34
|
||||
SYS_SEND_QUEUE EQU 35
|
||||
SYS_RECV_QUEUE EQU 36
|
||||
@@ -66,36 +66,47 @@ SYS_SET_CONSOLE_CURSOR EQU 62
|
||||
SYS_FILL_CONSOLE_CHARACTER EQU 63
|
||||
SYS_FILL_CONSOLE_ATTRIBUTE EQU 64
|
||||
SYS_PUT_SCREEN_CHAR EQU 65
|
||||
SYS_BLIT_SCREEN EQU 66
|
||||
SYS_CLEAR_SCREEN EQU 67
|
||||
SYS_GET_SCREEN_SIZE EQU 68
|
||||
SYS_QUERY_DISPLAY_INFO EQU 69
|
||||
SYS_POLL_KEYBOARD EQU 70
|
||||
SYS_IS_KEY_DOWN EQU 71
|
||||
SYS_READ_KEYBOARD_CHAR EQU 72
|
||||
SYS_READ_KEY EQU 73
|
||||
SYS_GET_KEY_MODIFIERS EQU 74
|
||||
SYS_POLL_MOUSE EQU 75
|
||||
SYS_READ_MOUSE EQU 76
|
||||
SYS_GET_MOUSE_POS EQU 77
|
||||
SYS_GET_TICK EQU 78
|
||||
SYS_PRINT_DEBUG EQU 79
|
||||
SYS_QUERY_TRAP_FRAME EQU 80
|
||||
SYS_CREATE_SOCKET EQU 81
|
||||
SYS_BIND_SOCKET EQU 82
|
||||
SYS_LISTEN_SOCKET EQU 83
|
||||
SYS_ACCEPT_SOCKET EQU 84
|
||||
SYS_CONNECT_SOCKET EQU 85
|
||||
SYS_SEND_SOCKET EQU 86
|
||||
SYS_RECV_SOCKET EQU 87
|
||||
SYS_SHUTDOWN_SOCKET EQU 88
|
||||
SYS_SEND_TO_SOCKET EQU 89
|
||||
SYS_RECV_FROM_SOCKET EQU 90
|
||||
SYS_RESOLVE_HOST EQU 91
|
||||
SYS_SET_SOCKET_OPTION EQU 92
|
||||
SYS_GET_SOCKET_OPTION EQU 93
|
||||
SYS_POLL EQU 94
|
||||
SYS_GET_MEMORY_STATUS EQU 95
|
||||
SYS_QUERY_KERNEL_STATS EQU 96
|
||||
SYS_GET_SYSTEM_TIME EQU 97
|
||||
SYS_MAX EQU 98
|
||||
SYS_CLEAR_SCREEN EQU 66
|
||||
SYS_GET_SCREEN_SIZE EQU 67
|
||||
SYS_QUERY_DISPLAY_INFO EQU 68
|
||||
SYS_POLL_KEYBOARD EQU 69
|
||||
SYS_IS_KEY_DOWN EQU 70
|
||||
SYS_READ_KEYBOARD_CHAR EQU 71
|
||||
SYS_READ_KEY EQU 72
|
||||
SYS_GET_KEY_MODIFIERS EQU 73
|
||||
SYS_POLL_MOUSE EQU 74
|
||||
SYS_READ_MOUSE EQU 75
|
||||
SYS_GET_MOUSE_POS EQU 76
|
||||
SYS_GET_TICK EQU 77
|
||||
SYS_PRINT_DEBUG EQU 78
|
||||
SYS_QUERY_TRAP_FRAME EQU 79
|
||||
SYS_CREATE_SOCKET EQU 80
|
||||
SYS_BIND_SOCKET EQU 81
|
||||
SYS_LISTEN_SOCKET EQU 82
|
||||
SYS_ACCEPT_SOCKET EQU 83
|
||||
SYS_CONNECT_SOCKET EQU 84
|
||||
SYS_SEND_SOCKET EQU 85
|
||||
SYS_RECV_SOCKET EQU 86
|
||||
SYS_SHUTDOWN_SOCKET EQU 87
|
||||
SYS_SEND_TO_SOCKET EQU 88
|
||||
SYS_RECV_FROM_SOCKET EQU 89
|
||||
SYS_RESOLVE_HOST EQU 90
|
||||
SYS_SET_SOCKET_OPTION EQU 91
|
||||
SYS_GET_SOCKET_OPTION EQU 92
|
||||
SYS_POLL EQU 93
|
||||
SYS_GET_MEMORY_STATUS EQU 94
|
||||
SYS_QUERY_KERNEL_STATS EQU 95
|
||||
SYS_GET_SYSTEM_TIME EQU 96
|
||||
SYS_CREATE_FILE_MAPPING EQU 97
|
||||
SYS_MAP_FILE EQU 98
|
||||
SYS_CREATE_SURFACE EQU 99
|
||||
SYS_MAP_SURFACE EQU 100
|
||||
SYS_QUERY_SURFACE EQU 101
|
||||
SYS_PRESENT_SURFACE EQU 102
|
||||
SYS_GET_THREAD_LIST EQU 103
|
||||
SYS_READ_INPUT_EVENT EQU 104
|
||||
SYS_PEEK_INPUT_EVENT EQU 105
|
||||
SYS_WAIT_MANY EQU 106
|
||||
SYS_OPEN_INPUT_QUEUE EQU 107
|
||||
SYS_FLUSH_FILE EQU 108
|
||||
SYS_MAX EQU 109
|
||||
@@ -12,7 +12,8 @@
|
||||
; 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_STUB2/3 - 4 or fewer args: pure register passing, no stack read.
|
||||
; SYSCALL_STUB5 - exactly 5 args: loads [rsp+28h] -> R10 only.
|
||||
; SYSCALL_STUB6 - 5 or 6 args: loads [rsp+28h] -> R10 and [rsp+30h] -> R11.
|
||||
;
|
||||
; Handle lifecycle:
|
||||
@@ -44,6 +45,27 @@ Sys&name PROC EXPORT
|
||||
Sys&name ENDP
|
||||
ENDM
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
SYSCALL_STUB3 MACRO name, nr
|
||||
PUBLIC Sys&name
|
||||
Sys&name PROC EXPORT
|
||||
mov rax, nr
|
||||
int 40h
|
||||
ret
|
||||
Sys&name ENDP
|
||||
ENDM
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
SYSCALL_STUB5 MACRO name, nr
|
||||
PUBLIC Sys&name
|
||||
Sys&name PROC EXPORT
|
||||
mov r10, [rsp+28h] ; 5th argument
|
||||
mov rax, nr
|
||||
int 40h
|
||||
ret
|
||||
Sys&name ENDP
|
||||
ENDM
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
SYSCALL_STUB6 MACRO name, nr
|
||||
PUBLIC Sys&name
|
||||
@@ -62,14 +84,16 @@ ENDM
|
||||
SYSCALL_STUB2 ExitProcess, SYS_EXIT_PROCESS
|
||||
SYSCALL_STUB2 ExitThread, SYS_EXIT_THREAD
|
||||
SYSCALL_STUB2 CreateProcess, SYS_CREATE_PROCESS
|
||||
SYSCALL_STUB2 WaitForProcess, SYS_WAIT_FOR_PROCESS
|
||||
SYSCALL_STUB3 WaitSingle, SYS_WAIT_SINGLE
|
||||
SYSCALL_STUB2 WaitMany, SYS_WAIT_MANY
|
||||
SYSCALL_STUB2 OpenInputQueue, SYS_OPEN_INPUT_QUEUE
|
||||
SYSCALL_STUB2 GetProcessExitCode, SYS_GET_PROCESS_EXIT_CODE
|
||||
SYSCALL_STUB2 KillProcess, SYS_KILL_PROCESS
|
||||
SYSCALL_STUB2 GetProcessList, SYS_GET_PROCESS_LIST
|
||||
SYSCALL_STUB2 GetProcessPid, SYS_GET_PROCESS_PID
|
||||
SYSCALL_STUB2 GetThreadList, SYS_GET_THREAD_LIST
|
||||
|
||||
SYSCALL_STUB2 CreateThread, SYS_CREATE_THREAD
|
||||
SYSCALL_STUB2 WaitThread, SYS_WAIT_THREAD
|
||||
SYSCALL_STUB2 GetCurrentTid, SYS_GET_CURRENT_TID
|
||||
SYSCALL_STUB2 GetCurrentPid, SYS_GET_CURRENT_PID
|
||||
SYSCALL_STUB2 SetThreadPriority, SYS_SET_THREAD_PRIORITY
|
||||
@@ -120,19 +144,16 @@ SYSCALL_STUB2 CloseHandle, SYS_CLOSE_HANDLE
|
||||
; Synchronization objects
|
||||
; ---------------------------------------------------------------------------
|
||||
SYSCALL_STUB2 CreateMutex, SYS_CREATE_MUTEX
|
||||
SYSCALL_STUB2 AcquireMutex, SYS_ACQUIRE_MUTEX
|
||||
SYSCALL_STUB2 TryAcquireMutex, SYS_TRY_ACQUIRE_MUTEX
|
||||
SYSCALL_STUB2 ReleaseMutex, SYS_RELEASE_MUTEX
|
||||
|
||||
SYSCALL_STUB2 CreateSem, SYS_CREATE_SEM
|
||||
SYSCALL_STUB2 WaitSem, SYS_WAIT_SEM
|
||||
SYSCALL_STUB2 TryWaitSem, SYS_TRY_WAIT_SEM
|
||||
SYSCALL_STUB2 SignalSem, SYS_SIGNAL_SEM
|
||||
|
||||
SYSCALL_STUB2 CreateEvent, SYS_CREATE_EVENT
|
||||
SYSCALL_STUB2 SignalEvent, SYS_SIGNAL_EVENT
|
||||
SYSCALL_STUB2 ClearEvent, SYS_CLEAR_EVENT
|
||||
SYSCALL_STUB2 WaitEvent, SYS_WAIT_EVENT
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; Queues and named queues
|
||||
@@ -159,6 +180,7 @@ SYSCALL_STUB2 SeekFile, SYS_SEEK_FILE
|
||||
SYSCALL_STUB2 GetFilePos, SYS_GET_FILE_POS
|
||||
SYSCALL_STUB2 GetFileSize, SYS_GET_FILE_SIZE
|
||||
SYSCALL_STUB2 TruncateFile, SYS_TRUNCATE_FILE
|
||||
SYSCALL_STUB2 FlushFile, SYS_FLUSH_FILE
|
||||
SYSCALL_STUB2 DeleteFile, SYS_DELETE_FILE
|
||||
SYSCALL_STUB2 RenameFile, SYS_RENAME_FILE
|
||||
|
||||
@@ -178,7 +200,10 @@ SYSCALL_STUB2 SetConsoleCursor, SYS_SET_CONSOLE_CURSOR
|
||||
SYSCALL_STUB6 FillConsoleCharacter, SYS_FILL_CONSOLE_CHARACTER
|
||||
SYSCALL_STUB6 FillConsoleAttribute, SYS_FILL_CONSOLE_ATTRIBUTE
|
||||
SYSCALL_STUB6 PutScreenChar, SYS_PUT_SCREEN_CHAR
|
||||
SYSCALL_STUB6 BlitScreen, SYS_BLIT_SCREEN
|
||||
SYSCALL_STUB5 CreateSurface, SYS_CREATE_SURFACE
|
||||
SYSCALL_STUB2 MapSurface, SYS_MAP_SURFACE
|
||||
SYSCALL_STUB2 QuerySurface, SYS_QUERY_SURFACE
|
||||
SYSCALL_STUB2 PresentSurface, SYS_PRESENT_SURFACE
|
||||
SYSCALL_STUB2 ClearScreen, SYS_CLEAR_SCREEN
|
||||
SYSCALL_STUB2 GetScreenSize, SYS_GET_SCREEN_SIZE
|
||||
SYSCALL_STUB2 QueryDisplayInfo, SYS_QUERY_DISPLAY_INFO
|
||||
@@ -191,6 +216,8 @@ SYSCALL_STUB2 IsKeyDown, SYS_IS_KEY_DOWN
|
||||
SYSCALL_STUB2 ReadKeyboardChar, SYS_READ_KEYBOARD_CHAR
|
||||
SYSCALL_STUB2 ReadKey, SYS_READ_KEY
|
||||
SYSCALL_STUB2 GetKeyModifiers, SYS_GET_KEY_MODIFIERS
|
||||
SYSCALL_STUB2 ReadInputEvent, SYS_READ_INPUT_EVENT
|
||||
SYSCALL_STUB2 PeekInputEvent, SYS_PEEK_INPUT_EVENT
|
||||
|
||||
SYSCALL_STUB2 PollMouse, SYS_POLL_MOUSE
|
||||
SYSCALL_STUB2 ReadMouse, SYS_READ_MOUSE
|
||||
@@ -224,5 +251,7 @@ SYSCALL_STUB2 Poll, SYS_POLL
|
||||
SYSCALL_STUB2 GetMemoryStatus, SYS_GET_MEMORY_STATUS
|
||||
SYSCALL_STUB2 QueryKernelStats, SYS_QUERY_KERNEL_STATS
|
||||
SYSCALL_STUB2 GetSystemTime, SYS_GET_SYSTEM_TIME
|
||||
SYSCALL_STUB5 CreateFileMapping, SYS_CREATE_FILE_MAPPING
|
||||
SYSCALL_STUB6 MapFile, SYS_MAP_FILE
|
||||
|
||||
END
|
||||
@@ -76,9 +76,9 @@ void _start(void)
|
||||
}
|
||||
|
||||
debug_print("[init] waiting\n");
|
||||
s = SysWaitForProcess(h);
|
||||
s = SysWait(h, SYS_WAIT_FLAG_NONE);
|
||||
if (s != STATUS_SUCCESS)
|
||||
debug_print("[init] WARNING: SysWaitForProcess returned error\n");
|
||||
debug_print("[init] WARNING: SysWait returned error\n");
|
||||
|
||||
int exitCode = 0;
|
||||
if (SysGetProcessExitCode(h, &exitCode) != STATUS_SUCCESS) {
|
||||
|
||||
@@ -15,10 +15,35 @@ static void print(const char* s) {
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
// Opens file.
|
||||
static uint32_t open_file(const char* path, uint32_t* handleOut) {
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut };
|
||||
return SysOpenFile(&req);
|
||||
// Opens the first available executable payload for async I/O.
|
||||
// Tests are usually launched from /apps, so a bare "system/init.exe"
|
||||
// resolves as /apps/system/init.exe and fails. Prefer the test binary itself,
|
||||
// then fall back to absolute/drive-qualified paths.
|
||||
static uint32_t open_test_file(uint32_t* handleOut, const char** pathOut) {
|
||||
static const char* paths[] = {
|
||||
"async_apc.exe",
|
||||
"/apps/async_apc.exe",
|
||||
"1:/apps/async_apc.exe",
|
||||
"0:/apps/async_apc.exe",
|
||||
"/system/init.exe",
|
||||
"0:/system/init.exe",
|
||||
"1:/system/init.exe",
|
||||
"system/init.exe",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
|
||||
uint32_t h = 0;
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h };
|
||||
uint32_t s = SysOpenFile(&req);
|
||||
if (s == SYS_STATUS_SUCCESS) {
|
||||
*handleOut = h;
|
||||
if (pathOut)
|
||||
*pathOut = paths[i];
|
||||
return SYS_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return SYS_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Prints hex8.
|
||||
@@ -74,7 +99,8 @@ int main(void) {
|
||||
}
|
||||
|
||||
uint32_t file = 0;
|
||||
s = open_file("system/init.exe", &file);
|
||||
const char* openedPath = 0;
|
||||
s = open_test_file(&file, &openedPath);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
print("async_apc: open failed status=");
|
||||
print_u64(s);
|
||||
@@ -82,6 +108,10 @@ int main(void) {
|
||||
SysExitProcess(1);
|
||||
}
|
||||
|
||||
print("async_apc: using ");
|
||||
print(openedPath ? openedPath : "<unknown>");
|
||||
print("\n");
|
||||
|
||||
uint32_t event = 0;
|
||||
s = SysCreateEvent(EVENT_AUTO_RESET, 0, &event);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
@@ -125,7 +155,7 @@ int main(void) {
|
||||
}
|
||||
|
||||
while (!state.Delivered) {
|
||||
s = SysWaitEvent(event, 1);
|
||||
s = SysWait(event, SYS_WAIT_FLAG_ALERTABLE);
|
||||
print("async_apc: alertable wait returned ");
|
||||
print_u64(s);
|
||||
print("\n");
|
||||
|
||||
@@ -14,10 +14,35 @@ static void print(const char* s) {
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
// Opens file.
|
||||
static uint32_t open_file(const char* path, uint32_t* handleOut) {
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), handleOut };
|
||||
return SysOpenFile(&req);
|
||||
// Opens the first available executable payload for async I/O.
|
||||
// Tests are usually launched from /apps, so a bare "system/init.exe"
|
||||
// resolves as /apps/system/init.exe and fails. Prefer the test binary itself,
|
||||
// then fall back to absolute/drive-qualified paths.
|
||||
static uint32_t open_test_file(uint32_t* handleOut, const char** pathOut) {
|
||||
static const char* paths[] = {
|
||||
"async_cancel.exe",
|
||||
"/apps/async_cancel.exe",
|
||||
"1:/apps/async_cancel.exe",
|
||||
"0:/apps/async_cancel.exe",
|
||||
"/system/init.exe",
|
||||
"0:/system/init.exe",
|
||||
"1:/system/init.exe",
|
||||
"system/init.exe",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
|
||||
uint32_t h = 0;
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h };
|
||||
uint32_t s = SysOpenFile(&req);
|
||||
if (s == SYS_STATUS_SUCCESS) {
|
||||
*handleOut = h;
|
||||
if (pathOut)
|
||||
*pathOut = paths[i];
|
||||
return SYS_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return SYS_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Prints u64.
|
||||
@@ -46,12 +71,17 @@ int main(void) {
|
||||
uint32_t event = 0;
|
||||
uint32_t request = 0;
|
||||
|
||||
uint32_t s = open_file("system/init.exe", &file);
|
||||
const char* openedPath = 0;
|
||||
uint32_t s = open_test_file(&file, &openedPath);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
print("async_cancel: open failed status="); print_u64(s); print("\n");
|
||||
SysExitProcess(1);
|
||||
}
|
||||
|
||||
print("async_cancel: using ");
|
||||
print(openedPath ? openedPath : "<unknown>");
|
||||
print("\n");
|
||||
|
||||
s = SysCreateEvent(EVENT_AUTO_RESET, 0, &event);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
print("async_cancel: event create failed status="); print_u64(s); print("\n");
|
||||
@@ -90,7 +120,7 @@ int main(void) {
|
||||
print("async_cancel: cancel returned "); print_u64(cs); print("\n");
|
||||
|
||||
while (iosb.Status == SYS_STATUS_PENDING) {
|
||||
s = SysWaitEvent(event, 1);
|
||||
s = SysWait(event, SYS_WAIT_FLAG_ALERTABLE);
|
||||
print("async_cancel: wait returned "); print_u64(s); print("\n");
|
||||
if (s != SYS_STATUS_SUCCESS && s != SYS_STATUS_KERNEL_APC && s != SYS_STATUS_USER_APC && s != SYS_STATUS_ALERTED)
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
// Dirty file-cache writeback regression test.
|
||||
// Verifies buffered writes dirty cache pages, explicit flush writes them back, and close flushes synchronously.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define PAGE_SIZE_BYTES 4096u
|
||||
#define TEST_SIZE (PAGE_SIZE_BYTES * 4u)
|
||||
#define REWRITE_SIZE (PAGE_SIZE_BYTES * 2u)
|
||||
#define CLOSE_SIZE (PAGE_SIZE_BYTES * 3u)
|
||||
|
||||
static uint8_t gPatternA[TEST_SIZE];
|
||||
static uint8_t gPatternB[TEST_SIZE];
|
||||
static uint8_t gReadback[TEST_SIZE];
|
||||
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) {
|
||||
print("0");
|
||||
return;
|
||||
}
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i--) {
|
||||
char c[2] = { buf[i], 0 };
|
||||
print(c);
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
print("dirty_writeback: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("dirty_writeback: ");
|
||||
print(what);
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
|
||||
memset(out, 0, sizeof(*out));
|
||||
return SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, out, sizeof(*out), 0);
|
||||
}
|
||||
|
||||
static void delete_path(const char* path) {
|
||||
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
|
||||
(void)SysDeleteFile(&req);
|
||||
}
|
||||
|
||||
static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
|
||||
static const char* paths[] = {
|
||||
"dirty_writeback.tmp",
|
||||
"/dirty_writeback.tmp",
|
||||
"1:/dirty_writeback.tmp",
|
||||
"0:/dirty_writeback.tmp",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
|
||||
uint32_t h = 0;
|
||||
delete_path(paths[i]);
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h };
|
||||
uint32_t s = SysCreateFile(&req);
|
||||
if (s == SYS_STATUS_SUCCESS) {
|
||||
*fileOut = h;
|
||||
if (pathOut) *pathOut = paths[i];
|
||||
return SYS_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return SYS_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
static uint32_t open_path(const char* path, uint32_t* fileOut) {
|
||||
uint32_t h = 0;
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), &h };
|
||||
uint32_t s = SysOpenFile(&req);
|
||||
if (s == SYS_STATUS_SUCCESS)
|
||||
*fileOut = h;
|
||||
return s;
|
||||
}
|
||||
|
||||
static void fill_patterns(void) {
|
||||
for (size_t i = 0; i < sizeof(gPatternA); ++i) {
|
||||
gPatternA[i] = (uint8_t)(0x11u + (uint8_t)(i * 13u + (i >> 6)));
|
||||
gPatternB[i] = (uint8_t)(0x80u + (uint8_t)(i * 31u + (i >> 4)));
|
||||
}
|
||||
}
|
||||
|
||||
static void checked_write(uint32_t file, const void* buffer, size_t size, const char* what) {
|
||||
size_t done = 0;
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &done);
|
||||
uint32_t s = SysWriteFile(&req);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status(what, s);
|
||||
if (done != size) fail("short write");
|
||||
}
|
||||
|
||||
static void checked_read(uint32_t file, void* buffer, size_t size, size_t* gotOut, const char* what) {
|
||||
size_t got = 0;
|
||||
memset(buffer, 0xCC, size);
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &got);
|
||||
uint32_t s = SysReadFile(&req);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status(what, s);
|
||||
if (gotOut) *gotOut = got;
|
||||
}
|
||||
|
||||
static void checked_seek(uint32_t file, uint64_t offset, const char* what) {
|
||||
uint32_t s = SysSeekFile(file, offset);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status(what, s);
|
||||
}
|
||||
|
||||
static void expect_writeback_progress(const SYS_FILE_CACHE_STATS* before,
|
||||
const SYS_FILE_CACHE_STATS* after,
|
||||
const char* what) {
|
||||
if (after->FlushFileFailures != before->FlushFileFailures)
|
||||
fail("flush failure counter increased");
|
||||
if (after->WritebackFailures != before->WritebackFailures)
|
||||
fail("writeback failure counter increased");
|
||||
if (after->WritebackBytes <= before->WritebackBytes) {
|
||||
print("dirty_writeback: ");
|
||||
print(what);
|
||||
print(" did not write back bytes\n");
|
||||
exit_now(1);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
print("dirty_writeback: start\n");
|
||||
fill_patterns();
|
||||
|
||||
uint32_t file = 0;
|
||||
const char* path = 0;
|
||||
uint32_t s = create_temp(&file, &path);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create temp", s);
|
||||
|
||||
print("dirty_writeback: using ");
|
||||
print(path ? path : "<unknown>");
|
||||
print("\n");
|
||||
|
||||
SYS_FILE_CACHE_STATS before;
|
||||
SYS_FILE_CACHE_STATS afterWrite;
|
||||
SYS_FILE_CACHE_STATS afterFlush;
|
||||
SYS_FILE_CACHE_STATS beforeClose;
|
||||
SYS_FILE_CACHE_STATS afterClose;
|
||||
|
||||
if (query_cache(&before) != SYS_STATUS_SUCCESS) fail("initial stats query failed");
|
||||
|
||||
checked_write(file, gPatternA, sizeof(gPatternA), "write pattern A");
|
||||
|
||||
if (query_cache(&afterWrite) != SYS_STATUS_SUCCESS) fail("post-write stats query failed");
|
||||
if (afterWrite.DirtyMarks <= before.DirtyMarks) fail("write did not mark dirty cache pages");
|
||||
if (afterWrite.DirtyPages <= before.DirtyPages) fail("write did not leave dirty pages resident");
|
||||
|
||||
checked_seek(file, 0, "seek readback before flush");
|
||||
size_t got = 0;
|
||||
checked_read(file, gReadback, sizeof(gReadback), &got, "read before flush");
|
||||
if (got != sizeof(gPatternA)) fail("read before flush size mismatch");
|
||||
if (memcmp(gReadback, gPatternA, sizeof(gPatternA)) != 0) fail("cached read before flush mismatch");
|
||||
|
||||
s = SysFlushFile(file);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("explicit flush", s);
|
||||
|
||||
if (query_cache(&afterFlush) != SYS_STATUS_SUCCESS) fail("post-flush stats query failed");
|
||||
if (afterFlush.FlushFileCalls <= afterWrite.FlushFileCalls) fail("flush call counter did not increase");
|
||||
expect_writeback_progress(&afterWrite, &afterFlush, "explicit flush");
|
||||
|
||||
checked_seek(file, 0, "seek rewrite");
|
||||
checked_write(file, gPatternB, REWRITE_SIZE, "rewrite pattern B");
|
||||
checked_seek(file, REWRITE_SIZE, "seek close-tail");
|
||||
checked_write(file, gPatternA + REWRITE_SIZE, CLOSE_SIZE - REWRITE_SIZE, "write close tail");
|
||||
|
||||
if (query_cache(&beforeClose) != SYS_STATUS_SUCCESS) fail("pre-close stats query failed");
|
||||
if (beforeClose.DirtyMarks <= afterFlush.DirtyMarks) fail("rewrite did not mark dirty pages");
|
||||
|
||||
s = SysCloseHandle(file);
|
||||
file = 0;
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("close", s);
|
||||
|
||||
if (query_cache(&afterClose) != SYS_STATUS_SUCCESS) fail("post-close stats query failed");
|
||||
if (afterClose.FlushFileCalls <= beforeClose.FlushFileCalls) fail("close did not flush file");
|
||||
expect_writeback_progress(&beforeClose, &afterClose, "close flush");
|
||||
|
||||
s = open_path(path, &file);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("reopen", s);
|
||||
|
||||
checked_read(file, gReadback, CLOSE_SIZE, &got, "read after reopen");
|
||||
if (got != CLOSE_SIZE) fail("read after reopen size mismatch");
|
||||
if (memcmp(gReadback, gPatternB, REWRITE_SIZE) != 0) fail("rewrite data not durable after close");
|
||||
if (memcmp(gReadback + REWRITE_SIZE, gPatternA + REWRITE_SIZE,
|
||||
CLOSE_SIZE - REWRITE_SIZE) != 0) fail("tail data not durable after close");
|
||||
|
||||
s = SysCloseHandle(file);
|
||||
file = 0;
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("close reopened", s);
|
||||
delete_path(path);
|
||||
|
||||
print("dirty_writeback: dirty-marks+=");
|
||||
print_u64(afterClose.DirtyMarks - before.DirtyMarks);
|
||||
print(" writeback-bytes+=");
|
||||
print_u64(afterClose.WritebackBytes - before.WritebackBytes);
|
||||
print(" flushes+=");
|
||||
print_u64(afterClose.FlushFileCalls - before.FlushFileCalls);
|
||||
print("\n");
|
||||
|
||||
print("dirty_writeback: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// User-mode regression test for mmap-style native file mappings.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define TEST_MAP_SIZE 8192u
|
||||
|
||||
static uint8_t gExpected[TEST_MAP_SIZE];
|
||||
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) {
|
||||
print("0");
|
||||
return;
|
||||
}
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i--) {
|
||||
char c = buf[i];
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
// SysExitProcess should not return, but keep tests from falling through to
|
||||
// the synthetic entry frame if a broken kernel build returns anyway.
|
||||
for (;;)
|
||||
SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("mmap_smoke: ");
|
||||
print(what);
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void fail_msg(const char* msg) {
|
||||
print("mmap_smoke: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static uint32_t open_first_existing(uint32_t* fileOut, const char** pathOut) {
|
||||
static const char* candidates[] = {
|
||||
"mmap_smoke.exe",
|
||||
"/apps/mmap_smoke.exe",
|
||||
"0:/apps/mmap_smoke.exe",
|
||||
"1:/apps/mmap_smoke.exe",
|
||||
|
||||
"/system/init.exe",
|
||||
"0:/system/init.exe",
|
||||
"1:/system/init.exe",
|
||||
"system/init.exe",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) {
|
||||
uint32_t file = 0;
|
||||
SYS_PATH_HANDLE_REQUEST openReq = { SysSvFromCstr(candidates[i]), &file };
|
||||
uint32_t s = SysOpenFile(&openReq);
|
||||
if (s == SYS_STATUS_SUCCESS) {
|
||||
*fileOut = file;
|
||||
if (pathOut)
|
||||
*pathOut = candidates[i];
|
||||
return SYS_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return SYS_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
print("mmap_smoke: start\n");
|
||||
|
||||
uint32_t file = 0;
|
||||
const char* openedPath = 0;
|
||||
uint32_t s = open_first_existing(&file, &openedPath);
|
||||
if (s != SYS_STATUS_SUCCESS)
|
||||
fail_status("open test file", s);
|
||||
|
||||
print("mmap_smoke: using ");
|
||||
print(openedPath ? openedPath : "<unknown>");
|
||||
print("\n");
|
||||
|
||||
size_t got = 0;
|
||||
memset(gExpected, 0xA5, sizeof(gExpected));
|
||||
|
||||
SYS_FILE_IO_REQUEST ioReq = SysMakeFileIoRequest(file, gExpected, sizeof(gExpected), &got);
|
||||
s = SysReadFile(&ioReq);
|
||||
if (s != SYS_STATUS_SUCCESS)
|
||||
fail_status("read baseline", s);
|
||||
if (got < 64)
|
||||
fail_msg("baseline read was unexpectedly short");
|
||||
|
||||
print("mmap_smoke: baseline read OK\n");
|
||||
|
||||
uint32_t mapping = 0;
|
||||
s = SysCreateFileMapping(file,
|
||||
SYS_VM_READ,
|
||||
SYS_MAP_FLAG_NONE,
|
||||
sizeof(gExpected),
|
||||
&mapping);
|
||||
if (s != SYS_STATUS_SUCCESS)
|
||||
fail_status("create file mapping", s);
|
||||
|
||||
print("mmap_smoke: mapping created\n");
|
||||
|
||||
// The mapping object must hold its own file reference. Closing the source
|
||||
// handle before SysMapFile catches lifetime bugs in the object layer.
|
||||
SysCloseHandle(file);
|
||||
file = 0;
|
||||
|
||||
print("mmap_smoke: source handle closed\n");
|
||||
|
||||
void* base = 0;
|
||||
s = SysMapFile(mapping,
|
||||
0,
|
||||
sizeof(gExpected),
|
||||
SYS_MAP_ACCESS_READ,
|
||||
SYS_MAP_FLAG_NONE,
|
||||
&base);
|
||||
|
||||
SysCloseHandle(mapping);
|
||||
mapping = 0;
|
||||
|
||||
if (s != SYS_STATUS_SUCCESS)
|
||||
fail_status("map file view", s);
|
||||
if (!base)
|
||||
fail_msg("map returned null base");
|
||||
|
||||
print("mmap_smoke: view mapped\n");
|
||||
|
||||
volatile uint8_t* view = (volatile uint8_t*)base;
|
||||
|
||||
// Touch both pages before comparing. This ensures the test exercises
|
||||
// demand-faulted file-backed VAD pages rather than only VAD creation.
|
||||
(void)view[0];
|
||||
print("mmap_smoke: first page fault OK\n");
|
||||
|
||||
(void)view[4096];
|
||||
print("mmap_smoke: second page fault OK\n");
|
||||
|
||||
for (size_t i = 0; i < got; ++i) {
|
||||
if (view[i] != gExpected[i]) {
|
||||
print("mmap_smoke: byte mismatch at ");
|
||||
print_u64(i);
|
||||
print(" expected=");
|
||||
print_u64(gExpected[i]);
|
||||
print(" got=");
|
||||
print_u64(view[i]);
|
||||
print("\n");
|
||||
SysFreeVm(base);
|
||||
exit_now(1);
|
||||
}
|
||||
}
|
||||
|
||||
print("mmap_smoke: mapped bytes match\n");
|
||||
|
||||
// If the file is shorter than TEST_MAP_SIZE, the mapped tail must be
|
||||
// zero-filled rather than exposing stale physical memory.
|
||||
for (size_t i = got; i < sizeof(gExpected) && i < got + 64; ++i) {
|
||||
if (view[i] != 0) {
|
||||
print("mmap_smoke: nonzero EOF fill at ");
|
||||
print_u64(i);
|
||||
print(" got=");
|
||||
print_u64(view[i]);
|
||||
print("\n");
|
||||
SysFreeVm(base);
|
||||
exit_now(1);
|
||||
}
|
||||
}
|
||||
|
||||
print("mmap_smoke: EOF fill OK\n");
|
||||
|
||||
s = SysFreeVm(base);
|
||||
if (s != SYS_STATUS_SUCCESS)
|
||||
fail_status("unmap", s);
|
||||
|
||||
print("mmap_smoke: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// Page-cache write/truncate invalidation regression test.
|
||||
// Verifies cached clean pages are not reused after file write or truncate.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define TEST_SIZE 8192u
|
||||
#define TRUNC_SIZE 1024u
|
||||
|
||||
static uint8_t gOriginal[TEST_SIZE];
|
||||
static uint8_t gReplacement[4096];
|
||||
static uint8_t gReadback[TEST_SIZE];
|
||||
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) { print("0"); return; }
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i--) {
|
||||
char c[2] = { buf[i], 0 };
|
||||
print(c);
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
print("pcache_invalidate: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("pcache_invalidate: ");
|
||||
print(what);
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
|
||||
memset(out, 0, sizeof(*out));
|
||||
return SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, out, sizeof(*out), 0);
|
||||
}
|
||||
|
||||
static void delete_path(const char* path) {
|
||||
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
|
||||
(void)SysDeleteFile(&req);
|
||||
}
|
||||
|
||||
static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
|
||||
static const char* paths[] = {
|
||||
"pcache_invalidate.tmp",
|
||||
"/pcache_invalidate.tmp",
|
||||
"1:/pcache_invalidate.tmp",
|
||||
"0:/pcache_invalidate.tmp",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
|
||||
uint32_t h = 0;
|
||||
delete_path(paths[i]);
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h };
|
||||
uint32_t s = SysCreateFile(&req);
|
||||
if (s == SYS_STATUS_SUCCESS) {
|
||||
*fileOut = h;
|
||||
if (pathOut) *pathOut = paths[i];
|
||||
return SYS_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return SYS_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
static void fill_patterns(void) {
|
||||
for (size_t i = 0; i < TEST_SIZE; ++i)
|
||||
gOriginal[i] = (uint8_t)(0x30u + (uint8_t)(i * 17u + (i >> 7)));
|
||||
for (size_t i = 0; i < sizeof(gReplacement); ++i)
|
||||
gReplacement[i] = (uint8_t)(0xA0u + (uint8_t)(i * 29u + (i >> 5)));
|
||||
}
|
||||
|
||||
static void checked_write(uint32_t file, const void* buffer, size_t size, const char* what) {
|
||||
size_t done = 0;
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &done);
|
||||
uint32_t s = SysWriteFile(&req);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status(what, s);
|
||||
if (done != size) fail("short write");
|
||||
}
|
||||
|
||||
static void checked_read(uint32_t file, void* buffer, size_t size, size_t* gotOut, const char* what) {
|
||||
size_t got = 0;
|
||||
memset(buffer, 0xCC, size);
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &got);
|
||||
uint32_t s = SysReadFile(&req);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status(what, s);
|
||||
if (gotOut) *gotOut = got;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
print("pcache_invalidate: start\n");
|
||||
fill_patterns();
|
||||
|
||||
uint32_t file = 0;
|
||||
const char* path = 0;
|
||||
uint32_t s = create_temp(&file, &path);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create temp", s);
|
||||
|
||||
print("pcache_invalidate: using ");
|
||||
print(path ? path : "<unknown>");
|
||||
print("\n");
|
||||
|
||||
checked_write(file, gOriginal, sizeof(gOriginal), "write original");
|
||||
|
||||
s = SysSeekFile(file, 0);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("seek fill", s);
|
||||
|
||||
SYS_FILE_CACHE_STATS before;
|
||||
SYS_FILE_CACHE_STATS afterFill;
|
||||
SYS_FILE_CACHE_STATS afterSecond;
|
||||
SYS_FILE_CACHE_STATS afterWrite;
|
||||
SYS_FILE_CACHE_STATS afterRewriteRead;
|
||||
SYS_FILE_CACHE_STATS afterTruncate;
|
||||
if (query_cache(&before) != SYS_STATUS_SUCCESS) fail("initial stats query failed");
|
||||
|
||||
size_t got = 0;
|
||||
checked_read(file, gReadback, sizeof(gReadback), &got, "first read");
|
||||
if (got != sizeof(gOriginal)) fail("first read size mismatch");
|
||||
if (memcmp(gReadback, gOriginal, sizeof(gOriginal)) != 0) fail("first read data mismatch");
|
||||
|
||||
if (query_cache(&afterFill) != SYS_STATUS_SUCCESS) fail("fill stats query failed");
|
||||
if (afterFill.Loads <= before.Loads) fail("initial read did not populate cache");
|
||||
|
||||
s = SysSeekFile(file, 0);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("seek second", s);
|
||||
checked_read(file, gReadback, sizeof(gReadback), &got, "second read");
|
||||
if (got != sizeof(gOriginal)) fail("second read size mismatch");
|
||||
if (memcmp(gReadback, gOriginal, sizeof(gOriginal)) != 0) fail("second read data mismatch");
|
||||
|
||||
if (query_cache(&afterSecond) != SYS_STATUS_SUCCESS) fail("second stats query failed");
|
||||
// Do not require this second read to hit: the cache is intentionally
|
||||
// bounded and pcache_pressure.exe may have left it at the soft limit, so
|
||||
// the newly-populated temp pages are eligible for immediate pressure
|
||||
// eviction. This test is about write/truncate freshness; mmap/read reuse
|
||||
// is covered by pcache_mmap_read_reuse.exe.
|
||||
|
||||
s = SysSeekFile(file, 0);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("seek rewrite", s);
|
||||
checked_write(file, gReplacement, sizeof(gReplacement), "rewrite first page");
|
||||
|
||||
if (query_cache(&afterWrite) != SYS_STATUS_SUCCESS) fail("post-write stats query failed");
|
||||
if (afterWrite.Invalidations <= afterSecond.Invalidations) fail("write did not invalidate cached page");
|
||||
|
||||
uint32_t mapping = 0;
|
||||
s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, sizeof(gReplacement), &mapping);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create mapping after rewrite", s);
|
||||
|
||||
void* mappedBase = 0;
|
||||
s = SysMapFile(mapping, 0, sizeof(gReplacement), SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &mappedBase);
|
||||
SysCloseHandle(mapping);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("map after rewrite", s);
|
||||
if (!mappedBase) fail("map after rewrite returned null");
|
||||
|
||||
volatile uint8_t* mappedView = (volatile uint8_t*)mappedBase;
|
||||
(void)mappedView[0];
|
||||
(void)mappedView[sizeof(gReplacement) - 1];
|
||||
if (memcmp((const void*)mappedBase, gReplacement, sizeof(gReplacement)) != 0) {
|
||||
SysFreeVm(mappedBase);
|
||||
fail("stale mmap data after write invalidation");
|
||||
}
|
||||
s = SysFreeVm(mappedBase);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("unmap after rewrite", s);
|
||||
|
||||
s = SysSeekFile(file, 0);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("seek verify rewrite", s);
|
||||
checked_read(file, gReadback, sizeof(gReadback), &got, "read after rewrite");
|
||||
if (got != sizeof(gOriginal)) fail("rewrite read size mismatch");
|
||||
if (memcmp(gReadback, gReplacement, sizeof(gReplacement)) != 0) fail("stale data after write invalidation");
|
||||
if (memcmp(gReadback + sizeof(gReplacement), gOriginal + sizeof(gReplacement),
|
||||
sizeof(gOriginal) - sizeof(gReplacement)) != 0) fail("unwritten page changed after rewrite");
|
||||
|
||||
if (query_cache(&afterRewriteRead) != SYS_STATUS_SUCCESS) fail("rewrite-read stats query failed");
|
||||
|
||||
s = SysTruncateFile(file, TRUNC_SIZE);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("truncate", s);
|
||||
if (SysGetFileSize(file) != TRUNC_SIZE) fail("truncate size mismatch");
|
||||
|
||||
if (query_cache(&afterTruncate) != SYS_STATUS_SUCCESS) fail("truncate stats query failed");
|
||||
if (afterTruncate.Invalidations <= afterRewriteRead.Invalidations) fail("truncate did not invalidate cached tail");
|
||||
|
||||
s = SysSeekFile(file, 0);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("seek truncated", s);
|
||||
checked_read(file, gReadback, sizeof(gReadback), &got, "read truncated");
|
||||
if (got != TRUNC_SIZE) fail("truncated read length mismatch");
|
||||
if (memcmp(gReadback, gReplacement, TRUNC_SIZE) != 0) fail("truncated data mismatch");
|
||||
|
||||
SysCloseHandle(file);
|
||||
delete_path(path);
|
||||
|
||||
print("pcache_invalidate: invalidations+=");
|
||||
print_u64(afterTruncate.Invalidations - before.Invalidations);
|
||||
print(" loads+=");
|
||||
print_u64(afterTruncate.Loads - before.Loads);
|
||||
print(" hits+=");
|
||||
print_u64(afterTruncate.Hits - before.Hits);
|
||||
print("\n");
|
||||
|
||||
print("pcache_invalidate: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Page-cache mmap->read reuse regression test.
|
||||
// Faults file pages through mmap first, then verifies SysReadFile reuses those pages.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define TEST_SIZE 8192u
|
||||
|
||||
static uint8_t gReadback[TEST_SIZE];
|
||||
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) { print("0"); return; }
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i--) {
|
||||
char c[2] = { buf[i], 0 };
|
||||
print(c);
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
print("pcache_mmap_read_reuse: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("pcache_mmap_read_reuse: ");
|
||||
print(what);
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
|
||||
memset(out, 0, sizeof(*out));
|
||||
return SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, out, sizeof(*out), 0);
|
||||
}
|
||||
|
||||
static uint32_t open_test_file(uint32_t* out, const char** pathOut) {
|
||||
static const char* paths[] = {
|
||||
"pcache_mmap_read_reuse.exe",
|
||||
"/apps/pcache_mmap_read_reuse.exe",
|
||||
"1:/apps/pcache_mmap_read_reuse.exe",
|
||||
"0:/apps/pcache_mmap_read_reuse.exe",
|
||||
"pcache_smoke.exe",
|
||||
"/apps/pcache_smoke.exe",
|
||||
"1:/apps/pcache_smoke.exe",
|
||||
"0:/apps/pcache_smoke.exe",
|
||||
"/system/init.exe",
|
||||
"0:/system/init.exe",
|
||||
"1:/system/init.exe",
|
||||
"system/init.exe",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
|
||||
uint32_t h = 0;
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h };
|
||||
uint32_t s = SysOpenFile(&req);
|
||||
if (s == SYS_STATUS_SUCCESS) {
|
||||
*out = h;
|
||||
if (pathOut) *pathOut = paths[i];
|
||||
return SYS_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
return SYS_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
print("pcache_mmap_read_reuse: start\n");
|
||||
|
||||
SYS_FILE_CACHE_STATS before;
|
||||
SYS_FILE_CACHE_STATS afterFault;
|
||||
SYS_FILE_CACHE_STATS afterRead;
|
||||
uint32_t s = query_cache(&before);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("query initial stats", s);
|
||||
|
||||
uint32_t file = 0;
|
||||
const char* path = 0;
|
||||
s = open_test_file(&file, &path);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("open test file", s);
|
||||
|
||||
uint64_t fileSize = SysGetFileSize(file);
|
||||
if (fileSize < 4096) fail("test file too small");
|
||||
size_t mapSize = (fileSize < TEST_SIZE) ? (size_t)fileSize : TEST_SIZE;
|
||||
|
||||
print("pcache_mmap_read_reuse: using ");
|
||||
print(path ? path : "<unknown>");
|
||||
print(" size=");
|
||||
print_u64(fileSize);
|
||||
print("\n");
|
||||
|
||||
uint32_t mapping = 0;
|
||||
s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, mapSize, &mapping);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create mapping", s);
|
||||
|
||||
void* base = 0;
|
||||
s = SysMapFile(mapping, 0, mapSize, SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base);
|
||||
SysCloseHandle(mapping);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("map file", s);
|
||||
if (!base) fail("map returned null");
|
||||
|
||||
volatile uint8_t* view = (volatile uint8_t*)base;
|
||||
for (size_t i = 0; i < mapSize; i += 4096)
|
||||
(void)view[i];
|
||||
(void)view[mapSize - 1];
|
||||
|
||||
s = query_cache(&afterFault);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("query after mmap faults", s);
|
||||
if (afterFault.Loads <= before.Loads) {
|
||||
SysFreeVm(base);
|
||||
fail("mmap faults did not populate file cache");
|
||||
}
|
||||
|
||||
s = SysSeekFile(file, 0);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
SysFreeVm(base);
|
||||
fail_status("seek", s);
|
||||
}
|
||||
|
||||
size_t got = 0;
|
||||
memset(gReadback, 0xCC, sizeof(gReadback));
|
||||
SYS_FILE_IO_REQUEST readReq = SysMakeFileIoRequest(file, gReadback, mapSize, &got);
|
||||
s = SysReadFile(&readReq);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
SysFreeVm(base);
|
||||
fail_status("read", s);
|
||||
}
|
||||
if (got != mapSize) {
|
||||
SysFreeVm(base);
|
||||
fail("read length mismatch");
|
||||
}
|
||||
if (memcmp(gReadback, (const void*)base, mapSize) != 0) {
|
||||
SysFreeVm(base);
|
||||
fail("read data differs from mapped data");
|
||||
}
|
||||
|
||||
s = query_cache(&afterRead);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
SysFreeVm(base);
|
||||
fail_status("query after read", s);
|
||||
}
|
||||
if (afterRead.Hits <= afterFault.Hits) {
|
||||
SysFreeVm(base);
|
||||
fail("read did not reuse mmap-populated cache pages");
|
||||
}
|
||||
|
||||
SysFreeVm(base);
|
||||
SysCloseHandle(file);
|
||||
|
||||
print("pcache_mmap_read_reuse: loads+=");
|
||||
print_u64(afterRead.Loads - before.Loads);
|
||||
print(" hits+=");
|
||||
print_u64(afterRead.Hits - before.Hits);
|
||||
print("\n");
|
||||
print("pcache_mmap_read_reuse: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
// Multi-threaded file-cache/VAD fault regression test.
|
||||
// Several threads map the same file mapping and fault/read pages concurrently.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define THREAD_COUNT 6u
|
||||
#define ITERATIONS 16u
|
||||
#define TEST_SIZE 32768u
|
||||
|
||||
static uint8_t gPattern[TEST_SIZE];
|
||||
|
||||
typedef struct THREAD_CTX {
|
||||
uint32_t Index;
|
||||
uint32_t Mapping;
|
||||
volatile uint32_t Status;
|
||||
volatile uint32_t IterationsDone;
|
||||
} THREAD_CTX;
|
||||
|
||||
static THREAD_CTX gThreads[THREAD_COUNT];
|
||||
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) { print("0"); return; }
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i--) {
|
||||
char c[2] = { buf[i], 0 };
|
||||
print(c);
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
print("pcache_multithread: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("pcache_multithread: ");
|
||||
print(what);
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
|
||||
memset(out, 0, sizeof(*out));
|
||||
return SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, out, sizeof(*out), 0);
|
||||
}
|
||||
|
||||
static void delete_path(const char* path) {
|
||||
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
|
||||
(void)SysDeleteFile(&req);
|
||||
}
|
||||
|
||||
static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
|
||||
static const char* paths[] = {
|
||||
"pcache_multithread.tmp",
|
||||
"/pcache_multithread.tmp",
|
||||
"1:/pcache_multithread.tmp",
|
||||
"0:/pcache_multithread.tmp",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
|
||||
uint32_t h = 0;
|
||||
delete_path(paths[i]);
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h };
|
||||
uint32_t s = SysCreateFile(&req);
|
||||
if (s == SYS_STATUS_SUCCESS) {
|
||||
*fileOut = h;
|
||||
if (pathOut) *pathOut = paths[i];
|
||||
return SYS_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return SYS_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
static void fill_pattern(void) {
|
||||
for (size_t i = 0; i < TEST_SIZE; ++i)
|
||||
gPattern[i] = (uint8_t)(0x5Au ^ (uint8_t)(i * 13u) ^ (uint8_t)(i >> 3));
|
||||
}
|
||||
|
||||
static uint32_t validate_mapping(const uint8_t* base) {
|
||||
for (size_t i = 0; i < TEST_SIZE; ++i) {
|
||||
if (base[i] != gPattern[i])
|
||||
return 0x10000000u | (uint32_t)(i & 0x0FFFFFFFu);
|
||||
}
|
||||
return SYS_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static void thread_main(uintptr_t arg) {
|
||||
THREAD_CTX* ctx = (THREAD_CTX*)arg;
|
||||
if (!ctx) SysExitThread(1);
|
||||
|
||||
for (uint32_t iter = 0; iter < ITERATIONS; ++iter) {
|
||||
void* base = 0;
|
||||
uint32_t s = SysMapFile(ctx->Mapping, 0, TEST_SIZE,
|
||||
SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base);
|
||||
if (s != SYS_STATUS_SUCCESS || !base) {
|
||||
ctx->Status = s ? s : SYS_STATUS_INVALID_PARAMETER;
|
||||
SysExitThread(1);
|
||||
}
|
||||
|
||||
volatile uint8_t* touch = (volatile uint8_t*)base;
|
||||
for (size_t off = 0; off < TEST_SIZE; off += 4096)
|
||||
(void)touch[off];
|
||||
(void)touch[TEST_SIZE - 1];
|
||||
|
||||
s = validate_mapping((const uint8_t*)base);
|
||||
uint32_t fs = SysFreeVm(base);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
ctx->Status = s;
|
||||
SysExitThread(1);
|
||||
}
|
||||
if (fs != SYS_STATUS_SUCCESS) {
|
||||
ctx->Status = fs;
|
||||
SysExitThread(1);
|
||||
}
|
||||
ctx->IterationsDone++;
|
||||
}
|
||||
|
||||
ctx->Status = SYS_STATUS_SUCCESS;
|
||||
SysExitThread(0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
print("pcache_multithread: start\n");
|
||||
fill_pattern();
|
||||
|
||||
uint32_t file = 0;
|
||||
const char* path = 0;
|
||||
uint32_t s = create_temp(&file, &path);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create temp", s);
|
||||
|
||||
size_t written = 0;
|
||||
SYS_FILE_IO_REQUEST writeReq = SysMakeFileIoRequest(file, gPattern, sizeof(gPattern), &written);
|
||||
s = SysWriteFile(&writeReq);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("write pattern", s);
|
||||
if (written != sizeof(gPattern)) fail("short write");
|
||||
|
||||
uint32_t mapping = 0;
|
||||
s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create mapping", s);
|
||||
|
||||
SYS_FILE_CACHE_STATS before;
|
||||
SYS_FILE_CACHE_STATS after;
|
||||
if (query_cache(&before) != SYS_STATUS_SUCCESS) fail("initial stats query failed");
|
||||
|
||||
uint32_t handles[THREAD_COUNT];
|
||||
uint32_t tids[THREAD_COUNT];
|
||||
memset(handles, 0, sizeof(handles));
|
||||
memset(tids, 0, sizeof(tids));
|
||||
memset(gThreads, 0, sizeof(gThreads));
|
||||
|
||||
for (uint32_t i = 0; i < THREAD_COUNT; ++i) {
|
||||
gThreads[i].Index = i;
|
||||
gThreads[i].Mapping = mapping;
|
||||
gThreads[i].Status = SYS_STATUS_PENDING;
|
||||
SYS_CREATE_THREAD_REQUEST req = {
|
||||
(uintptr_t)thread_main,
|
||||
(uintptr_t)&gThreads[i],
|
||||
1,
|
||||
0,
|
||||
&handles[i],
|
||||
&tids[i]
|
||||
};
|
||||
s = SysCreateThread(&req);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create thread", s);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < THREAD_COUNT; ++i) {
|
||||
s = SysWait(handles[i], SYS_WAIT_FLAG_NONE);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("wait thread", s);
|
||||
SysCloseHandle(handles[i]);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < THREAD_COUNT; ++i) {
|
||||
if (gThreads[i].Status != SYS_STATUS_SUCCESS) {
|
||||
print("pcache_multithread: worker ");
|
||||
print_u64(i);
|
||||
print(" status=");
|
||||
print_u64(gThreads[i].Status);
|
||||
print(" iterations=");
|
||||
print_u64(gThreads[i].IterationsDone);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
if (gThreads[i].IterationsDone != ITERATIONS) fail("worker iteration count mismatch");
|
||||
}
|
||||
|
||||
if (query_cache(&after) != SYS_STATUS_SUCCESS) fail("final stats query failed");
|
||||
if (after.Loads <= before.Loads) fail("concurrent mmap did not load cache pages");
|
||||
if (after.Hits <= before.Hits) fail("concurrent mmap did not reuse cache pages");
|
||||
|
||||
SysCloseHandle(mapping);
|
||||
SysCloseHandle(file);
|
||||
delete_path(path);
|
||||
|
||||
print("pcache_multithread: loads+=");
|
||||
print_u64(after.Loads - before.Loads);
|
||||
print(" hits+=");
|
||||
print_u64(after.Hits - before.Hits);
|
||||
print(" races+=");
|
||||
print_u64(after.RaceDiscards - before.RaceDiscards);
|
||||
print("\n");
|
||||
print("pcache_multithread: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Sequential file-cache pressure test.
|
||||
// Reads a large read-only asset through SysReadFile and checks that the
|
||||
// read-only page cache stays bounded by dropping clean pages.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define READ_CHUNK 4096u
|
||||
#define MIN_PRESSURE_BYTES (3u * 1024u * 1024u)
|
||||
#define RESIDENT_LIMIT_PAGES 640u
|
||||
|
||||
static uint8_t gBuf[READ_CHUNK];
|
||||
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) { print("0"); return; }
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i--) {
|
||||
char c[2] = { buf[i], 0 };
|
||||
print(c);
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
print("pcache_pressure: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("pcache_pressure: ");
|
||||
print(what);
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
|
||||
return SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, out, sizeof(*out), 0);
|
||||
}
|
||||
|
||||
static uint32_t open_first_large(uint32_t* fileOut, const char** pathOut, uint64_t* sizeOut) {
|
||||
static const char* candidates[] = {
|
||||
"1:/apps/video.bk2",
|
||||
"0:/apps/video.bk2",
|
||||
"/apps/video.bk2",
|
||||
"video.bk2",
|
||||
|
||||
"1:/apps/doom1.wad",
|
||||
"0:/apps/doom1.wad",
|
||||
"/apps/doom1.wad",
|
||||
"doom1.wad",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) {
|
||||
uint32_t h = 0;
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(candidates[i]), &h };
|
||||
uint32_t s = SysOpenFile(&req);
|
||||
if (s != SYS_STATUS_SUCCESS)
|
||||
continue;
|
||||
|
||||
uint64_t size = SysGetFileSize(h);
|
||||
if (size >= MIN_PRESSURE_BYTES) {
|
||||
*fileOut = h;
|
||||
*pathOut = candidates[i];
|
||||
*sizeOut = size;
|
||||
return SYS_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
SysCloseHandle(h);
|
||||
}
|
||||
|
||||
return SYS_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
print("pcache_pressure: start\n");
|
||||
|
||||
SYS_FILE_CACHE_STATS before;
|
||||
SYS_FILE_CACHE_STATS after;
|
||||
if (query_cache(&before) != SYS_STATUS_SUCCESS)
|
||||
fail("initial cache stats query failed");
|
||||
|
||||
uint32_t file = 0;
|
||||
const char* path = 0;
|
||||
uint64_t size = 0;
|
||||
uint32_t s = open_first_large(&file, &path, &size);
|
||||
if (s != SYS_STATUS_SUCCESS)
|
||||
fail_status("open large asset", s);
|
||||
|
||||
print("pcache_pressure: using ");
|
||||
print(path ? path : "<unknown>");
|
||||
print(" size=");
|
||||
print_u64(size);
|
||||
print("\n");
|
||||
|
||||
uint64_t total = 0;
|
||||
uint64_t target = size < (8u * 1024u * 1024u) ? size : (8u * 1024u * 1024u);
|
||||
while (total < target) {
|
||||
size_t got = 0;
|
||||
SYS_FILE_IO_REQUEST readReq = SysMakeFileIoRequest(file, gBuf, sizeof(gBuf), &got);
|
||||
s = SysReadFile(&readReq);
|
||||
if (s != SYS_STATUS_SUCCESS)
|
||||
fail_status("read", s);
|
||||
if (got == 0)
|
||||
break;
|
||||
total += got;
|
||||
}
|
||||
|
||||
SysCloseHandle(file);
|
||||
|
||||
if (total < MIN_PRESSURE_BYTES)
|
||||
fail("asset read was too short to pressure the cache");
|
||||
|
||||
if (query_cache(&after) != SYS_STATUS_SUCCESS)
|
||||
fail("final cache stats query failed");
|
||||
|
||||
uint64_t loads = after.Loads - before.Loads;
|
||||
uint64_t hits = after.Hits - before.Hits;
|
||||
uint64_t drops = after.Drops - before.Drops;
|
||||
uint64_t raReq = after.ReadAheadRequests - before.ReadAheadRequests;
|
||||
uint64_t raLoad = after.ReadAheadPagesLoaded - before.ReadAheadPagesLoaded;
|
||||
uint64_t raThrottle = after.ReadAheadPagesThrottled - before.ReadAheadPagesThrottled;
|
||||
uint64_t batchReads = after.BatchReads - before.BatchReads;
|
||||
uint64_t batchPages = after.BatchPagesLoaded - before.BatchPagesLoaded;
|
||||
|
||||
print("pcache_pressure: read bytes="); print_u64(total);
|
||||
print(" loads+="); print_u64(loads);
|
||||
print(" hits+="); print_u64(hits);
|
||||
print(" drops+="); print_u64(drops);
|
||||
print(" resident="); print_u64(after.ResidentPages);
|
||||
print(" active="); print_u64(after.ActivePages);
|
||||
print(" standby="); print_u64(after.StandbyPages);
|
||||
print(" max="); print_u64(after.MaxResidentPages);
|
||||
print(" readahead-req+="); print_u64(raReq);
|
||||
print(" readahead-load+="); print_u64(raLoad);
|
||||
print(" readahead-throttle+="); print_u64(raThrottle);
|
||||
print(" batch-reads+="); print_u64(batchReads);
|
||||
print(" batch-pages+="); print_u64(batchPages);
|
||||
print("\n");
|
||||
|
||||
if (loads < 512)
|
||||
fail("expected at least 512 cache page loads");
|
||||
if (after.ResidentPages > RESIDENT_LIMIT_PAGES)
|
||||
fail("resident page cache exceeded pressure bound");
|
||||
if (loads > RESIDENT_LIMIT_PAGES && drops == 0)
|
||||
fail("cache did not drop clean pages under pressure");
|
||||
|
||||
print("pcache_pressure: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// Read-only page-cache smoke test.
|
||||
// Verifies that ReadFile and file-backed mappings share the same cached pages.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define TEST_SIZE 8192u
|
||||
|
||||
static uint8_t gA[TEST_SIZE];
|
||||
static uint8_t gB[TEST_SIZE];
|
||||
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) { print("0"); return; }
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i--) {
|
||||
char c = buf[i];
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
print("pcache_smoke: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t s) {
|
||||
print("pcache_smoke: "); print(what); print(" failed status="); print_u64(s); print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
|
||||
memset(out, 0, sizeof(*out));
|
||||
return SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, out, sizeof(*out), 0);
|
||||
}
|
||||
|
||||
static uint32_t open_self(uint32_t* out) {
|
||||
static const char* paths[] = {
|
||||
"pcache_smoke.exe",
|
||||
"/apps/pcache_smoke.exe",
|
||||
"Y:/apps/pcache_smoke.exe",
|
||||
"0:/apps/pcache_smoke.exe",
|
||||
"1:/apps/pcache_smoke.exe",
|
||||
"/apps/shell_native.exe",
|
||||
"0:/apps/shell_native.exe",
|
||||
"1:/apps/shell_native.exe",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
|
||||
uint32_t h = 0;
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h };
|
||||
uint32_t s = SysOpenFile(&req);
|
||||
if (s == SYS_STATUS_SUCCESS) {
|
||||
*out = h;
|
||||
print("pcache_smoke: using "); print(paths[i]); print("\n");
|
||||
return SYS_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
return SYS_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
print("pcache_smoke: start\n");
|
||||
|
||||
SYS_FILE_CACHE_STATS before, afterFirst, afterSecond, afterMap;
|
||||
uint32_t s = query_cache(&before);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("query initial stats", s);
|
||||
|
||||
uint32_t file = 0;
|
||||
s = open_self(&file);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("open test file", s);
|
||||
|
||||
size_t gotA = 0;
|
||||
memset(gA, 0xA5, sizeof(gA));
|
||||
SYS_FILE_IO_REQUEST readReqA = SysMakeFileIoRequest(file, gA, sizeof(gA), &gotA);
|
||||
s = SysReadFile(&readReqA);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("first read", s);
|
||||
if (gotA < 4096) fail("first read too short");
|
||||
print("pcache_smoke: first read OK\n");
|
||||
|
||||
s = query_cache(&afterFirst);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("query after first read", s);
|
||||
if (afterFirst.Loads <= before.Loads)
|
||||
fail("first read did not load file-cache pages");
|
||||
|
||||
s = SysSeekFile(file, 0);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("seek", s);
|
||||
|
||||
size_t gotB = 0;
|
||||
memset(gB, 0x5A, sizeof(gB));
|
||||
SYS_FILE_IO_REQUEST readReqB = SysMakeFileIoRequest(file, gB, sizeof(gB), &gotB);
|
||||
s = SysReadFile(&readReqB);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("second read", s);
|
||||
if (gotB != gotA) fail("second read length mismatch");
|
||||
if (memcmp(gA, gB, gotA) != 0) fail("second read data mismatch");
|
||||
print("pcache_smoke: second read OK\n");
|
||||
|
||||
s = query_cache(&afterSecond);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("query after second read", s);
|
||||
if (afterSecond.Hits <= afterFirst.Hits)
|
||||
fail("second read did not hit file cache");
|
||||
|
||||
uint32_t mapping = 0;
|
||||
s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, sizeof(gA), &mapping);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create mapping", s);
|
||||
SysCloseHandle(file);
|
||||
file = 0;
|
||||
|
||||
void* base = 0;
|
||||
s = SysMapFile(mapping, 0, sizeof(gA), SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base);
|
||||
SysCloseHandle(mapping);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("map file", s);
|
||||
if (!base) fail("map returned null");
|
||||
|
||||
volatile uint8_t* view = (volatile uint8_t*)base;
|
||||
(void)view[0];
|
||||
(void)view[4096];
|
||||
print("pcache_smoke: mmap faults OK\n");
|
||||
|
||||
if (memcmp((const void*)base, gA, gotA) != 0) {
|
||||
SysFreeVm(base);
|
||||
fail("mapped data mismatch");
|
||||
}
|
||||
|
||||
s = query_cache(&afterMap);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("query after map", s);
|
||||
if (afterMap.Hits <= afterSecond.Hits)
|
||||
fail("mmap faults did not hit already cached pages");
|
||||
|
||||
SysFreeVm(base);
|
||||
|
||||
print("pcache_smoke: stats loads="); print_u64(afterMap.Loads - before.Loads);
|
||||
print(" hits="); print_u64(afterMap.Hits - before.Hits);
|
||||
print(" resident="); print_u64(afterMap.ResidentPages); print("\n");
|
||||
print("pcache_smoke: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// Shared writable mmap regression test.
|
||||
// Verifies that writable mapped-file views dirty the coherent file cache and flush through the normal file path.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define PAGE_SIZE_BYTES 4096u
|
||||
#define TEST_SIZE (PAGE_SIZE_BYTES * 3u)
|
||||
|
||||
static uint8_t gInitial[TEST_SIZE];
|
||||
static uint8_t gExpected[TEST_SIZE];
|
||||
static uint8_t gReadback[TEST_SIZE];
|
||||
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) { print("0"); return; }
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i--) {
|
||||
char c[2] = { buf[i], 0 };
|
||||
print(c);
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
print("shared_mmap: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("shared_mmap: ");
|
||||
print(what);
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
|
||||
memset(out, 0, sizeof(*out));
|
||||
return SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, out, sizeof(*out), 0);
|
||||
}
|
||||
|
||||
static void delete_path(const char* path) {
|
||||
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
|
||||
(void)SysDeleteFile(&req);
|
||||
}
|
||||
|
||||
static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
|
||||
static const char* paths[] = {
|
||||
"shared_mmap.tmp",
|
||||
"/shared_mmap.tmp",
|
||||
"1:/shared_mmap.tmp",
|
||||
"0:/shared_mmap.tmp",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
|
||||
uint32_t h = 0;
|
||||
delete_path(paths[i]);
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(paths[i]), &h };
|
||||
uint32_t s = SysCreateFile(&req);
|
||||
if (s == SYS_STATUS_SUCCESS) {
|
||||
*fileOut = h;
|
||||
if (pathOut) *pathOut = paths[i];
|
||||
return SYS_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
return SYS_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
static uint32_t open_path(const char* path, uint32_t* fileOut) {
|
||||
uint32_t h = 0;
|
||||
SYS_PATH_HANDLE_REQUEST req = { SysSvFromCstr(path), &h };
|
||||
uint32_t s = SysOpenFile(&req);
|
||||
if (s == SYS_STATUS_SUCCESS)
|
||||
*fileOut = h;
|
||||
return s;
|
||||
}
|
||||
|
||||
static void checked_seek(uint32_t file, uint64_t offset, const char* what) {
|
||||
uint32_t s = SysSeekFile(file, offset);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status(what, s);
|
||||
}
|
||||
|
||||
static void checked_write(uint32_t file, const void* buffer, size_t size, const char* what) {
|
||||
size_t done = 0;
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &done);
|
||||
uint32_t s = SysWriteFile(&req);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status(what, s);
|
||||
if (done != size) fail("short write");
|
||||
}
|
||||
|
||||
static void checked_read(uint32_t file, void* buffer, size_t size, size_t* gotOut, const char* what) {
|
||||
size_t got = 0;
|
||||
memset(buffer, 0xCC, size);
|
||||
SYS_FILE_IO_REQUEST req = SysMakeFileIoRequest(file, buffer, size, &got);
|
||||
uint32_t s = SysReadFile(&req);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status(what, s);
|
||||
if (gotOut) *gotOut = got;
|
||||
}
|
||||
|
||||
static void fill_initial(void) {
|
||||
for (size_t i = 0; i < TEST_SIZE; ++i) {
|
||||
gInitial[i] = (uint8_t)(0x20u + (uint8_t)(i * 7u + (i >> 5)));
|
||||
gExpected[i] = gInitial[i];
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
print("shared_mmap: start\n");
|
||||
fill_initial();
|
||||
|
||||
uint32_t file = 0;
|
||||
const char* path = 0;
|
||||
uint32_t s = create_temp(&file, &path);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create temp", s);
|
||||
|
||||
print("shared_mmap: creating temporary file ");
|
||||
print(path ? path : "<unknown>");
|
||||
print("\n");
|
||||
|
||||
checked_write(file, gInitial, TEST_SIZE, "initial write");
|
||||
s = SysFlushFile(file);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("initial flush", s);
|
||||
|
||||
uint32_t mapping = 0;
|
||||
s = SysCreateFileMapping(file, SYS_VM_READWRITE, SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create mapping", s);
|
||||
|
||||
uint8_t* view = 0;
|
||||
s = SysMapFile(mapping, 0, TEST_SIZE, SYS_MAP_ACCESS_READ | SYS_MAP_ACCESS_WRITE,
|
||||
SYS_MAP_FLAG_NONE, (void**)&view);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("map shared write", s);
|
||||
|
||||
for (size_t i = 128; i < 128 + 3000; ++i) {
|
||||
view[i] = (uint8_t)(0xA0u + (uint8_t)i);
|
||||
gExpected[i] = view[i];
|
||||
}
|
||||
for (size_t i = PAGE_SIZE_BYTES + 17; i < PAGE_SIZE_BYTES + 17 + 2048; ++i) {
|
||||
view[i] = (uint8_t)(0x50u + (uint8_t)(i >> 1));
|
||||
gExpected[i] = view[i];
|
||||
}
|
||||
|
||||
SYS_FILE_CACHE_STATS beforeFlush;
|
||||
SYS_FILE_CACHE_STATS afterFlush;
|
||||
if (query_cache(&beforeFlush) != SYS_STATUS_SUCCESS) fail("pre-flush stats failed");
|
||||
|
||||
s = SysFlushFile(file);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("flush mapped writes", s);
|
||||
|
||||
if (query_cache(&afterFlush) != SYS_STATUS_SUCCESS) fail("post-flush stats failed");
|
||||
if (afterFlush.MappedDirtyPages <= beforeFlush.MappedDirtyPages)
|
||||
fail("mapped dirty pages were not harvested");
|
||||
if (afterFlush.WritebackBytes <= beforeFlush.WritebackBytes)
|
||||
fail("mapped writes did not write back");
|
||||
|
||||
checked_seek(file, 0, "seek after mapped flush");
|
||||
size_t got = 0;
|
||||
checked_read(file, gReadback, TEST_SIZE, &got, "read after mapped flush");
|
||||
if (got != TEST_SIZE) fail("read after mapped flush size mismatch");
|
||||
if (memcmp(gReadback, gExpected, TEST_SIZE) != 0) fail("mapped flush data mismatch");
|
||||
|
||||
for (size_t i = PAGE_SIZE_BYTES * 2 + 3; i < PAGE_SIZE_BYTES * 2 + 900; ++i) {
|
||||
view[i] = (uint8_t)(0xC0u ^ (uint8_t)i);
|
||||
gExpected[i] = view[i];
|
||||
}
|
||||
|
||||
s = SysFreeVm(view);
|
||||
view = 0;
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("unmap", s);
|
||||
|
||||
s = SysCloseHandle(mapping);
|
||||
mapping = 0;
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("close mapping", s);
|
||||
|
||||
s = SysCloseHandle(file);
|
||||
file = 0;
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("close file", s);
|
||||
|
||||
s = open_path(path, &file);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("reopen", s);
|
||||
|
||||
checked_read(file, gReadback, TEST_SIZE, &got, "read after close");
|
||||
if (got != TEST_SIZE) fail("read after close size mismatch");
|
||||
if (memcmp(gReadback, gExpected, TEST_SIZE) != 0) fail("unmap/close data mismatch");
|
||||
|
||||
s = SysCloseHandle(file);
|
||||
file = 0;
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("close reopened", s);
|
||||
delete_path(path);
|
||||
|
||||
print("shared_mmap: mapped-dirty-pages+=");
|
||||
print_u64(afterFlush.MappedDirtyPages - beforeFlush.MappedDirtyPages);
|
||||
print(" writeback-bytes+=");
|
||||
print_u64(afterFlush.WritebackBytes - beforeFlush.WritebackBytes);
|
||||
print("\n");
|
||||
print("shared_mmap: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Native surface validation test.
|
||||
// Exercises invalid parameters and clipped dirty rectangles without depending on windowing.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define SURFACE_W 32u
|
||||
#define SURFACE_H 24u
|
||||
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) { print("0"); return; }
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i--) {
|
||||
char c = buf[i];
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
print("surface_invalid_rect: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("surface_invalid_rect: ");
|
||||
print(what);
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void expect_status(const char* what, uint32_t got, uint32_t expected) {
|
||||
if (got == expected) return;
|
||||
print("surface_invalid_rect: ");
|
||||
print(what);
|
||||
print(" returned ");
|
||||
print_u64(got);
|
||||
print(" expected ");
|
||||
print_u64(expected);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
print("surface_invalid_rect: start\n");
|
||||
|
||||
uint32_t bad = 0;
|
||||
uint32_t s = SysCreateSurface(0, SURFACE_H, SYS_SURFACE_FORMAT_XRGB8888, SYS_SURFACE_FLAG_NONE, &bad);
|
||||
expect_status("zero width", s, SYS_STATUS_INVALID_PARAMETER);
|
||||
|
||||
s = SysCreateSurface(SURFACE_W, SURFACE_H, 0x9999u, SYS_SURFACE_FLAG_NONE, &bad);
|
||||
expect_status("bad format", s, SYS_STATUS_NOT_SUPPORTED);
|
||||
|
||||
uint32_t surface = 0;
|
||||
s = SysCreateSurface(SURFACE_W, SURFACE_H, SYS_SURFACE_FORMAT_XRGB8888,
|
||||
SYS_SURFACE_FLAG_NONE, &surface);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create surface", s);
|
||||
|
||||
void* base = 0;
|
||||
uint32_t pitch = 0;
|
||||
s = SysMapSurface(surface, &base, &pitch);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("map surface", s);
|
||||
if (!base || pitch < SURFACE_W * sizeof(uint32_t)) fail("invalid mapping");
|
||||
|
||||
s = SysPresentSurface(surface, 0, 1);
|
||||
expect_status("null rect array with nonzero count", s, SYS_STATUS_INVALID_PARAMETER);
|
||||
|
||||
DISPLAY_RECT rects[SYS_SURFACE_MAX_RECTS + 1];
|
||||
memset(rects, 0, sizeof(rects));
|
||||
s = SysPresentSurface(surface, rects, SYS_SURFACE_MAX_RECTS + 1);
|
||||
expect_status("too many rects", s, SYS_STATUS_INVALID_PARAMETER);
|
||||
|
||||
DISPLAY_RECT clipped = { -8, -8, 24, 24 };
|
||||
s = SysPresentSurface(surface, &clipped, 1);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("present clipped rect", s);
|
||||
|
||||
DISPLAY_RECT empty = { 10000, 10000, 16, 16 };
|
||||
s = SysPresentSurface(surface, &empty, 1);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("present empty clipped rect", s);
|
||||
|
||||
SysCloseHandle(surface);
|
||||
print("surface_invalid_rect: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Native surface multithreaded present test.
|
||||
// Multiple user threads present one process-private surface to catch mapping and scratch-buffer races.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define SURFACE_W 96u
|
||||
#define SURFACE_H 64u
|
||||
#define THREAD_COUNT 4u
|
||||
#define ITERATIONS 32u
|
||||
|
||||
typedef struct WORKER_STATE {
|
||||
uint32_t Index;
|
||||
uint32_t Surface;
|
||||
uint8_t* Base;
|
||||
uint32_t Pitch;
|
||||
uint32_t Status;
|
||||
uint32_t IterationsDone;
|
||||
} WORKER_STATE;
|
||||
|
||||
static WORKER_STATE gWorkers[THREAD_COUNT];
|
||||
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) { print("0"); return; }
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i--) {
|
||||
char c = buf[i];
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
print("surface_multithread_present: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("surface_multithread_present: ");
|
||||
print(what);
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void worker_main(uintptr_t arg) {
|
||||
WORKER_STATE* st = (WORKER_STATE*)arg;
|
||||
if (!st) SysExitThread(1);
|
||||
uint32_t bandHeight = SURFACE_H / THREAD_COUNT;
|
||||
uint32_t y0 = st->Index * bandHeight;
|
||||
uint32_t h = bandHeight;
|
||||
if (st->Index == THREAD_COUNT - 1)
|
||||
h = SURFACE_H - y0;
|
||||
|
||||
for (uint32_t iter = 0; iter < ITERATIONS; ++iter) {
|
||||
for (uint32_t y = 0; y < h; ++y) {
|
||||
uint32_t* row = (uint32_t*)(st->Base + (size_t)(y0 + y) * st->Pitch);
|
||||
for (uint32_t x = 0; x < SURFACE_W; ++x)
|
||||
row[x] = 0x00010101u * ((st->Index + 1u) * 32u + (iter & 31u));
|
||||
}
|
||||
|
||||
DISPLAY_RECT rect = { 0, (int32_t)y0, (int32_t)SURFACE_W, (int32_t)h };
|
||||
uint32_t s = SysPresentSurface(st->Surface, &rect, 1);
|
||||
if (s != SYS_STATUS_SUCCESS) {
|
||||
st->Status = s;
|
||||
SysExitThread(1);
|
||||
}
|
||||
st->IterationsDone++;
|
||||
SysYield();
|
||||
}
|
||||
|
||||
st->Status = SYS_STATUS_SUCCESS;
|
||||
SysExitThread(0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
print("surface_multithread_present: start\n");
|
||||
|
||||
uint32_t surface = 0;
|
||||
uint32_t s = SysCreateSurface(SURFACE_W, SURFACE_H, SYS_SURFACE_FORMAT_XRGB8888,
|
||||
SYS_SURFACE_FLAG_NONE, &surface);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create surface", s);
|
||||
|
||||
void* base = 0;
|
||||
uint32_t pitch = 0;
|
||||
s = SysMapSurface(surface, &base, &pitch);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("map surface", s);
|
||||
if (!base || pitch < SURFACE_W * sizeof(uint32_t)) fail("invalid mapping");
|
||||
|
||||
uint32_t handles[THREAD_COUNT];
|
||||
uint32_t tids[THREAD_COUNT];
|
||||
memset(handles, 0, sizeof(handles));
|
||||
memset(tids, 0, sizeof(tids));
|
||||
memset(gWorkers, 0, sizeof(gWorkers));
|
||||
|
||||
for (uint32_t i = 0; i < THREAD_COUNT; ++i) {
|
||||
gWorkers[i].Index = i;
|
||||
gWorkers[i].Surface = surface;
|
||||
gWorkers[i].Base = (uint8_t*)base;
|
||||
gWorkers[i].Pitch = pitch;
|
||||
gWorkers[i].Status = SYS_STATUS_PENDING;
|
||||
SYS_CREATE_THREAD_REQUEST req = {
|
||||
(uintptr_t)worker_main,
|
||||
(uintptr_t)&gWorkers[i],
|
||||
1,
|
||||
0,
|
||||
&handles[i],
|
||||
&tids[i]
|
||||
};
|
||||
s = SysCreateThread(&req);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create thread", s);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < THREAD_COUNT; ++i) {
|
||||
s = SysWait(handles[i], SYS_WAIT_FLAG_NONE);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("wait thread", s);
|
||||
SysCloseHandle(handles[i]);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < THREAD_COUNT; ++i) {
|
||||
if (gWorkers[i].Status != SYS_STATUS_SUCCESS) {
|
||||
print("surface_multithread_present: worker ");
|
||||
print_u64(i);
|
||||
print(" status=");
|
||||
print_u64(gWorkers[i].Status);
|
||||
print(" iterations=");
|
||||
print_u64(gWorkers[i].IterationsDone);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
if (gWorkers[i].IterationsDone != ITERATIONS) fail("worker iteration count mismatch");
|
||||
}
|
||||
|
||||
SysCloseHandle(surface);
|
||||
print("surface_multithread_present: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Native surface smoke test.
|
||||
// Verifies that process-private surface handles can be created, mapped, queried, presented, and closed.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <ntlite/syscall.h>
|
||||
|
||||
#define SURFACE_W 64u
|
||||
#define SURFACE_H 48u
|
||||
|
||||
static void print(const char* s) {
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
|
||||
static void print_u64(uint64_t v) {
|
||||
char buf[32];
|
||||
int i = 0;
|
||||
if (v == 0) { print("0"); return; }
|
||||
while (v && i < (int)sizeof(buf)) {
|
||||
buf[i++] = (char)('0' + (v % 10));
|
||||
v /= 10;
|
||||
}
|
||||
while (i--) {
|
||||
char c = buf[i];
|
||||
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
|
||||
SysWriteConsole(&req);
|
||||
}
|
||||
}
|
||||
|
||||
static void exit_now(int code) {
|
||||
for (;;) SysExitProcess(code);
|
||||
}
|
||||
|
||||
static void fail(const char* msg) {
|
||||
print("surface_smoke: ");
|
||||
print(msg);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
static void fail_status(const char* what, uint32_t status) {
|
||||
print("surface_smoke: ");
|
||||
print(what);
|
||||
print(" failed status=");
|
||||
print_u64(status);
|
||||
print("\n");
|
||||
exit_now(1);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
print("surface_smoke: start\n");
|
||||
|
||||
uint32_t surface = 0;
|
||||
uint32_t s = SysCreateSurface(SURFACE_W, SURFACE_H, SYS_SURFACE_FORMAT_XRGB8888,
|
||||
SYS_SURFACE_FLAG_NONE, &surface);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("create surface", s);
|
||||
if (!surface) fail("surface handle is zero");
|
||||
|
||||
void* base = 0;
|
||||
uint32_t pitch = 0;
|
||||
s = SysMapSurface(surface, &base, &pitch);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("map surface", s);
|
||||
if (!base || pitch < SURFACE_W * sizeof(uint32_t)) fail("invalid mapping");
|
||||
|
||||
SYS_SURFACE_DESC desc;
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
s = SysQuerySurface(surface, &desc);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("query surface", s);
|
||||
if (desc.Width != SURFACE_W || desc.Height != SURFACE_H) fail("query dimensions mismatch");
|
||||
if (desc.PitchBytes != pitch || desc.Format != SYS_SURFACE_FORMAT_XRGB8888) fail("query metadata mismatch");
|
||||
if (desc.Base != base) fail("query base mismatch");
|
||||
|
||||
uint8_t* rows = (uint8_t*)base;
|
||||
for (uint32_t y = 0; y < SURFACE_H; ++y) {
|
||||
uint32_t* row = (uint32_t*)(rows + (size_t)y * pitch);
|
||||
for (uint32_t x = 0; x < SURFACE_W; ++x)
|
||||
row[x] = 0x00202020u + ((x & 0xffu) << 16) + ((y & 0xffu) << 8);
|
||||
}
|
||||
|
||||
DISPLAY_RECT rect = { 0, 0, (int32_t)SURFACE_W, (int32_t)SURFACE_H };
|
||||
s = SysPresentSurface(surface, &rect, 1);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("present surface", s);
|
||||
|
||||
s = SysPresentSurface(surface, 0, 0);
|
||||
if (s != SYS_STATUS_SUCCESS) fail_status("present full surface", s);
|
||||
|
||||
SysCloseHandle(surface);
|
||||
print("surface_smoke: PASS\n");
|
||||
exit_now(0);
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user