Signed-off-by: Rémi Bernon rbernon@codeweavers.com ---
This patch series is targetted around this specific status code to tell system APCs from user APCs, although it is not strictly necessary.
We could already use the APC call type to tell the difference, but I believe using the specific status makes the logic clearer.
dlls/ntdll/server.c | 2 +- dlls/ntdll/sync.c | 2 +- server/thread.c | 20 +++++++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/dlls/ntdll/server.c b/dlls/ntdll/server.c index 089eb3b89aa..73f4d86cf4d 100644 --- a/dlls/ntdll/server.c +++ b/dlls/ntdll/server.c @@ -624,7 +624,7 @@ unsigned int server_select( const select_op_t *select_op, data_size_t size, UINT } SERVER_END_REQ; if (ret == STATUS_PENDING) ret = wait_select_reply( &cookie ); - if (ret != STATUS_USER_APC) break; + if (ret != STATUS_USER_APC && ret != STATUS_KERNEL_APC) break; if (invoke_apc( &call, &result )) { /* if we ran a user apc we have to check once more if additional apcs are queued, diff --git a/dlls/ntdll/sync.c b/dlls/ntdll/sync.c index c4885973b68..d09b90a9273 100644 --- a/dlls/ntdll/sync.c +++ b/dlls/ntdll/sync.c @@ -2497,7 +2497,7 @@ NTSTATUS WINAPI RtlWaitOnAddress( const void *addr, const void *cmp, SIZE_T size RtlLeaveCriticalSection( &addr_section );
if (ret == STATUS_PENDING) ret = wait_select_reply( &cookie ); - if (ret != STATUS_USER_APC) break; + if (ret != STATUS_USER_APC && ret != STATUS_KERNEL_APC) break; if (invoke_apc( &call, &result )) { /* if we ran a user apc we have to check once more if additional apcs are queued, diff --git a/server/thread.c b/server/thread.c index aec4c1acf4e..d82463c6baf 100644 --- a/server/thread.c +++ b/server/thread.c @@ -749,7 +749,7 @@ static int check_wait( struct thread *thread ) assert( wait );
if ((wait->flags & SELECT_INTERRUPTIBLE) && !list_empty( &thread->system_apc )) - return STATUS_USER_APC; + return STATUS_KERNEL_APC;
/* Suspended threads may not acquire locks, but they can run system APCs */ if (thread->process->suspend + thread->suspend > 0) return -1; @@ -1083,12 +1083,11 @@ void thread_cancel_apc( struct thread *thread, struct object *owner, enum apc_ty }
/* remove the head apc from the queue; the returned object must be released by the caller */ -static struct thread_apc *thread_dequeue_apc( struct thread *thread, int system_only ) +static struct thread_apc *thread_dequeue_apc( struct thread *thread, int system ) { struct thread_apc *apc = NULL; - struct list *ptr = list_head( &thread->system_apc ); + struct list *ptr = list_head( system ? &thread->system_apc : &thread->user_apc );
- if (!ptr && !system_only) ptr = list_head( &thread->user_apc ); if (ptr) { apc = LIST_ENTRY( ptr, struct thread_apc, entry ); @@ -1585,7 +1584,7 @@ DECL_HANDLER(select) { for (;;) { - if (!(apc = thread_dequeue_apc( current, !(req->flags & SELECT_ALERTABLE) ))) + if (!(apc = thread_dequeue_apc( current, 0 ))) break; /* Optimization: ignore APC_NONE calls, they are only used to * wake up a thread, but since we got here the thread woke up already. @@ -1602,6 +1601,17 @@ DECL_HANDLER(select) release_object( apc ); } } + else if (get_error() == STATUS_KERNEL_APC) + { + if (!(apc = thread_dequeue_apc( current, 1 ))) + return; + + if (!(reply->apc_handle = alloc_handle( current->process, apc, SYNCHRONIZE, 0 ))) + return; + + reply->call = apc->call; + release_object( apc ); + } }
/* queue an APC for a thread or process */
The wait_select_reply call may return STATUS_USER_APC/STATUS_KERNEL_APC, depending on which APC is about to be returned but the apc call will always be APC_NONE right after the wait. It needs an additional select request to actually return the call.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/ntdll/server.c | 15 +++++++++------ dlls/ntdll/sync.c | 8 +++++--- 2 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/dlls/ntdll/server.c b/dlls/ntdll/server.c index 73f4d86cf4d..9e5ef4dde12 100644 --- a/dlls/ntdll/server.c +++ b/dlls/ntdll/server.c @@ -623,9 +623,13 @@ unsigned int server_select( const select_op_t *select_op, data_size_t size, UINT call = reply->call; } SERVER_END_REQ; - if (ret == STATUS_PENDING) ret = wait_select_reply( &cookie ); - if (ret != STATUS_USER_APC && ret != STATUS_KERNEL_APC) break; - if (invoke_apc( &call, &result )) + + /* don't signal multiple times */ + if (size >= sizeof(select_op->signal_and_wait) && select_op->op == SELECT_SIGNAL_AND_WAIT) + size = offsetof( select_op_t, signal_and_wait.signal ); + + if ((ret == STATUS_USER_APC || ret == STATUS_KERNEL_APC) && + invoke_apc( &call, &result )) { /* if we ran a user apc we have to check once more if additional apcs are queued, * but we don't want to wait */ @@ -634,9 +638,8 @@ unsigned int server_select( const select_op_t *select_op, data_size_t size, UINT size = 0; }
- /* don't signal multiple times */ - if (size >= sizeof(select_op->signal_and_wait) && select_op->op == SELECT_SIGNAL_AND_WAIT) - size = offsetof( select_op_t, signal_and_wait.signal ); + if (ret == STATUS_PENDING) ret = wait_select_reply( &cookie ); + if (ret != STATUS_USER_APC && ret != STATUS_KERNEL_APC) break; }
if (ret == STATUS_TIMEOUT && user_apc) ret = STATUS_USER_APC; diff --git a/dlls/ntdll/sync.c b/dlls/ntdll/sync.c index d09b90a9273..38ed3e04eff 100644 --- a/dlls/ntdll/sync.c +++ b/dlls/ntdll/sync.c @@ -2496,15 +2496,17 @@ NTSTATUS WINAPI RtlWaitOnAddress( const void *addr, const void *cmp, SIZE_T size
RtlLeaveCriticalSection( &addr_section );
- if (ret == STATUS_PENDING) ret = wait_select_reply( &cookie ); - if (ret != STATUS_USER_APC && ret != STATUS_KERNEL_APC) break; - if (invoke_apc( &call, &result )) + if ((ret == STATUS_USER_APC || ret == STATUS_KERNEL_APC) && + invoke_apc( &call, &result )) { /* if we ran a user apc we have to check once more if additional apcs are queued, * but we don't want to wait */ abs_timeout = 0; user_apc = TRUE; } + + if (ret == STATUS_PENDING) ret = wait_select_reply( &cookie ); + if (ret != STATUS_USER_APC && ret != STATUS_KERNEL_APC) break; }
if (ret == STATUS_TIMEOUT && user_apc) ret = STATUS_USER_APC;
Hi Rémi,
On 04.02.2020 11:09, Rémi Bernon wrote:
@@ -634,9 +638,8 @@ unsigned int server_select( const select_op_t *select_op, data_size_t size, UINT size = 0; }
/* don't signal multiple times */
if (size >= sizeof(select_op->signal_and_wait) && select_op->op == SELECT_SIGNAL_AND_WAIT)
size = offsetof( select_op_t, signal_and_wait.signal );
if (ret == STATUS_PENDING) ret = wait_select_reply( &cookie );
if (ret != STATUS_USER_APC && ret != STATUS_KERNEL_APC) break; }
After your reordering, it's essentially do {} while() loop, maybe you could use that.
Thanks,
Jacek
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/ntdll/ntdll_misc.h | 2 +- dlls/ntdll/server.c | 14 ++++++-------- dlls/ntdll/sync.c | 5 +++-- 3 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/dlls/ntdll/ntdll_misc.h b/dlls/ntdll/ntdll_misc.h index e1fb1e9eba7..bec88e6f07b 100644 --- a/dlls/ntdll/ntdll_misc.h +++ b/dlls/ntdll/ntdll_misc.h @@ -115,7 +115,7 @@ extern NTSTATUS alloc_object_attributes( const OBJECT_ATTRIBUTES *attr, struct o data_size_t *ret_len ) DECLSPEC_HIDDEN; extern NTSTATUS validate_open_object_attributes( const OBJECT_ATTRIBUTES *attr ) DECLSPEC_HIDDEN; extern int wait_select_reply( void *cookie ) DECLSPEC_HIDDEN; -extern BOOL invoke_apc( const apc_call_t *call, apc_result_t *result ) DECLSPEC_HIDDEN; +extern void invoke_apc( const apc_call_t *call, apc_result_t *result ) DECLSPEC_HIDDEN;
/* module handling */ extern LIST_ENTRY tls_links DECLSPEC_HIDDEN; diff --git a/dlls/ntdll/server.c b/dlls/ntdll/server.c index 9e5ef4dde12..ca7ee166a3d 100644 --- a/dlls/ntdll/server.c +++ b/dlls/ntdll/server.c @@ -384,11 +384,11 @@ int wait_select_reply( void *cookie ) /*********************************************************************** * invoke_apc * - * Invoke a single APC. Return TRUE if a user APC has been run. + * Invoke a single APC. + * */ -BOOL invoke_apc( const apc_call_t *call, apc_result_t *result ) +void invoke_apc( const apc_call_t *call, apc_result_t *result ) { - BOOL user_apc = FALSE; SIZE_T size; void *addr; pe_image_info_t image_info; @@ -403,7 +403,6 @@ BOOL invoke_apc( const apc_call_t *call, apc_result_t *result ) { void (WINAPI *func)(ULONG_PTR,ULONG_PTR,ULONG_PTR) = wine_server_get_ptr( call->user.func ); func( call->user.args[0], call->user.args[1], call->user.args[2] ); - user_apc = TRUE; break; } case APC_TIMER: @@ -411,7 +410,6 @@ BOOL invoke_apc( const apc_call_t *call, apc_result_t *result ) void (WINAPI *func)(void*, unsigned int, unsigned int) = wine_server_get_ptr( call->timer.func ); func( wine_server_get_ptr( call->timer.arg ), (DWORD)call->timer.time, (DWORD)(call->timer.time >> 32) ); - user_apc = TRUE; break; } case APC_ASYNC_IO: @@ -587,7 +585,6 @@ BOOL invoke_apc( const apc_call_t *call, apc_result_t *result ) server_protocol_error( "get_apc_request: bad type %d\n", call->type ); break; } - return user_apc; }
@@ -628,9 +625,10 @@ unsigned int server_select( const select_op_t *select_op, data_size_t size, UINT if (size >= sizeof(select_op->signal_and_wait) && select_op->op == SELECT_SIGNAL_AND_WAIT) size = offsetof( select_op_t, signal_and_wait.signal );
- if ((ret == STATUS_USER_APC || ret == STATUS_KERNEL_APC) && - invoke_apc( &call, &result )) + if (ret == STATUS_KERNEL_APC) invoke_apc( &call, &result ); + if (ret == STATUS_USER_APC) { + invoke_apc( &call, &result ); /* if we ran a user apc we have to check once more if additional apcs are queued, * but we don't want to wait */ abs_timeout = 0; diff --git a/dlls/ntdll/sync.c b/dlls/ntdll/sync.c index 38ed3e04eff..8ec05103675 100644 --- a/dlls/ntdll/sync.c +++ b/dlls/ntdll/sync.c @@ -2496,9 +2496,10 @@ NTSTATUS WINAPI RtlWaitOnAddress( const void *addr, const void *cmp, SIZE_T size
RtlLeaveCriticalSection( &addr_section );
- if ((ret == STATUS_USER_APC || ret == STATUS_KERNEL_APC) && - invoke_apc( &call, &result )) + if (ret == STATUS_KERNEL_APC) invoke_apc( &call, &result ); + if (ret == STATUS_USER_APC) { + invoke_apc( &call, &result ); /* if we ran a user apc we have to check once more if additional apcs are queued, * but we don't want to wait */ abs_timeout = 0;
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/ntdll/server.c | 39 ++++++++++++++++++++++----------------- dlls/ntdll/sync.c | 31 ++++++++++++++++++------------- 2 files changed, 40 insertions(+), 30 deletions(-)
diff --git a/dlls/ntdll/server.c b/dlls/ntdll/server.c index ca7ee166a3d..0716398e58e 100644 --- a/dlls/ntdll/server.c +++ b/dlls/ntdll/server.c @@ -606,26 +606,31 @@ unsigned int server_select( const select_op_t *select_op, data_size_t size, UINT
for (;;) { - SERVER_START_REQ( select ) + for (;;) { - req->flags = flags; - req->cookie = wine_server_client_ptr( &cookie ); - req->prev_apc = apc_handle; - req->timeout = abs_timeout; - wine_server_add_data( req, &result, sizeof(result) ); - wine_server_add_data( req, select_op, size ); - ret = wine_server_call( req ); - abs_timeout = reply->timeout; - apc_handle = reply->apc_handle; - call = reply->call; - } - SERVER_END_REQ; + SERVER_START_REQ( select ) + { + req->flags = flags; + req->cookie = wine_server_client_ptr( &cookie ); + req->prev_apc = apc_handle; + req->timeout = abs_timeout; + wine_server_add_data( req, &result, sizeof(result) ); + wine_server_add_data( req, select_op, size ); + ret = wine_server_call( req ); + abs_timeout = reply->timeout; + apc_handle = reply->apc_handle; + call = reply->call; + } + SERVER_END_REQ; + + /* don't signal multiple times */ + if (size >= sizeof(select_op->signal_and_wait) && select_op->op == SELECT_SIGNAL_AND_WAIT) + size = offsetof( select_op_t, signal_and_wait.signal );
- /* don't signal multiple times */ - if (size >= sizeof(select_op->signal_and_wait) && select_op->op == SELECT_SIGNAL_AND_WAIT) - size = offsetof( select_op_t, signal_and_wait.signal ); + if (ret != STATUS_KERNEL_APC) break; + invoke_apc( &call, &result ); + }
- if (ret == STATUS_KERNEL_APC) invoke_apc( &call, &result ); if (ret == STATUS_USER_APC) { invoke_apc( &call, &result ); diff --git a/dlls/ntdll/sync.c b/dlls/ntdll/sync.c index 8ec05103675..41fcf498d09 100644 --- a/dlls/ntdll/sync.c +++ b/dlls/ntdll/sync.c @@ -2479,24 +2479,29 @@ NTSTATUS WINAPI RtlWaitOnAddress( const void *addr, const void *cmp, SIZE_T size return STATUS_SUCCESS; }
- SERVER_START_REQ( select ) + for (;;) { - req->flags = SELECT_INTERRUPTIBLE; - req->cookie = wine_server_client_ptr( &cookie ); - req->prev_apc = apc_handle; - req->timeout = abs_timeout; - wine_server_add_data( req, &result, sizeof(result) ); - wine_server_add_data( req, &select_op, sizeof(select_op.keyed_event) ); - ret = wine_server_call( req ); - abs_timeout = reply->timeout; - apc_handle = reply->apc_handle; - call = reply->call; + SERVER_START_REQ( select ) + { + req->flags = SELECT_INTERRUPTIBLE; + req->cookie = wine_server_client_ptr( &cookie ); + req->prev_apc = apc_handle; + req->timeout = abs_timeout; + wine_server_add_data( req, &result, sizeof(result) ); + wine_server_add_data( req, &select_op, sizeof(select_op.keyed_event) ); + ret = wine_server_call( req ); + abs_timeout = reply->timeout; + apc_handle = reply->apc_handle; + call = reply->call; + } + SERVER_END_REQ; + + if (ret != STATUS_KERNEL_APC) break; + invoke_apc( &call, &result ); } - SERVER_END_REQ;
RtlLeaveCriticalSection( &addr_section );
- if (ret == STATUS_KERNEL_APC) invoke_apc( &call, &result ); if (ret == STATUS_USER_APC) { invoke_apc( &call, &result );
This makes sure that system APC, such as APC_BREAK_PROCESS do not get interrupted in the middle of their execution, and that the APC completion notification is always correctly sent back to the caller.
Otherwise DbgBreakProcess sometimes did not return until WaitForDebugEvent/ContinueDebugEvent are called, because of a race condition between the APC servicing thread, and the newly created exception thread.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/ntdll/server.c | 5 ++++- dlls/ntdll/sync.c | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/dlls/ntdll/server.c b/dlls/ntdll/server.c index 0716398e58e..a870644fd44 100644 --- a/dlls/ntdll/server.c +++ b/dlls/ntdll/server.c @@ -601,11 +601,13 @@ unsigned int server_select( const select_op_t *select_op, data_size_t size, UINT apc_call_t call; apc_result_t result; timeout_t abs_timeout = timeout ? timeout->QuadPart : TIMEOUT_INFINITE; + sigset_t old_set;
memset( &result, 0, sizeof(result) );
for (;;) { + pthread_sigmask( SIG_BLOCK, &server_block_set, &old_set ); for (;;) { SERVER_START_REQ( select ) @@ -616,7 +618,7 @@ unsigned int server_select( const select_op_t *select_op, data_size_t size, UINT req->timeout = abs_timeout; wine_server_add_data( req, &result, sizeof(result) ); wine_server_add_data( req, select_op, size ); - ret = wine_server_call( req ); + ret = server_call_unlocked( req ); abs_timeout = reply->timeout; apc_handle = reply->apc_handle; call = reply->call; @@ -630,6 +632,7 @@ unsigned int server_select( const select_op_t *select_op, data_size_t size, UINT if (ret != STATUS_KERNEL_APC) break; invoke_apc( &call, &result ); } + pthread_sigmask( SIG_SETMASK, &old_set, NULL );
if (ret == STATUS_USER_APC) { diff --git a/dlls/ntdll/sync.c b/dlls/ntdll/sync.c index 41fcf498d09..cd7ea682c92 100644 --- a/dlls/ntdll/sync.c +++ b/dlls/ntdll/sync.c @@ -2457,6 +2457,7 @@ NTSTATUS WINAPI RtlWaitOnAddress( const void *addr, const void *cmp, SIZE_T size apc_call_t call; apc_result_t result; timeout_t abs_timeout = timeout ? timeout->QuadPart : TIMEOUT_INFINITE; + sigset_t old_set;
if (size != 1 && size != 2 && size != 4 && size != 8) return STATUS_INVALID_PARAMETER; @@ -2479,6 +2480,7 @@ NTSTATUS WINAPI RtlWaitOnAddress( const void *addr, const void *cmp, SIZE_T size return STATUS_SUCCESS; }
+ pthread_sigmask( SIG_BLOCK, &server_block_set, &old_set ); for (;;) { SERVER_START_REQ( select ) @@ -2489,7 +2491,7 @@ NTSTATUS WINAPI RtlWaitOnAddress( const void *addr, const void *cmp, SIZE_T size req->timeout = abs_timeout; wine_server_add_data( req, &result, sizeof(result) ); wine_server_add_data( req, &select_op, sizeof(select_op.keyed_event) ); - ret = wine_server_call( req ); + ret = server_call_unlocked( req ); abs_timeout = reply->timeout; apc_handle = reply->apc_handle; call = reply->call; @@ -2499,6 +2501,7 @@ NTSTATUS WINAPI RtlWaitOnAddress( const void *addr, const void *cmp, SIZE_T size if (ret != STATUS_KERNEL_APC) break; invoke_apc( &call, &result ); } + pthread_sigmask( SIG_SETMASK, &old_set, NULL );
RtlLeaveCriticalSection( &addr_section );
Hi Rémi,
On 04.02.2020 11:09, Rémi Bernon wrote:
}
- else if (get_error() == STATUS_KERNEL_APC)
- {
if (!(apc = thread_dequeue_apc( current, 1 )))
return;
Unlike user APC case (where we have a loop), thread_dequeue_apc will never return NULL here.
if (!(reply->apc_handle = alloc_handle( current->process, apc, SYNCHRONIZE, 0 )))
return;
You leak apc in error case here and leave it in an inconsistent state (it will never finish, blocking the caller). And yeah, current code is not exactly right neither, but a bit better error handling would be nice.
Thanks,
Jacek