Files
nt-lite/code/user/tests/exception_smoke/exception_smoke.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

263 lines
8.4 KiB
C

// Native exception pipeline integration smoke test.
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <intrin.h>
#include <ntlite/exception.h>
#include <ntlite/syscall.h>
#include <ntlite/process_loader.h>
#define CHILD_DIVIDE_BY_ZERO "child-divide-by-zero"
#define CHILD_ACCESS_VIOLATION "child-access-violation"
#define CHILD_INVALID_RESUME "child-invalid-resume"
#define CHILD_NONCONTINUABLE "child-noncontinuable"
#define SMOKE_NONCONTINUABLE_CODE 0xE1111003u
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
// Prints one unsigned integer.
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 = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
// Terminates the current process.
static void exit_now(int32_t code) {
while (true) SysExitProcess(code);
}
// Fails the test with one message.
static void fail(const char* message) {
print("exception_smoke: ");
print(message);
print("\n");
exit_now(1);
}
// Fails the test with one status value.
static void fail_status(const char* what, uint32_t status) {
print("exception_smoke: ");
print(what);
print(" status=");
print_u64(status);
print("\n");
exit_now(1);
}
// Returns whether a string view exactly matches a literal string.
static int sv_equals_cstr(SYS_STRING_VIEW sv, const char* literal) {
size_t len = strlen(literal);
return sv.Length == len && sv.Data && memcmp(sv.Data, literal, len) == 0;
}
// Returns the current process command line.
static SYS_STRING_VIEW current_command_line(void) {
SYS_PEB* peb = SysGetPeb();
if (!peb)
return SysSvFromParts(NULL, 0);
return peb->CommandLine;
}
// Requires one syscall to return the expected status.
static void expect_status(const char* what, uint32_t got, uint32_t expected) {
if (got != expected) {
print("exception_smoke: ");
print(what);
print(" expected=");
print_u64(expected);
print(" got=");
print_u64(got);
print("\n");
exit_now(1);
}
}
// Runs this image as a child and requires the expected exit code.
static void run_child_expect_exit(const char* mode, uint32_t expectedExit) {
SYS_PEB* peb = SysGetPeb();
if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0)
fail("missing image path");
uint32_t child = 0;
NTL_CREATE_SYSTEM_PROCESS_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
request.Path = peb->ImagePath;
request.CommandLine = SysSvFromCstr(mode);
request.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
request.ResolveModuleName = NtlResolveSystemModuleName;
request.ProcessHandleOut = &child;
uint32_t status = NtlCreateSystemProcess(&request);
if (status != NTLSTATUS_SUCCESS)
fail_status("create child", status);
status = SysWait(child, SYS_WAIT_FLAG_NONE);
if (status != NTLSTATUS_SUCCESS)
fail_status("wait child", status);
int32_t exitCode = 0;
status = SysGetProcessExitCode(child, &exitCode);
if (status != NTLSTATUS_SUCCESS)
fail_status("query child", status);
SysCloseHandle(child);
if ((uint32_t)exitCode != expectedExit) {
print("exception_smoke: child ");
print(mode);
print(" expected exit=");
print_u64(expectedExit);
print(" got=");
print_u64((uint32_t)exitCode);
print("\n");
exit_now(1);
}
}
// Triggers a divide-by-zero with no registered native handlers.
static void child_divide_by_zero(void) {
volatile int one = 1;
volatile int zero = 0;
volatile int sink = one / zero;
(void)sink;
exit_now(2);
}
// Observes an access violation and exits from inside the native handler.
static int access_violation_handler(SYS_EXCEPTION_RECORD* record,
SYS_USER_CONTEXT* context,
void* userContext) {
(void)context;
if (userContext != (void*)(uintptr_t)0xA11C)
exit_now(10);
if (!record || record->Code != SYS_EXCEPTION_CODE_ACCESS_VIOLATION)
exit_now(11);
if (record->FaultAddress != 0)
exit_now(12);
if (record->ParameterCount != 2 || record->Parameters[1] != 0)
exit_now(13);
exit_now(0);
return NTL_EXCEPTION_TERMINATE_PROCESS;
}
// Triggers a null read so the handler can inspect the fault address.
static void child_access_violation(void) {
void* handle = NtlAddExceptionHandler(1, access_violation_handler, (void*)(uintptr_t)0xA11C);
if (!handle)
exit_now(20);
volatile uint8_t* p = (volatile uint8_t*)0;
volatile uint8_t value = *p;
(void)value;
exit_now(21);
}
// Returns a resume context with an invalid user RIP.
static int invalid_resume_handler(SYS_EXCEPTION_RECORD* record,
SYS_USER_CONTEXT* context,
void* userContext) {
(void)userContext;
if (!record || !context || record->Code != SYS_EXCEPTION_CODE_BREAKPOINT)
exit_now(30);
context->Rip = 0;
return NTL_EXCEPTION_CONTINUE_EXECUTION;
}
// Triggers a breakpoint whose handler asks the kernel to resume at RIP zero.
static void child_invalid_resume(void) {
void* handle = NtlAddExceptionHandler(1, invalid_resume_handler, NULL);
if (!handle)
exit_now(31);
__debugbreak();
exit_now(32);
}
// Attempts to continue a noncontinuable user-raised exception.
static int noncontinuable_handler(SYS_EXCEPTION_RECORD* record,
SYS_USER_CONTEXT* context,
void* userContext) {
(void)context;
(void)userContext;
if (!record || record->Code != SYS_EXCEPTION_CODE_USER_RAISED ||
record->UserCode != SMOKE_NONCONTINUABLE_CODE)
exit_now(40);
if ((record->Flags & SYS_EXCEPTION_FLAG_NONCONTINUABLE) == 0)
exit_now(41);
return NTL_EXCEPTION_CONTINUE_EXECUTION;
}
// Raises a noncontinuable exception that a handler must not resume.
static void child_noncontinuable(void) {
void* handle = NtlAddExceptionHandler(1, noncontinuable_handler, NULL);
if (!handle)
exit_now(42);
SYS_RAISE_EXCEPTION_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Flags = SYS_RAISE_EXCEPTION_FLAG_NONCONTINUABLE;
request.UserCode = SMOKE_NONCONTINUABLE_CODE;
uint32_t status = SysRaiseException(&request);
exit_now((int32_t)status);
}
// Dispatches the child process mode selected by the command line.
static void run_child_if_requested(void) {
SYS_STRING_VIEW command = current_command_line();
if (sv_equals_cstr(command, CHILD_DIVIDE_BY_ZERO))
child_divide_by_zero();
if (sv_equals_cstr(command, CHILD_ACCESS_VIOLATION))
child_access_violation();
if (sv_equals_cstr(command, CHILD_INVALID_RESUME))
child_invalid_resume();
if (sv_equals_cstr(command, CHILD_NONCONTINUABLE))
child_noncontinuable();
}
// Runs validation that does not intentionally fault this process.
static void verify_continue_validation(void) {
expect_status("null continue", SysContinueException(NULL), NTLSTATUS_INVALID_PARAMETER);
SYS_CONTINUE_EXCEPTION_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Disposition = SYS_EXCEPTION_DISPOSITION_CONTINUE_EXECUTION;
expect_status("inactive continue", SysContinueException(&request), NTLSTATUS_INVALID_PARAMETER);
}
// Runs the native exception integration smoke test.
int main(void) {
run_child_if_requested();
print("exception_smoke: start\n");
verify_continue_validation();
run_child_expect_exit(CHILD_DIVIDE_BY_ZERO, NTLSTATUS_PROCESS_DIVIDE_BY_ZERO);
run_child_expect_exit(CHILD_ACCESS_VIOLATION, NTLSTATUS_SUCCESS);
run_child_expect_exit(CHILD_INVALID_RESUME, NTLSTATUS_ACCESS_DENIED);
run_child_expect_exit(CHILD_NONCONTINUABLE, NTLSTATUS_PROCESS_UNKNOWN_FAULT);
print("exception_smoke: PASS\n");
exit_now(0);
return 0;
}