[PATCH v4 0/11] MR10058: Draft: ntdll: Spawn main thread as a separate thread.
This makes sure macOS main thread is a Wine thread as in https://gitlab.winehq.org/wine/wine/-/merge_requests/9579, while keeping its event loop usable for GUI, and makes every other platform work the same. Their main thread is parked for now, but the idea is to use the poll loop for various unix side internal purposes, such as winewayland pipe polling, compositing, etc. On macOS the main loop would be used similarly. -- v4: ntdll: Use CFRunLoopRun to implement sched_run on macOS. ntdll: Spawn the application main thread as a separate thread. server: Initialize process tracing in get_startup_info. ntdll: Send process PEB / initial TEB in get_startup_info. ntdll: Call get_startup_info request even when it is empty. ntdll: Pass the startup info size to init_startup_info. server: Create the new process first thread in new_process request. server: Return new process thread handle from get_new_process_info. ntdll: Pass thread security descriptor to new_process request. ntdll: Pass the main thread flags in the new_process request. server: Pass a thread flags parameter to create_thread. https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
From: Rémi Bernon <rbernon@codeweavers.com> --- server/request.c | 2 +- server/thread.c | 12 +++++++----- server/thread.h | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/server/request.c b/server/request.c index 432a5918892..89bba1d9faa 100644 --- a/server/request.c +++ b/server/request.c @@ -563,7 +563,7 @@ static void master_socket_poll_event( struct fd *fd, int event ) fcntl( client, F_SETFL, O_NONBLOCK ); if ((process = create_process( client, NULL, 0, NULL, NULL, NULL, 0, NULL ))) { - create_thread( -1, process, NULL ); + create_thread( -1, process, 0, NULL ); release_object( process ); } } diff --git a/server/thread.c b/server/thread.c index 3aed496450a..ad59361e53b 100644 --- a/server/thread.c +++ b/server/thread.c @@ -501,7 +501,8 @@ static struct context *create_thread_context( struct thread *thread ) /* create a new thread */ -struct thread *create_thread( int fd, struct process *process, const struct security_descriptor *sd ) +struct thread *create_thread( int fd, struct process *process, unsigned int flags, + const struct security_descriptor *sd ) { struct desktop *desktop; struct thread *thread; @@ -578,6 +579,10 @@ struct thread *create_thread( int fd, struct process *process, const struct secu set_fd_events( thread->request_fd, POLLIN ); /* start listening to events */ add_process_thread( thread->process, thread ); + + if (flags & THREAD_CREATE_FLAGS_CREATE_SUSPENDED) thread->suspend++; + thread->dbg_hidden = !!(flags & THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER); + thread->bypass_proc_suspend = !!(flags & THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE); return thread; error: @@ -1696,12 +1701,9 @@ DECL_HANDLER(new_thread) goto done; } - if ((thread = create_thread( request_fd, process, sd ))) + if ((thread = create_thread( request_fd, process, req->flags, sd ))) { thread->system_regs = current->system_regs; - if (req->flags & THREAD_CREATE_FLAGS_CREATE_SUSPENDED) thread->suspend++; - thread->dbg_hidden = !!(req->flags & THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER); - thread->bypass_proc_suspend = !!(req->flags & THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE); reply->tid = get_thread_id( thread ); if ((reply->handle = alloc_handle_no_access_check( current->process, thread, req->access, objattr->attributes ))) diff --git a/server/thread.h b/server/thread.h index 77ea355483d..317979fc6cd 100644 --- a/server/thread.h +++ b/server/thread.h @@ -105,7 +105,7 @@ extern struct thread *current; /* thread functions */ -extern struct thread *create_thread( int fd, struct process *process, +extern struct thread *create_thread( int fd, struct process *process, unsigned int flags, const struct security_descriptor *sd ); extern struct thread *get_thread_from_id( thread_id_t id ); extern struct thread *get_thread_from_handle( obj_handle_t handle, unsigned int access ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/ntdll/unix/process.c | 3 ++- server/process.c | 11 ++++++----- server/process.h | 1 + server/protocol.def | 3 ++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/dlls/ntdll/unix/process.c b/dlls/ntdll/unix/process.c index 3a47d5a950a..758a5bf9d35 100644 --- a/dlls/ntdll/unix/process.c +++ b/dlls/ntdll/unix/process.c @@ -823,7 +823,8 @@ NTSTATUS WINAPI NtCreateUserProcess( HANDLE *process_handle_ptr, HANDLE *thread_ req->token = wine_server_obj_handle( token ); req->debug = wine_server_obj_handle( debug ); req->parent_process = wine_server_obj_handle( parent ); - req->flags = process_flags; + req->process_flags = process_flags; + req->thread_flags = thread_flags; req->socket_fd = socketfd[1]; req->access = process_access; req->machine = machine; diff --git a/server/process.c b/server/process.c index b30c835f74f..50cd140c8c6 100644 --- a/server/process.c +++ b/server/process.c @@ -1202,7 +1202,7 @@ DECL_HANDLER(new_process) /* If a job further in the job chain does not permit breakaway process creation * succeeds and the process which is trying to breakaway is assigned to that job. */ - if (parent->job && (req->flags & PROCESS_CREATE_FLAGS_BREAKAWAY) && + if (parent->job && (req->process_flags & PROCESS_CREATE_FLAGS_BREAKAWAY) && !(parent->job->limit_flags & (JOB_OBJECT_LIMIT_BREAKAWAY_OK | JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK))) { set_error( STATUS_ACCESS_DENIED ); @@ -1320,18 +1320,19 @@ DECL_HANDLER(new_process) goto done; } - if (!(process = create_process( socket_fd, parent, req->flags, info->data, sd, + if (!(process = create_process( socket_fd, parent, req->process_flags, info->data, sd, handles, req->handles_size / sizeof(*handles), token ))) goto done; process->machine = req->machine; process->startup_info = (struct startup_info *)grab_object( info ); + process->thread_flags = req->thread_flags; job = parent->job; while (job) { if (!(job->limit_flags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK) - && !(req->flags & PROCESS_CREATE_FLAGS_BREAKAWAY + && !(req->process_flags & PROCESS_CREATE_FLAGS_BREAKAWAY && job->limit_flags & JOB_OBJECT_LIMIT_BREAKAWAY_OK)) { add_job_process( job, process ); @@ -1361,7 +1362,7 @@ DECL_HANDLER(new_process) info->data->console = duplicate_handle( parent, info->data->console, process, 0, 0, DUPLICATE_SAME_ACCESS ); - if (!(req->flags & PROCESS_CREATE_FLAGS_INHERIT_HANDLES) && info->data->console != 1) + if (!(req->process_flags & PROCESS_CREATE_FLAGS_INHERIT_HANDLES) && info->data->console != 1) { info->data->hstdin = duplicate_handle( parent, info->data->hstdin, process, 0, 0, DUPLICATE_SAME_ACCESS | DUPLICATE_SAME_ATTRIBUTES ); @@ -1378,7 +1379,7 @@ DECL_HANDLER(new_process) if (debug_obj) { process->debug_obj = debug_obj; - process->debug_children = !(req->flags & PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT); + process->debug_children = !(req->process_flags & PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT); } else if (parent->debug_children) { diff --git a/server/process.h b/server/process.h index 5c136fb5103..5c49daab3f0 100644 --- a/server/process.h +++ b/server/process.h @@ -53,6 +53,7 @@ struct process int unix_pid; /* Unix pid for final SIGKILL */ int exit_code; /* process exit code */ int running_threads; /* number of threads running in this process */ + unsigned int thread_flags; /* first thread flags */ timeout_t start_time; /* absolute time at process start */ timeout_t end_time; /* absolute time at process end */ affinity_t affinity; /* process affinity mask */ diff --git a/server/protocol.def b/server/protocol.def index 0ac64297026..36e6230ebf6 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1087,7 +1087,8 @@ typedef volatile struct obj_handle_t token; /* process token */ obj_handle_t debug; /* process debug object */ obj_handle_t parent_process; /* parent process */ - unsigned int flags; /* process creation flags */ + unsigned int process_flags; /* process creation flags */ + unsigned int thread_flags; /* process creation flags */ int socket_fd; /* file descriptor for process socket */ unsigned int access; /* access rights for process object */ unsigned short machine; /* architecture that the new process will use */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/ntdll/unix/process.c | 12 ++++++++++-- server/process.c | 24 +++++++++++++++++++++++- server/process.h | 1 + server/protocol.def | 2 ++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/dlls/ntdll/unix/process.c b/dlls/ntdll/unix/process.c index 758a5bf9d35..005b90eb05e 100644 --- a/dlls/ntdll/unix/process.c +++ b/dlls/ntdll/unix/process.c @@ -690,8 +690,8 @@ NTSTATUS WINAPI NtCreateUserProcess( HANDLE *process_handle_ptr, HANDLE *thread_ unsigned int status; BOOL success = FALSE; HANDLE file_handle, process_info = 0, process_handle = 0, thread_handle = 0; - struct object_attributes *objattr; - data_size_t attr_len; + struct object_attributes *objattr, *thread_objattr; + data_size_t attr_len, thread_attr_len; char *winedebug = NULL; char *unix_name = NULL; struct startup_info_data *startup_info = NULL; @@ -782,6 +782,11 @@ NTSTATUS WINAPI NtCreateUserProcess( HANDLE *process_handle_ptr, HANDLE *thread_ env_size = get_env_size( params, &winedebug ); if ((status = alloc_object_attributes( process_attr, &objattr, &attr_len ))) goto done; + if ((status = alloc_object_attributes( thread_attr, &thread_objattr, &thread_attr_len ))) + { + free( thread_objattr ); + goto done; + } if ((status = alloc_handle_list( handles_attr, &handles, &handles_size ))) { @@ -831,9 +836,11 @@ NTSTATUS WINAPI NtCreateUserProcess( HANDLE *process_handle_ptr, HANDLE *thread_ req->info_size = startup_info_size; req->handles_size = handles_size; req->jobs_size = jobs_size; + req->sd_len = thread_objattr ? thread_objattr->sd_len : 0; wine_server_add_data( req, objattr, attr_len ); wine_server_add_data( req, handles, handles_size ); wine_server_add_data( req, jobs, jobs_size ); + wine_server_add_data( req, thread_objattr + 1, req->sd_len ); wine_server_add_data( req, startup_info, startup_info_size ); wine_server_add_data( req, params->Environment, env_size ); if (!(status = wine_server_call( req ))) @@ -845,6 +852,7 @@ NTSTATUS WINAPI NtCreateUserProcess( HANDLE *process_handle_ptr, HANDLE *thread_ } SERVER_END_REQ; close( socketfd[1] ); + free( thread_objattr ); free( objattr ); free( handles ); free( jobs ); diff --git a/server/process.c b/server/process.c index 50cd140c8c6..5e44e6faaa6 100644 --- a/server/process.c +++ b/server/process.c @@ -675,6 +675,8 @@ struct process *create_process( int fd, struct process *parent, unsigned int fla process->unix_pid = -1; process->exit_code = STILL_ACTIVE; process->running_threads = 0; + process->thread_flags = 0; + process->thread_sd = NULL; process->priority = PROCESS_PRIOCLASS_NORMAL; process->base_priority = 8; process->disable_boost = 0; @@ -801,6 +803,7 @@ static void process_destroy( struct object *obj ) free( process->rawinput_devices ); free( process->dir_cache ); free( process->image ); + free( process->thread_sd ); } /* dump a process on stdout for debugging purposes */ @@ -1152,7 +1155,7 @@ DECL_HANDLER(new_process) struct startup_info *info; const void *info_ptr; struct unicode_str name, desktop_path = {0}; - const struct security_descriptor *sd; + const struct security_descriptor *sd, *thread_sd = NULL; const struct object_attributes *objattr = get_req_object_attributes( &sd, &name, NULL ); struct process *process = NULL; struct token *token = NULL; @@ -1255,6 +1258,24 @@ DECL_HANDLER(new_process) info_ptr = (const char *)info_ptr + req->jobs_size; info->data_size -= req->jobs_size; } + if (req->sd_len > info->data_size) + { + set_error( STATUS_INVALID_PARAMETER ); + close( socket_fd ); + goto done; + } + if (req->sd_len) + { + thread_sd = info_ptr; + info_ptr = (const char *)thread_sd + req->sd_len; + info->data_size -= req->sd_len; + } + if (thread_sd && !sd_is_valid( thread_sd, req->sd_len )) + { + set_error( STATUS_INVALID_PARAMETER ); + close( socket_fd ); + goto done; + } job_handle_count = req->jobs_size / sizeof(*handles); for (i = 0; i < job_handle_count; ++i) @@ -1327,6 +1348,7 @@ DECL_HANDLER(new_process) process->machine = req->machine; process->startup_info = (struct startup_info *)grab_object( info ); process->thread_flags = req->thread_flags; + if (thread_sd && !(process->thread_sd = memdup( thread_sd, req->sd_len ))) goto done; job = parent->job; while (job) diff --git a/server/process.h b/server/process.h index 5c49daab3f0..b2292440c8b 100644 --- a/server/process.h +++ b/server/process.h @@ -54,6 +54,7 @@ struct process int exit_code; /* process exit code */ int running_threads; /* number of threads running in this process */ unsigned int thread_flags; /* first thread flags */ + struct security_descriptor *thread_sd; /* first thread security descriptor */ timeout_t start_time; /* absolute time at process start */ timeout_t end_time; /* absolute time at process end */ affinity_t affinity; /* process affinity mask */ diff --git a/server/protocol.def b/server/protocol.def index 36e6230ebf6..64e91bccc87 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1095,9 +1095,11 @@ typedef volatile struct data_size_t info_size; /* size of startup info */ data_size_t handles_size; /* length of explicit handles list */ data_size_t jobs_size; /* length of jobs list */ + data_size_t sd_len; /* size of security descriptor */ VARARG(objattr,object_attributes); /* object attributes */ VARARG(handles,uints,handles_size); /* handles list */ VARARG(jobs,uints,jobs_size); /* jobs list */ + VARARG(sd,security_descriptor,sd_len); /* thread security descriptor */ VARARG(info,startup_info,info_size); /* startup information */ VARARG(env,unicode_str); /* environment for new process */ @REPLY -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/ntdll/unix/process.c | 13 ++++++------- server/process.c | 18 +++++++++++++----- server/protocol.def | 4 ++++ server/thread.c | 1 + 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/dlls/ntdll/unix/process.c b/dlls/ntdll/unix/process.c index 005b90eb05e..8ebb5a3e376 100644 --- a/dlls/ntdll/unix/process.c +++ b/dlls/ntdll/unix/process.c @@ -877,15 +877,10 @@ NTSTATUS WINAPI NtCreateUserProcess( HANDLE *process_handle_ptr, HANDLE *thread_ SERVER_START_REQ( new_thread ) { req->process = wine_server_obj_handle( process_handle ); - req->access = thread_access; req->flags = thread_flags; req->request_fd = -1; wine_server_add_data( req, objattr, attr_len ); - if (!(status = wine_server_call( req ))) - { - thread_handle = wine_server_ptr_handle( reply->handle ); - id.UniqueThread = ULongToHandle( reply->tid ); - } + status = wine_server_call( req ); } SERVER_END_REQ; free( objattr ); @@ -903,10 +898,14 @@ NTSTATUS WINAPI NtCreateUserProcess( HANDLE *process_handle_ptr, HANDLE *thread_ NtWaitForSingleObject( process_info, FALSE, NULL ); SERVER_START_REQ( get_new_process_info ) { - req->info = wine_server_obj_handle( process_info ); + req->info = wine_server_obj_handle( process_info ); + req->access = thread_access; + req->attributes = thread_attr ? thread_attr->Attributes : 0; wine_server_call( req ); success = reply->success; status = reply->exit_code; + thread_handle = wine_server_ptr_handle( reply->handle ); + id.UniqueThread = ULongToHandle( reply->tid ); } SERVER_END_REQ; diff --git a/server/process.c b/server/process.c index 5e44e6faaa6..78727080e9d 100644 --- a/server/process.c +++ b/server/process.c @@ -1431,14 +1431,22 @@ DECL_HANDLER(new_process) DECL_HANDLER(get_new_process_info) { struct startup_info *info; + struct thread *thread; - if ((info = (struct startup_info *)get_handle_obj( current->process, req->info, - 0, &startup_info_ops ))) + if (!(info = (struct startup_info *)get_handle_obj( current->process, req->info, 0, &startup_info_ops ))) return; + if (!(thread = get_process_first_thread( info->process ))) { - reply->success = is_process_init_done( info->process ); - reply->exit_code = info->process->exit_code; - release_object( info ); + set_error( STATUS_INVALID_PARAMETER ); + goto done; } + + reply->tid = get_thread_id( thread ); + reply->handle = alloc_handle_no_access_check( current->process, thread, req->access, req->attributes ); + reply->success = is_process_init_done( info->process ); + reply->exit_code = info->process->exit_code; + +done: + release_object( info ); } /* Itererate processes using global process list */ diff --git a/server/protocol.def b/server/protocol.def index 64e91bccc87..306031ea289 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1112,7 +1112,11 @@ typedef volatile struct /* Retrieve information about a newly started process */ @REQ(get_new_process_info) obj_handle_t info; /* info handle returned from new_process_request */ + unsigned int access; /* wanted handle access rights */ + unsigned int attributes; /* handle object attributes */ @REPLY + thread_id_t tid; /* main thread id */ + obj_handle_t handle; /* main thread handle (in the current process) */ int success; /* did the process start successfully? */ int exit_code; /* process exit code if failed */ @END diff --git a/server/thread.c b/server/thread.c index ad59361e53b..472fd85790a 100644 --- a/server/thread.c +++ b/server/thread.c @@ -1705,6 +1705,7 @@ DECL_HANDLER(new_thread) { thread->system_regs = current->system_regs; reply->tid = get_thread_id( thread ); + if (request_fd == -1) goto done; /* thread handle will be returned from get_new_process_info */ if ((reply->handle = alloc_handle_no_access_check( current->process, thread, req->access, objattr->attributes ))) { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/ntdll/unix/process.c | 14 -------------- server/process.c | 6 +++++- server/thread.c | 15 +++------------ 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/dlls/ntdll/unix/process.c b/dlls/ntdll/unix/process.c index 8ebb5a3e376..2fe507d5f53 100644 --- a/dlls/ntdll/unix/process.c +++ b/dlls/ntdll/unix/process.c @@ -872,20 +872,6 @@ NTSTATUS WINAPI NtCreateUserProcess( HANDLE *process_handle_ptr, HANDLE *thread_ goto done; } - if ((status = alloc_object_attributes( thread_attr, &objattr, &attr_len ))) goto done; - - SERVER_START_REQ( new_thread ) - { - req->process = wine_server_obj_handle( process_handle ); - req->flags = thread_flags; - req->request_fd = -1; - wine_server_add_data( req, objattr, attr_len ); - status = wine_server_call( req ); - } - SERVER_END_REQ; - free( objattr ); - if (status) goto done; - /* create the child process */ if ((status = spawn_process( params, socketfd[0], unixdir, winedebug, &pe_info ))) goto done; diff --git a/server/process.c b/server/process.c index 78727080e9d..4c778d800db 100644 --- a/server/process.c +++ b/server/process.c @@ -1161,7 +1161,7 @@ DECL_HANDLER(new_process) struct token *token = NULL; struct debug_obj *debug_obj = NULL; struct process *parent; - struct thread *parent_thread = current; + struct thread *thread, *parent_thread = current; int socket_fd = thread_get_inflight_fd( current, req->socket_fd ); const obj_handle_t *handles = NULL; const obj_handle_t *job_handles = NULL; @@ -1415,6 +1415,10 @@ DECL_HANDLER(new_process) info->data->process_group_id = process->group_id; info->process = (struct process *)grab_object( process ); + + if (!(thread = create_thread( -1, process, process->thread_flags, process->thread_sd ))) goto done; + thread->system_regs = current->system_regs; + reply->info = alloc_handle( current->process, info, SYNCHRONIZE, 0 ); reply->pid = get_process_id( process ); reply->handle = alloc_handle_no_access_check( current->process, process, req->access, objattr->attributes ); diff --git a/server/thread.c b/server/thread.c index 472fd85790a..54b1b5e2c00 100644 --- a/server/thread.c +++ b/server/thread.c @@ -1676,17 +1676,9 @@ DECL_HANDLER(new_thread) if (process != current->process) { - if (request_fd != -1) /* can't create a request fd in a different process */ - { - close( request_fd ); - set_error( STATUS_INVALID_PARAMETER ); - goto done; - } - if (process->running_threads) /* only the initial thread can be created in another process */ - { - set_error( STATUS_ACCESS_DENIED ); - goto done; - } + if (request_fd != -1) close( request_fd ); + set_error( STATUS_ACCESS_DENIED ); + goto done; } else if (request_fd == -1 || fcntl( request_fd, F_SETFL, O_NONBLOCK ) == -1) { @@ -1705,7 +1697,6 @@ DECL_HANDLER(new_thread) { thread->system_regs = current->system_regs; reply->tid = get_thread_id( thread ); - if (request_fd == -1) goto done; /* thread handle will be returned from get_new_process_info */ if ((reply->handle = alloc_handle_no_access_check( current->process, thread, req->access, objattr->attributes ))) { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/ntdll/unix/env.c | 11 +++++------ dlls/ntdll/unix/loader.c | 5 +++-- dlls/ntdll/unix/unix_private.h | 3 +-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/dlls/ntdll/unix/env.c b/dlls/ntdll/unix/env.c index 0fb7c59e9d9..92fbc2dc7d5 100644 --- a/dlls/ntdll/unix/env.c +++ b/dlls/ntdll/unix/env.c @@ -67,7 +67,6 @@ WINE_DEFAULT_DEBUG_CHANNEL(environ); PEB *peb = NULL; WOW_PEB *wow_peb = NULL; USHORT *uctable = NULL, *lctable = NULL; -SIZE_T startup_info_size = 0; BOOL is_prefix_bootstrap = FALSE; static const WCHAR bootstrapW[] = {'W','I','N','E','B','O','O','T','S','T','R','A','P','M','O','D','E'}; @@ -1998,29 +1997,29 @@ static RTL_USER_PROCESS_PARAMETERS *build_initial_params( void **module ) /************************************************************************* * init_startup_info */ -void init_startup_info(void) +void init_startup_info( SIZE_T info_size ) { WCHAR *src, *dst, *env; void *module = NULL; unsigned int status; - SIZE_T size, info_size, env_size, env_pos; + SIZE_T size, env_size, env_pos; RTL_USER_PROCESS_PARAMETERS *params = NULL; struct startup_info_data *info; UNICODE_STRING nt_name; USHORT machine; - if (!startup_info_size) + if (!info_size) { params = build_initial_params( &module ); init_peb( params, module ); return; } - info = malloc( startup_info_size ); + info = malloc( info_size ); SERVER_START_REQ( get_startup_info ) { - wine_server_set_reply( req, info, startup_info_size ); + wine_server_set_reply( req, info, info_size ); status = wine_server_call( req ); machine = reply->machine; info_size = reply->info_size; diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index 28fc09cd354..20b050c3999 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -1857,14 +1857,15 @@ static ULONG_PTR get_image_address(void) static void start_main_thread(void) { TEB *teb = virtual_alloc_first_teb(); + SIZE_T info_size; signal_init_threading(); dbg_init(); - startup_info_size = server_init_process(); + info_size = server_init_process(); virtual_map_user_shared_data(); init_cpu_info(); init_files(); - init_startup_info(); + init_startup_info( info_size ); *(ULONG_PTR *)&peb->CloudFileFlags = get_image_address(); set_load_order_app_name( main_wargv[0] ); init_thread_stack( teb, 0, 0, 0 ); diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h index 10f6bb2c63c..c547b334fa5 100644 --- a/dlls/ntdll/unix/unix_private.h +++ b/dlls/ntdll/unix/unix_private.h @@ -188,7 +188,6 @@ extern pthread_key_t teb_key; extern PEB *peb; extern USHORT *uctable; extern USHORT *lctable; -extern SIZE_T startup_info_size; extern BOOL is_prefix_bootstrap; extern int main_argc; extern char **main_argv; @@ -206,7 +205,7 @@ extern struct _KUSER_SHARED_DATA *user_shared_data; extern ULONG process_cookie; extern void init_environment(void); -extern void init_startup_info(void); +extern void init_startup_info( SIZE_T info_size ); extern void *create_startup_info( const UNICODE_STRING *nt_image, ULONG process_flags, const RTL_USER_PROCESS_PARAMETERS *params, const struct pe_image_info *pe_info, DWORD *info_size ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/ntdll/unix/env.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/dlls/ntdll/unix/env.c b/dlls/ntdll/unix/env.c index 92fbc2dc7d5..48960d8890d 100644 --- a/dlls/ntdll/unix/env.c +++ b/dlls/ntdll/unix/env.c @@ -2008,18 +2008,11 @@ void init_startup_info( SIZE_T info_size ) UNICODE_STRING nt_name; USHORT machine; - if (!info_size) - { - params = build_initial_params( &module ); - init_peb( params, module ); - return; - } - - info = malloc( info_size ); + info = info_size ? malloc( info_size ) : NULL; SERVER_START_REQ( get_startup_info ) { - wine_server_set_reply( req, info, info_size ); + if (info) wine_server_set_reply( req, info, info_size ); status = wine_server_call( req ); machine = reply->machine; info_size = reply->info_size; @@ -2028,6 +2021,13 @@ void init_startup_info( SIZE_T info_size ) SERVER_END_REQ; assert( !status ); + if (!info) + { + params = build_initial_params( &module ); + init_peb( params, module ); + return; + } + env = malloc( env_size * sizeof(WCHAR) ); memcpy( env, (char *)info + info_size, env_size * sizeof(WCHAR) ); env_pos = env_size - 1; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/ntdll/unix/env.c | 4 ++++ dlls/ntdll/unix/server.c | 6 ------ server/process.c | 6 +++--- server/protocol.def | 4 ++-- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/dlls/ntdll/unix/env.c b/dlls/ntdll/unix/env.c index 48960d8890d..c8ba6cf4371 100644 --- a/dlls/ntdll/unix/env.c +++ b/dlls/ntdll/unix/env.c @@ -2004,6 +2004,7 @@ void init_startup_info( SIZE_T info_size ) unsigned int status; SIZE_T size, env_size, env_pos; RTL_USER_PROCESS_PARAMETERS *params = NULL; + const TEB64 *teb64 = NtCurrentTeb64(); struct startup_info_data *info; UNICODE_STRING nt_name; USHORT machine; @@ -2012,6 +2013,9 @@ void init_startup_info( SIZE_T info_size ) SERVER_START_REQ( get_startup_info ) { + /* always send the native PEB / TEB */ + req->peb = teb64 ? teb64->Peb : wine_server_client_ptr( peb ); + req->teb = wine_server_client_ptr( teb64 ? (void *)teb64 : NtCurrentTeb() ); if (info) wine_server_set_reply( req, info, info_size ); status = wine_server_call( req ); machine = reply->machine; diff --git a/dlls/ntdll/unix/server.c b/dlls/ntdll/unix/server.c index 10167d24215..469b2c8b130 100644 --- a/dlls/ntdll/unix/server.c +++ b/dlls/ntdll/unix/server.c @@ -1731,7 +1731,6 @@ size_t server_init_process(void) */ void server_init_process_done(void) { - void *teb; unsigned int status; int suspend; FILE_FS_DEVICE_INFORMATION info; @@ -1752,14 +1751,9 @@ void server_init_process_done(void) thread_data->syscall_table = KeServiceDescriptorTable; thread_data->syscall_trace = TRACE_ON(syscall); - /* always send the native TEB */ - if (!(teb = NtCurrentTeb64())) teb = NtCurrentTeb(); - /* Signal the parent process to continue */ SERVER_START_REQ( init_process_done ) { - req->teb = wine_server_client_ptr( teb ); - req->peb = NtCurrentTeb64() ? NtCurrentTeb64()->Peb : wine_server_client_ptr( peb ); status = wine_server_call( req ); suspend = reply->suspend; } diff --git a/server/process.c b/server/process.c index 4c778d800db..bda3223a573 100644 --- a/server/process.c +++ b/server/process.c @@ -1494,6 +1494,9 @@ DECL_HANDLER(get_startup_info) struct startup_info *info = process->startup_info; data_size_t size; + current->teb = req->teb; + process->peb = req->peb; + if (!info) return; /* we return the data directly without making a copy so this can only be called once */ @@ -1517,9 +1520,6 @@ DECL_HANDLER(init_process_done) return; } - current->teb = req->teb; - process->peb = req->peb; - process->start_time = current_time; init_process_tracing( process ); diff --git a/server/protocol.def b/server/protocol.def index 306031ea289..3d5c14bdc03 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1137,6 +1137,8 @@ typedef volatile struct /* Retrieve the new process startup info */ @REQ(get_startup_info) + client_ptr_t teb; /* TEB of new thread (in process address space) */ + client_ptr_t peb; /* PEB of new process (in process address space) */ @REPLY data_size_t info_size; /* size of startup info */ unsigned short machine; /* architecture for the new process */ @@ -1147,8 +1149,6 @@ typedef volatile struct /* Signal the end of the process initialization */ @REQ(init_process_done) - client_ptr_t teb; /* TEB of new thread (in process address space) */ - client_ptr_t peb; /* PEB of new process (in process address space) */ @REPLY int suspend; /* is process suspended? */ @END -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/ntdll/unix/server.c | 7 +++---- server/process.c | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/dlls/ntdll/unix/server.c b/dlls/ntdll/unix/server.c index 469b2c8b130..5ccd649fdc8 100644 --- a/dlls/ntdll/unix/server.c +++ b/dlls/ntdll/unix/server.c @@ -1656,6 +1656,9 @@ size_t server_init_process(void) /* work around Ubuntu's ptrace breakage */ if (server_pid != -1) prctl( 0x59616d61 /* PR_SET_PTRACER */, server_pid ); #endif +#ifdef __APPLE__ + send_server_task_port(); +#endif /* ignore SIGPIPE so that we get an EPIPE error instead */ sig_act.sa_handler = SIG_IGN; @@ -1740,10 +1743,6 @@ void server_init_process_done(void) chdir( "/" ); close( initial_cwd ); -#ifdef __APPLE__ - send_server_task_port(); -#endif - /* Install signal handlers; this cannot be done earlier, since we cannot * send exceptions to the debugger before the create process event that * is sent by init_process_done */ diff --git a/server/process.c b/server/process.c index bda3223a573..e54a8f13df8 100644 --- a/server/process.c +++ b/server/process.c @@ -1496,6 +1496,7 @@ DECL_HANDLER(get_startup_info) current->teb = req->teb; process->peb = req->peb; + init_process_tracing( process ); if (!info) return; @@ -1521,8 +1522,6 @@ DECL_HANDLER(init_process_done) } process->start_time = current_time; - - init_process_tracing( process ); generate_startup_debug_events( process ); set_process_startup_state( process, STARTUP_DONE ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/ntdll/Makefile.in | 1 + dlls/ntdll/unix/env.c | 2 ++ dlls/ntdll/unix/loader.c | 2 +- dlls/ntdll/unix/sched.c | 35 +++++++++++++++++++++++++++++++++ dlls/ntdll/unix/server.c | 24 ++++++++++------------- dlls/ntdll/unix/thread.c | 6 +++++- dlls/ntdll/unix/unix_private.h | 5 ++++- server/process.c | 31 ++++++++++++++++++++++------- server/process.h | 3 +++ server/protocol.def | 11 ++--------- server/request.c | 2 +- server/thread.c | 36 +++++++++++++++++++++++----------- server/thread.h | 5 +++-- 13 files changed, 116 insertions(+), 47 deletions(-) create mode 100644 dlls/ntdll/unix/sched.c diff --git a/dlls/ntdll/Makefile.in b/dlls/ntdll/Makefile.in index ad5c3bdc60f..5515e836129 100644 --- a/dlls/ntdll/Makefile.in +++ b/dlls/ntdll/Makefile.in @@ -56,6 +56,7 @@ SOURCES = \ unix/loadorder.c \ unix/process.c \ unix/registry.c \ + unix/sched.c \ unix/security.c \ unix/serial.c \ unix/server.c \ diff --git a/dlls/ntdll/unix/env.c b/dlls/ntdll/unix/env.c index c8ba6cf4371..39702e5e0a4 100644 --- a/dlls/ntdll/unix/env.c +++ b/dlls/ntdll/unix/env.c @@ -68,6 +68,7 @@ PEB *peb = NULL; WOW_PEB *wow_peb = NULL; USHORT *uctable = NULL, *lctable = NULL; BOOL is_prefix_bootstrap = FALSE; +LONG init_redirect = FALSE; static const WCHAR bootstrapW[] = {'W','I','N','E','B','O','O','T','S','T','R','A','P','M','O','D','E'}; @@ -1990,6 +1991,7 @@ static RTL_USER_PROCESS_PARAMETERS *build_initial_params( void **module ) get_initial_console( params ); + init_redirect = NtCurrentTeb64() && NtCurrentTeb64()->TlsSlots[WOW64_TLS_FILESYSREDIR]; return params; } diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index 20b050c3999..bfa9121e813 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -1873,7 +1873,7 @@ static void start_main_thread(void) load_ntdll(); load_wow64_ntdll( main_image_info.Machine ); load_apiset_dll(); - server_init_process_done(); + server_start_main_thread(); } #ifdef __ANDROID__ diff --git a/dlls/ntdll/unix/sched.c b/dlls/ntdll/unix/sched.c new file mode 100644 index 00000000000..583f7dee1fe --- /dev/null +++ b/dlls/ntdll/unix/sched.c @@ -0,0 +1,35 @@ +/* + * Copyright 2026 Rémi Bernon for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include <stddef.h> +#include <stdarg.h> + +#include <poll.h> + +#include "unix_private.h" + +void sched_run(void) +{ + for (;;) poll( NULL, 0, -1 ); +} diff --git a/dlls/ntdll/unix/server.c b/dlls/ntdll/unix/server.c index 5ccd649fdc8..0da12a920de 100644 --- a/dlls/ntdll/unix/server.c +++ b/dlls/ntdll/unix/server.c @@ -1668,7 +1668,7 @@ size_t server_init_process(void) reply_pipe = init_thread_pipe(); - SERVER_START_REQ( init_first_thread ) + SERVER_START_REQ( init_process ) { req->unix_pid = getpid(); req->unix_tid = get_unix_tid(); @@ -1695,7 +1695,7 @@ size_t server_init_process(void) SERVER_END_REQ; close( reply_pipe ); - if (ret) server_protocol_error( "init_first_thread failed with status %x\n", ret ); + if (ret) server_protocol_error( "init_process failed with status %x\n", ret ); if (!supported_machines_count) fatal_error( "'%s' is a 64-bit installation, it cannot be used with a 32-bit wineserver.\n", @@ -1730,14 +1730,14 @@ size_t server_init_process(void) /*********************************************************************** - * server_init_process_done + * server_start_main_thread */ -void server_init_process_done(void) +void server_start_main_thread(void) { unsigned int status; - int suspend; FILE_FS_DEVICE_INFORMATION info; struct ntdll_thread_data *thread_data = ntdll_get_thread_data(); + HANDLE handle; if (!get_device_info( initial_cwd, &info ) && (info.Characteristics & FILE_REMOVABLE_MEDIA)) chdir( "/" ); @@ -1750,16 +1750,12 @@ void server_init_process_done(void) thread_data->syscall_table = KeServiceDescriptorTable; thread_data->syscall_trace = TRACE_ON(syscall); - /* Signal the parent process to continue */ - SERVER_START_REQ( init_process_done ) - { - status = wine_server_call( req ); - suspend = reply->suspend; - } - SERVER_END_REQ; - + status = NtCreateThreadEx( &handle, THREAD_ALL_ACCESS, NULL, NtCurrentProcess(), + main_image_info.TransferAddress, peb, 0, 0, 0, 0, NULL ); assert( !status ); - signal_start_thread( main_image_info.TransferAddress, peb, suspend, NtCurrentTeb() ); + NtClose( handle ); + + sched_run(); } diff --git a/dlls/ntdll/unix/thread.c b/dlls/ntdll/unix/thread.c index cba34ed8ca7..1c458c61dae 100644 --- a/dlls/ntdll/unix/thread.c +++ b/dlls/ntdll/unix/thread.c @@ -84,7 +84,7 @@ WINE_DECLARE_DEBUG_CHANNEL(threadname); pthread_key_t teb_key = 0; -static LONG nb_threads = 1; +static LONG nb_threads = 0; static inline int get_unix_exit_code( NTSTATUS status ) { @@ -1436,6 +1436,10 @@ NTSTATUS WINAPI NtCreateThreadEx( HANDLE *handle, ACCESS_MASK access, OBJECT_ATT wow_teb->SkipThreadAttach = teb->SkipThreadAttach; wow_teb->SkipLoaderInit = teb->SkipLoaderInit; } +#ifndef _WIN64 + if (InterlockedExchange( &init_redirect, FALSE ) && teb->GdiBatchCount) + ((TEB64 *)teb->GdiBatchCount)->TlsSlots[WOW64_TLS_FILESYSREDIR] = TRUE; +#endif thread_data = (struct ntdll_thread_data *)&teb->GdiTebBatch; thread_data->request_fd = request_pipe[1]; diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h index c547b334fa5..ff705312515 100644 --- a/dlls/ntdll/unix/unix_private.h +++ b/dlls/ntdll/unix/unix_private.h @@ -189,6 +189,7 @@ extern PEB *peb; extern USHORT *uctable; extern USHORT *lctable; extern BOOL is_prefix_bootstrap; +extern LONG init_redirect; extern int main_argc; extern char **main_argv; extern WCHAR **main_wargv; @@ -236,7 +237,7 @@ extern int server_get_unix_fd( HANDLE handle, unsigned int wanted_access, int *u extern int wine_server_receive_fd( obj_handle_t *handle ); extern void process_exit_wrapper( int status ) DECLSPEC_NORETURN; extern size_t server_init_process(void); -extern void server_init_process_done(void); +extern void server_start_main_thread(void); extern void server_init_thread( void *entry_point, BOOL *suspend ); extern int server_pipe( int fd[2] ); @@ -387,6 +388,8 @@ extern NTSTATUS wow64_wine_server_handle_to_fd( void *args ); extern NTSTATUS wow64_wine_spawnvp( void *args ); #endif +extern void sched_run(void); + extern void dbg_init(void); extern void close_inproc_sync( HANDLE handle ); diff --git a/server/process.c b/server/process.c index e54a8f13df8..77755f69bf0 100644 --- a/server/process.c +++ b/server/process.c @@ -665,6 +665,7 @@ struct process *create_process( int fd, struct process *parent, unsigned int fla } process->sync = NULL; process->parent_id = 0; + process->sched_thread = NULL; process->debug_obj = NULL; process->debug_event = NULL; process->handles = NULL; @@ -773,6 +774,15 @@ data_size_t get_process_startup_info_size( struct process *process ) return info->data_size; } +struct security_descriptor *get_first_thread_info( struct process *process, unsigned int *flags ) +{ + struct startup_info *info = process->startup_info; + + if (!info) return NULL; + *flags = process->thread_flags; + return process->thread_sd; +} + /* destroy a process when its refcount is 0 */ static void process_destroy( struct object *obj ) { @@ -780,6 +790,7 @@ static void process_destroy( struct object *obj ) assert( obj->ops == &process_ops ); /* we can't have a thread remaining */ + assert( !process->sched_thread ); assert( list_empty( &process->thread_list )); assert( list_empty( &process->asyncs )); @@ -984,6 +995,11 @@ void kill_console_processes( struct thread *renderer, int exit_code ) /* a process has been killed (i.e. its last thread died) */ static void process_killed( struct process *process ) { + assert( process->sched_thread ); + kill_thread( process->sched_thread, 0 ); + release_object( process->sched_thread ); + process->sched_thread = NULL; + assert( list_empty( &process->thread_list )); process->end_time = current_time; close_process_desktop( process ); @@ -1009,6 +1025,8 @@ static void process_killed( struct process *process ) /* add a thread to a process running threads list */ void add_process_thread( struct process *process, struct thread *thread ) { + assert( process->sched_thread ); + list_add_tail( &process->thread_list, &thread->proc_entry ); if (!process->running_threads++) { @@ -1031,6 +1049,7 @@ void remove_process_thread( struct process *process, struct thread *thread ) { assert( process->running_threads > 0 ); assert( !list_empty( &process->thread_list )); + assert( process->sched_thread ); list_remove( &thread->proc_entry ); @@ -1416,7 +1435,7 @@ DECL_HANDLER(new_process) info->process = (struct process *)grab_object( process ); - if (!(thread = create_thread( -1, process, process->thread_flags, process->thread_sd ))) goto done; + if (!(thread = create_thread( -1, process, 0, NULL ))) goto done; thread->system_regs = current->system_regs; reply->info = alloc_handle( current->process, info, SYNCHRONIZE, 0 ); @@ -1511,24 +1530,19 @@ DECL_HANDLER(get_startup_info) } /* signal the end of the process initialization */ -DECL_HANDLER(init_process_done) +void init_process_done( struct process *process ) { - struct process *process = current->process; - if (is_process_init_done(process)) { set_error( STATUS_INVALID_PARAMETER ); return; } - process->start_time = current_time; - generate_startup_debug_events( process ); set_process_startup_state( process, STARTUP_DONE ); if (process->image_info.subsystem != IMAGE_SUBSYSTEM_WINDOWS_CUI) process->idle_event = create_event( NULL, NULL, 0, 1, 0, NULL ); if (process->debug_obj) set_process_debug_flag( process, 1 ); - reply->suspend = (current->suspend || process->suspend); } /* open a handle to a process */ @@ -1706,6 +1720,7 @@ void set_process_base_priority( struct process *process, int base_priority ) process->base_priority = base_priority; + if ((thread = process->sched_thread)) set_thread_base_priority( thread, thread->base_priority ); LIST_FOR_EACH_ENTRY( thread, &process->thread_list, struct thread, proc_entry ) { set_thread_base_priority( thread, thread->base_priority ); @@ -1751,6 +1766,7 @@ static void set_process_disable_boost( struct process *process, int disable_boos process->disable_boost = disable_boost; + if ((thread = process->sched_thread)) set_thread_disable_boost( thread, disable_boost ); LIST_FOR_EACH_ENTRY( thread, &process->thread_list, struct thread, proc_entry ) { set_thread_disable_boost( thread, disable_boost ); @@ -1769,6 +1785,7 @@ static void set_process_affinity( struct process *process, affinity_t affinity ) process->affinity = affinity; + if ((thread = process->sched_thread)) set_thread_affinity( thread, affinity ); LIST_FOR_EACH_ENTRY( thread, &process->thread_list, struct thread, proc_entry ) { set_thread_affinity( thread, affinity ); diff --git a/server/process.h b/server/process.h index b2292440c8b..d9fc2b9c3a1 100644 --- a/server/process.h +++ b/server/process.h @@ -39,6 +39,7 @@ struct process struct object *sync; /* sync object for wait/signal */ struct list entry; /* entry in system-wide process list */ process_id_t parent_id; /* parent process id (at the time of creation) */ + struct thread *sched_thread; /* sched / unix main thread */ struct list thread_list; /* thread list */ struct debug_obj *debug_obj; /* debug object debugging this process */ struct debug_event *debug_event; /* debug event being sent to debugger */ @@ -101,7 +102,9 @@ extern struct process *create_process( int fd, struct process *parent, unsigned const struct startup_info_data *info, const struct security_descriptor *sd, const obj_handle_t *handles, unsigned int handle_count, struct token *token ); +extern void init_process_done( struct process *process ); extern data_size_t get_process_startup_info_size( struct process *process ); +extern struct security_descriptor *get_first_thread_info( struct process *process, unsigned int *flags ); extern struct thread *get_process_first_thread( struct process *process ); extern struct process *get_process_from_id( process_id_t id ); extern struct process *get_process_from_handle( obj_handle_t handle, unsigned int access ); diff --git a/server/protocol.def b/server/protocol.def index 3d5c14bdc03..4abe764961d 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1147,15 +1147,8 @@ typedef volatile struct @END -/* Signal the end of the process initialization */ -@REQ(init_process_done) -@REPLY - int suspend; /* is process suspended? */ -@END - - -/* Initialize the first thread of a new process */ -@REQ(init_first_thread) +/* Initialize a new process */ +@REQ(init_process) int unix_pid; /* Unix pid of new process */ int unix_tid; /* Unix tid of new thread */ int debug_level; /* new debug level */ diff --git a/server/request.c b/server/request.c index 89bba1d9faa..045d13126d8 100644 --- a/server/request.c +++ b/server/request.c @@ -410,7 +410,7 @@ int receive_fd( struct process *process ) struct thread *thread; if (data.tid) thread = get_thread_from_id( data.tid ); - else thread = (struct thread *)grab_object( get_process_first_thread( process )); + else thread = (struct thread *)grab_object( process->sched_thread ); if (!thread || thread->process != process || thread->state == TERMINATED) { diff --git a/server/thread.c b/server/thread.c index 54b1b5e2c00..8005d51a973 100644 --- a/server/thread.c +++ b/server/thread.c @@ -506,9 +506,9 @@ struct thread *create_thread( int fd, struct process *process, unsigned int flag { struct desktop *desktop; struct thread *thread; - int request_pipe[2]; + int is_sched, request_pipe[2]; - if (fd == -1) + if ((is_sched = (fd == -1))) { if (pipe( request_pipe ) == -1) { @@ -540,13 +540,16 @@ struct thread *create_thread( int fd, struct process *process, unsigned int flag init_thread_structure( thread ); + thread->is_sched = is_sched; thread->process = (struct process *)grab_object( process ); thread->desktop = 0; thread->affinity = process->affinity; thread->disable_boost = process->disable_boost; if (!current) current = thread; - list_add_tail( &thread_list, &thread->entry ); + /* avoid adding kernel threads to the global thread list */ + if (thread->is_sched) list_init( &thread->entry ); + else list_add_tail( &thread_list, &thread->entry ); if (sd && !set_sd_defaults_from_token( &thread->obj, sd, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | @@ -567,7 +570,7 @@ struct thread *create_thread( int fd, struct process *process, unsigned int flag if (!(thread->sync = create_internal_sync( 1, 0 ))) goto error; if (get_inproc_device_fd() >= 0 && !(thread->alert_sync = create_inproc_internal_sync( 1, 0 ))) goto error; - if (process->desktop) + if (!thread->is_sched && process->desktop) { if (!(desktop = get_desktop_obj( process, process->desktop, 0 ))) clear_error(); /* ignore errors */ else @@ -578,7 +581,8 @@ struct thread *create_thread( int fd, struct process *process, unsigned int flag } set_fd_events( thread->request_fd, POLLIN ); /* start listening to events */ - add_process_thread( thread->process, thread ); + if (is_sched) process->sched_thread = (struct thread *)grab_object( thread ); + else add_process_thread( process, thread ); if (flags & THREAD_CREATE_FLAGS_CREATE_SUSPENDED) thread->suspend++; thread->dbg_hidden = !!(flags & THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER); @@ -1612,6 +1616,7 @@ int thread_get_inflight_fd( struct thread *thread, int client ) /* kill a thread on the spot */ void kill_thread( struct thread *thread, int violent_death ) { + struct process *process = thread->process; if (thread->state == TERMINATED) return; /* already killed */ thread->state = TERMINATED; thread->exit_time = current_time; @@ -1631,7 +1636,8 @@ void kill_thread( struct thread *thread, int violent_death ) signal_sync( thread->sync ); if (violent_death) send_thread_signal( thread, SIGQUIT ); cleanup_thread( thread ); - remove_process_thread( thread->process, thread ); + if (thread->is_sched) kill_process( process, violent_death ); + else remove_process_thread( process, thread ); release_object( thread ); } @@ -1664,9 +1670,10 @@ DECL_HANDLER(new_thread) struct thread *thread; struct process *process; struct unicode_str name; - const struct security_descriptor *sd; + const struct security_descriptor *sd, *first_sd; const struct object_attributes *objattr = get_req_object_attributes( &sd, &name, NULL ); int request_fd = thread_get_inflight_fd( current, req->request_fd ); + unsigned int flags = req->flags; if (!(process = get_process_from_handle( req->process, 0 ))) { @@ -1693,7 +1700,10 @@ DECL_HANDLER(new_thread) goto done; } - if ((thread = create_thread( request_fd, process, req->flags, sd ))) + /* if first thread, use security descriptor from startup info */ + if ((first_sd = get_first_thread_info( process, &flags ))) sd = first_sd; + + if ((thread = create_thread( request_fd, process, flags, sd ))) { thread->system_regs = current->system_regs; reply->tid = get_thread_id( thread ); @@ -1740,8 +1750,8 @@ static int init_thread( struct thread *thread, int reply_fd, int wait_fd ) return 0; } -/* initialize the first thread of a new process */ -DECL_HANDLER(init_first_thread) +/* initialize a new process */ +DECL_HANDLER(init_process) { struct process *process = current->process; int fd; @@ -1750,6 +1760,7 @@ DECL_HANDLER(init_first_thread) current->unix_pid = process->unix_pid = req->unix_pid; current->unix_tid = req->unix_tid; + process->start_time = current_time; if (!process->parent_id) process->affinity = current->affinity = get_thread_affinity( current ); @@ -1778,6 +1789,7 @@ DECL_HANDLER(init_first_thread) /* initialize a new thread */ DECL_HANDLER(init_thread) { + struct thread *first_thread = get_process_first_thread( current->process ); if (!init_thread( current, req->reply_fd, req->wait_fd )) return; if (!is_valid_address(req->teb)) @@ -1792,11 +1804,13 @@ DECL_HANDLER(init_thread) current->entry_point = req->entry; init_thread_context( current ); - generate_debug_event( current, DbgCreateThreadStateChange, &req->entry ); + if (current == first_thread) generate_startup_debug_events( current->process ); + else generate_debug_event( current, DbgCreateThreadStateChange, &req->entry ); set_thread_base_priority( current, current->base_priority ); set_thread_affinity( current, current->affinity ); reply->suspend = (is_thread_suspended( current ) || current->context != NULL); + if (current == first_thread) init_process_done( current->process ); } /* terminate a thread */ diff --git a/server/thread.h b/server/thread.h index 317979fc6cd..0a1210f82d4 100644 --- a/server/thread.h +++ b/server/thread.h @@ -88,8 +88,9 @@ struct thread int base_priority; /* base priority level (relative to process base priority class) */ int disable_boost; /* disable thread priority boost */ int suspend; /* suspend count */ - int dbg_hidden; /* hidden from debugger */ - int bypass_proc_suspend; /* will still run if the process is suspended */ + unsigned int is_sched:1; /* sched / unix main thread */ + unsigned int dbg_hidden:1; /* hidden from debugger */ + unsigned int bypass_proc_suspend:1; /* will still run if the process is suspended */ obj_handle_t desktop; /* desktop handle */ int desktop_users; /* number of objects using the thread desktop */ timeout_t creation_time; /* Thread creation time */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
From: Rémi Bernon <rbernon@codeweavers.com> --- dlls/ntdll/unix/loader.c | 50 ---------------------------------------- dlls/ntdll/unix/sched.c | 20 ++++++++++++++++ 2 files changed, 20 insertions(+), 50 deletions(-) diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index bfa9121e813..8af1f624913 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -1999,45 +1999,8 @@ jint JNI_OnLoad( JavaVM *vm, void *reserved ) #endif /* __ANDROID__ */ #ifdef __APPLE__ -static void *apple_wine_thread( void *arg ) -{ - start_main_thread(); - return NULL; -} - -/*********************************************************************** - * apple_create_wine_thread - * - * Spin off a secondary thread to complete Wine initialization, leaving - * the original thread for the Mac frameworks. - * - * Invoked as a CFRunLoopSource perform callback. - */ -static void apple_create_wine_thread( void *arg ) -{ - pthread_t thread; - pthread_attr_t attr; - - pthread_attr_init( &attr ); - pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); - if (pthread_create( &thread, &attr, apple_wine_thread, NULL )) exit(1); - pthread_attr_destroy( &attr ); -} - - -/*********************************************************************** - * apple_main_thread - * - * Park the process's original thread in a Core Foundation run loop for - * use by the Mac frameworks, especially receiving and handling - * distributed notifications. Spin off a new thread for the rest of the - * Wine initialization. - */ static void apple_main_thread(void) { - CFRunLoopSourceContext source_context = { 0 }; - CFRunLoopSourceRef source; - if (!pthread_main_np()) return; #pragma clang diagnostic push @@ -2052,19 +2015,6 @@ static void apple_main_thread(void) * center scheduled on this thread's run loop. In theory, it's scheduled * in the first thread to ask for it. */ CFNotificationCenterGetDistributedCenter(); - - /* We use this run loop source for two purposes. First, a run loop exits - * if it has no more sources scheduled. So, we need at least one source - * to keep the run loop running. Second, although it's not critical, it's - * preferable for the Wine initialization to not proceed until we know - * the run loop is running. So, we signal our source immediately after - * adding it and have its callback spin off the Wine thread. */ - source_context.perform = apple_create_wine_thread; - source = CFRunLoopSourceCreate( NULL, 0, &source_context ); - CFRunLoopAddSource( CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes ); - CFRunLoopSourceSignal( source ); - CFRelease( source ); - CFRunLoopRun(); /* Should never return, except on error. */ } #endif /* __APPLE__ */ diff --git a/dlls/ntdll/unix/sched.c b/dlls/ntdll/unix/sched.c index 583f7dee1fe..960187730fc 100644 --- a/dlls/ntdll/unix/sched.c +++ b/dlls/ntdll/unix/sched.c @@ -29,7 +29,27 @@ #include "unix_private.h" +#ifdef __APPLE__ + +#include <CoreFoundation/CoreFoundation.h> + +void sched_run(void) +{ +/* + CFRunLoopSourceRef source = CFRunLoopSourceCreate( NULL, 0, NULL ); + CFRunLoopAddSource( CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes ); + CFRelease( source ); +*/ + + CFRunLoopRun(); /* Should never return, except on error. */ + assert( 0 ); +} + +#else + void sched_run(void) { for (;;) poll( NULL, 0, -1 ); } + +#endif -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10058
Yeah this way it looks like it shouldn't be visible to app: - there is no handle to it (or otherwise it is possible to enumerate all the system handles and find it); - it is not in process thread list and not in global thread list. Thanks a lot for that. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10058#note_129680
On Sat Feb 14 10:17:32 2026 +0000, Paul Gofman wrote:
Yeah this way it looks like it shouldn't be visible to app: - there is no handle to it (or otherwise it is possible to enumerate all the system handles and find it); - it is not in process thread list and not in global thread list. Thanks a lot for that. YW, then there's still the concern you raised about sending window messages. Do you have something specific in mind about the functions that may be used to figure the thread the messages are coming from? Would this be about window message hooks?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10058#note_129686
On Sat Feb 14 10:17:32 2026 +0000, Rémi Bernon wrote:
YW, then there's still the concern you raised about sending window messages. Do you have something specific in mind about the functions that may be used to figure the thread the messages are coming from? Would this be about window message hooks? Thinking of this part, I came to another question to previous part: if app does OpenThread() with this thread ID, will it still succeed or not? Looks like there is another point of leaking this thread info, get_ptid_entry() doesn't use thread lists. I think to be fully correct that should properly fail as will non-existent thread ID.
I think that in the first place under no circumstances this thread can execute any PE code (do any user callbacks). If a hook would execute in that thread that can't be happening. Then, I am not sure I understand the intention what this thread would do which would imply sending "not system originated" messages directly to other threads. First, regardless of all these low level issues, it can't probably do SendMessage() anyway, even with timeout, it is not supposed to block ever? Then, in general, thread ID of this thread should never appear anywhere user visible, it is like it doesn't exist (it makes no sense to have thread id somewhere for thread which doesn't exist from app's standpoint). Do you have any specific example what are you planning to do from this thread message wise other than sending HW input messages (which just go to server, delivered to message queues differently and originate from "system" on Windows)? I don't yet understand why that general thread would want to behave as a normal thread in window messages exchange at all. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10058#note_129698
Do you have any specific example what are you planning to do from this thread message wise other than sending HW input messages (which just go to server, delivered to message queues differently and originate from "system" on Windows)?
Well for a start it is meant to run macOS host GUI integration, and similarly on Wayland would replace the existing Wayland compositor dispatcher thread. On macOS right now we use a custom event dispatch mechanism from the GUI thread to other window threads (ofc because that thread is not a Wine thread yet) and it would be better to get rid of this and use window messages directly. On Wayland the compositor dispatcher thread already uses window messages, some Wine internal, some not. Even if we can hope that we would only use internal messages eventually, various feedback and interaction from user drivers to win32u still *probably* rely on sending normal window messages. On Windows I assume these would be coming from dwm.exe, or some other system process, or are generated by the win32k kernel module. Similarly, it may be difficult to rule out PE callbacks entirely, although I'm not completely sure which part would need them it's hard to be sure that there are none. Maybe we can try not allocating a user stack for that thread and see what breaks, but overall that sounds like a lot of restriction when the threads it's meant to replace didn't have them. Another way perhaps, would be to park this thread until some module would need it, and then when it does, make it as if it was a newly created thread. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10058#note_129701
Even if we can hope that we would only use internal messages eventually, various feedback and interaction from user drivers to win32u still _probably_ rely on sending normal window messages. On Windows I assume these would be coming from dwm.exe, or some other system process, or are generated by the win32k kernel module.
T think it can only work as win kernel module from the app's view, and win32k can't send messages directly to threads, it can only put those to queues on the kernel side. So should we be doing probably? If that is WINE internal messages from user drivers, maybe it can be made an execption to receive those? Provided that thread's message processing won't reflect itself in any possible user hooks. Or they would need a special "kernel side" messaging mechanism that won't go through normal NtUserGetMessage / NtUserPostMessage with all the accompnying processing and hooking.
Similarly, it may be difficult to rule out PE callbacks entirely, although I'm not completely sure which part would need them it's hard to be sure that there are none. Maybe we can try not allocating a user stack for that thread and see what breaks, but overall that sounds like a lot of restriction when the threads it's meant to replace didn't have them.
Yes, user stack is not needed for it and I'd think not allocating it would be a good idea. PE code can't work there. First, even for "normal" apps, this thread doesn't have loader initialized (and initializaing it will immediately make it interfere with everything and very broadly visible), that alone puts a lot of unobvious restrictions on what the thread cannot do (starting from HeapAlloc even maybe), but that is only the top of that. Then, we should assume that any PE code can be hooked / injected and check specifically for unexpected threads doing things. Thread exection is the most commong hacking technique and DRMs / anticheats are most keen of detecting weird threads. Apart from all those concerns, I think we are sticking to strict architecture rules on this level. This is "kernel" thread which exists on Unix side only. The only differentce the new way is adding is allowing it to interact with server and otherwise use kernel side API. But not to present itself on the user / PE side anyhow. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10058#note_129702
The only differentce the new way is adding is allowing it to interact with server
I think if treating this thread this way seems to defend its purpose then maybe we should reconsider it from scratch, and at very least it can't be created that early, can't be created from ntdll and should go from some GUI component which would create it just as a normal thread later (while that upfront doesn't look to me as an ideal approach). I believe technically we'd be better off with system "kernel" thread but treated strictly as such without shortcuts: only "kernel" activity and maybe not trying to use user level win32u api at all. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10058#note_129705
On Fri Feb 13 12:53:15 2026 +0000, Rémi Bernon wrote:
I don't see it exiting immediately, though the documentation indicates that it might exit after all sources are removed. Maybe that can happen after some have been added? Still haven't seen it return spuriously for now. In any case there's some issue that seem to happen on Gitlab and which I'm not able to reproduce locally, causing the prefix creation to fail in the test jobs... I'll need to figure this out before this can be merged. I tested out the first version of this and I also didn’t see CFRunLoopRun() returning, maybe there’s a source being implicitly added. I’ll test the latest version out, including on older OS versions.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10058#note_129722
On Mon Feb 16 14:26:40 2026 +0000, Paul Gofman wrote:
Does THREAD_CREATE_FLAGS_LOADER_WORKER somehow hide thread completely on Windows? I doubt so, probably there is no way to hide user space thread completely. Loader is a different aspect. Threads may skip loader but they are still visible through NtGetNextThread or NtQuerySystemInformation( SystemProcessInformation ). So I am not 100% sure that Windows doesn't have such a way but doubt it does. I didn't mean that these flags were usable from user space or that they would hide the thread at all, but rather that they seem to indicate that more threads may be present at program startup, for DLL loading purposes. More research [^1] [^2] [^3] [^4] seems to indicate that it is the case indeed on Windows 10.
This sample application also shows an extra thread being present as early as main executable TLS callbacks are executed (idk if there's any user code that can execute earlier than that): ```C #include <stdio.h> #include <ntstatus.h> #define WIN32_NO_STATUS #include <windef.h> #include <winbase.h> #include <winternl.h> void __stdcall tls_callback( void *dll_handle, int reason, void *reserved ) { HANDLE thread = 0, prev = 0; UINT status; while (!(status = NtGetNextThread( (void *)-1, prev, THREAD_ALL_ACCESS, OBJ_INHERIT, 0, &thread ))) { void *entry = (void *)0xdeadbeef; THREAD_BASIC_INFORMATION info; NtClose( prev ); status = NtQueryInformationThread( thread, ThreadBasicInformation, &info, sizeof(info), NULL ); if (status) printf( "ThreadBasicInformation status %#x\n", status ); status = NtQueryInformationThread( thread, ThreadQuerySetWin32StartAddress, &entry, sizeof(entry), NULL ); if (status) printf( "ThreadQuerySetWin32StartAddress status %#x\n", status ); printf( "%p:%p teb %p entry %p\n", info.ClientId.UniqueProcess, info.ClientId.UniqueThread, info.TebBaseAddress, entry ); prev = thread; } if (status && status != STATUS_NO_MORE_ENTRIES) printf( "NtGetNextThread status %#x\n", status ); if (prev) NtClose( prev ); do { static volatile int i = 0; while (!i) {} } while (0); } static const void *tls_callbacks[] = { tls_callback, 0 }; static int tls_index; IMAGE_TLS_DIRECTORY _tls_used = { 0, 0, (UINT_PTR)&tls_index, (UINT_PTR)tls_callbacks }; int main(int argc, char* argv[]) { do { static volatile int i = 0; while (!i) {} } while (0); } ``` IMO this tends to indicate that having an internal thread present on process startup is not so bad. We might need to make it go through ntdll PE side to make sure it has some PE side frames but we can also maybe question whether this is actually necessary. [^1]: https://stackoverflow.com/questions/78458827/how-to-correctly-determine-a-pr... [^2]: https://stackoverflow.com/questions/42789199/why-there-are-three-unexpected-... [^3]: https://bugzilla.mozilla.org/show_bug.cgi?id=1515088 [^4]: https://searchfox.org/firefox-main/source/security/sandbox/chromium-shim/pat... -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10058#note_129737
On Mon Feb 16 14:31:37 2026 +0000, Rémi Bernon wrote:
I didn't mean that these flags were usable from user space or that they would hide the thread at all, but rather that they seem to indicate that more threads may be present at program startup, for DLL loading purposes. More research [^1] [^2] [^3] [^4] seems to indicate that it is the case indeed on Windows 10. This sample application also shows an extra thread being present as early as main executable TLS callbacks are executed (idk if there's any user code that can execute earlier than that): ```C #include <stdio.h> #include <ntstatus.h> #define WIN32_NO_STATUS #include <windef.h> #include <winbase.h> #include <winternl.h> void __stdcall tls_callback( void *dll_handle, int reason, void *reserved ) { HANDLE thread = 0, prev = 0; UINT status; while (!(status = NtGetNextThread( GetCurrentProcess(), prev, THREAD_ALL_ACCESS, OBJ_INHERIT, 0, &thread ))) { void *entry = (void *)0xdeadbeef; THREAD_BASIC_INFORMATION info; NtClose( prev ); status = NtQueryInformationThread( thread, ThreadBasicInformation, &info, sizeof(info), NULL ); if (status) printf( "ThreadBasicInformation status %#x\n", status ); status = NtQueryInformationThread( thread, ThreadQuerySetWin32StartAddress, &entry, sizeof(entry), NULL ); if (status) printf( "ThreadQuerySetWin32StartAddress status %#x\n", status ); printf( "%p:%p teb %p entry %p\n", info.ClientId.UniqueProcess, info.ClientId.UniqueThread, info.TebBaseAddress, entry ); prev = thread; } if (status && status != STATUS_NO_MORE_ENTRIES) printf( "NtGetNextThread status %#x\n", status ); if (prev) NtClose( prev ); do { static volatile int i = 0; while (!i) {} } while (0); } static const void *tls_callbacks[] = { tls_callback, 0 }; static int tls_index; IMAGE_TLS_DIRECTORY _tls_used = { 0, 0, (UINT_PTR)&tls_index, (UINT_PTR)tls_callbacks }; int main(int argc, char* argv[]) { do { static volatile int i = 0; while (!i) {} } while (0); } ``` Process explorer also shows the same thing, with an extra thread being there at process startup, doing some ntdll internal work. Also fwiw neither this thread or the main thread have executable entry point as `ThreadQuerySetWin32StartAddress` entry, but rather some ntdll functions, to the contrary to what we do in Wine. IMO this tends to indicate that having an internal thread present on process startup is not so bad. We might need to make it go through ntdll PE side to make sure it has some PE side frames but we can also maybe question whether this is actually necessary. [^1]: https://stackoverflow.com/questions/78458827/how-to-correctly-determine-a-pr... [^2]: https://stackoverflow.com/questions/42789199/why-there-are-three-unexpected-... [^3]: https://bugzilla.mozilla.org/show_bug.cgi?id=1515088 [^4]: https://searchfox.org/firefox-main/source/security/sandbox/chromium-shim/pat... Checking at TLS callback is too late. At this point ntdll loader is initialized and TLS callbacks for dependencies is already executed. User app may start the process suspended and inject a hook for LdrInitializeThunk, or just check the extra threads from the other (e. g., launcher) process. A clean and probably easiest way to check if there are any threads early is to first start process suspended and enumerate the threads in it from the launcher. Maybe worth checking to be sure, but IIRC I did that in the past and there should be no such.
Then, if we want to mind LdrpWorkCallback() in some way I think we should first implement that doing the same as on Windows. It is not so easy to do, I think it will first need to redo loader locking in a modern Windows launcher way (I attempted once in the context of the other rare issues current extra locking is causing but initial attempt was too bad, that needs to be redone differently starting from managing modern Windows DLL load status evolution). And then I doubt those threads can be abused for this purpose, those do not persist throughout (or maybe it can even, but it should be explored in details first what they do and how can be used). But that is quite some work to implement properly first, and that is definitely not something which would create an extra thread from kernel before LdrInitializeThunk is even executed first time. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10058#note_129739
participants (4)
-
Brendan Shanks (@bshanks) -
Paul Gofman (@gofman) -
Rémi Bernon -
Rémi Bernon (@rbernon)