windows.c (63784B)
1 /* Windows host environment. Replaces posix.c and posix_dbg.c on Win32 2 * builds; common.c is reused unchanged. Built against the Win32 API with 3 * MinGW-w64 in mind. 4 * 5 * Coverage: 6 * - file_io (CreateFileW/ReadFile/WriteFile + UTF-8 path conversion) 7 * - path helpers (GetFileAttributesExW for exists/mtime, mkdir_p via 8 * CreateDirectoryW, mark_executable is a no-op on NTFS) 9 * - stdin / read_line / edit_temp (GetTempPath + system()) 10 * - monotonic time (QueryPerformanceCounter) 11 * - SIGINT shim via SetConsoleCtrlHandler 12 * - dlsym via GetProcAddress over an EnumProcessModules snapshot 13 * - execmem: dual-mapping via CreateFileMappingW + MapViewOfFile; the 14 * write alias is RW, the runtime alias is RX after a protect flip. 15 * Single-mapping fallback uses VirtualAlloc. 16 * - dbg_os: CreateThread for the worker, event objects, vectored 17 * exception handling for SEGV/ILL/BP/etc, __try/__except guarded_copy, 18 * setjmp/longjmp for call_with_catch / thread_abort, SuspendThread + 19 * GetThreadContext for the interrupt path (the interrupt's on_fault 20 * runs on the *caller* thread; natural faults run on the worker 21 * thread inside the VEH, matching POSIX semantics there). 22 * 23 * The W^X model on Windows mirrors the Linux/FreeBSD memfd path: a single 24 * pagefile-backed file-mapping object is mapped twice -- once RW (write 25 * alias) and once RX (runtime alias). The dbg code-write path translates 26 * a runtime address into the corresponding write alias via the same 27 * exec_dual registry the POSIX side uses (re-implemented locally with 28 * SRWLock since we don't link the POSIX TUs). Single-mapping reservations 29 * (no KIT_PROT_EXEC) just VirtualAlloc a single RW region. 30 */ 31 32 #ifndef WIN32_LEAN_AND_MEAN 33 #define WIN32_LEAN_AND_MEAN 34 #endif 35 #ifndef _WIN32_WINNT 36 #define _WIN32_WINNT \ 37 0x0601 /* Windows 7+: SRWLock, AddVectoredExceptionHandler */ 38 #endif 39 /* windows.h must precede psapi.h/process.h: the mingw SDK headers use 40 * windows.h's WINBOOL/DWORD/LPVOID etc. and do not self-include it. Kept in 41 * its own include group so a formatter's alphabetic sort can't reorder it 42 * after psapi.h (which breaks the mingw cross-build). */ 43 #include <io.h> 44 #include <process.h> 45 #include <psapi.h> 46 #include <stdint.h> 47 #include <stdio.h> 48 #include <windows.h> 49 #define _CRT_RAND_S /* enable rand_s (OS CSPRNG) */ 50 #include <setjmp.h> 51 #include <stdlib.h> 52 #include <string.h> 53 #include <sys/stat.h> 54 #include <time.h> 55 56 #include "env_internal.h" 57 58 extern char** _environ; 59 60 /* Win32 dbg interrupt code: a synthetic signo handed up to on_fault. The 61 * value just needs to be distinct from real exception codes; we pick a 62 * small positive int so it round-trips through the int field of 63 * KitDbgOs.interrupt_signo. */ 64 #define DBG_WIN_INTERRUPT_SIGNO 100 65 66 /* ============================================================ 67 * UTF-8 <-> UTF-16 path conversion 68 * ============================================================ */ 69 70 /* Convert a UTF-8 path to a freshly-malloc'd wide string. Returns NULL on 71 * empty input or allocation failure. Callers free with `free`. */ 72 static wchar_t* widen(const char* utf8) { 73 int need; 74 wchar_t* w; 75 if (!utf8 || !*utf8) return NULL; 76 need = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); 77 if (need <= 0) return NULL; 78 w = (wchar_t*)malloc((size_t)need * sizeof(wchar_t)); 79 if (!w) return NULL; 80 if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, w, need) <= 0) { 81 free(w); 82 return NULL; 83 } 84 return w; 85 } 86 87 static char* narrow(const wchar_t* wide) { 88 int need; 89 char* out; 90 if (!wide) return NULL; 91 need = WideCharToMultiByte(CP_UTF8, 0, wide, -1, NULL, 0, NULL, NULL); 92 if (need <= 0) return NULL; 93 out = (char*)malloc((size_t)need); 94 if (!out) return NULL; 95 if (WideCharToMultiByte(CP_UTF8, 0, wide, -1, out, need, NULL, NULL) <= 0) { 96 free(out); 97 return NULL; 98 } 99 return out; 100 } 101 102 /* ============================================================ 103 * exec_dual registry (write/runtime alias bookkeeping) 104 * ============================================================ */ 105 106 typedef struct ExecDualNode { 107 void* write_base; 108 void* runtime_base; 109 size_t size; 110 struct ExecDualNode* next; 111 } ExecDualNode; 112 113 static ExecDualNode* g_jit_dual_map; 114 static SRWLOCK g_jit_dual_map_lock = SRWLOCK_INIT; 115 116 static void exec_dual_register_w(void* write_base, void* runtime_base, 117 size_t size) { 118 ExecDualNode* n; 119 if (write_base == runtime_base) return; 120 n = (ExecDualNode*)malloc(sizeof(*n)); 121 if (!n) return; 122 n->write_base = write_base; 123 n->runtime_base = runtime_base; 124 n->size = size; 125 AcquireSRWLockExclusive(&g_jit_dual_map_lock); 126 n->next = g_jit_dual_map; 127 g_jit_dual_map = n; 128 ReleaseSRWLockExclusive(&g_jit_dual_map_lock); 129 } 130 131 static void exec_dual_unregister_w(void* runtime_base) { 132 ExecDualNode** pp; 133 AcquireSRWLockExclusive(&g_jit_dual_map_lock); 134 for (pp = &g_jit_dual_map; *pp; pp = &(*pp)->next) { 135 if ((*pp)->runtime_base == runtime_base) { 136 ExecDualNode* dead = *pp; 137 *pp = dead->next; 138 free(dead); 139 break; 140 } 141 } 142 ReleaseSRWLockExclusive(&g_jit_dual_map_lock); 143 } 144 145 static int exec_dual_lookup_w(void* runtime_addr, size_t n, void** write_out) { 146 ExecDualNode* cur; 147 uintptr_t a = (uintptr_t)runtime_addr; 148 AcquireSRWLockShared(&g_jit_dual_map_lock); 149 for (cur = g_jit_dual_map; cur; cur = cur->next) { 150 uintptr_t base = (uintptr_t)cur->runtime_base; 151 if (a >= base && a + n <= base + cur->size) { 152 *write_out = (void*)((uintptr_t)cur->write_base + (a - base)); 153 ReleaseSRWLockShared(&g_jit_dual_map_lock); 154 return 0; 155 } 156 } 157 ReleaseSRWLockShared(&g_jit_dual_map_lock); 158 return 1; 159 } 160 161 /* ============================================================ 162 * exec memory: dual-map via CreateFileMappingW, single via VirtualAlloc 163 * ============================================================ */ 164 165 typedef struct ExecMemTokenWin { 166 HANDLE mapping; /* NULL for single-mapping reservations */ 167 void* write_addr; 168 void* runtime_addr; 169 size_t size; 170 } ExecMemTokenWin; 171 172 static DWORD kit_to_win_prot(int prot) { 173 int r = (prot & KIT_PROT_READ) != 0; 174 int w = (prot & KIT_PROT_WRITE) != 0; 175 int x = (prot & KIT_PROT_EXEC) != 0; 176 if (x && w) return PAGE_EXECUTE_READWRITE; 177 if (x && r) return PAGE_EXECUTE_READ; 178 if (x) return PAGE_EXECUTE; 179 if (w) return PAGE_READWRITE; 180 if (r) return PAGE_READONLY; 181 return PAGE_NOACCESS; 182 } 183 184 static size_t driver_host_page_size_win(void) { 185 SYSTEM_INFO si; 186 GetSystemInfo(&si); 187 return si.dwPageSize ? (size_t)si.dwPageSize : (size_t)0x1000; 188 } 189 190 static KitStatus execmem_reserve_single_win(size_t size, 191 KitExecMemRegion* out) { 192 void* p = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 193 if (!p) return KIT_NOMEM; 194 out->write = p; 195 out->runtime = p; 196 out->size = size; 197 out->token = NULL; 198 return KIT_OK; 199 } 200 201 static KitStatus execmem_reserve_dual_win(size_t size, KitExecMemRegion* out) { 202 HANDLE map; 203 void* w; 204 void* r; 205 ExecMemTokenWin* tok; 206 DWORD lo = (DWORD)(size & 0xFFFFFFFFu); 207 DWORD hi = (DWORD)((uint64_t)size >> 32); 208 209 /* PAGE_EXECUTE_READWRITE on the section object is the max protection any 210 * view can request; per-view protections are narrower (RW vs RX). */ 211 map = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 212 hi, lo, NULL); 213 if (!map) return KIT_ERR; 214 215 w = MapViewOfFile(map, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, size); 216 if (!w) { 217 CloseHandle(map); 218 return KIT_NOMEM; 219 } 220 /* Include FILE_MAP_WRITE in the runtime/exec view's mapping access. A view's 221 * VirtualProtect ceiling is bounded by its map-access flags, not just the 222 * section's max protection: a view mapped READ|EXECUTE can be reprotected to 223 * R/RX/RO but NOT to PAGE_READWRITE (err 87). JIT images carry writable 224 * runtime segments (an import GOT, .data/.bss) whose final perms are RW, so 225 * the exec view must permit write to be VirtualProtect'd RW. Per-segment page 226 * protection still enforces W^X (code stays RX, never writable). */ 227 r = MapViewOfFile(map, FILE_MAP_READ | FILE_MAP_WRITE | FILE_MAP_EXECUTE, 0, 228 0, size); 229 if (!r) { 230 UnmapViewOfFile(w); 231 CloseHandle(map); 232 return KIT_NOMEM; 233 } 234 235 tok = (ExecMemTokenWin*)malloc(sizeof(*tok)); 236 if (!tok) { 237 UnmapViewOfFile(r); 238 UnmapViewOfFile(w); 239 CloseHandle(map); 240 return KIT_NOMEM; 241 } 242 tok->mapping = map; 243 tok->write_addr = w; 244 tok->runtime_addr = r; 245 tok->size = size; 246 247 exec_dual_register_w(w, r, size); 248 249 out->write = w; 250 out->runtime = r; 251 out->size = size; 252 out->token = tok; 253 return KIT_OK; 254 } 255 256 static KitStatus execmem_reserve_win(void* user, size_t size, int prot, 257 KitExecMemRegion* out) { 258 (void)user; 259 if (!out || !size) return KIT_INVALID; 260 if (prot & KIT_PROT_EXEC) return execmem_reserve_dual_win(size, out); 261 return execmem_reserve_single_win(size, out); 262 } 263 264 static KitStatus execmem_protect_win(void* user, void* addr, size_t size, 265 int prot) { 266 DWORD old; 267 (void)user; 268 return VirtualProtect(addr, size, kit_to_win_prot(prot), &old) ? KIT_OK 269 : KIT_ERR; 270 } 271 272 static void execmem_release_win(void* user, KitExecMemRegion* region) { 273 (void)user; 274 if (!region || !region->size) return; 275 if (region->token) { 276 ExecMemTokenWin* tok = (ExecMemTokenWin*)region->token; 277 if (tok->runtime_addr && tok->runtime_addr != tok->write_addr) { 278 exec_dual_unregister_w(tok->runtime_addr); 279 UnmapViewOfFile(tok->runtime_addr); 280 } 281 if (tok->write_addr) UnmapViewOfFile(tok->write_addr); 282 if (tok->mapping) CloseHandle(tok->mapping); 283 free(tok); 284 } else if (region->write) { 285 VirtualFree(region->write, 0, MEM_RELEASE); 286 } 287 region->write = NULL; 288 region->runtime = NULL; 289 region->size = 0; 290 region->token = NULL; 291 } 292 293 static void execmem_flush_icache_win(void* user, void* addr, size_t size) { 294 (void)user; 295 FlushInstructionCache(GetCurrentProcess(), addr, size); 296 } 297 298 static KitExecMem g_execmem_win; 299 300 /* ============================================================ 301 * Writer vtables: HANDLE-backed and stdio-backed 302 * ============================================================ */ 303 304 typedef struct DriverHandleWriter { 305 KitWriter base; 306 KitHeap* heap; 307 HANDLE h; 308 KitStatus status; 309 uint64_t pos; 310 } DriverHandleWriter; 311 312 static KitStatus hw_write(KitWriter* w, const void* data, size_t n) { 313 DriverHandleWriter* fw = (DriverHandleWriter*)w; 314 const unsigned char* p = (const unsigned char*)data; 315 if (fw->status != KIT_OK) return fw->status; 316 while (n > 0) { 317 DWORD chunk = n > 0x40000000u ? 0x40000000u : (DWORD)n; 318 DWORD wrote = 0; 319 if (!WriteFile(fw->h, p, chunk, &wrote, NULL) || wrote == 0) { 320 fw->status = KIT_IO; 321 return KIT_IO; 322 } 323 p += wrote; 324 n -= wrote; 325 fw->pos += wrote; 326 } 327 return KIT_OK; 328 } 329 330 static KitStatus hw_seek(KitWriter* w, uint64_t off) { 331 DriverHandleWriter* fw = (DriverHandleWriter*)w; 332 LARGE_INTEGER li; 333 if (fw->status != KIT_OK) return fw->status; 334 li.QuadPart = (LONGLONG)off; 335 if (!SetFilePointerEx(fw->h, li, NULL, FILE_BEGIN)) { 336 fw->status = KIT_IO; 337 return KIT_IO; 338 } 339 fw->pos = off; 340 return KIT_OK; 341 } 342 343 static uint64_t hw_tell(KitWriter* w) { return ((DriverHandleWriter*)w)->pos; } 344 static KitStatus hw_status(KitWriter* w) { 345 return ((DriverHandleWriter*)w)->status; 346 } 347 static void hw_close(KitWriter* w) { 348 DriverHandleWriter* fw = (DriverHandleWriter*)w; 349 if (fw->h && fw->h != INVALID_HANDLE_VALUE) CloseHandle(fw->h); 350 fw->heap->free(fw->heap, fw, sizeof(*fw)); 351 } 352 353 static KitWriter* driver_writer_handle(KitHeap* h, HANDLE fh) { 354 DriverHandleWriter* fw = (DriverHandleWriter*)h->alloc( 355 h, sizeof(*fw), _Alignof(DriverHandleWriter)); 356 if (!fw) return NULL; 357 fw->base.write = hw_write; 358 fw->base.seek = hw_seek; 359 fw->base.tell = hw_tell; 360 fw->base.status = hw_status; 361 fw->base.close = hw_close; 362 fw->heap = h; 363 fw->h = fh; 364 fw->status = KIT_OK; 365 fw->pos = 0; 366 return &fw->base; 367 } 368 369 typedef struct DriverStdioWriter { 370 KitWriter base; 371 KitHeap* heap; 372 FILE* fp; 373 KitStatus status; 374 } DriverStdioWriter; 375 376 static KitStatus stdio_w_write(KitWriter* w, const void* data, size_t n) { 377 DriverStdioWriter* sw = (DriverStdioWriter*)w; 378 if (n) { 379 size_t got = fwrite(data, 1, n, sw->fp); 380 if (got != n) { 381 sw->status = KIT_IO; 382 return KIT_IO; 383 } 384 } 385 return KIT_OK; 386 } 387 static KitStatus stdio_w_seek(KitWriter* w, uint64_t off) { 388 DriverStdioWriter* sw = (DriverStdioWriter*)w; 389 return fseek(sw->fp, (long)off, SEEK_SET) == 0 ? KIT_OK : KIT_IO; 390 } 391 static uint64_t stdio_w_tell(KitWriter* w) { 392 long t = ftell(((DriverStdioWriter*)w)->fp); 393 return t < 0 ? 0u : (uint64_t)t; 394 } 395 static KitStatus stdio_w_status(KitWriter* w) { 396 DriverStdioWriter* sw = (DriverStdioWriter*)w; 397 if (sw->status != KIT_OK) return sw->status; 398 return ferror(sw->fp) ? KIT_IO : KIT_OK; 399 } 400 static void stdio_w_close(KitWriter* w) { 401 DriverStdioWriter* sw = (DriverStdioWriter*)w; 402 fflush(sw->fp); 403 sw->heap->free(sw->heap, sw, sizeof(*sw)); 404 } 405 406 static KitWriter* driver_stdio_writer(DriverEnv* e, FILE* fp) { 407 DriverStdioWriter* sw = (DriverStdioWriter*)e->heap->alloc( 408 e->heap, sizeof(*sw), _Alignof(DriverStdioWriter)); 409 if (!sw) return NULL; 410 sw->base.write = stdio_w_write; 411 sw->base.seek = stdio_w_seek; 412 sw->base.tell = stdio_w_tell; 413 sw->base.status = stdio_w_status; 414 sw->base.close = stdio_w_close; 415 sw->heap = e->heap; 416 sw->fp = fp; 417 sw->status = KIT_OK; 418 return &sw->base; 419 } 420 421 KitWriter* driver_stdout_writer(DriverEnv* e) { 422 return driver_stdio_writer(e, stdout); 423 } 424 425 KitWriter* driver_stderr_writer(DriverEnv* e) { 426 return driver_stdio_writer(e, stderr); 427 } 428 429 const char* const* driver_environ(void) { return (const char* const*)_environ; } 430 431 /* ============================================================ 432 * file_io (CreateFileW + ReadFile/WriteFile) 433 * ============================================================ */ 434 435 static KitStatus win_read_all(void* user, const char* path, KitFileData* out) { 436 DriverEnv* env = (DriverEnv*)user; 437 wchar_t* wpath; 438 HANDLE h; 439 LARGE_INTEGER sz; 440 size_t size; 441 size_t got; 442 void* buf; 443 444 wpath = widen(path); 445 if (!wpath) return KIT_NOT_FOUND; 446 h = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 447 FILE_ATTRIBUTE_NORMAL, NULL); 448 free(wpath); 449 if (h == INVALID_HANDLE_VALUE) return KIT_NOT_FOUND; 450 if (!GetFileSizeEx(h, &sz)) { 451 CloseHandle(h); 452 return KIT_IO; 453 } 454 size = (size_t)sz.QuadPart; 455 buf = size ? env->heap->alloc(env->heap, size, 1) : NULL; 456 if (size && !buf) { 457 CloseHandle(h); 458 return KIT_NOMEM; 459 } 460 got = 0; 461 while (got < size) { 462 DWORD chunk = 463 (size - got) > 0x40000000u ? 0x40000000u : (DWORD)(size - got); 464 DWORD n = 0; 465 if (!ReadFile(h, (unsigned char*)buf + got, chunk, &n, NULL) || n == 0) { 466 env->heap->free(env->heap, buf, size); 467 CloseHandle(h); 468 return KIT_IO; 469 } 470 got += n; 471 } 472 CloseHandle(h); 473 out->data = (const uint8_t*)buf; 474 out->size = size; 475 out->token = buf; 476 return KIT_OK; 477 } 478 479 static void win_release(void* user, KitFileData* d) { 480 DriverEnv* env = (DriverEnv*)user; 481 if (d->token) env->heap->free(env->heap, d->token, d->size); 482 d->data = NULL; 483 d->size = 0; 484 d->token = NULL; 485 } 486 487 static KitStatus win_open_writer(void* user, const char* path, 488 KitWriter** out) { 489 DriverEnv* env = (DriverEnv*)user; 490 wchar_t* wpath = widen(path); 491 HANDLE h; 492 KitWriter* w; 493 if (!wpath) return KIT_IO; 494 h = CreateFileW(wpath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 495 FILE_ATTRIBUTE_NORMAL, NULL); 496 free(wpath); 497 if (h == INVALID_HANDLE_VALUE) return KIT_IO; 498 w = driver_writer_handle(env->heap, h); 499 if (!w) { 500 CloseHandle(h); 501 return KIT_NOMEM; 502 } 503 *out = w; 504 return KIT_OK; 505 } 506 507 /* ============================================================ 508 * Path helpers 509 * ============================================================ */ 510 511 int driver_path_exists(const char* path) { 512 WIN32_FILE_ATTRIBUTE_DATA fad; 513 wchar_t* wpath; 514 BOOL ok; 515 if (!path) return 0; 516 wpath = widen(path); 517 if (!wpath) return 0; 518 ok = GetFileAttributesExW(wpath, GetFileExInfoStandard, &fad); 519 free(wpath); 520 return ok ? 1 : 0; 521 } 522 523 /* Convert a FILETIME (100-ns ticks since 1601-01-01 UTC) to ns since the 524 * Unix epoch (1970-01-01 UTC). 11644473600 sec is the gap. */ 525 static int64_t filetime_to_unix_ns(FILETIME ft) { 526 uint64_t t = ((uint64_t)ft.dwHighDateTime << 32) | (uint64_t)ft.dwLowDateTime; 527 /* Subtract Win-to-Unix epoch offset in 100-ns ticks. */ 528 static const uint64_t EPOCH_DIFF_100NS = 116444736000000000ull; 529 if (t < EPOCH_DIFF_100NS) t = EPOCH_DIFF_100NS; 530 return (int64_t)((t - EPOCH_DIFF_100NS) * 100ull); 531 } 532 533 int driver_path_mtime_ns(const char* path, int64_t* out) { 534 WIN32_FILE_ATTRIBUTE_DATA fad; 535 wchar_t* wpath; 536 BOOL ok; 537 if (!path || !out) return 1; 538 wpath = widen(path); 539 if (!wpath) return 1; 540 ok = GetFileAttributesExW(wpath, GetFileExInfoStandard, &fad); 541 free(wpath); 542 if (!ok) return 1; 543 *out = filetime_to_unix_ns(fad.ftLastWriteTime); 544 return 0; 545 } 546 547 int driver_path_stat(const char* path, uint64_t* out_size, 548 uint64_t* out_mtime_ns, uint8_t* out_filetype) { 549 WIN32_FILE_ATTRIBUTE_DATA fad; 550 wchar_t* wpath; 551 BOOL ok; 552 DWORD err; 553 if (!path) return 2; 554 wpath = widen(path); 555 if (!wpath) return 2; 556 ok = GetFileAttributesExW(wpath, GetFileExInfoStandard, &fad); 557 err = ok ? 0 : GetLastError(); 558 free(wpath); 559 if (!ok) { 560 return (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) ? 1 : 2; 561 } 562 *out_mtime_ns = (uint64_t)filetime_to_unix_ns(fad.ftLastWriteTime); 563 if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 564 *out_size = 0u; 565 *out_filetype = 3; /* DIRECTORY */ 566 } else if (fad.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { 567 *out_size = ((uint64_t)fad.nFileSizeHigh << 32) | fad.nFileSizeLow; 568 *out_filetype = 7; /* SYMBOLIC_LINK */ 569 } else { 570 *out_size = ((uint64_t)fad.nFileSizeHigh << 32) | fad.nFileSizeLow; 571 *out_filetype = 4; /* REGULAR_FILE */ 572 } 573 return 0; 574 } 575 576 typedef struct DriverDirEntryRec { 577 char* name; 578 size_t name_alloc; 579 uint32_t name_len; 580 uint64_t ino; 581 uint64_t size; 582 uint64_t mtime_ns; 583 uint8_t filetype; 584 } DriverDirEntryRec; 585 586 struct DriverDirHandle { 587 DriverEnv* env; 588 DriverDirEntryRec* entries; 589 size_t entries_alloc; 590 uint64_t count; 591 }; 592 593 static char* driver_join_path(DriverEnv* env, const char* a, const char* b); 594 595 DriverDirHandle* driver_open_dir(DriverEnv* env, const char* path) { 596 char* pattern; 597 wchar_t* wpattern; 598 WIN32_FIND_DATAW fd; 599 HANDLE h; 600 DWORD last; 601 DriverDirHandle* dh; 602 uint64_t cap = 0; 603 uint64_t count = 0; 604 605 if (!env || !path) return NULL; 606 pattern = driver_join_path(env, path, "*"); 607 if (!pattern) return NULL; 608 wpattern = widen(pattern); 609 driver_free(env, pattern, kit_slice_cstr(pattern).len + 1u); 610 if (!wpattern) return NULL; 611 612 h = FindFirstFileW(wpattern, &fd); 613 free(wpattern); 614 if (h == INVALID_HANDLE_VALUE) return NULL; 615 616 dh = (DriverDirHandle*)env->heap->alloc(env->heap, sizeof(*dh), 617 _Alignof(DriverDirHandle)); 618 if (!dh) { 619 FindClose(h); 620 return NULL; 621 } 622 memset(dh, 0, sizeof(*dh)); 623 dh->env = env; 624 625 for (;;) { 626 char* name; 627 size_t name_len; 628 DriverDirEntryRec* e; 629 630 name = narrow(fd.cFileName); 631 if (!name) goto fail; 632 if (driver_streq(name, ".") || driver_streq(name, "..")) { 633 free(name); 634 goto loop_next; 635 } 636 637 name_len = kit_slice_cstr(name).len; 638 639 /* grow entry array */ 640 if (count >= cap) { 641 uint64_t new_cap = cap ? cap * 2u : 8u; 642 size_t new_alloc = (size_t)new_cap * sizeof(DriverDirEntryRec); 643 DriverDirEntryRec* nv = (DriverDirEntryRec*)env->heap->alloc( 644 env->heap, new_alloc, _Alignof(DriverDirEntryRec)); 645 if (!nv) { 646 free(name); 647 goto fail; 648 } 649 if (dh->entries) { 650 memcpy(nv, dh->entries, (size_t)count * sizeof(DriverDirEntryRec)); 651 env->heap->free(env->heap, dh->entries, dh->entries_alloc); 652 } 653 dh->entries = nv; 654 dh->entries_alloc = new_alloc; 655 cap = new_cap; 656 } 657 658 e = &dh->entries[count]; 659 memset(e, 0, sizeof(*e)); 660 e->name_alloc = name_len + 1u; 661 e->name = (char*)env->heap->alloc(env->heap, e->name_alloc, 1u); 662 if (!e->name) { 663 free(name); 664 goto fail; 665 } 666 memcpy(e->name, name, name_len + 1u); 667 e->name_len = (uint32_t)name_len; 668 free(name); 669 670 e->size = ((uint64_t)fd.nFileSizeHigh << 32) | fd.nFileSizeLow; 671 e->mtime_ns = (uint64_t)filetime_to_unix_ns(fd.ftLastWriteTime); 672 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 673 e->filetype = 3; 674 else if (fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) 675 e->filetype = 7; 676 else 677 e->filetype = 4; 678 ++count; 679 680 loop_next: 681 if (!FindNextFileW(h, &fd)) { 682 last = GetLastError(); 683 if (last == ERROR_NO_MORE_FILES) break; 684 goto fail; 685 } 686 } 687 688 FindClose(h); 689 dh->count = count; 690 return dh; 691 692 fail: 693 FindClose(h); 694 driver_close_dir(env, dh); 695 return NULL; 696 } 697 698 int driver_read_dir_entry(DriverDirHandle* h, uint64_t index, 699 const char** out_name, uint32_t* out_name_len, 700 uint64_t* out_ino, uint64_t* out_size, 701 uint64_t* out_mtime_ns, uint8_t* out_filetype) { 702 DriverDirEntryRec* e; 703 if (!h || index >= h->count) return 1; 704 e = &h->entries[index]; 705 *out_name = e->name; 706 *out_name_len = e->name_len; 707 *out_ino = e->ino; 708 *out_size = e->size; 709 *out_mtime_ns = e->mtime_ns; 710 *out_filetype = e->filetype; 711 return 0; 712 } 713 714 void driver_close_dir(DriverEnv* env, DriverDirHandle* h) { 715 uint64_t i; 716 if (!h) return; 717 if (!env) env = h->env; 718 for (i = 0; i < h->count; ++i) { 719 DriverDirEntryRec* e = &h->entries[i]; 720 if (e->name) env->heap->free(env->heap, e->name, e->name_alloc); 721 } 722 if (h->entries) env->heap->free(env->heap, h->entries, h->entries_alloc); 723 env->heap->free(env->heap, h, sizeof(*h)); 724 } 725 726 int driver_mkdir_p(DriverEnv* env, const char* path) { 727 size_t len; 728 char* buf; 729 size_t i; 730 if (!path || !path[0]) return 1; 731 len = kit_slice_cstr(path).len; 732 buf = (char*)driver_alloc(env, len + 1); 733 if (!buf) return 1; 734 memcpy(buf, path, len + 1); 735 736 /* Walk separators, accepting both '/' and '\\'. Skip the drive prefix 737 * ("C:") and the leading separator(s) of a UNC path so we don't try to 738 * CreateDirectory("\\\\server"). */ 739 for (i = 0; i <= len; ++i) { 740 int at_end = (i == len); 741 char ch = buf[i]; 742 int is_sep = (ch == '/' || ch == '\\'); 743 int do_create = at_end || is_sep; 744 if (!do_create) continue; 745 if (!at_end) buf[i] = '\0'; 746 /* Skip pure roots: "", ".", drive-letter-only ("C:"), and UNC roots 747 * ("\\\\server" or "\\\\server\\share"). */ 748 if (buf[0] == '\0') { 749 /* nothing yet */ 750 } else if (driver_streq(buf, ".")) { 751 /* skip */ 752 } else if (i >= 2 && buf[1] == ':' && buf[2] == '\0') { 753 /* "C:" — drive prefix only */ 754 } else { 755 wchar_t* wpath = widen(buf); 756 if (!wpath) { 757 driver_free(env, buf, len + 1); 758 return 1; 759 } 760 if (!CreateDirectoryW(wpath, NULL)) { 761 DWORD err = GetLastError(); 762 if (err != ERROR_ALREADY_EXISTS) { 763 free(wpath); 764 driver_free(env, buf, len + 1); 765 return 1; 766 } 767 } 768 { 769 WIN32_FILE_ATTRIBUTE_DATA fad; 770 BOOL ok = GetFileAttributesExW(wpath, GetFileExInfoStandard, &fad); 771 free(wpath); 772 if (!ok || !(fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { 773 driver_free(env, buf, len + 1); 774 return 1; 775 } 776 } 777 } 778 if (!at_end) buf[i] = ch; 779 } 780 781 driver_free(env, buf, len + 1); 782 return 0; 783 } 784 785 int driver_mark_executable_output(const char* path) { 786 /* Windows has no Unix +x bit; file extension governs executability and 787 * NTFS ACLs are inherited from the parent directory. No-op success. */ 788 (void)path; 789 return 0; 790 } 791 792 /* ---------------- self executable path ---------------- */ 793 /* GetModuleFileNameW(NULL) reports the path of the running image. A return 794 * equal to the buffer size means truncation (older Windows doesn't fail), so 795 * grow until the result fits, then narrow to UTF-8. */ 796 int driver_self_exe_path(DriverEnv* env, char** out, size_t* out_size) { 797 DWORD cap = 256; 798 wchar_t* wbuf = NULL; 799 char* narrowed; 800 size_t size; 801 if (!env || !out || !out_size) return 1; 802 for (;;) { 803 wchar_t* nb = (wchar_t*)realloc(wbuf, (size_t)cap * sizeof(wchar_t)); 804 DWORD n; 805 if (!nb) { 806 free(wbuf); 807 return 1; 808 } 809 wbuf = nb; 810 n = GetModuleFileNameW(NULL, wbuf, cap); 811 if (n == 0) { 812 free(wbuf); 813 return 1; 814 } 815 if (n < cap) break; /* fit: n excludes the terminator on success */ 816 if (cap >= (1u << 20)) { 817 free(wbuf); 818 return 1; 819 } 820 cap *= 2; 821 } 822 narrowed = narrow(wbuf); /* malloc'd UTF-8 */ 823 free(wbuf); 824 if (!narrowed) return 1; 825 size = driver_strlen(narrowed) + 1u; 826 *out = (char*)driver_alloc(env, size); 827 if (!*out) { 828 free(narrowed); 829 return 1; 830 } 831 driver_memcpy(*out, narrowed, size); 832 *out_size = size; 833 free(narrowed); 834 return 0; 835 } 836 837 /* ---------------- link helpers (install) ---------------- */ 838 839 #ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 840 #define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x2 841 #endif 842 843 int driver_create_symlink(const char* target, const char* link_path) { 844 wchar_t* wtarget; 845 wchar_t* wlink; 846 BOOLEAN ok; 847 if (!target || !link_path) return 1; 848 wtarget = widen(target); 849 wlink = widen(link_path); 850 if (!wtarget || !wlink) { 851 free(wtarget); 852 free(wlink); 853 return 1; 854 } 855 /* Prefer the unprivileged (Developer Mode) flag; fall back without it for 856 * older hosts that reject the unknown flag. */ 857 ok = CreateSymbolicLinkW(wlink, wtarget, 858 SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE); 859 if (!ok) ok = CreateSymbolicLinkW(wlink, wtarget, 0); 860 free(wtarget); 861 free(wlink); 862 return ok ? 0 : 1; 863 } 864 865 int driver_create_hardlink(const char* target, const char* link_path) { 866 wchar_t* wtarget; 867 wchar_t* wlink; 868 BOOL ok; 869 if (!target || !link_path) return 1; 870 wtarget = widen(target); 871 wlink = widen(link_path); 872 if (!wtarget || !wlink) { 873 free(wtarget); 874 free(wlink); 875 return 1; 876 } 877 ok = CreateHardLinkW(wlink, wtarget, NULL); 878 free(wtarget); 879 free(wlink); 880 return ok ? 0 : 1; 881 } 882 883 int driver_remove_file(const char* path) { 884 wchar_t* wpath; 885 BOOL ok; 886 if (!path) return 1; 887 wpath = widen(path); 888 if (!wpath) return 1; 889 ok = DeleteFileW(wpath); 890 if (!ok) { 891 DWORD err = GetLastError(); 892 if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) ok = TRUE; 893 } 894 free(wpath); 895 return ok ? 0 : 1; 896 } 897 898 int driver_path_lexists(const char* path) { 899 wchar_t* wpath; 900 DWORD attr; 901 if (!path) return 0; 902 wpath = widen(path); 903 if (!wpath) return 0; 904 /* GetFileAttributesW does not traverse reparse points, so a dangling 905 * symlink reports its own attributes rather than failing. */ 906 attr = GetFileAttributesW(wpath); 907 free(wpath); 908 return attr != INVALID_FILE_ATTRIBUTES; 909 } 910 911 static char* driver_join_path(DriverEnv* env, const char* a, const char* b) { 912 size_t al = kit_slice_cstr(a).len; 913 size_t bl = kit_slice_cstr(b).len; 914 int slash = al > 0 && a[al - 1u] != '/' && a[al - 1u] != '\\'; 915 char* out = (char*)driver_alloc(env, al + (slash ? 1u : 0u) + bl + 1u); 916 if (!out) return NULL; 917 memcpy(out, a, al); 918 if (slash) out[al++] = '/'; 919 memcpy(out + al, b, bl); 920 out[al + bl] = '\0'; 921 return out; 922 } 923 924 static int driver_walk_regular_files_at(DriverEnv* env, const char* dir, 925 const char* rel, DriverWalkFileFn cb, 926 void* user) { 927 char* pattern; 928 wchar_t* wpattern; 929 WIN32_FIND_DATAW fd; 930 HANDLE h; 931 DWORD last; 932 int rc = 1; 933 934 pattern = driver_join_path(env, dir, "*"); 935 if (!pattern) return 1; 936 wpattern = widen(pattern); 937 driver_free(env, pattern, kit_slice_cstr(pattern).len + 1u); 938 if (!wpattern) return 1; 939 940 h = FindFirstFileW(wpattern, &fd); 941 free(wpattern); 942 if (h == INVALID_HANDLE_VALUE) { 943 last = GetLastError(); 944 return last == ERROR_FILE_NOT_FOUND ? 0 : 1; 945 } 946 947 for (;;) { 948 char* name = narrow(fd.cFileName); 949 char* child = NULL; 950 char* child_rel = NULL; 951 int child_rc = 0; 952 if (!name) goto loop_fail; 953 if (driver_streq(name, ".") || driver_streq(name, "..")) { 954 free(name); 955 goto loop_next; 956 } 957 child = driver_join_path(env, dir, name); 958 child_rel = rel && rel[0] ? driver_join_path(env, rel, name) 959 : driver_join_path(env, "", name); 960 if (!child || !child_rel) goto loop_fail; 961 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { 962 if ((fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { 963 child_rc = 1; 964 } else { 965 child_rc = 966 driver_walk_regular_files_at(env, child, child_rel, cb, user); 967 } 968 } else { 969 child_rc = cb(user, child, child_rel, 0); 970 } 971 if (child_rel) 972 driver_free(env, child_rel, kit_slice_cstr(child_rel).len + 1u); 973 if (child) driver_free(env, child, kit_slice_cstr(child).len + 1u); 974 free(name); 975 if (child_rc) goto out; 976 977 loop_next: 978 if (!FindNextFileW(h, &fd)) break; 979 continue; 980 981 loop_fail: 982 if (child_rel) 983 driver_free(env, child_rel, kit_slice_cstr(child_rel).len + 1u); 984 if (child) driver_free(env, child, kit_slice_cstr(child).len + 1u); 985 if (name) free(name); 986 goto out; 987 } 988 989 last = GetLastError(); 990 rc = last == ERROR_NO_MORE_FILES ? 0 : 1; 991 992 out: 993 FindClose(h); 994 return rc; 995 } 996 997 int driver_walk_regular_files(DriverEnv* env, const char* root, 998 DriverWalkFileFn cb, void* user) { 999 if (!env || !root || !root[0] || !cb) return 1; 1000 return driver_walk_regular_files_at(env, root, "", cb, user); 1001 } 1002 1003 /* ============================================================ 1004 * Time 1005 * ============================================================ */ 1006 1007 uint64_t driver_now_ns(void) { 1008 /* QueryPerformanceCounter is monotonic across cores on every supported 1009 * Windows version since Vista. */ 1010 LARGE_INTEGER freq; 1011 LARGE_INTEGER ctr; 1012 if (!QueryPerformanceFrequency(&freq) || freq.QuadPart <= 0) return 0; 1013 if (!QueryPerformanceCounter(&ctr)) return 0; 1014 /* Avoid 128-bit multiply: split ticks into integer-seconds and remainder. */ 1015 { 1016 int64_t sec = ctr.QuadPart / freq.QuadPart; 1017 int64_t rem = ctr.QuadPart % freq.QuadPart; 1018 return (uint64_t)sec * 1000000000ull + 1019 (uint64_t)((rem * 1000000000) / freq.QuadPart); 1020 } 1021 } 1022 1023 int driver_random_bytes(uint8_t* out, size_t n) { 1024 /* rand_s draws from the OS CSPRNG (RtlGenRandom) without linking bcrypt. */ 1025 size_t off = 0; 1026 if (!out) return 1; 1027 while (off < n) { 1028 unsigned int v; 1029 size_t chunk; 1030 if (rand_s(&v) != 0) return 1; 1031 chunk = n - off < sizeof v ? n - off : sizeof v; 1032 memcpy(out + off, &v, chunk); 1033 off += chunk; 1034 } 1035 return 0; 1036 } 1037 1038 /* ============================================================ 1039 * load helpers 1040 * ============================================================ */ 1041 1042 int driver_load_bytes(const KitFileIO* io, const char* tool, const char* path, 1043 DriverLoad* out, KitSlice* in) { 1044 out->loaded = 0; 1045 out->fd.data = NULL; 1046 out->fd.size = 0; 1047 out->fd.token = NULL; 1048 if (!io || !io->read_all) { 1049 driver_errf(tool, "host file I/O unavailable"); 1050 return 1; 1051 } 1052 if (io->read_all(io->user, path, &out->fd) != KIT_OK) { 1053 driver_errf(tool, "failed to read: %.*s", 1054 KIT_SLICE_ARG(kit_slice_cstr(path))); 1055 return 1; 1056 } 1057 out->loaded = 1; 1058 in->data = out->fd.data; 1059 in->len = out->fd.size; 1060 return 0; 1061 } 1062 1063 void driver_release_bytes(const KitFileIO* io, DriverLoad* lf) { 1064 if (!lf || !lf->loaded) return; 1065 if (io && io->release) io->release(io->user, &lf->fd); 1066 lf->loaded = 0; 1067 } 1068 1069 /* ============================================================ 1070 * stdin / edit_temp / read_line 1071 * ============================================================ */ 1072 1073 int driver_read_stdin(DriverEnv* e, uint8_t** out_data, size_t* out_size) { 1074 size_t cap = 4096; 1075 size_t len = 0; 1076 uint8_t* buf = e->heap->alloc(e->heap, cap, 1); 1077 HANDLE h = GetStdHandle(STD_INPUT_HANDLE); 1078 if (!buf) return 0; 1079 for (;;) { 1080 DWORD n; 1081 if (len == cap) { 1082 size_t newcap = cap * 2; 1083 uint8_t* nb = e->heap->realloc(e->heap, buf, cap, newcap, 1); 1084 if (!nb) { 1085 e->heap->free(e->heap, buf, cap); 1086 return 0; 1087 } 1088 buf = nb; 1089 cap = newcap; 1090 } 1091 if (!ReadFile(h, buf + len, (DWORD)(cap - len), &n, NULL)) { 1092 if (GetLastError() == ERROR_BROKEN_PIPE) break; /* EOF on pipe */ 1093 e->heap->free(e->heap, buf, cap); 1094 return 0; 1095 } 1096 if (n == 0) break; 1097 len += n; 1098 } 1099 if (len < cap) { 1100 uint8_t* shrunk = len ? e->heap->realloc(e->heap, buf, cap, len, 1) : NULL; 1101 if (len && !shrunk) { 1102 *out_data = buf; 1103 *out_size = cap; 1104 return 1; 1105 } 1106 if (!len) { 1107 e->heap->free(e->heap, buf, cap); 1108 buf = NULL; 1109 } else { 1110 buf = shrunk; 1111 } 1112 } 1113 *out_data = buf; 1114 *out_size = len; 1115 return 1; 1116 } 1117 1118 static int driver_write_handle_all(HANDLE h, const uint8_t* data, size_t n) { 1119 size_t off = 0; 1120 while (off < n) { 1121 DWORD chunk = (n - off) > 0x40000000u ? 0x40000000u : (DWORD)(n - off); 1122 DWORD wr = 0; 1123 if (!WriteFile(h, data + off, chunk, &wr, NULL) || wr == 0) return 0; 1124 off += wr; 1125 } 1126 return 1; 1127 } 1128 1129 int driver_edit_temp(DriverEnv* e, const char* suffix, const uint8_t* initial, 1130 size_t initial_size, uint8_t** out_data, 1131 size_t* out_size) { 1132 /* Windows temp-file dance: 1133 * - GetTempPathW gives us %TMP%/%TEMP%/%USERPROFILE% with trailing '\\'. 1134 * - GetTempFileNameW makes a unique "<dir>\\cfXXXX.tmp" path. We ignore 1135 * the ".tmp" and append our requested suffix below by renaming. 1136 * - We rename to "<base><suffix>" so the editor sees the right extension. 1137 * - Editor is launched via system() so shell quoting / PATH lookup is 1138 * handled by cmd.exe. 1139 */ 1140 wchar_t tmp_dir[MAX_PATH + 1]; 1141 wchar_t tmp_path[MAX_PATH + 1]; 1142 DWORD got; 1143 HANDLE h = INVALID_HANDLE_VALUE; 1144 wchar_t* wsuffix = NULL; 1145 wchar_t final_path[MAX_PATH + 64]; 1146 size_t final_len; 1147 int ok = 0; 1148 KitFileData fd_data; 1149 const char* editor; 1150 char* cmd = NULL; 1151 int rc; 1152 size_t cmd_cap; 1153 char utf8_path[MAX_PATH * 4 + 1]; 1154 int utf8_len; 1155 1156 if (!out_data || !out_size) return 0; 1157 *out_data = NULL; 1158 *out_size = 0; 1159 1160 got = GetTempPathW(MAX_PATH + 1, tmp_dir); 1161 if (got == 0 || got > MAX_PATH) return 0; 1162 if (GetTempFileNameW(tmp_dir, L"cf", 0, tmp_path) == 0) return 0; 1163 1164 /* Build the final path = tmp_path with ".tmp" stripped + requested suffix. 1165 * GetTempFileNameW already created the file at tmp_path; we MoveFileEx to 1166 * rename it to final_path. */ 1167 { 1168 size_t tplen = wcslen(tmp_path); 1169 /* Strip the ".tmp" extension GetTempFileNameW appends. */ 1170 if (tplen >= 4 && tmp_path[tplen - 4] == L'.') tplen -= 4; 1171 if (tplen >= sizeof(final_path) / sizeof(wchar_t) - 16) { 1172 DeleteFileW(tmp_path); 1173 return 0; 1174 } 1175 memcpy(final_path, tmp_path, tplen * sizeof(wchar_t)); 1176 final_path[tplen] = L'\0'; 1177 final_len = tplen; 1178 if (suffix && *suffix) { 1179 wsuffix = widen(suffix); 1180 if (!wsuffix) { 1181 DeleteFileW(tmp_path); 1182 return 0; 1183 } 1184 { 1185 size_t sl = wcslen(wsuffix); 1186 if (final_len + sl + 1 >= sizeof(final_path) / sizeof(wchar_t)) { 1187 free(wsuffix); 1188 DeleteFileW(tmp_path); 1189 return 0; 1190 } 1191 memcpy(final_path + final_len, wsuffix, sl * sizeof(wchar_t)); 1192 final_len += sl; 1193 final_path[final_len] = L'\0'; 1194 } 1195 free(wsuffix); 1196 } 1197 if (!MoveFileExW(tmp_path, final_path, MOVEFILE_REPLACE_EXISTING)) { 1198 DeleteFileW(tmp_path); 1199 return 0; 1200 } 1201 } 1202 1203 /* Open the renamed file and write initial contents. */ 1204 h = CreateFileW(final_path, GENERIC_WRITE, FILE_SHARE_READ, NULL, 1205 TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 1206 if (h == INVALID_HANDLE_VALUE) goto out; 1207 if (initial_size && 1208 !driver_write_handle_all(h, initial ? initial : (const uint8_t*)"", 1209 initial_size)) 1210 goto out; 1211 CloseHandle(h); 1212 h = INVALID_HANDLE_VALUE; 1213 1214 /* Convert final_path back to UTF-8 for the editor command. */ 1215 utf8_len = WideCharToMultiByte(CP_UTF8, 0, final_path, -1, utf8_path, 1216 (int)sizeof(utf8_path), NULL, NULL); 1217 if (utf8_len <= 0) goto out; 1218 1219 editor = getenv("VISUAL"); 1220 if (!editor || !*editor) editor = getenv("EDITOR"); 1221 if (!editor || !*editor) editor = "notepad"; 1222 cmd_cap = strlen(editor) + (size_t)utf8_len + 16; 1223 cmd = (char*)malloc(cmd_cap); 1224 if (!cmd) goto out; 1225 /* Wrap the entire command in extra quotes so cmd.exe /S doesn't strip 1226 * matching outer quotes when the editor path itself has spaces. */ 1227 rc = snprintf(cmd, cmd_cap, "\"\"%s\" \"%s\"\"", editor, utf8_path); 1228 if (rc < 0 || (size_t)rc >= cmd_cap) goto out; 1229 if (system(cmd) != 0) goto out; 1230 1231 fd_data.data = NULL; 1232 fd_data.size = 0; 1233 fd_data.token = NULL; 1234 if (win_read_all(e, utf8_path, &fd_data) != KIT_OK) goto out; 1235 *out_data = (uint8_t*)fd_data.data; 1236 *out_size = fd_data.size; 1237 ok = 1; 1238 1239 out: 1240 if (h != INVALID_HANDLE_VALUE) CloseHandle(h); 1241 if (cmd) free(cmd); 1242 DeleteFileW(final_path); 1243 return ok; 1244 } 1245 1246 int driver_read_line(char* buf, size_t cap) { 1247 size_t len = 0; 1248 if (!buf || cap < 2) return -1; 1249 for (;;) { 1250 int c = fgetc(stdin); 1251 if (c == EOF) { 1252 buf[len] = '\0'; 1253 if (ferror(stdin)) return -1; 1254 if (len == 0) return 0; 1255 return (int)len; 1256 } 1257 if (c == '\n') { 1258 /* Strip a trailing CR if present (CRLF line ending). */ 1259 if (len > 0 && buf[len - 1] == '\r') --len; 1260 buf[len] = '\0'; 1261 return (int)len; 1262 } 1263 if (len + 1 < cap) buf[len++] = (char)c; 1264 } 1265 } 1266 1267 static int win_line_history_add(DriverEnv* env, DriverLineHistory* h, 1268 const char* line, size_t len) { 1269 char** ni; 1270 size_t* ns; 1271 char* copy; 1272 uint32_t nc; 1273 if (!env || !h || !line || len == 0) return 0; 1274 if (h->count == h->cap) { 1275 size_t old_items = (size_t)h->cap * sizeof(*h->items); 1276 size_t new_items; 1277 size_t old_sizes = (size_t)h->cap * sizeof(*h->sizes); 1278 size_t new_sizes; 1279 nc = h->cap ? h->cap * 2u : 32u; 1280 new_items = (size_t)nc * sizeof(*h->items); 1281 new_sizes = (size_t)nc * sizeof(*h->sizes); 1282 ni = (char**)env->heap->realloc(env->heap, h->items, old_items, new_items, 1283 _Alignof(char*)); 1284 if (!ni) return 1; 1285 h->items = ni; 1286 ns = (size_t*)env->heap->realloc(env->heap, h->sizes, old_sizes, new_sizes, 1287 _Alignof(size_t)); 1288 if (!ns) return 1; 1289 h->sizes = ns; 1290 h->cap = nc; 1291 } 1292 copy = (char*)driver_alloc(env, len + 1u); 1293 if (!copy) return 1; 1294 memcpy(copy, line, len); 1295 copy[len] = '\0'; 1296 h->items[h->count] = copy; 1297 h->sizes[h->count] = len + 1u; 1298 h->count++; 1299 return 0; 1300 } 1301 1302 int driver_read_line_edit(DriverEnv* env, const char* prompt, char* buf, 1303 size_t cap, DriverLineHistory* hist, 1304 DriverLineCompleteFn complete, void* complete_user) { 1305 int n; 1306 (void)complete; 1307 (void)complete_user; 1308 if (prompt && *prompt) { 1309 fputs(prompt, stdout); 1310 fflush(stdout); 1311 } 1312 n = driver_read_line(buf, cap); 1313 if (n > 0 && hist) (void)win_line_history_add(env, hist, buf, (size_t)n); 1314 return n; 1315 } 1316 1317 /* ============================================================ 1318 * dlsym via GetProcAddress over loaded modules 1319 * ============================================================ */ 1320 1321 /* Snapshot the loaded modules at first call and remember them. We don't 1322 * track DLL load/unload events; that's a reasonable trade-off for a JIT 1323 * (the relevant DLLs -- ucrt, kernel32, the kit binary -- are loaded 1324 * before the JIT extern resolver runs). */ 1325 static HMODULE g_dlsym_modules[256]; 1326 static DWORD g_dlsym_count; 1327 static SRWLOCK g_dlsym_lock = SRWLOCK_INIT; 1328 static int g_dlsym_inited; 1329 1330 static void dlsym_init_once(void) { 1331 HANDLE proc; 1332 DWORD need = 0; 1333 AcquireSRWLockExclusive(&g_dlsym_lock); 1334 if (g_dlsym_inited) { 1335 ReleaseSRWLockExclusive(&g_dlsym_lock); 1336 return; 1337 } 1338 proc = GetCurrentProcess(); 1339 if (EnumProcessModules(proc, g_dlsym_modules, sizeof(g_dlsym_modules), 1340 &need)) { 1341 DWORD have = need / (DWORD)sizeof(HMODULE); 1342 g_dlsym_count = have > (DWORD)(sizeof(g_dlsym_modules) / sizeof(HMODULE)) 1343 ? (DWORD)(sizeof(g_dlsym_modules) / sizeof(HMODULE)) 1344 : have; 1345 } else { 1346 /* Fall back to the executable module and the CRT/kernel essentials. */ 1347 g_dlsym_modules[0] = GetModuleHandleW(NULL); 1348 g_dlsym_modules[1] = GetModuleHandleW(L"kernel32.dll"); 1349 g_dlsym_modules[2] = GetModuleHandleW(L"msvcrt.dll"); 1350 g_dlsym_modules[3] = GetModuleHandleW(L"ucrtbase.dll"); 1351 g_dlsym_count = 4; 1352 } 1353 g_dlsym_inited = 1; 1354 ReleaseSRWLockExclusive(&g_dlsym_lock); 1355 } 1356 1357 static void* win_dlsym(const char* name) { 1358 DWORD i; 1359 if (!name) return NULL; 1360 dlsym_init_once(); 1361 /* Try the source-level name; if that misses and there's a leading '_' 1362 * (Mach-O-mangled), try without it. */ 1363 AcquireSRWLockShared(&g_dlsym_lock); 1364 for (i = 0; i < g_dlsym_count; ++i) { 1365 void* p; 1366 if (!g_dlsym_modules[i]) continue; 1367 p = (void*)GetProcAddress(g_dlsym_modules[i], name); 1368 if (p) { 1369 ReleaseSRWLockShared(&g_dlsym_lock); 1370 return p; 1371 } 1372 } 1373 if (name[0] == '_' && name[1] != '\0') { 1374 for (i = 0; i < g_dlsym_count; ++i) { 1375 void* p; 1376 if (!g_dlsym_modules[i]) continue; 1377 p = (void*)GetProcAddress(g_dlsym_modules[i], name + 1); 1378 if (p) { 1379 ReleaseSRWLockShared(&g_dlsym_lock); 1380 return p; 1381 } 1382 } 1383 } 1384 ReleaseSRWLockShared(&g_dlsym_lock); 1385 return NULL; 1386 } 1387 1388 static int win_name_eq(KitSlice s, const char* lit, size_t litlen) { 1389 return s.len == litlen && memcmp(s.s, lit, litlen) == 0; 1390 } 1391 1392 /* libucrt-static stdio entry points handed to JIT'd code. 1393 * 1394 * With kit's UCRT profile (__USE_MINGW_ANSI_STDIO=0), mingw's <stdio.h> 1395 * resolves the printf/scanf family to out-of-line wrappers that live ONLY in 1396 * libucrt.a — ucrtbase.dll exports just the __stdio_common_v* cores (and 1397 * __local_stdio_* helpers the wrappers call). An AOT `kit cc` link pulls the 1398 * wrappers from libucrt.a; the in-process JIT (`kit run`) resolves externs via 1399 * dlsym over loaded DLLs (win_dlsym), which can't see them, so a printf-using 1400 * JIT program fails with an undefined `printf`. kit.exe statically links 1401 * libucrt.a, so taking each wrapper's address here pulls its definition into 1402 * our own image and lets us hand the JIT our copy. Functions that ARE 1403 * ucrtbase.dll exports (puts, fputs, fflush, putchar, __stdio_common_v*, 1404 * __acrt_iob_func, …) resolve through win_dlsym above and never reach this 1405 * table. The __local_stdio_*_options helpers' storage only ever holds the 1406 * default (0) flags, so sharing one instance between the host and the JIT'd 1407 * program is benign. */ 1408 void* driver_dlsym_resolver(void* user, KitSlice name_s) { 1409 void* p; 1410 (void)user; 1411 if (!name_s.s || name_s.len == 0) return NULL; 1412 p = win_dlsym(name_s.s); 1413 if (p) return p; 1414 /* Match by source name; #fn stringizes it and sizeof-1 is its length. */ 1415 #define KIT_STATIC_STDIO(fn) \ 1416 if (win_name_eq(name_s, #fn, sizeof(#fn) - 1u)) return (void*)(uintptr_t)&fn 1417 KIT_STATIC_STDIO(__local_stdio_printf_options); 1418 KIT_STATIC_STDIO(__local_stdio_scanf_options); 1419 KIT_STATIC_STDIO(printf); 1420 KIT_STATIC_STDIO(fprintf); 1421 KIT_STATIC_STDIO(sprintf); 1422 KIT_STATIC_STDIO(snprintf); 1423 KIT_STATIC_STDIO(_snprintf); 1424 KIT_STATIC_STDIO(vprintf); 1425 KIT_STATIC_STDIO(vfprintf); 1426 KIT_STATIC_STDIO(vsprintf); 1427 KIT_STATIC_STDIO(vsnprintf); 1428 KIT_STATIC_STDIO(_vsnprintf); 1429 KIT_STATIC_STDIO(scanf); 1430 KIT_STATIC_STDIO(fscanf); 1431 KIT_STATIC_STDIO(sscanf); 1432 KIT_STATIC_STDIO(_snscanf); 1433 KIT_STATIC_STDIO(vscanf); 1434 KIT_STATIC_STDIO(vfscanf); 1435 KIT_STATIC_STDIO(vsscanf); 1436 KIT_STATIC_STDIO(_scprintf); 1437 KIT_STATIC_STDIO(_vscprintf); 1438 #undef KIT_STATIC_STDIO 1439 return NULL; 1440 } 1441 1442 /* ============================================================ 1443 * SIGINT shim via SetConsoleCtrlHandler 1444 * ============================================================ */ 1445 1446 static void (*g_ctrlc_cb)(void*); 1447 static void* g_ctrlc_cb_user; 1448 1449 static BOOL WINAPI ctrlc_trampoline(DWORD type) { 1450 if (type == CTRL_C_EVENT || type == CTRL_BREAK_EVENT) { 1451 if (g_ctrlc_cb) g_ctrlc_cb(g_ctrlc_cb_user); 1452 return TRUE; /* handled */ 1453 } 1454 return FALSE; 1455 } 1456 1457 int driver_install_sigint(void (*cb)(void*), void* user) { 1458 g_ctrlc_cb = cb; 1459 g_ctrlc_cb_user = user; 1460 return SetConsoleCtrlHandler(ctrlc_trampoline, TRUE) ? 0 : 1; 1461 } 1462 1463 void driver_restore_sigint(void) { 1464 SetConsoleCtrlHandler(ctrlc_trampoline, FALSE); 1465 g_ctrlc_cb = NULL; 1466 g_ctrlc_cb_user = NULL; 1467 } 1468 1469 /* No fault-guard on Windows yet (the POSIX path uses sigaction + sigsetjmp; a 1470 * vectored-exception-handler port is a follow-up). Run the entry directly so 1471 * `kit run` still executes the program; on_crash never fires. */ 1472 int driver_run_with_crash_guard(DriverEnv* env, KitArchKind arch, 1473 DriverRunEntryFn entry, int argc, char** argv, 1474 int* ret_out, DriverRunCrashFn on_crash, 1475 void* user) { 1476 (void)env; 1477 (void)arch; 1478 (void)on_crash; 1479 (void)user; 1480 *ret_out = entry(argc, argv); 1481 return 0; 1482 } 1483 1484 /* ============================================================ 1485 * Win32 CONTEXT <-> KitUnwindFrame marshalling 1486 * ============================================================ */ 1487 1488 /* Inline per-arch marshalling: the Win32 dbg path is short enough that a 1489 * separate uctx_*_windows.c isn't worth the extra TU. Only the host arch 1490 * matters; cross-arch debug isn't a Win32 concern (the worker runs in our 1491 * own process). */ 1492 1493 #if defined(_M_X64) || defined(__x86_64__) 1494 static void ctx_to_frame(const CONTEXT* c, KitUnwindFrame* f) { 1495 memset(f, 0, sizeof(*f)); 1496 f->pc = (uint64_t)c->Rip; 1497 f->cfa = (uint64_t)c->Rsp; 1498 /* SysV DWARF mapping: rax=0, rdx=1, rcx=2, rbx=3, rsi=4, rdi=5, rbp=6, 1499 * rsp=7, r8..r15=8..15. We use the same mapping so kit's DWARF reader 1500 * can interpret these directly. */ 1501 f->regs[0] = c->Rax; 1502 f->regs[1] = c->Rdx; 1503 f->regs[2] = c->Rcx; 1504 f->regs[3] = c->Rbx; 1505 f->regs[4] = c->Rsi; 1506 f->regs[5] = c->Rdi; 1507 f->regs[6] = c->Rbp; 1508 f->regs[7] = c->Rsp; 1509 f->regs[8] = c->R8; 1510 f->regs[9] = c->R9; 1511 f->regs[10] = c->R10; 1512 f->regs[11] = c->R11; 1513 f->regs[12] = c->R12; 1514 f->regs[13] = c->R13; 1515 f->regs[14] = c->R14; 1516 f->regs[15] = c->R15; 1517 } 1518 1519 static void frame_to_ctx(const KitUnwindFrame* f, CONTEXT* c) { 1520 c->Rip = f->pc; 1521 c->Rax = f->regs[0]; 1522 c->Rdx = f->regs[1]; 1523 c->Rcx = f->regs[2]; 1524 c->Rbx = f->regs[3]; 1525 c->Rsi = f->regs[4]; 1526 c->Rdi = f->regs[5]; 1527 c->Rbp = f->regs[6]; 1528 c->Rsp = f->regs[7]; 1529 c->R8 = f->regs[8]; 1530 c->R9 = f->regs[9]; 1531 c->R10 = f->regs[10]; 1532 c->R11 = f->regs[11]; 1533 c->R12 = f->regs[12]; 1534 c->R13 = f->regs[13]; 1535 c->R14 = f->regs[14]; 1536 c->R15 = f->regs[15]; 1537 } 1538 #elif defined(_M_ARM64) || defined(__aarch64__) 1539 static void ctx_to_frame(const CONTEXT* c, KitUnwindFrame* f) { 1540 unsigned i; 1541 memset(f, 0, sizeof(*f)); 1542 f->pc = (uint64_t)c->Pc; 1543 f->cfa = (uint64_t)c->Sp; 1544 for (i = 0; i < 31; ++i) f->regs[i] = c->X[i]; 1545 f->regs[31] = c->Sp; 1546 } 1547 1548 static void frame_to_ctx(const KitUnwindFrame* f, CONTEXT* c) { 1549 unsigned i; 1550 c->Pc = f->pc; 1551 for (i = 0; i < 31; ++i) c->X[i] = f->regs[i]; 1552 c->Sp = f->regs[31]; 1553 } 1554 #else 1555 static void ctx_to_frame(const CONTEXT* c, KitUnwindFrame* f) { 1556 (void)c; 1557 memset(f, 0, sizeof(*f)); 1558 } 1559 static void frame_to_ctx(const KitUnwindFrame* f, CONTEXT* c) { 1560 (void)f; 1561 (void)c; 1562 } 1563 #endif 1564 1565 /* Map a Win32 exception code to the POSIX-style signo the dbg session 1566 * expects. Anything we don't recognize falls through as a generic SEGV. */ 1567 static int exception_code_to_signo(DWORD code) { 1568 switch (code) { 1569 case EXCEPTION_BREAKPOINT: 1570 case EXCEPTION_SINGLE_STEP: 1571 return 5; /* SIGTRAP */ 1572 case EXCEPTION_ILLEGAL_INSTRUCTION: 1573 case EXCEPTION_PRIV_INSTRUCTION: 1574 return 4; /* SIGILL */ 1575 case EXCEPTION_INT_DIVIDE_BY_ZERO: 1576 case EXCEPTION_INT_OVERFLOW: 1577 case EXCEPTION_FLT_DIVIDE_BY_ZERO: 1578 case EXCEPTION_FLT_OVERFLOW: 1579 case EXCEPTION_FLT_UNDERFLOW: 1580 case EXCEPTION_FLT_INVALID_OPERATION: 1581 case EXCEPTION_FLT_DENORMAL_OPERAND: 1582 case EXCEPTION_FLT_INEXACT_RESULT: 1583 case EXCEPTION_FLT_STACK_CHECK: 1584 return 8; /* SIGFPE */ 1585 case EXCEPTION_DATATYPE_MISALIGNMENT: 1586 return 7; /* SIGBUS */ 1587 case EXCEPTION_ACCESS_VIOLATION: 1588 case EXCEPTION_IN_PAGE_ERROR: 1589 case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: 1590 case EXCEPTION_STACK_OVERFLOW: 1591 default: 1592 return 11; /* SIGSEGV */ 1593 } 1594 } 1595 1596 /* ============================================================ 1597 * dbg_os: threads, events, exceptions, guarded_copy 1598 * ============================================================ */ 1599 1600 static KitDbgSignalOps g_dbg_ops; 1601 static int g_dbg_ops_set; 1602 static void* g_dbg_session; 1603 static DWORD g_dbg_worker_tid; 1604 static int g_dbg_worker_tid_valid; 1605 static PVOID g_veh_cookie; 1606 1607 /* Guarded-copy landing for the VEH path. Both __try/__except and a VEH 1608 * fallback set g_guard_armed; if the VEH fires while it's set the 1609 * exception is "translated" into a longjmp via the VEH's ability to 1610 * rewrite the resume context. We use __try/__except for the primary 1611 * path and treat the VEH armed-check as a backstop. */ 1612 static __declspec(thread) int g_guard_armed; 1613 static __declspec(thread) jmp_buf g_guard_buf; 1614 1615 static int win_dbg_caller_is_worker(void) { 1616 return g_dbg_worker_tid_valid && GetCurrentThreadId() == g_dbg_worker_tid; 1617 } 1618 1619 /* --- thread shim --- */ 1620 1621 typedef struct DbgWinThread { 1622 HANDLE handle; 1623 DWORD tid; 1624 void (*fn)(void*); 1625 void* arg; 1626 } DbgWinThread; 1627 1628 static unsigned __stdcall dbg_thread_trampoline(void* p) { 1629 DbgWinThread* t = (DbgWinThread*)p; 1630 t->fn(t->arg); 1631 return 0; 1632 } 1633 1634 static KitStatus dbg_thread_start_win(void* user, void (*fn)(void*), void* arg, 1635 void** thread_out) { 1636 DbgWinThread* t; 1637 uintptr_t h; 1638 (void)user; 1639 t = (DbgWinThread*)malloc(sizeof(*t)); 1640 if (!t) return KIT_NOMEM; 1641 t->fn = fn; 1642 t->arg = arg; 1643 /* _beginthreadex sets up the CRT TLS for the new thread; pure CreateThread 1644 * leaves the CRT in an undefined state for code that calls printf/malloc. */ 1645 h = _beginthreadex(NULL, 0, dbg_thread_trampoline, t, 0, (unsigned*)&t->tid); 1646 if (h == 0) { 1647 free(t); 1648 return KIT_ERR; 1649 } 1650 t->handle = (HANDLE)h; 1651 g_dbg_worker_tid = t->tid; 1652 g_dbg_worker_tid_valid = 1; 1653 *thread_out = t; 1654 return KIT_OK; 1655 } 1656 1657 static void dbg_thread_join_win(void* user, void* thread) { 1658 DbgWinThread* t = (DbgWinThread*)thread; 1659 (void)user; 1660 if (!t) return; 1661 WaitForSingleObject(t->handle, INFINITE); 1662 CloseHandle(t->handle); 1663 g_dbg_worker_tid_valid = 0; 1664 free(t); 1665 } 1666 1667 /* Interrupt the worker by suspending it, snapshotting its CONTEXT, 1668 * calling on_fault on the caller thread (the REPL), and writing back any 1669 * register edits. This differs from POSIX (where the signal handler runs 1670 * the on_fault on the worker), but the observable contract -- "the worker 1671 * is stopped while on_fault runs and resumes with the edited frame" -- 1672 * matches. Locking quirks of SuspendThread aren't a concern here: the 1673 * worker is, by construction, running JIT'd user code that holds no host 1674 * locks. */ 1675 static KitStatus dbg_thread_interrupt_win(void* user, void* thread) { 1676 DbgWinThread* t = (DbgWinThread*)thread; 1677 CONTEXT ctx; 1678 KitUnwindFrame frame; 1679 KitStatus rc; 1680 (void)user; 1681 if (!t || !g_dbg_ops_set || !g_dbg_ops.on_fault) return KIT_INVALID; 1682 if (SuspendThread(t->handle) == (DWORD)-1) return KIT_ERR; 1683 memset(&ctx, 0, sizeof(ctx)); 1684 ctx.ContextFlags = CONTEXT_FULL; 1685 if (!GetThreadContext(t->handle, &ctx)) { 1686 ResumeThread(t->handle); 1687 return KIT_ERR; 1688 } 1689 ctx_to_frame(&ctx, &frame); 1690 rc = g_dbg_ops.on_fault(g_dbg_session, DBG_WIN_INTERRUPT_SIGNO, &frame); 1691 if (rc == KIT_OK) { 1692 frame_to_ctx(&frame, &ctx); 1693 SetThreadContext(t->handle, &ctx); 1694 } 1695 ResumeThread(t->handle); 1696 return rc; 1697 } 1698 1699 /* --- event shim --- */ 1700 1701 static KitStatus dbg_event_new_win(void* user, void** event_out) { 1702 HANDLE h; 1703 (void)user; 1704 /* Manual-reset so a signaler that races ahead of the waiter doesn't lose 1705 * the wake-up; the dbg core explicitly resets via event_reset. */ 1706 h = CreateEventW(NULL, TRUE, FALSE, NULL); 1707 if (!h) return KIT_ERR; 1708 *event_out = (void*)h; 1709 return KIT_OK; 1710 } 1711 1712 static void dbg_event_free_win(void* user, void* ev) { 1713 (void)user; 1714 if (ev) CloseHandle((HANDLE)ev); 1715 } 1716 1717 static KitStatus dbg_event_wait_win(void* user, void* ev) { 1718 (void)user; 1719 if (WaitForSingleObject((HANDLE)ev, INFINITE) != WAIT_OBJECT_0) 1720 return KIT_ERR; 1721 /* Auto-reset semantics expected by dbg_event_wait on POSIX (it clears 1722 * the flag after consuming). Mirror that here so the contract holds. */ 1723 ResetEvent((HANDLE)ev); 1724 return KIT_OK; 1725 } 1726 1727 static KitStatus dbg_event_signal_win(void* user, void* ev) { 1728 (void)user; 1729 return SetEvent((HANDLE)ev) ? KIT_OK : KIT_ERR; 1730 } 1731 1732 static KitStatus dbg_event_reset_win(void* user, void* ev) { 1733 (void)user; 1734 return ResetEvent((HANDLE)ev) ? KIT_OK : KIT_ERR; 1735 } 1736 1737 /* --- vectored exception handler --- */ 1738 1739 /* Recovery thunk: when guarded_copy's __try/__except is unavailable (or 1740 * for natural faults raised inside a guarded region) we hand control back 1741 * to the longjmp target by rewriting the resume context. */ 1742 static LONG WINAPI dbg_veh(EXCEPTION_POINTERS* ep) { 1743 DWORD code = ep->ExceptionRecord->ExceptionCode; 1744 KitUnwindFrame frame; 1745 KitStatus rc; 1746 int signo; 1747 1748 /* Filter out C++-style exceptions / debug strings we don't care about. */ 1749 if (code == 0x406D1388u /* MS_VC_EXCEPTION (SetThreadName) */ || 1750 code == 0xE06D7363u /* CXX EH */ || code == DBG_PRINTEXCEPTION_C || 1751 code == DBG_PRINTEXCEPTION_WIDE_C) 1752 return EXCEPTION_CONTINUE_SEARCH; 1753 1754 /* SEGV/BUS during a guarded_copy: bail out without involving the 1755 * session. The VEH backstop only fires if SEH/__try wasn't compiled in 1756 * for the function that armed the guard. */ 1757 if (g_guard_armed && 1758 (code == EXCEPTION_ACCESS_VIOLATION || code == EXCEPTION_IN_PAGE_ERROR || 1759 code == EXCEPTION_DATATYPE_MISALIGNMENT)) { 1760 g_guard_armed = 0; 1761 longjmp(g_guard_buf, 1); 1762 /* not reached */ 1763 return EXCEPTION_CONTINUE_EXECUTION; 1764 } 1765 1766 if (!win_dbg_caller_is_worker() || !g_dbg_ops_set || !g_dbg_ops.on_fault) 1767 return EXCEPTION_CONTINUE_SEARCH; 1768 1769 signo = exception_code_to_signo(code); 1770 ctx_to_frame(ep->ContextRecord, &frame); 1771 rc = g_dbg_ops.on_fault(g_dbg_session, signo, &frame); 1772 if (rc != KIT_OK) return EXCEPTION_CONTINUE_SEARCH; 1773 frame_to_ctx(&frame, ep->ContextRecord); 1774 return EXCEPTION_CONTINUE_EXECUTION; 1775 } 1776 1777 static KitStatus dbg_signals_install_win(void* user, const KitDbgSignalOps* ops, 1778 void* session) { 1779 (void)user; 1780 if (g_veh_cookie) return KIT_ERR; 1781 g_dbg_ops = *ops; 1782 g_dbg_ops_set = 1; 1783 g_dbg_session = session; 1784 /* First==1 ensures we run before any other VEH and before the system's 1785 * default last-chance handler. */ 1786 g_veh_cookie = AddVectoredExceptionHandler(1, dbg_veh); 1787 if (!g_veh_cookie) { 1788 memset(&g_dbg_ops, 0, sizeof(g_dbg_ops)); 1789 g_dbg_ops_set = 0; 1790 g_dbg_session = NULL; 1791 return KIT_ERR; 1792 } 1793 return KIT_OK; 1794 } 1795 1796 static void dbg_signals_uninstall_win(void* user) { 1797 (void)user; 1798 if (g_veh_cookie) { 1799 RemoveVectoredExceptionHandler(g_veh_cookie); 1800 g_veh_cookie = NULL; 1801 } 1802 memset(&g_dbg_ops, 0, sizeof(g_dbg_ops)); 1803 g_dbg_ops_set = 0; 1804 g_dbg_session = NULL; 1805 } 1806 1807 /* --- W^X transitions --- */ 1808 1809 static size_t page_floor(size_t v, size_t pg) { return v & ~(pg - 1); } 1810 static size_t page_ceil(size_t v, size_t pg) { 1811 return (v + pg - 1) & ~(pg - 1); 1812 } 1813 1814 static KitStatus dbg_code_write_begin_win(void* user, void* runtime_addr, 1815 size_t n, void** write_out) { 1816 size_t pg; 1817 uintptr_t a; 1818 uintptr_t base; 1819 size_t span; 1820 DWORD old; 1821 (void)user; 1822 if (!runtime_addr || !n || !write_out) return KIT_INVALID; 1823 /* Dual-mapped reservation: write through the alias, no protect flip. */ 1824 if (exec_dual_lookup_w(runtime_addr, n, write_out) == 0) return KIT_OK; 1825 /* Single-mapping fallback: transient PAGE_EXECUTE_READWRITE. */ 1826 pg = driver_host_page_size_win(); 1827 a = (uintptr_t)runtime_addr; 1828 base = page_floor(a, pg); 1829 span = page_ceil((a - base) + n, pg); 1830 if (!VirtualProtect((void*)base, span, PAGE_EXECUTE_READWRITE, &old)) 1831 return KIT_ERR; 1832 *write_out = runtime_addr; 1833 return KIT_OK; 1834 } 1835 1836 static void dbg_code_write_end_win(void* user, void* runtime_addr, size_t n) { 1837 void* w; 1838 size_t pg; 1839 uintptr_t a; 1840 uintptr_t base; 1841 size_t span; 1842 DWORD old; 1843 (void)user; 1844 if (exec_dual_lookup_w(runtime_addr, n, &w) == 0) 1845 return; /* nothing to flip */ 1846 pg = driver_host_page_size_win(); 1847 a = (uintptr_t)runtime_addr; 1848 base = page_floor(a, pg); 1849 span = page_ceil((a - base) + n, pg); 1850 VirtualProtect((void*)base, span, PAGE_EXECUTE_READ, &old); 1851 } 1852 1853 static void dbg_flush_icache_win(void* user, void* runtime_addr, size_t n) { 1854 (void)user; 1855 FlushInstructionCache(GetCurrentProcess(), runtime_addr, n); 1856 } 1857 1858 /* --- guarded copy via SEH __try/__except --- */ 1859 1860 static KitStatus dbg_guarded_copy_win(void* user, void* dst, const void* src, 1861 size_t n) { 1862 (void)user; 1863 /* MinGW-w64 supports __try/__except natively for the SEH model on x86_64 1864 * and arm64 (GCC: -fseh-exceptions; clang: built-in). The VEH backstop 1865 * above catches it if the toolchain ever falls back to setjmp-based EH. */ 1866 if (setjmp(g_guard_buf) != 0) { 1867 g_guard_armed = 0; 1868 return KIT_ERR; 1869 } 1870 g_guard_armed = 1; 1871 #if defined(__kit__) 1872 /* kit's C frontend implements no SEH (__try/__except). Rely on the VEH 1873 * backstop (dbg_veh) installed by dbg_signals_install_win: it sees the 1874 * thread-local g_guard_armed and longjmps back through g_guard_buf on an 1875 * access fault, which is the exact fallback the comment above anticipates. */ 1876 memcpy(dst, src, n); 1877 g_guard_armed = 0; 1878 return KIT_OK; 1879 #else 1880 __try { 1881 memcpy(dst, src, n); 1882 g_guard_armed = 0; 1883 return KIT_OK; 1884 } __except (EXCEPTION_EXECUTE_HANDLER) { 1885 g_guard_armed = 0; 1886 return KIT_ERR; 1887 } 1888 #endif 1889 } 1890 1891 /* --- call_with_catch / thread_abort (longjmp-based) --- */ 1892 1893 static __declspec(thread) jmp_buf g_dbg_abort_buf; 1894 1895 static int dbg_call_with_catch_win(void* user, void (*fn)(void*), void* arg) { 1896 (void)user; 1897 if (setjmp(g_dbg_abort_buf) == 0) { 1898 fn(arg); 1899 return 0; 1900 } 1901 return 1; 1902 } 1903 1904 static void dbg_thread_abort_win(void* user) { 1905 (void)user; 1906 longjmp(g_dbg_abort_buf, 1); 1907 } 1908 1909 static KitDbgOs g_dbg_os_win = { 1910 .thread_start = dbg_thread_start_win, 1911 .thread_join = dbg_thread_join_win, 1912 .thread_interrupt = dbg_thread_interrupt_win, 1913 .event_new = dbg_event_new_win, 1914 .event_free = dbg_event_free_win, 1915 .event_wait = dbg_event_wait_win, 1916 .event_signal = dbg_event_signal_win, 1917 .event_reset = dbg_event_reset_win, 1918 .signals_install = dbg_signals_install_win, 1919 .signals_uninstall = dbg_signals_uninstall_win, 1920 .interrupt_signo = DBG_WIN_INTERRUPT_SIGNO, 1921 .trap_signo = 5, 1922 .code_write_begin = dbg_code_write_begin_win, 1923 .code_write_end = dbg_code_write_end_win, 1924 .flush_icache = dbg_flush_icache_win, 1925 .guarded_copy = dbg_guarded_copy_win, 1926 .call_with_catch = dbg_call_with_catch_win, 1927 .thread_abort = dbg_thread_abort_win, 1928 .user = NULL, 1929 }; 1930 1931 /* JIT thread-local access resolves to in-image storage (single-threaded JIT: 1932 * the in-image TLS data is the single instance — see src/link/link_jit.c), so 1933 * no host-provided per-thread TLS vtable is needed. */ 1934 1935 /* ============================================================ 1936 * host target 1937 * ============================================================ */ 1938 1939 static KitArchKind host_arch_self_win(void) { 1940 #if defined(_M_X64) || defined(__x86_64__) 1941 return KIT_ARCH_X86_64; 1942 #elif defined(_M_ARM64) || defined(__aarch64__) 1943 return KIT_ARCH_ARM_64; 1944 #elif defined(_M_IX86) || defined(__i386__) 1945 return KIT_ARCH_X86_32; 1946 #elif defined(_M_ARM) || defined(__arm__) 1947 return KIT_ARCH_ARM_32; 1948 #else 1949 return KIT_ARCH_X86_64; 1950 #endif 1951 } 1952 1953 KitTargetSpec driver_host_target(void) { 1954 KitTargetSpec t; 1955 t.arch = host_arch_self_win(); 1956 t.os = KIT_OS_WINDOWS; 1957 t.obj = KIT_OBJ_COFF; 1958 t.ptr_size = (uint8_t)sizeof(void*); 1959 t.ptr_align = (uint8_t)sizeof(void*); 1960 t.big_endian = 0; 1961 t.pic = driver_default_pic(t.obj, t.os); 1962 t.code_model = KIT_CM_DEFAULT; 1963 return t; 1964 } 1965 1966 /* No host probe on Windows: the MinGW sysroot is supplied via --sysroot or the 1967 * KIT_SYSROOT env var (handled in the cc driver), not auto-discovered from a 1968 * fixed install path. */ 1969 int driver_default_hosted_dirs(DriverEnv* env, KitTargetSpec target, 1970 DriverHostedDirs* out) { 1971 (void)env; 1972 (void)target; 1973 (void)out; 1974 return 1; 1975 } 1976 1977 /* ============================================================ 1978 * env wiring 1979 * ============================================================ */ 1980 1981 static char g_cache_dir_win[4096]; 1982 1983 void driver_env_init(DriverEnv* e) { 1984 e->heap = &g_heap_libc; 1985 e->diag = &g_diag_stderr; 1986 e->file_io.read_all = win_read_all; 1987 e->file_io.release = win_release; 1988 e->file_io.open_writer = win_open_writer; 1989 e->file_io.user = e; 1990 1991 g_execmem_win.page_size = driver_host_page_size_win(); 1992 g_execmem_win.reserve = execmem_reserve_win; 1993 g_execmem_win.protect = execmem_protect_win; 1994 g_execmem_win.release = execmem_release_win; 1995 g_execmem_win.flush_icache = execmem_flush_icache_win; 1996 g_execmem_win.user = NULL; 1997 e->execmem = &g_execmem_win; 1998 1999 e->dbg_os = &g_dbg_os_win; 2000 e->metrics = NULL; 2001 2002 { 2003 /* XDG_CACHE_HOME wins if set (cross-platform tooling convention), 2004 * otherwise fall back to %LOCALAPPDATA%\\kit, otherwise a 2005 * build-tree-local path. */ 2006 const char* xdg = getenv("XDG_CACHE_HOME"); 2007 const char* lap = getenv("LOCALAPPDATA"); 2008 if (xdg && *xdg) { 2009 snprintf(g_cache_dir_win, sizeof(g_cache_dir_win), "%s/kit", xdg); 2010 } else if (lap && *lap) { 2011 snprintf(g_cache_dir_win, sizeof(g_cache_dir_win), "%s\\kit", lap); 2012 } else { 2013 snprintf(g_cache_dir_win, sizeof(g_cache_dir_win), "build\\kit-cache"); 2014 } 2015 g_cache_dir_win[sizeof(g_cache_dir_win) - 1] = '\0'; 2016 e->cache_dir = g_cache_dir_win; 2017 } 2018 2019 { 2020 const char* sde = getenv("SOURCE_DATE_EPOCH"); 2021 if (sde && *sde) { 2022 char* endp = NULL; 2023 long long v = strtoll(sde, &endp, 10); 2024 e->now = (endp != sde && v >= 0) ? (int64_t)v : (int64_t)-1; 2025 } else { 2026 time_t t = time(NULL); 2027 e->now = (t == (time_t)-1) ? (int64_t)-1 : (int64_t)t; 2028 } 2029 } 2030 } 2031 2032 void driver_env_fini(DriverEnv* e) { (void)e; } 2033 2034 KitContext driver_env_to_context(const DriverEnv* e) { 2035 KitContext c; 2036 c.heap = e->heap; 2037 c.file_io = &e->file_io; 2038 c.diag = e->diag; 2039 c.metrics = e->metrics; 2040 c.now = e->now; 2041 return c; 2042 } 2043 2044 KitJitHost driver_env_to_jit_host(const DriverEnv* e) { 2045 KitJitHost h; 2046 h.execmem = e->execmem; 2047 return h; 2048 } 2049 2050 KitDbgHost driver_env_to_dbg_host(const DriverEnv* e) { 2051 KitDbgHost h; 2052 h.os = e->dbg_os; 2053 return h; 2054 }