Signed-off-by: Nikolay Sivov nsivov@codeweavers.com ---
Version 3 adds more tests, specifically to show that reaching thread suspend limit is not an error condition for NtSuspendProcess(). What's left is using same thread list loops as Michael's patch, while counter check loop is removed.
dlls/ntdll/process.c | 24 +++++- dlls/ntdll/tests/exception.c | 151 ++++++++++++++++++++++++++++++++- include/wine/server_protocol.h | 32 ++++++- server/process.c | 38 +++++++++ server/protocol.def | 12 +++ server/request.h | 8 ++ server/thread.c | 4 +- server/thread.h | 2 + server/trace.c | 16 ++++ 9 files changed, 278 insertions(+), 9 deletions(-)
diff --git a/dlls/ntdll/process.c b/dlls/ntdll/process.c index 6c6c427372..573a7a771d 100644 --- a/dlls/ntdll/process.c +++ b/dlls/ntdll/process.c @@ -763,8 +763,16 @@ NTSTATUS WINAPI NtOpenProcess(PHANDLE handle, ACCESS_MASK access, */ NTSTATUS WINAPI NtResumeProcess( HANDLE handle ) { - FIXME("stub: %p\n", handle); - return STATUS_NOT_IMPLEMENTED; + NTSTATUS ret; + + SERVER_START_REQ( resume_process ) + { + req->handle = wine_server_obj_handle( handle ); + ret = wine_server_call( req ); + } + SERVER_END_REQ; + + return ret; }
/****************************************************************************** @@ -773,8 +781,16 @@ NTSTATUS WINAPI NtResumeProcess( HANDLE handle ) */ NTSTATUS WINAPI NtSuspendProcess( HANDLE handle ) { - FIXME("stub: %p\n", handle); - return STATUS_NOT_IMPLEMENTED; + NTSTATUS ret; + + SERVER_START_REQ( suspend_process ) + { + req->handle = wine_server_obj_handle( handle ); + ret = wine_server_call( req ); + } + SERVER_END_REQ; + + return ret; }
diff --git a/dlls/ntdll/tests/exception.c b/dlls/ntdll/tests/exception.c index b7bf2748b5..3b3f72f07d 100644 --- a/dlls/ntdll/tests/exception.c +++ b/dlls/ntdll/tests/exception.c @@ -53,6 +53,8 @@ static NTSTATUS (WINAPI *pNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, static NTSTATUS (WINAPI *pNtSetInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG); static BOOL (WINAPI *pIsWow64Process)(HANDLE, PBOOL); static NTSTATUS (WINAPI *pNtClose)(HANDLE); +static NTSTATUS (WINAPI *pNtSuspendProcess)(HANDLE process); +static NTSTATUS (WINAPI *pNtResumeProcess)(HANDLE process);
#if defined(__x86_64__) typedef struct @@ -158,6 +160,9 @@ static VOID (CDECL *pRtlUnwindEx)(VOID*, VOID*, EXCEPTION_RECORD*, VOID*, C static int (CDECL *p_setjmp)(_JUMP_BUFFER*); #endif
+static int my_argc; +static char** my_argv; + #ifdef __i386__
#ifndef __WINE_WINTRNL_H @@ -167,8 +172,6 @@ static int (CDECL *p_setjmp)(_JUMP_BUFFER*); #define MEM_EXECUTE_OPTION_PERMANENT 0x08 #endif
-static int my_argc; -static char** my_argv; static int test_stage;
static BOOL is_wow64; @@ -3190,6 +3193,138 @@ static void test_suspend_thread(void) CloseHandle(thread); }
+static const char *suspend_process_event_name = "suspend_process_event"; +static const char *suspend_process_event2_name = "suspend_process_event2"; + +static DWORD WINAPI dummy_thread_proc( void *arg ) +{ + return 0; +} + +static void suspend_process_proc(void) +{ + HANDLE event = OpenEventA(SYNCHRONIZE, FALSE, suspend_process_event_name); + HANDLE event2 = OpenEventA(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, suspend_process_event2_name); + unsigned int count; + NTSTATUS status; + HANDLE thread; + + ok(event != NULL, "Failed to open event handle.\n"); + ok(event2 != NULL, "Failed to open event handle.\n"); + + thread = CreateThread(NULL, 0, dummy_thread_proc, 0, CREATE_SUSPENDED, NULL); + ok(thread != NULL, "Failed to create auxiliary thread.\n"); + + /* Suspend up to limit. */ + while (!(status = NtSuspendThread(thread, NULL))) + ; + ok(status == STATUS_SUSPEND_COUNT_EXCEEDED, "Unexpected status %#x.\n", status); + + for (;;) + { + SetEvent(event2); + if (WaitForSingleObject(event, 100) == WAIT_OBJECT_0) + break; + } + + status = NtSuspendThread(thread, &count); + ok(!status, "Failed to suspend a thread, status %#x.\n", status); + ok(count == 125, "Unexpected suspend count %u.\n", count); + + status = NtResumeThread(thread, NULL); + ok(!status, "Failed to resume a thread, status %#x.\n", status); + + CloseHandle(event); + CloseHandle(event2); +} + +static void test_suspend_process(void) +{ + PROCESS_INFORMATION info; + char path_name[MAX_PATH]; + STARTUPINFOA startup; + HANDLE event, event2; + NTSTATUS status; + char **argv; + DWORD ret; + + event = CreateEventA(NULL, FALSE, FALSE, suspend_process_event_name); + ok(event != NULL, "Failed to create event.\n"); + + event2 = CreateEventA(NULL, FALSE, FALSE, suspend_process_event2_name); + ok(event2 != NULL, "Failed to create event.\n"); + + winetest_get_mainargs(&argv); + memset(&startup, 0, sizeof(startup)); + startup.cb = sizeof(startup); + sprintf(path_name, "%s exception suspend_process", argv[0]); + + ret = CreateProcessA(NULL, path_name, NULL, NULL, FALSE, 0, NULL, NULL, &startup, &info), + ok(ret, "Failed to create target process.\n"); + + /* New process signals this event. */ + ResetEvent(event2); + ret = WaitForSingleObject(event2, INFINITE); + ok(ret == WAIT_OBJECT_0, "Wait failed, %#x.\n", ret); + + /* Suspend main thread */ + status = NtSuspendThread(info.hThread, &ret); + ok(!status && !ret, "Failed to suspend main thread, status %#x.\n", status); + + /* Process wasn't suspended yet. */ + status = pNtResumeProcess(info.hProcess); + ok(!status, "Failed to resume a process, status %#x.\n", status); + + status = pNtSuspendProcess(0); + ok(status == STATUS_INVALID_HANDLE, "Unexpected status %#x.\n", status); + + status = pNtResumeProcess(info.hProcess); + ok(!status, "Failed to resume a process, status %#x.\n", status); + + ResetEvent(event2); + ret = WaitForSingleObject(event2, 200); + ok(ret == WAIT_OBJECT_0, "Wait failed.\n"); + + status = pNtSuspendProcess(info.hProcess); + ok(!status, "Failed to suspend a process, status %#x.\n", status); + + status = NtSuspendThread(info.hThread, &ret); + ok(!status && ret == 1, "Failed to suspend main thread, status %#x.\n", status); + status = NtResumeThread(info.hThread, &ret); + ok(!status && ret == 2, "Failed to resume main thread, status %#x.\n", status); + + ResetEvent(event2); + ret = WaitForSingleObject(event2, 200); + ok(ret == WAIT_TIMEOUT, "Wait failed.\n"); + + status = pNtSuspendProcess(info.hProcess); + ok(!status, "Failed to suspend a process, status %#x.\n", status); + + status = pNtResumeProcess(info.hProcess); + ok(!status, "Failed to resume a process, status %#x.\n", status); + + ResetEvent(event2); + ret = WaitForSingleObject(event2, 200); + ok(ret == WAIT_TIMEOUT, "Wait failed.\n"); + + status = pNtResumeProcess(info.hProcess); + ok(!status, "Failed to resume a process, status %#x.\n", status); + + ResetEvent(event2); + ret = WaitForSingleObject(event2, 200); + ok(ret == WAIT_OBJECT_0, "Wait failed.\n"); + + SetEvent(event); + + winetest_wait_child_process(info.hProcess); + + CloseHandle(info.hProcess); + CloseHandle(info.hThread); + + CloseHandle(event); + CloseHandle(event2); +} + START_TEST(exception) { HMODULE hntdll = GetModuleHandleA("ntdll.dll"); @@ -3197,6 +3332,14 @@ START_TEST(exception) HMODULE hmsvcrt = LoadLibraryA("msvcrt.dll"); #endif
+ my_argc = winetest_get_mainargs( &my_argv ); + + if (my_argc >= 3 && !strcmp(my_argv[2], "suspend_process")) + { + suspend_process_proc(); + return; + } + code_mem = VirtualAlloc(NULL, 65536, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if(!code_mem) { trace("VirtualAlloc failed\n"); @@ -3224,6 +3367,8 @@ START_TEST(exception) pNtSetInformationProcess = (void*)GetProcAddress( hntdll, "NtSetInformationProcess" ); pIsWow64Process = (void *)GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsWow64Process"); + pNtSuspendProcess = (void *)GetProcAddress( hntdll, "NtSuspendProcess" ); + pNtResumeProcess = (void *)GetProcAddress( hntdll, "NtResumeProcess" );
#ifdef __i386__ if (!pIsWow64Process || !pIsWow64Process( GetCurrentProcess(), &is_wow64 )) is_wow64 = FALSE; @@ -3309,6 +3454,7 @@ START_TEST(exception) test_prot_fault(); test_thread_context(); test_suspend_thread(); + test_suspend_process();
#elif defined(__x86_64__) pRtlAddFunctionTable = (void *)GetProcAddress( hntdll, @@ -3351,6 +3497,7 @@ START_TEST(exception) test_dpe_exceptions(); test_wow64_context(); test_suspend_thread(); + test_suspend_process();
if (pRtlAddFunctionTable && pRtlDeleteFunctionTable && pRtlInstallFunctionTableCallback && pRtlLookupFunctionEntry) test_dynamic_unwind(); diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 88b933cc14..c3d1bb6a59 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -5742,6 +5742,30 @@ struct terminate_job_reply };
+ +struct suspend_process_request +{ + struct request_header __header; + obj_handle_t handle; +}; +struct suspend_process_reply +{ + struct reply_header __header; +}; + + + +struct resume_process_request +{ + struct request_header __header; + obj_handle_t handle; +}; +struct resume_process_reply +{ + struct reply_header __header; +}; + + enum request { REQ_new_process, @@ -6040,6 +6064,8 @@ enum request REQ_set_job_limits, REQ_set_job_completion_port, REQ_terminate_job, + REQ_suspend_process, + REQ_resume_process, REQ_NB_REQUESTS };
@@ -6343,6 +6369,8 @@ union generic_request struct set_job_limits_request set_job_limits_request; struct set_job_completion_port_request set_job_completion_port_request; struct terminate_job_request terminate_job_request; + struct suspend_process_request suspend_process_request; + struct resume_process_request resume_process_request; }; union generic_reply { @@ -6644,8 +6672,10 @@ union generic_reply struct set_job_limits_reply set_job_limits_reply; struct set_job_completion_port_reply set_job_completion_port_reply; struct terminate_job_reply terminate_job_reply; + struct suspend_process_reply suspend_process_reply; + struct resume_process_reply resume_process_reply; };
-#define SERVER_PROTOCOL_VERSION 578 +#define SERVER_PROTOCOL_VERSION 579
#endif /* __WINE_WINE_SERVER_PROTOCOL_H */ diff --git a/server/process.c b/server/process.c index 473d3b1a27..1260baf0a9 100644 --- a/server/process.c +++ b/server/process.c @@ -1705,3 +1705,41 @@ DECL_HANDLER(set_job_completion_port)
release_object( job ); } + +/* Suspend a process */ +DECL_HANDLER(suspend_process) +{ + struct process *process; + + if ((process = get_process_from_handle( req->handle, PROCESS_SUSPEND_RESUME ))) + { + struct list *ptr, *next; + + LIST_FOR_EACH_SAFE( ptr, next, &process->thread_list ) + { + struct thread *thread = LIST_ENTRY( ptr, struct thread, proc_entry ); + suspend_thread( thread ); + } + + release_object( process ); + } +} + +/* Resume a process */ +DECL_HANDLER(resume_process) +{ + struct process *process; + + if ((process = get_process_from_handle( req->handle, PROCESS_SUSPEND_RESUME ))) + { + struct list *ptr, *next; + + LIST_FOR_EACH_SAFE( ptr, next, &process->thread_list ) + { + struct thread *thread = LIST_ENTRY( ptr, struct thread, proc_entry ); + resume_thread( thread ); + } + + release_object( process ); + } +} diff --git a/server/protocol.def b/server/protocol.def index b6ad514463..888e22faf7 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -3909,3 +3909,15 @@ struct handle_info obj_handle_t handle; /* handle to the job */ int status; /* process exit code */ @END + + +/* Suspend a process */ +@REQ(suspend_process) + obj_handle_t handle; /* process handle */ +@END + + +/* Resume a process */ +@REQ(resume_process) + obj_handle_t handle; /* process handle */ +@END diff --git a/server/request.h b/server/request.h index a3e95137e1..a21252c7a6 100644 --- a/server/request.h +++ b/server/request.h @@ -408,6 +408,8 @@ DECL_HANDLER(process_in_job); DECL_HANDLER(set_job_limits); DECL_HANDLER(set_job_completion_port); DECL_HANDLER(terminate_job); +DECL_HANDLER(suspend_process); +DECL_HANDLER(resume_process);
#ifdef WANT_REQUEST_HANDLERS
@@ -710,6 +712,8 @@ static const req_handler req_handlers[REQ_NB_REQUESTS] = (req_handler)req_set_job_limits, (req_handler)req_set_job_completion_port, (req_handler)req_terminate_job, + (req_handler)req_suspend_process, + (req_handler)req_resume_process, };
C_ASSERT( sizeof(affinity_t) == 8 ); @@ -2436,6 +2440,10 @@ C_ASSERT( sizeof(struct set_job_completion_port_request) == 32 ); C_ASSERT( FIELD_OFFSET(struct terminate_job_request, handle) == 12 ); C_ASSERT( FIELD_OFFSET(struct terminate_job_request, status) == 16 ); C_ASSERT( sizeof(struct terminate_job_request) == 24 ); +C_ASSERT( FIELD_OFFSET(struct suspend_process_request, handle) == 12 ); +C_ASSERT( sizeof(struct suspend_process_request) == 16 ); +C_ASSERT( FIELD_OFFSET(struct resume_process_request, handle) == 12 ); +C_ASSERT( sizeof(struct resume_process_request) == 16 );
#endif /* WANT_REQUEST_HANDLERS */
diff --git a/server/thread.c b/server/thread.c index b1d324f7de..7057c9bbd0 100644 --- a/server/thread.c +++ b/server/thread.c @@ -570,7 +570,7 @@ void stop_thread_if_suspended( struct thread *thread ) }
/* suspend a thread */ -static int suspend_thread( struct thread *thread ) +int suspend_thread( struct thread *thread ) { int old_count = thread->suspend; if (thread->suspend < MAXIMUM_SUSPEND_COUNT) @@ -582,7 +582,7 @@ static int suspend_thread( struct thread *thread ) }
/* resume a thread */ -static int resume_thread( struct thread *thread ) +int resume_thread( struct thread *thread ) { int old_count = thread->suspend; if (thread->suspend > 0) diff --git a/server/thread.h b/server/thread.h index bafc08ed42..8bf7d73ead 100644 --- a/server/thread.h +++ b/server/thread.h @@ -131,6 +131,8 @@ extern struct token *thread_get_impersonation_token( struct thread *thread ); extern int set_thread_affinity( struct thread *thread, affinity_t affinity ); extern int is_cpu_supported( enum cpu_type cpu ); extern unsigned int get_supported_cpu_mask(void); +extern int suspend_thread( struct thread *thread ); +extern int resume_thread( struct thread *thread );
/* ptrace functions */
diff --git a/server/trace.c b/server/trace.c index d4d0c3eb8d..5c6325a257 100644 --- a/server/trace.c +++ b/server/trace.c @@ -4582,6 +4582,16 @@ static void dump_terminate_job_request( const struct terminate_job_request *req fprintf( stderr, ", status=%d", req->status ); }
+static void dump_suspend_process_request( const struct suspend_process_request *req ) +{ + fprintf( stderr, " handle=%04x", req->handle ); +} + +static void dump_resume_process_request( const struct resume_process_request *req ) +{ + fprintf( stderr, " handle=%04x", req->handle ); +} + static const dump_func req_dumpers[REQ_NB_REQUESTS] = { (dump_func)dump_new_process_request, (dump_func)dump_exec_process_request, @@ -4879,6 +4889,8 @@ static const dump_func req_dumpers[REQ_NB_REQUESTS] = { (dump_func)dump_set_job_limits_request, (dump_func)dump_set_job_completion_port_request, (dump_func)dump_terminate_job_request, + (dump_func)dump_suspend_process_request, + (dump_func)dump_resume_process_request, };
static const dump_func reply_dumpers[REQ_NB_REQUESTS] = { @@ -5178,6 +5190,8 @@ static const dump_func reply_dumpers[REQ_NB_REQUESTS] = { NULL, NULL, NULL, + NULL, + NULL, };
static const char * const req_names[REQ_NB_REQUESTS] = { @@ -5477,6 +5491,8 @@ static const char * const req_names[REQ_NB_REQUESTS] = { "set_job_limits", "set_job_completion_port", "terminate_job", + "suspend_process", + "resume_process", };
static const struct