From 3de37fefce2196029e1935acf2a1767f997e74b7 Mon Sep 17 00:00:00 2001 From: Dmitry Timoshkov Date: Tue, 30 Apr 2013 17:20:24 +0900 Subject: [PATCH] kernel32: Add some tests for PEB and loader locks. --- dlls/kernel32/tests/loader.c | 170 +++++++++++++++++++++++++++++++++-- 1 file changed, 161 insertions(+), 9 deletions(-) diff --git a/dlls/kernel32/tests/loader.c b/dlls/kernel32/tests/loader.c index fa8e97410d7..d11919643fb 100644 --- a/dlls/kernel32/tests/loader.c +++ b/dlls/kernel32/tests/loader.c @@ -53,6 +53,10 @@ static void (WINAPI *pLdrShutdownProcess)(void); static BOOLEAN (WINAPI *pRtlDllShutdownInProgress)(void); static NTSTATUS (WINAPI *pNtAllocateVirtualMemory)(HANDLE, PVOID *, ULONG, SIZE_T *, ULONG, ULONG); static NTSTATUS (WINAPI *pNtFreeVirtualMemory)(HANDLE, PVOID *, SIZE_T *, ULONG); +static NTSTATUS (WINAPI *pLdrLockLoaderLock)(ULONG, ULONG *, ULONG *); +static NTSTATUS (WINAPI *pLdrUnlockLoaderLock)(ULONG, ULONG); +static void (WINAPI *pRtlAcquirePebLock)(void); +static void (WINAPI *pRtlReleasePebLock)(void); static PVOID RVAToAddr(DWORD_PTR rva, HMODULE module) { @@ -1054,12 +1058,14 @@ nt4_is_broken: } #define MAX_COUNT 10 -static HANDLE attached_thread[MAX_COUNT], stop_event, event, mutex, semaphore; +static HANDLE attached_thread[MAX_COUNT]; static DWORD attached_thread_count; -static int test_dll_phase; +HANDLE stop_event, event, mutex, semaphore, loader_lock_event, peb_lock_event, ack_event; +static int test_dll_phase, inside_loader_lock, inside_peb_lock; static DWORD WINAPI mutex_thread_proc(void *param) { + HANDLE wait_list[3]; DWORD ret; ret = WaitForSingleObject(mutex, 0); @@ -1067,10 +1073,31 @@ static DWORD WINAPI mutex_thread_proc(void *param) SetEvent(param); + wait_list[0] = stop_event; + wait_list[1] = loader_lock_event; + wait_list[2] = peb_lock_event; + while (1) { trace("%04u: mutex_thread_proc: still alive\n", GetCurrentThreadId()); - if (WaitForSingleObject(stop_event, 50) != WAIT_TIMEOUT) break; + ret = WaitForMultipleObjects(3, wait_list, FALSE, 50); + if (ret == WAIT_OBJECT_0) break; + else if (ret == WAIT_OBJECT_0 + 1) + { + ULONG loader_lock_magic; + trace("%04u: mutex_thread_proc: Entering loader lock\n", GetCurrentThreadId()); + ret = pLdrLockLoaderLock(0, NULL, &loader_lock_magic); + ok(!ret, "LdrLockLoaderLock error %#x\n", ret); + inside_loader_lock++; + SetEvent(ack_event); + } + else if (ret == WAIT_OBJECT_0 + 2) + { + trace("%04u: mutex_thread_proc: Entering PEB lock\n", GetCurrentThreadId()); + pRtlAcquirePebLock(); + inside_peb_lock++; + SetEvent(ack_event); + } } trace("%04u: mutex_thread_proc: exiting\n", GetCurrentThreadId()); @@ -1113,6 +1140,13 @@ static BOOL WINAPI dll_entry_point(HINSTANCE hinst, DWORD reason, LPVOID param) static LONG noop_thread_started; DWORD ret; + ok(!inside_loader_lock, "inside_loader_lock should not be set\n"); + /* FIXME: remove once Wine is fixed */ + if (inside_peb_lock && reason == DLL_PROCESS_DETACH) todo_wine + ok(!inside_peb_lock, "inside_peb_lock should not be set\n"); + else + ok(!inside_peb_lock, "inside_peb_lock should not be set\n"); + switch (reason) { case DLL_PROCESS_ATTACH: @@ -1133,6 +1167,13 @@ static BOOL WINAPI dll_entry_point(HINSTANCE hinst, DWORD reason, LPVOID param) trace("dll: %p, DLL_PROCESS_DETACH, %p\n", hinst, param); + if (test_dll_phase == 4 || test_dll_phase == 5) + { +todo_wine + ok(0, "dll_entry_point(DLL_PROCESS_DETACH) should not be called\n"); + break; + } + if (test_dll_phase == 0 || test_dll_phase == 3) ok(param != NULL, "dll: param %p\n", param); else @@ -1369,6 +1410,18 @@ static void child_process(const char *dll_name, DWORD target_offset) event = CreateEvent(NULL, TRUE, FALSE, NULL); ok(event != 0, "CreateEvent error %d\n", GetLastError()); + SetLastError(0xdeadbeef); + loader_lock_event = CreateEvent(NULL, FALSE, FALSE, NULL); + ok(loader_lock_event != 0, "CreateEvent error %d\n", GetLastError()); + + SetLastError(0xdeadbeef); + peb_lock_event = CreateEvent(NULL, FALSE, FALSE, NULL); + ok(peb_lock_event != 0, "CreateEvent error %d\n", GetLastError()); + + SetLastError(0xdeadbeef); + ack_event = CreateEvent(NULL, FALSE, FALSE, NULL); + ok(ack_event != 0, "CreateEvent error %d\n", GetLastError()); + file = CreateFile(dll_name, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, 0); if (file == INVALID_HANDLE_VALUE) { @@ -1527,6 +1580,42 @@ todo_wine CloseHandle(stop_event); break; + case 4: + trace("setting loader_lock_event\n"); + SetEvent(loader_lock_event); + WaitForSingleObject(ack_event, 1000); + ok(inside_loader_lock != 0, "inside_loader_lock is not set\n"); + + /* calling NtTerminateProcess should not cause a deadlock */ + trace("call NtTerminateProcess(0, 198)\n"); + ret = pNtTerminateProcess(0, 198); + ok(!ret, "NtTerminateProcess error %#x\n", ret); + + *child_failures = winetest_get_failures(); + + /* Windows fails to release loader lock acquired from another thread, + * so the LdrUnlockLoaderLock call fails here and ExitProcess deadlocks + * later on, so NtTerminateProcess is used instead. + */ + trace("call NtTerminateProcess(GetCurrentProcess(), 198)\n"); + pNtTerminateProcess(GetCurrentProcess(), 198); + ok(0, "NtTerminateProcess should not return\n"); + break; + + case 5: + trace("setting peb_lock_event\n"); + SetEvent(peb_lock_event); + WaitForSingleObject(ack_event, 1000); + ok(inside_peb_lock != 0, "inside_peb_lock is not set\n"); + + *child_failures = winetest_get_failures(); + + /* calling ExitProcess should cause a deadlock */ + trace("call ExitProcess(198)\n"); + ExitProcess(198); + ok(0, "ExitProcess should not return\n"); + break; + default: assert(0); break; @@ -1534,6 +1623,7 @@ todo_wine if (test_dll_phase == 0) expected_code = 195; else if (test_dll_phase == 3) expected_code = 196; + else if (test_dll_phase == 4) expected_code = 198; else expected_code = STILL_ACTIVE; if (expected_code == STILL_ACTIVE) @@ -1561,7 +1651,7 @@ todo_wine *child_failures = winetest_get_failures(); - trace("call ExitProcess()\n"); + trace("call ExitProcess(195)\n"); ExitProcess(195); } @@ -1685,8 +1775,9 @@ static void test_ExitProcess(void) sprintf(cmdline, "\"%s\" loader %s %u 0", argv[0], dll_name, target_offset); ret = CreateProcess(argv[0], cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); ok(ret, "CreateProcess(%s) error %d\n", cmdline, GetLastError()); - ret = WaitForSingleObject(pi.hProcess, 30000); + ret = WaitForSingleObject(pi.hProcess, 10000); ok(ret == WAIT_OBJECT_0, "child process failed to terminate\n"); + if (ret != WAIT_OBJECT_0) TerminateProcess(pi.hProcess, 0); GetExitCodeProcess(pi.hProcess, &ret); ok(ret == 195, "expected exit code 195, got %u\n", ret); if (*child_failures) @@ -1702,8 +1793,9 @@ static void test_ExitProcess(void) sprintf(cmdline, "\"%s\" loader %s %u 1", argv[0], dll_name, target_offset); ret = CreateProcess(argv[0], cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); ok(ret, "CreateProcess(%s) error %d\n", cmdline, GetLastError()); - ret = WaitForSingleObject(pi.hProcess, 30000); + ret = WaitForSingleObject(pi.hProcess, 10000); ok(ret == WAIT_OBJECT_0, "child process failed to terminate\n"); + if (ret != WAIT_OBJECT_0) TerminateProcess(pi.hProcess, 0); GetExitCodeProcess(pi.hProcess, &ret); ok(ret == 195, "expected exit code 195, got %u\n", ret); if (*child_failures) @@ -1719,8 +1811,9 @@ static void test_ExitProcess(void) sprintf(cmdline, "\"%s\" loader %s %u 2", argv[0], dll_name, target_offset); ret = CreateProcess(argv[0], cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); ok(ret, "CreateProcess(%s) error %d\n", cmdline, GetLastError()); - ret = WaitForSingleObject(pi.hProcess, 30000); + ret = WaitForSingleObject(pi.hProcess, 10000); ok(ret == WAIT_OBJECT_0, "child process failed to terminate\n"); + if (ret != WAIT_OBJECT_0) TerminateProcess(pi.hProcess, 0); GetExitCodeProcess(pi.hProcess, &ret); ok(ret == 197, "expected exit code 197, got %u\n", ret); if (*child_failures) @@ -1736,8 +1829,9 @@ static void test_ExitProcess(void) sprintf(cmdline, "\"%s\" loader %s %u 3", argv[0], dll_name, target_offset); ret = CreateProcess(argv[0], cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); ok(ret, "CreateProcess(%s) error %d\n", cmdline, GetLastError()); - ret = WaitForSingleObject(pi.hProcess, 30000); + ret = WaitForSingleObject(pi.hProcess, 10000); ok(ret == WAIT_OBJECT_0, "child process failed to terminate\n"); + if (ret != WAIT_OBJECT_0) TerminateProcess(pi.hProcess, 0); GetExitCodeProcess(pi.hProcess, &ret); ok(ret == 195, "expected exit code 195, got %u\n", ret); if (*child_failures) @@ -1748,6 +1842,60 @@ static void test_ExitProcess(void) CloseHandle(pi.hThread); CloseHandle(pi.hProcess); + /* phase 4 */ + if (pLdrLockLoaderLock && pLdrUnlockLoaderLock) + { + *child_failures = -1; + sprintf(cmdline, "\"%s\" loader %s %u 4", argv[0], dll_name, target_offset); + ret = CreateProcess(argv[0], cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + ok(ret, "CreateProcess(%s) error %d\n", cmdline, GetLastError()); + ret = WaitForSingleObject(pi.hProcess, 10000); + ok(ret == WAIT_OBJECT_0, "child process failed to terminate\n"); + if (ret != WAIT_OBJECT_0) TerminateProcess(pi.hProcess, 0); + GetExitCodeProcess(pi.hProcess, &ret); + ok(ret == 198, "expected exit code 198, got %u\n", ret); + if (*child_failures) + { + trace("%d failures in child process\n", *child_failures); + winetest_add_failures(*child_failures); + } + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + else + win_skip("LdrLockLoaderLock/LdrUnlockLoaderLock are not available on this platform\n"); + + /* phase 5 */ + if (pRtlAcquirePebLock && pRtlReleasePebLock) + { + *child_failures = -1; + sprintf(cmdline, "\"%s\" loader %s %u 5", argv[0], dll_name, target_offset); + ret = CreateProcess(argv[0], cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + ok(ret, "CreateProcess(%s) error %d\n", cmdline, GetLastError()); + ret = WaitForSingleObject(pi.hProcess, 5000); +todo_wine + ok(ret == WAIT_TIMEOUT, "child process should fail to terminate\n"); + if (ret != WAIT_OBJECT_0) + { + trace("terminating child process\n"); + TerminateProcess(pi.hProcess, 199); + } + ret = WaitForSingleObject(pi.hProcess, 1000); + ok(ret == WAIT_OBJECT_0, "child process failed to terminate\n"); + GetExitCodeProcess(pi.hProcess, &ret); +todo_wine + ok(ret == 199, "expected exit code 199, got %u\n", ret); + if (*child_failures) + { + trace("%d failures in child process\n", *child_failures); + winetest_add_failures(*child_failures); + } + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + else + win_skip("RtlAcquirePebLock/RtlReleasePebLock are not available on this platform\n"); + /* test remote process termination */ SetLastError(0xdeadbeef); ret = CreateProcess(argv[0], NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi); @@ -1800,7 +1948,7 @@ static void test_ExitProcess(void) /* Calling GetExitCodeThread() without waiting for thread termination * leads to different results due to a race condition. */ - ret = WaitForSingleObject(thread, 100); + ret = WaitForSingleObject(thread, 1000); ok(ret == WAIT_OBJECT_0, "WaitForSingleObject failed: %x\n", ret); GetExitCodeThread(thread, &ret); ok(ret == 199, "expected exit code 199, got %u\n", ret); @@ -1929,6 +2077,10 @@ START_TEST(loader) pRtlDllShutdownInProgress = (void *)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlDllShutdownInProgress"); pNtAllocateVirtualMemory = (void *)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtAllocateVirtualMemory"); pNtFreeVirtualMemory = (void *)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtFreeVirtualMemory"); + pLdrLockLoaderLock = (void *)GetProcAddress(GetModuleHandle("ntdll.dll"), "LdrLockLoaderLock"); + pLdrUnlockLoaderLock = (void *)GetProcAddress(GetModuleHandle("ntdll.dll"), "LdrUnlockLoaderLock"); + pRtlAcquirePebLock = (void *)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlAcquirePebLock"); + pRtlReleasePebLock = (void *)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlReleasePebLock"); mapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 4096, "winetest_loader"); ok(mapping != 0, "CreateFileMapping failed\n");