From c38e6ed6862c4abfb6c3ab6c8cdd1ea13961969b Mon Sep 17 00:00:00 2001 From: inf Date: Tue, 17 Mar 2026 23:06:55 +0100 Subject: [PATCH] feat: remove kernel32.CreateThread, implement ntdll.NtCreateThreadEx inside WindowsThreadImpl (#31519) Co-authored-by: Ryan Liptak Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31519 Reviewed-by: Ryan Liptak Co-authored-by: inf Co-committed-by: inf --- lib/std/Thread.zig | 93 ++++++++++++++++++++++++++------- lib/std/os/windows.zig | 88 +++++++++++++++++++++++++++++++ lib/std/os/windows/kernel32.zig | 10 ---- lib/std/os/windows/ntdll.zig | 41 +++++++++++++++ 4 files changed, 204 insertions(+), 28 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 11466fb9a251a7ad44363995425ea9eccb8a19d1..b729cbe3593412c5781946579dd01e6a4b43428e 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -405,14 +405,19 @@ const Completion = std.atomic.Value(enum(if (builtin.zig_backend == .stage2_risc /// Performs implementation-agnostic thread setup (`maybeAttachSignalStack`), then calls the given /// thread entry point `f` with `args` and handles the result. fn callFn(comptime f: anytype, args: anytype) switch (Impl) { - WindowsThreadImpl => windows.DWORD, + WindowsThreadImpl => windows.NTSTATUS, LinuxThreadImpl => u8, PosixThreadImpl => ?*anyopaque, else => unreachable, } { maybeAttachSignalStack(); - const default_value = if (Impl == PosixThreadImpl) null else 0; + const default_value = switch (Impl) { + WindowsThreadImpl => .SUCCESS, + LinuxThreadImpl => 0, + PosixThreadImpl => null, + else => unreachable, + }; const bad_fn_ret = "expected return type of startFn to be 'u8', 'noreturn', '!noreturn', 'void', or '!void'"; switch (@typeInfo(@typeInfo(@TypeOf(f)).@"fn".return_type.?)) { @@ -429,12 +434,13 @@ fn callFn(comptime f: anytype, args: anytype) switch (Impl) { } const status = @call(.auto, f, args); - if (Impl != PosixThreadImpl) { - return status; + switch (Impl) { + WindowsThreadImpl => return @enumFromInt(status), + LinuxThreadImpl => return status, + // pthreads don't support exit status, ignore value + PosixThreadImpl => return default_value, + else => unreachable, } - - // pthreads don't support exit status, ignore value - return default_value; }, .error_union => |info| { switch (info.payload) { @@ -526,7 +532,7 @@ const WindowsThreadImpl = struct { fn_args: Args, thread: ThreadCompletion, - fn entryFn(raw_ptr: windows.PVOID) callconv(.winapi) windows.DWORD { + fn entryFn(raw_ptr: windows.PVOID) callconv(.winapi) windows.NTSTATUS { const self: *@This() = @ptrCast(@alignCast(raw_ptr)); defer switch (self.thread.completion.swap(.completed, .seq_cst)) { .running => {}, @@ -559,16 +565,67 @@ const WindowsThreadImpl = struct { // Its also fine if the limit here is incorrect as stack size is only a hint. const stack_size = @max(64 * 1024, std.math.lossyCast(u32, config.stack_size)); - instance.thread.thread_handle = windows.kernel32.CreateThread( - null, - stack_size, - Instance.entryFn, - instance, - 0, - null, - ) orelse { - const errno = windows.GetLastError(); - return windows.unexpectedError(errno); + // Intended to be equivalent to a kernel32.CreateThread call with no flags set. + // However, CreateThread is just a wrapper around CreateRemoteThreadEx, + // so that's the more relevant function in this context. + // + // https://github.com/wine-mirror/wine/blob/3d128be6400b3869119d293d0c8fa9e7702978f8/dlls/kernelbase/thread.c#L85 + instance.thread.thread_handle = blk: { + var active_ctx: ?windows.HANDLE = undefined; + // Note: Can return null on SUCCESS + switch (windows.ntdll.RtlGetActiveActivationContext(&active_ctx)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), + } + defer if (active_ctx) |ctx| windows.ntdll.RtlReleaseActivationContext(ctx); + + var teb: *windows.TEB = undefined; + var attr_list = windows.PS.ATTRIBUTE.LIST{ + .TotalLength = @sizeOf(windows.PS.ATTRIBUTE.LIST), + .Attributes = .{ + .{ + .Attribute = .TEB_ADDRESS, + .Size = @sizeOf(*windows.TEB), + .u = .{ + .ValuePtr = @ptrCast(&teb), + }, + .ReturnLength = null, + }, + }, + }; + + var thread_handle: windows.HANDLE = undefined; + switch (windows.ntdll.NtCreateThreadEx( + &thread_handle, + .{ .MAXIMUM_ALLOWED = true }, + &.{}, + windows.GetCurrentProcess(), + Instance.entryFn, + instance, + .{ .CREATE_SUSPENDED = true }, + 0, + @enumFromInt(stack_size), + .default, + &attr_list, + )) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), + } + + if (active_ctx) |ctx| { + var cookie: windows.ULONG = 0; + switch (windows.ntdll.RtlActivateActivationContextEx(0, teb, ctx, &cookie)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), + } + } + + switch (windows.ntdll.NtResumeThread(thread_handle, null)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), + } + + break :blk thread_handle; }; return Impl{ .thread = &instance.thread }; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 1dabd71422d8e715ec567011752df2acf452de0c..4ad01dff3fd33878ec8ab48c6f78034fa1e954d6 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -23,6 +23,75 @@ pub const nls = @import("windows/nls.zig"); pub const current_process: HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))); +pub const PS = struct { + pub const ATTRIBUTE = extern struct { + Attribute: Type, + Size: SIZE_T, + u: extern union { + Value: ULONG_PTR, + ValuePtr: PVOID, + }, + ReturnLength: ?*SIZE_T, + + /// https://ntdoc.m417z.com/ps_attribute_num + /// Tag type is `u16` based on PS_ATTRIBUTE_NUMBER_MASK being 0xFFFF + pub const NUM = enum(u16) { + ParentProcess = 0, + DebugObject, + Token, + ClientId, + TebAddress, + ImageName, + ImageInfo, + MemoryReserve, + PriorityClass, + ErrorMode, + StdHandleInfo, + HandleList, + GroupAffinity, + PreferredNode, + IdealProcessor, + UmsThread, + MitigationOptions, + ProtectionLevel, + SecureProcess, + JobList, + ChildProcessPolicy, + AllApplicationPackagesPolicy, + Win32kFilter, + SafeOpenPromptOriginClaim, + BnoIsolation, + DesktopAppPolicy, + Chpe, + MitigationAuditOptions, + MachineType, + ComponentFilter, + EnableOptionalXStateFeatures, + SupportedMachines, + SveVectorLength, + }; + + /// https://ntdoc.m417z.com/psattributevalue + pub const Type = enum(ULONG_PTR) { + TEB_ADDRESS = construct(.TebAddress, true, false, false), + _, + + pub fn construct(num: NUM, thread: bool, input: bool, additive: bool) ULONG_PTR { + var val: ULONG_PTR = @intFromEnum(num); + if (thread) val |= 0x10000; + if (input) val |= 0x20000; + if (additive) val |= 0x40000; + return val; + } + }; + + pub const LIST = extern struct { + TotalLength: SIZE_T, + Attributes: [1]ATTRIBUTE, + }; + }; +}; + pub const OBJECT = struct { // ref: um/winternl.h @@ -1251,6 +1320,24 @@ pub const THREAD = struct { Priority: KPRIORITY, BasePriority: KPRIORITY, }; + + pub const CREATE_FLAGS = packed struct(ULONG) { + CREATE_SUSPENDED: bool = false, + SKIP_THREAD_ATTACH: bool = false, + HIDE_FROM_DEBUGGER: bool = false, + LOADER_WORKER: bool = false, + SKIP_LOADER_INIT: bool = false, + BYPASS_PROCESS_FREEZE: bool = false, + Reserved6: u26 = 0, + + pub const NONE: CREATE_FLAGS = .{}; + }; + + pub const StackSize = enum(SIZE_T) { + /// The default size specified in the executable header + default = 0, + _, + }; }; pub const MEMORY = struct { @@ -3436,6 +3523,7 @@ pub const STARTF_USESIZE = 0x00000002; pub const STARTF_USESTDHANDLES = 0x00000100; pub const THREAD_START_ROUTINE = fn (LPVOID) callconv(.winapi) DWORD; +pub const USER_THREAD_START_ROUTINE = fn (LPVOID) callconv(.winapi) NTSTATUS; pub const SYSTEM_INFO = extern struct { anon1: extern union { diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index 649d09cc6256cccc49d9a18d2834e16e5450e36c..f32c37a11cee179f094c4c71fdce6b5709dbfebc 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -25,13 +25,3 @@ pub extern "kernel32" fn CreateProcessW( lpStartupInfo: *STARTUPINFOW, lpProcessInformation: *PROCESS.INFORMATION, ) callconv(.winapi) BOOL; - -// TODO: CreateRemoteThread with hProcess=NtCurrentProcess(). -pub extern "kernel32" fn CreateThread( - lpThreadAttributes: ?*SECURITY_ATTRIBUTES, - dwStackSize: SIZE_T, - lpStartAddress: *const THREAD_START_ROUTINE, - lpParameter: ?LPVOID, - dwCreationFlags: DWORD, - lpThreadId: ?*DWORD, -) callconv(.winapi) ?HANDLE; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index e4f299e4e6930979f5518f34de158e5794422f8f..8f3e5e1fc312a9bcdd666b59c601ac4d623c7d5b 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -55,6 +55,9 @@ const UNWIND_HISTORY_TABLE = windows.UNWIND_HISTORY_TABLE; const USHORT = windows.USHORT; const VECTORED_EXCEPTION_HANDLER = windows.VECTORED_EXCEPTION_HANDLER; const WORD = windows.WORD; +const USER_THREAD_START_ROUTINE = windows.USER_THREAD_START_ROUTINE; +const PS = windows.PS; +const TEB = windows.TEB; // ref: km/ntifs.h @@ -359,6 +362,21 @@ pub extern "ntdll" fn NtQuerySystemInformation( // ref none +pub extern "ntdll" fn RtlGetActiveActivationContext( + ActivationContext: *?HANDLE, +) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn RtlActivateActivationContextEx( + Flags: ULONG, + Teb: *TEB, + ActivationContext: HANDLE, + Cookie: *ULONG, +) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn RtlReleaseActivationContext( + ActivationContext: HANDLE, +) callconv(.winapi) void; + pub extern "ntdll" fn LdrAddRefDll( Flags: ULONG, DllHandle: PVOID, @@ -759,3 +777,26 @@ pub extern "ntdll" fn NtLoadKeyEx( RootHandle: ?*HANDLE, Reserved: ?*anyopaque, ) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn NtCreateThreadEx( + ThreadHandle: *HANDLE, + DesiredAccess: ACCESS_MASK, + ObjectAttributes: *const OBJECT.ATTRIBUTES, + ProcessHandle: HANDLE, + StartRoutine: *const USER_THREAD_START_ROUTINE, + Argument: ?PVOID, + CreateFlags: THREAD.CREATE_FLAGS, + ZeroBits: SIZE_T, + /// This value is rounded up to the nearest page. + /// If this value is larger than `StackReserve`, the reserved stack + /// size will be the rounded value of this parameter. + /// https://learn.microsoft.com/en-us/windows/win32/procthread/thread-stack-size + StackCommit: THREAD.StackSize, + StackReserve: THREAD.StackSize, + AttributeList: ?*PS.ATTRIBUTE.LIST, +) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn NtResumeThread( + ThreadHandle: HANDLE, + PreviousSuspendCount: ?*ULONG, +) callconv(.winapi) NTSTATUS; -- 2.54.0