authorgravatar for infirms@protonmail.cominf <infirms@protonmail.com> 2026-03-17 23:06:55+01:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-03-17 23:06:55+01:00
logc38e6ed6862c4abfb6c3ab6c8cdd1ea13961969b
tree27ec7ca64848b85e5c75c3d1c191ac5076725e86
parent4d5721214f31684e3bed3624878d8903fabe8e39

feat: remove kernel32.CreateThread, implement ntdll.NtCreateThreadEx inside WindowsThreadImpl (#31519)

Co-authored-by: Ryan Liptak <squeek502@hotmail.com> Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31519 Reviewed-by: Ryan Liptak <squeek502@noreply.codeberg.org> Co-authored-by: inf <infirms@protonmail.com> Co-committed-by: inf <infirms@protonmail.com>

4 files changed, 204 insertions(+), 28 deletions(-)

lib/std/Thread.zig+75-18
...@@ -405,14 +405,19 @@ const Completion = std.atomic.Value(enum(if (builtin.zig_backend == .stage2_risc...@@ -405,14 +405,19 @@ const Completion = std.atomic.Value(enum(if (builtin.zig_backend == .stage2_risc
405/// Performs implementation-agnostic thread setup (`maybeAttachSignalStack`), then calls the given405/// Performs implementation-agnostic thread setup (`maybeAttachSignalStack`), then calls the given
406/// thread entry point `f` with `args` and handles the result.406/// thread entry point `f` with `args` and handles the result.
407fn callFn(comptime f: anytype, args: anytype) switch (Impl) {407fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
408 WindowsThreadImpl => windows.DWORD,408 WindowsThreadImpl => windows.NTSTATUS,
409 LinuxThreadImpl => u8,409 LinuxThreadImpl => u8,
410 PosixThreadImpl => ?*anyopaque,410 PosixThreadImpl => ?*anyopaque,
411 else => unreachable,411 else => unreachable,
412} {412} {
413 maybeAttachSignalStack();413 maybeAttachSignalStack();
414414
415 const default_value = if (Impl == PosixThreadImpl) null else 0;415 const default_value = switch (Impl) {
416 WindowsThreadImpl => .SUCCESS,
417 LinuxThreadImpl => 0,
418 PosixThreadImpl => null,
419 else => unreachable,
420 };
416 const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', '!noreturn', 'void', or '!void'";421 const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', '!noreturn', 'void', or '!void'";
417422
418 switch (@typeInfo(@typeInfo(@TypeOf(f)).@"fn".return_type.?)) {423 switch (@typeInfo(@typeInfo(@TypeOf(f)).@"fn".return_type.?)) {
...@@ -429,12 +434,13 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {...@@ -429,12 +434,13 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) {
429 }434 }
430435
431 const status = @call(.auto, f, args);436 const status = @call(.auto, f, args);
432 if (Impl != PosixThreadImpl) {437 switch (Impl) {
433 return status;438 WindowsThreadImpl => return @enumFromInt(status),
439 LinuxThreadImpl => return status,
440 // pthreads don't support exit status, ignore value
441 PosixThreadImpl => return default_value,
442 else => unreachable,
434 }443 }
435
436 // pthreads don't support exit status, ignore value
437 return default_value;
438 },444 },
439 .error_union => |info| {445 .error_union => |info| {
440 switch (info.payload) {446 switch (info.payload) {
...@@ -526,7 +532,7 @@ const WindowsThreadImpl = struct {...@@ -526,7 +532,7 @@ const WindowsThreadImpl = struct {
526 fn_args: Args,532 fn_args: Args,
527 thread: ThreadCompletion,533 thread: ThreadCompletion,
528534
529 fn entryFn(raw_ptr: windows.PVOID) callconv(.winapi) windows.DWORD {535 fn entryFn(raw_ptr: windows.PVOID) callconv(.winapi) windows.NTSTATUS {
530 const self: *@This() = @ptrCast(@alignCast(raw_ptr));536 const self: *@This() = @ptrCast(@alignCast(raw_ptr));
531 defer switch (self.thread.completion.swap(.completed, .seq_cst)) {537 defer switch (self.thread.completion.swap(.completed, .seq_cst)) {
532 .running => {},538 .running => {},
...@@ -559,16 +565,67 @@ const WindowsThreadImpl = struct {...@@ -559,16 +565,67 @@ const WindowsThreadImpl = struct {
559 // Its also fine if the limit here is incorrect as stack size is only a hint.565 // Its also fine if the limit here is incorrect as stack size is only a hint.
560 const stack_size = @max(64 * 1024, std.math.lossyCast(u32, config.stack_size));566 const stack_size = @max(64 * 1024, std.math.lossyCast(u32, config.stack_size));
561567
562 instance.thread.thread_handle = windows.kernel32.CreateThread(568 // Intended to be equivalent to a kernel32.CreateThread call with no flags set.
563 null,569 // However, CreateThread is just a wrapper around CreateRemoteThreadEx,
564 stack_size,570 // so that's the more relevant function in this context.
565 Instance.entryFn,571 //
566 instance,572 // https://github.com/wine-mirror/wine/blob/3d128be6400b3869119d293d0c8fa9e7702978f8/dlls/kernelbase/thread.c#L85
567 0,573 instance.thread.thread_handle = blk: {
568 null,574 var active_ctx: ?windows.HANDLE = undefined;
569 ) orelse {575 // Note: Can return null on SUCCESS
570 const errno = windows.GetLastError();576 switch (windows.ntdll.RtlGetActiveActivationContext(&active_ctx)) {
571 return windows.unexpectedError(errno);577 .SUCCESS => {},
578 else => |status| return windows.unexpectedStatus(status),
579 }
580 defer if (active_ctx) |ctx| windows.ntdll.RtlReleaseActivationContext(ctx);
581
582 var teb: *windows.TEB = undefined;
583 var attr_list = windows.PS.ATTRIBUTE.LIST{
584 .TotalLength = @sizeOf(windows.PS.ATTRIBUTE.LIST),
585 .Attributes = .{
586 .{
587 .Attribute = .TEB_ADDRESS,
588 .Size = @sizeOf(*windows.TEB),
589 .u = .{
590 .ValuePtr = @ptrCast(&teb),
591 },
592 .ReturnLength = null,
593 },
594 },
595 };
596
597 var thread_handle: windows.HANDLE = undefined;
598 switch (windows.ntdll.NtCreateThreadEx(
599 &thread_handle,
600 .{ .MAXIMUM_ALLOWED = true },
601 &.{},
602 windows.GetCurrentProcess(),
603 Instance.entryFn,
604 instance,
605 .{ .CREATE_SUSPENDED = true },
606 0,
607 @enumFromInt(stack_size),
608 .default,
609 &attr_list,
610 )) {
611 .SUCCESS => {},
612 else => |status| return windows.unexpectedStatus(status),
613 }
614
615 if (active_ctx) |ctx| {
616 var cookie: windows.ULONG = 0;
617 switch (windows.ntdll.RtlActivateActivationContextEx(0, teb, ctx, &cookie)) {
618 .SUCCESS => {},
619 else => |status| return windows.unexpectedStatus(status),
620 }
621 }
622
623 switch (windows.ntdll.NtResumeThread(thread_handle, null)) {
624 .SUCCESS => {},
625 else => |status| return windows.unexpectedStatus(status),
626 }
627
628 break :blk thread_handle;
572 };629 };
573630
574 return Impl{ .thread = &instance.thread };631 return Impl{ .thread = &instance.thread };
lib/std/os/windows.zig+88
...@@ -23,6 +23,75 @@ pub const nls = @import("windows/nls.zig");...@@ -23,6 +23,75 @@ pub const nls = @import("windows/nls.zig");
2323
24pub const current_process: HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));24pub const current_process: HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));
2525
26pub const PS = struct {
27 pub const ATTRIBUTE = extern struct {
28 Attribute: Type,
29 Size: SIZE_T,
30 u: extern union {
31 Value: ULONG_PTR,
32 ValuePtr: PVOID,
33 },
34 ReturnLength: ?*SIZE_T,
35
36 /// https://ntdoc.m417z.com/ps_attribute_num
37 /// Tag type is `u16` based on PS_ATTRIBUTE_NUMBER_MASK being 0xFFFF
38 pub const NUM = enum(u16) {
39 ParentProcess = 0,
40 DebugObject,
41 Token,
42 ClientId,
43 TebAddress,
44 ImageName,
45 ImageInfo,
46 MemoryReserve,
47 PriorityClass,
48 ErrorMode,
49 StdHandleInfo,
50 HandleList,
51 GroupAffinity,
52 PreferredNode,
53 IdealProcessor,
54 UmsThread,
55 MitigationOptions,
56 ProtectionLevel,
57 SecureProcess,
58 JobList,
59 ChildProcessPolicy,
60 AllApplicationPackagesPolicy,
61 Win32kFilter,
62 SafeOpenPromptOriginClaim,
63 BnoIsolation,
64 DesktopAppPolicy,
65 Chpe,
66 MitigationAuditOptions,
67 MachineType,
68 ComponentFilter,
69 EnableOptionalXStateFeatures,
70 SupportedMachines,
71 SveVectorLength,
72 };
73
74 /// https://ntdoc.m417z.com/psattributevalue
75 pub const Type = enum(ULONG_PTR) {
76 TEB_ADDRESS = construct(.TebAddress, true, false, false),
77 _,
78
79 pub fn construct(num: NUM, thread: bool, input: bool, additive: bool) ULONG_PTR {
80 var val: ULONG_PTR = @intFromEnum(num);
81 if (thread) val |= 0x10000;
82 if (input) val |= 0x20000;
83 if (additive) val |= 0x40000;
84 return val;
85 }
86 };
87
88 pub const LIST = extern struct {
89 TotalLength: SIZE_T,
90 Attributes: [1]ATTRIBUTE,
91 };
92 };
93};
94
26pub const OBJECT = struct {95pub const OBJECT = struct {
27 // ref: um/winternl.h96 // ref: um/winternl.h
2897
...@@ -1251,6 +1320,24 @@ pub const THREAD = struct {...@@ -1251,6 +1320,24 @@ pub const THREAD = struct {
1251 Priority: KPRIORITY,1320 Priority: KPRIORITY,
1252 BasePriority: KPRIORITY,1321 BasePriority: KPRIORITY,
1253 };1322 };
1323
1324 pub const CREATE_FLAGS = packed struct(ULONG) {
1325 CREATE_SUSPENDED: bool = false,
1326 SKIP_THREAD_ATTACH: bool = false,
1327 HIDE_FROM_DEBUGGER: bool = false,
1328 LOADER_WORKER: bool = false,
1329 SKIP_LOADER_INIT: bool = false,
1330 BYPASS_PROCESS_FREEZE: bool = false,
1331 Reserved6: u26 = 0,
1332
1333 pub const NONE: CREATE_FLAGS = .{};
1334 };
1335
1336 pub const StackSize = enum(SIZE_T) {
1337 /// The default size specified in the executable header
1338 default = 0,
1339 _,
1340 };
1254};1341};
12551342
1256pub const MEMORY = struct {1343pub const MEMORY = struct {
...@@ -3436,6 +3523,7 @@ pub const STARTF_USESIZE = 0x00000002;...@@ -3436,6 +3523,7 @@ pub const STARTF_USESIZE = 0x00000002;
3436pub const STARTF_USESTDHANDLES = 0x00000100;3523pub const STARTF_USESTDHANDLES = 0x00000100;
34373524
3438pub const THREAD_START_ROUTINE = fn (LPVOID) callconv(.winapi) DWORD;3525pub const THREAD_START_ROUTINE = fn (LPVOID) callconv(.winapi) DWORD;
3526pub const USER_THREAD_START_ROUTINE = fn (LPVOID) callconv(.winapi) NTSTATUS;
34393527
3440pub const SYSTEM_INFO = extern struct {3528pub const SYSTEM_INFO = extern struct {
3441 anon1: extern union {3529 anon1: extern union {
lib/std/os/windows/kernel32.zig-10
...@@ -25,13 +25,3 @@ pub extern "kernel32" fn CreateProcessW(...@@ -25,13 +25,3 @@ pub extern "kernel32" fn CreateProcessW(
25 lpStartupInfo: *STARTUPINFOW,25 lpStartupInfo: *STARTUPINFOW,
26 lpProcessInformation: *PROCESS.INFORMATION,26 lpProcessInformation: *PROCESS.INFORMATION,
27) callconv(.winapi) BOOL;27) callconv(.winapi) BOOL;
28
29// TODO: CreateRemoteThread with hProcess=NtCurrentProcess().
30pub extern "kernel32" fn CreateThread(
31 lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
32 dwStackSize: SIZE_T,
33 lpStartAddress: *const THREAD_START_ROUTINE,
34 lpParameter: ?LPVOID,
35 dwCreationFlags: DWORD,
36 lpThreadId: ?*DWORD,
37) callconv(.winapi) ?HANDLE;
lib/std/os/windows/ntdll.zig+41
...@@ -55,6 +55,9 @@ const UNWIND_HISTORY_TABLE = windows.UNWIND_HISTORY_TABLE;...@@ -55,6 +55,9 @@ const UNWIND_HISTORY_TABLE = windows.UNWIND_HISTORY_TABLE;
55const USHORT = windows.USHORT;55const USHORT = windows.USHORT;
56const VECTORED_EXCEPTION_HANDLER = windows.VECTORED_EXCEPTION_HANDLER;56const VECTORED_EXCEPTION_HANDLER = windows.VECTORED_EXCEPTION_HANDLER;
57const WORD = windows.WORD;57const WORD = windows.WORD;
58const USER_THREAD_START_ROUTINE = windows.USER_THREAD_START_ROUTINE;
59const PS = windows.PS;
60const TEB = windows.TEB;
5861
59// ref: km/ntifs.h62// ref: km/ntifs.h
6063
...@@ -359,6 +362,21 @@ pub extern "ntdll" fn NtQuerySystemInformation(...@@ -359,6 +362,21 @@ pub extern "ntdll" fn NtQuerySystemInformation(
359362
360// ref none363// ref none
361364
365pub extern "ntdll" fn RtlGetActiveActivationContext(
366 ActivationContext: *?HANDLE,
367) callconv(.winapi) NTSTATUS;
368
369pub extern "ntdll" fn RtlActivateActivationContextEx(
370 Flags: ULONG,
371 Teb: *TEB,
372 ActivationContext: HANDLE,
373 Cookie: *ULONG,
374) callconv(.winapi) NTSTATUS;
375
376pub extern "ntdll" fn RtlReleaseActivationContext(
377 ActivationContext: HANDLE,
378) callconv(.winapi) void;
379
362pub extern "ntdll" fn LdrAddRefDll(380pub extern "ntdll" fn LdrAddRefDll(
363 Flags: ULONG,381 Flags: ULONG,
364 DllHandle: PVOID,382 DllHandle: PVOID,
...@@ -759,3 +777,26 @@ pub extern "ntdll" fn NtLoadKeyEx(...@@ -759,3 +777,26 @@ pub extern "ntdll" fn NtLoadKeyEx(
759 RootHandle: ?*HANDLE,777 RootHandle: ?*HANDLE,
760 Reserved: ?*anyopaque,778 Reserved: ?*anyopaque,
761) callconv(.winapi) NTSTATUS;779) callconv(.winapi) NTSTATUS;
780
781pub extern "ntdll" fn NtCreateThreadEx(
782 ThreadHandle: *HANDLE,
783 DesiredAccess: ACCESS_MASK,
784 ObjectAttributes: *const OBJECT.ATTRIBUTES,
785 ProcessHandle: HANDLE,
786 StartRoutine: *const USER_THREAD_START_ROUTINE,
787 Argument: ?PVOID,
788 CreateFlags: THREAD.CREATE_FLAGS,
789 ZeroBits: SIZE_T,
790 /// This value is rounded up to the nearest page.
791 /// If this value is larger than `StackReserve`, the reserved stack
792 /// size will be the rounded value of this parameter.
793 /// https://learn.microsoft.com/en-us/windows/win32/procthread/thread-stack-size
794 StackCommit: THREAD.StackSize,
795 StackReserve: THREAD.StackSize,
796 AttributeList: ?*PS.ATTRIBUTE.LIST,
797) callconv(.winapi) NTSTATUS;
798
799pub extern "ntdll" fn NtResumeThread(
800 ThreadHandle: HANDLE,
801 PreviousSuspendCount: ?*ULONG,
802) callconv(.winapi) NTSTATUS;