Intention is to use `CreateToolhelp32Snapshot` in `dbghelp.dll: EnumerateLoadedModulesW64`. Right now we can't because `CreateToolhelp32Snapshot` can't capture 32bit modules in wow64 processes, which is needed to support `SYMOPT_INCLUDE_32BIT_MODULES`.
The advantage of using `CreateToolhelp32Snapshot` in mainly performance. Right now `EnumerateLoadedModulesW64` is `O(n^2)`, because of each module it calls `GetModuleInformation` which is itself `O(n)` w.r.t. number of modules. Whereas with `CreateToolhelp32Snapshot` there is no need for `GetModuleInformation`, thus reduce the complexity to just `O(n)`.
* * *
P.S. I am also uncomfortable with the amount of similar code between `CreateToolhelp32Snapshot` and `EnumProcessModulesEx`. Can something be done here?
-- v9: kernel32: Implement TH32CS_SNAPMODULE32 support for CreateToolhelp32Snapshot. kernel32: Use GetCurrentProcess() handle in CreateToolhelp32Snapshot if possible. kernel32/tests: Test CreateToolhelp32Snapshot with TH32CS_SNAPMODULE32. kernel32: Fix CreateToolhelp32Snapshot on old WoW64. include: Add TH32CS_SNAPMODULE32. kernel32/tests: Handle ERROR_BAD_LENGTH from CreateToolhelp32Snapshot.
From: Yuxuan Shui yshui@codeweavers.com
CreateToolhelp32Snapshot returns INVALID_HANDLE_VALUE in case of failure, not NULL. --- dlls/kernel32/tests/toolhelp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dlls/kernel32/tests/toolhelp.c b/dlls/kernel32/tests/toolhelp.c index 02ec0a30b81..79dbb04ac88 100644 --- a/dlls/kernel32/tests/toolhelp.c +++ b/dlls/kernel32/tests/toolhelp.c @@ -388,7 +388,7 @@ static void test_module(DWORD pid, const char* expected[], unsigned num_expected ok(ARRAY_SIZE(found) >= num_expected, "Internal: bump found[] size\n");
hSnapshot = pCreateToolhelp32Snapshot( TH32CS_SNAPMODULE, pid ); - ok(hSnapshot != NULL, "Cannot create snapshot\n"); + ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot\n");
for (i = 0; i < num_expected; i++) found[i] = 0; me.dwSize = sizeof(me);
From: Yuxuan Shui yshui@codeweavers.com
According to Microsoft's documentation on CreateToolhelp32Snapshot:
If the function fails with ERROR_BAD_LENGTH, retry the function until it succeeds.
--- dlls/kernel32/tests/toolhelp.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/dlls/kernel32/tests/toolhelp.c b/dlls/kernel32/tests/toolhelp.c index 79dbb04ac88..239feceab33 100644 --- a/dlls/kernel32/tests/toolhelp.c +++ b/dlls/kernel32/tests/toolhelp.c @@ -375,6 +375,21 @@ static const char* sub_expected_modules[] = "ntdll.dll" };
+static HANDLE create_toolhelp_snapshot( DWORD flags, DWORD pid ) +{ + HANDLE hSnapshot; + int retries = 5; + + do + { + hSnapshot = pCreateToolhelp32Snapshot( flags, pid ); + if (hSnapshot != INVALID_HANDLE_VALUE || GetLastError() != ERROR_BAD_LENGTH) + break; + } while (--retries); + + return hSnapshot; +} + static void test_module(DWORD pid, const char* expected[], unsigned num_expected) { HANDLE hSnapshot; @@ -387,7 +402,7 @@ static void test_module(DWORD pid, const char* expected[], unsigned num_expected
ok(ARRAY_SIZE(found) >= num_expected, "Internal: bump found[] size\n");
- hSnapshot = pCreateToolhelp32Snapshot( TH32CS_SNAPMODULE, pid ); + hSnapshot = create_toolhelp_snapshot( TH32CS_SNAPMODULE, pid ); ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot\n");
for (i = 0; i < num_expected; i++) found[i] = 0;
From: Yuxuan Shui yshui@codeweavers.com
Reference: https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-cre... --- include/tlhelp32.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/tlhelp32.h b/include/tlhelp32.h index 4b4d224ce28..65e3c12f88b 100644 --- a/include/tlhelp32.h +++ b/include/tlhelp32.h @@ -31,6 +31,7 @@ extern "C" { #define TH32CS_SNAPPROCESS 0x00000002 #define TH32CS_SNAPTHREAD 0x00000004 #define TH32CS_SNAPMODULE 0x00000008 +#define TH32CS_SNAPMODULE32 0x00000010 #define TH32CS_SNAPALL (TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE) #define TH32CS_INHERIT 0x80000000
From: Yuxuan Shui yshui@codeweavers.com
On old WoW64 PEB->LdrData is NULL, so later ReadProcessMemory(&NULL->InLoadOrderModuleList.Flink) fails. Don't return failure in this case. --- dlls/kernel32/toolhelp.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/dlls/kernel32/toolhelp.c b/dlls/kernel32/toolhelp.c index 5e1ec84d100..8d8920703bd 100644 --- a/dlls/kernel32/toolhelp.c +++ b/dlls/kernel32/toolhelp.c @@ -92,11 +92,16 @@ static BOOL fetch_module( DWORD process, DWORD flags, LDR_DATA_TABLE_ENTRY **ldr if (set_ntstatus( NtQueryInformationProcess( hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL ))) { - if (ReadProcessMemory( hProcess, &pbi.PebBaseAddress->LdrData, - &pLdrData, sizeof(pLdrData), NULL ) && - ReadProcessMemory( hProcess, - &pLdrData->InLoadOrderModuleList.Flink, - &curr, sizeof(curr), NULL )) + if (!ReadProcessMemory( hProcess, &pbi.PebBaseAddress->LdrData, + &pLdrData, sizeof(pLdrData), NULL ) || + (pLdrData && !ReadProcessMemory( hProcess, &pLdrData->InLoadOrderModuleList.Flink, + &curr, sizeof(curr), NULL ))) + { + goto out; + } + + /* pLdrData is NULL on "old" wow64. Don't fail, just return an empty modules list. */ + if (pLdrData) { head = &pLdrData->InLoadOrderModuleList;
@@ -124,10 +129,11 @@ static BOOL fetch_module( DWORD process, DWORD flags, LDR_DATA_TABLE_ENTRY **ldr else HeapFree( GetProcessHeap(), 0, (*ldr_mod)[*num].BaseDllName.Buffer ); } - ret = TRUE; } + ret = TRUE; }
+out: if (process) CloseHandle( hProcess ); return ret; }
From: Yuxuan Shui yshui@codeweavers.com
--- dlls/kernel32/tests/toolhelp.c | 244 +++++++++++++++++++++++++++++---- 1 file changed, 219 insertions(+), 25 deletions(-)
diff --git a/dlls/kernel32/tests/toolhelp.c b/dlls/kernel32/tests/toolhelp.c index 239feceab33..2f5358e7ef1 100644 --- a/dlls/kernel32/tests/toolhelp.c +++ b/dlls/kernel32/tests/toolhelp.c @@ -30,6 +30,7 @@ #include "wine/test.h" #include "winuser.h" #include "winternl.h" +#include "winnls.h"
static char selfname[MAX_PATH];
@@ -360,21 +361,56 @@ static void test_thread(DWORD curr_pid, DWORD sub_pcs_pid) ok(!pThread32First( hSnapshot, &te ), "shouldn't return a thread\n"); }
-static const char* curr_expected_modules[] = +struct expected_module { + const char *module; + + /* + * 0 = C:\windows\system32\ + * 1 = C:\windows\syswow64\ + * 2 = don't check + */ + int wow64; +}; + +static struct expected_module curr_expected_modules[] = +{ + {"kernel32_test.exe", 2}, + {"kernel32.dll"}, + {"ntdll.dll"}, +}; + +static struct expected_module sub_expected_modules[] = +{ + {"kernel32_test.exe", 2}, + {"kernel32.dll"}, + {"shell32.dll"}, + {"ntdll.dll"}, +}; + +static struct expected_module msinfo32_32_expected_modules[] = { - "kernel32_test.exe", - "kernel32.dll", - "ntdll.dll" + {"msinfo32.exe", 1}, + {"kernel32.dll", 1}, + {"shell32.dll", 1}, + {"ntdll.dll", 1}, + {"ntdll.dll"}, + {"wow64.dll"}, + {"wow64win.dll"}, + {"wow64cpu.dll"}, };
-static const char* sub_expected_modules[] = +static struct expected_module msinfo32_64_expected_modules[] = { - "kernel32_test.exe", - "kernel32.dll", - "shell32.dll", - "ntdll.dll" + {"msinfo32.exe", 1}, + {"ntdll.dll"}, + {"wow64.dll"}, + {"wow64win.dll"}, + {"wow64cpu.dll"}, };
+static char syswow64[MAX_PATH], system32[MAX_PATH]; +static int syswow64_len, system32_len; + static HANDLE create_toolhelp_snapshot( DWORD flags, DWORD pid ) { HANDLE hSnapshot; @@ -390,20 +426,94 @@ static HANDLE create_toolhelp_snapshot( DWORD flags, DWORD pid ) return hSnapshot; }
-static void test_module(DWORD pid, const char* expected[], unsigned num_expected) +static BOOL match_module(const MODULEENTRY32 *module, const struct expected_module *pattern) +{ + if (lstrcmpiA(module->szModule, pattern->module)) return FALSE; + if (pattern->wow64 == 2) return TRUE; + if (pattern->wow64 == 1) + { + if (lstrlenA(module->szExePath) < syswow64_len) return FALSE; + return CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, module->szExePath, syswow64_len, + syswow64, syswow64_len) == CSTR_EQUAL; + } + if (lstrlenA(module->szExePath) < system32_len) return FALSE; + return CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, module->szExePath, system32_len, + system32, system32_len) == CSTR_EQUAL; +} + +static const BOOL is_win64 = sizeof(void*) > sizeof(int); + +static BOOL is_old_wow_pid(DWORD pid) +{ + PROCESS_BASIC_INFORMATION pbi; + PPEB_LDR_DATA pLdrData = NULL; + HANDLE hProcess = OpenProcess( PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid ); + NTSTATUS status = NtQueryInformationProcess( hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), + NULL ); + BOOL ret = FALSE; + + if (status != STATUS_SUCCESS) return FALSE; + + if (!IsWow64Process( hProcess, &ret ) || !ret) return FALSE; + + if (!ReadProcessMemory( hProcess, &pbi.PebBaseAddress->LdrData, &pLdrData, sizeof(pLdrData), NULL )) + { + CloseHandle( hProcess ); + return FALSE; + } + + ret = !pLdrData; + CloseHandle( hProcess ); + return ret; +} + +/* Test to ensure no module is returned when TH32CS_SNAPMODULE32 is set without TH32CS_SNAPMODULE */ +static void test_module32_only(DWORD pid) +{ + HANDLE hSnapshot; + MODULEENTRY32 me; + + hSnapshot = create_toolhelp_snapshot( TH32CS_SNAPMODULE32, pid ); + todo_wine ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot\n"); + if (hSnapshot == INVALID_HANDLE_VALUE && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) + { + skip("Cannot create snapshot handle\n"); + return; + } + + ok(!pModule32First( hSnapshot, &me ), "Got unexpected module entry\n"); + CloseHandle( hSnapshot ); +} + +static void test_module(DWORD pid, struct expected_module expected[], unsigned num_expected, BOOL module32) { + const BOOL is_old_wow = is_old_wow_pid(pid); HANDLE hSnapshot; PROCESSENTRY32 pe; THREADENTRY32 te; MODULEENTRY32 me; + DWORD snapshot_flags = TH32CS_SNAPMODULE | (module32 ? TH32CS_SNAPMODULE32 : 0); unsigned found[32]; unsigned i; + int expected_main_exe_count = 0; int num = 0;
ok(ARRAY_SIZE(found) >= num_expected, "Internal: bump found[] size\n");
- hSnapshot = create_toolhelp_snapshot( TH32CS_SNAPMODULE, pid ); - ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot\n"); + if (is_win64) + { + if (module32) expected_main_exe_count += 1; + if (!is_old_wow) expected_main_exe_count += 1; + } + else expected_main_exe_count = 1; + + hSnapshot = create_toolhelp_snapshot( snapshot_flags, pid ); + ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot %#lx\n", GetLastError()); + if (hSnapshot == INVALID_HANDLE_VALUE && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) + { + skip("CreateToolhelp32Snapshot doesn't support requested flags\n"); + return; + }
for (i = 0; i < num_expected; i++) found[i] = 0; me.dwSize = sizeof(me); @@ -415,13 +525,16 @@ static void test_module(DWORD pid, const char* expected[], unsigned num_expected me.th32ProcessID, me.modBaseAddr, me.modBaseSize, me.szExePath, me.szModule); ok(me.th32ProcessID == pid, "wrong returned process id\n"); for (i = 0; i < num_expected; i++) - if (!lstrcmpiA(expected[i], me.szModule)) found[i]++; + if (match_module(&me, &expected[i])) found[i]++; num++; } while (pModule32Next( hSnapshot, &me )); } - for (i = 0; i < num_expected; i++) + todo_if(winetest_platform_is_wine && module32 && is_win64) + ok(found[0] == expected_main_exe_count, "Main exe was found %d time(s)\n", found[0]); + for (i = 1; i < num_expected; i++) + todo_if((winetest_platform_is_wine && expected[i].wow64 == 1) || (is_old_wow && !expected[i].wow64)) ok(found[i] == 1, "Module %s is %s\n", - expected[i], found[i] ? "listed more than once" : "not listed"); + expected[i].module, found[i] ? "listed more than once" : "not listed");
/* check that first really resets the enumeration */ for (i = 0; i < num_expected; i++) found[i] = 0; @@ -433,13 +546,16 @@ static void test_module(DWORD pid, const char* expected[], unsigned num_expected trace("PID=%lx base=%p size=%lx %s %s\n", me.th32ProcessID, me.modBaseAddr, me.modBaseSize, me.szExePath, me.szModule); for (i = 0; i < num_expected; i++) - if (!lstrcmpiA(expected[i], me.szModule)) found[i]++; + if (match_module(&me, &expected[i])) found[i]++; num--; } while (pModule32Next( hSnapshot, &me )); } - for (i = 0; i < num_expected; i++) + todo_if(winetest_platform_is_wine && module32 && is_win64) + ok(found[0] == expected_main_exe_count, "Main exe was found %d time(s)\n", found[0]); + for (i = 1; i < num_expected; i++) + todo_if((winetest_platform_is_wine && expected[i].wow64 == 1) || (is_old_wow && !expected[i].wow64)) ok(found[i] == 1, "Module %s is %s\n", - expected[i], found[i] ? "listed more than once" : "not listed"); + expected[i].module, found[i] ? "listed more than once" : "not listed"); ok(!num, "mismatch in counting\n");
pe.dwSize = sizeof(pe); @@ -452,6 +568,42 @@ static void test_module(DWORD pid, const char* expected[], unsigned num_expected ok(!pModule32First( hSnapshot, &me ), "shouldn't return a module\n"); }
+struct startup_cb +{ + DWORD pid; + HWND wnd; +}; + +static BOOL CALLBACK startup_cb_window(HWND wnd, LPARAM lParam) +{ + struct startup_cb *info = (struct startup_cb*)lParam; + DWORD pid; + + if (GetWindowThreadProcessId(wnd, &pid) && info->pid == pid && IsWindowVisible(wnd)) + { + info->wnd = wnd; + return FALSE; + } + return TRUE; +} + +static BOOL wait_process_window_visible(HANDLE proc, DWORD pid, DWORD timeout) +{ + DWORD max_tc = GetTickCount() + timeout; + BOOL ret = WaitForInputIdle(proc, timeout); + struct startup_cb info = {pid, NULL}; + + if (!ret) + { + do + { + if (EnumWindows(startup_cb_window, (LPARAM)&info)) + Sleep(100); + } while (!info.wnd && GetTickCount() < max_tc); + } + return info.wnd != NULL; +} + START_TEST(toolhelp) { DWORD pid = GetCurrentProcessId(); @@ -459,12 +611,21 @@ START_TEST(toolhelp) char *p, module[MAX_PATH]; char buffer[MAX_PATH + 21]; SECURITY_ATTRIBUTES sa; - PROCESS_INFORMATION info; - STARTUPINFOA startup; + PROCESS_INFORMATION info, info32 = {0}; + STARTUPINFOA startup, startup32 = {0}; HANDLE ev1, ev2; DWORD w; HANDLE hkernel32 = GetModuleHandleA("kernel32"); HANDLE hntdll = GetModuleHandleA("ntdll.dll"); + BOOL ret; + + if (is_win64) + { + syswow64_len = GetSystemWow64DirectoryA(syswow64, ARRAY_SIZE(syswow64)); + ok(syswow64_len, "Can't GetSystemWow64DirectoryA, %#lx\n", GetLastError()); + } + system32_len = GetSystemDirectoryA(system32, ARRAY_SIZE(system32)); + ok(system32_len, "Can't GetSystemDirectoryA, %#lx\n", GetLastError());
pCreateToolhelp32Snapshot = (VOID *) GetProcAddress(hkernel32, "CreateToolhelp32Snapshot"); pModule32First = (VOID *) GetProcAddress(hkernel32, "Module32First"); @@ -510,15 +671,48 @@ START_TEST(toolhelp) GetModuleFileNameA( 0, module, sizeof(module) ); if (!(p = strrchr( module, '\' ))) p = module; else p++; - curr_expected_modules[0] = p; - sub_expected_modules[0] = p; + curr_expected_modules[0].module = p; + sub_expected_modules[0].module = p;
test_process(pid, info.dwProcessId); test_thread(pid, info.dwProcessId); test_main_thread(pid, GetCurrentThreadId()); - test_module(pid, curr_expected_modules, ARRAY_SIZE(curr_expected_modules)); - test_module(info.dwProcessId, sub_expected_modules, ARRAY_SIZE(sub_expected_modules)); + test_module(pid, curr_expected_modules, ARRAY_SIZE(curr_expected_modules), FALSE); + test_module(info.dwProcessId, sub_expected_modules, ARRAY_SIZE(sub_expected_modules), FALSE); + test_module32_only(pid); + test_module32_only(info.dwProcessId); + if (is_win64) + { + lstrcpyA(buffer, syswow64); + strcat(buffer, "\msinfo32.exe"); + ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &startup32, &info32); + if (ret) + { + ret = wait_process_window_visible(info32.hProcess, info32.dwProcessId, 5000); + ok(ret, "wait timed out\n"); + + trace("testing 32bit\n"); + test_module(info32.dwProcessId, msinfo32_32_expected_modules, + ARRAY_SIZE(msinfo32_32_expected_modules), TRUE); + test_module(info32.dwProcessId, msinfo32_64_expected_modules, + ARRAY_SIZE(msinfo32_64_expected_modules), FALSE); + test_module32_only(pid); + TerminateProcess(info32.hProcess, 0); + } + else + { + if (GetLastError() == ERROR_FILE_NOT_FOUND) + skip("Skip wow64 test on non compatible platform\n"); + else + ok(ret, "wow64 CreateProcess failed: %#lx\n", GetLastError()); + } + } + else + { + // what happens if TH32CS_SNAPMODULE32 is set on 32bit? + test_module(info.dwProcessId, sub_expected_modules, ARRAY_SIZE(sub_expected_modules), TRUE); + }
SetEvent(ev2); - wait_child_process( &info ); + wait_child_process(&info); }
From: Yuxuan Shui yshui@codeweavers.com
Reference: 4ea0354ecaf6c601d304cce4f7e5a4e6a8542490 --- dlls/kernel32/toolhelp.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/dlls/kernel32/toolhelp.c b/dlls/kernel32/toolhelp.c index 8d8920703bd..28971dbd07e 100644 --- a/dlls/kernel32/toolhelp.c +++ b/dlls/kernel32/toolhelp.c @@ -85,6 +85,7 @@ static BOOL fetch_module( DWORD process, DWORD flags, LDR_DATA_TABLE_ENTRY **ldr { hProcess = OpenProcess( PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, process ); if (!hProcess) return FALSE; + if (RtlIsCurrentProcess( hProcess )) hProcess = GetCurrentProcess(); } else hProcess = GetCurrentProcess();
From: Yuxuan Shui yshui@codeweavers.com
--- dlls/kernel32/kernel_main.c | 2 + dlls/kernel32/kernel_private.h | 1 + dlls/kernel32/tests/toolhelp.c | 19 +---- dlls/kernel32/toolhelp.c | 124 ++++++++++++++++++++++++++++++++- 4 files changed, 128 insertions(+), 18 deletions(-)
diff --git a/dlls/kernel32/kernel_main.c b/dlls/kernel32/kernel_main.c index edf54e84b72..410ba0088ac 100644 --- a/dlls/kernel32/kernel_main.c +++ b/dlls/kernel32/kernel_main.c @@ -35,6 +35,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(process);
static STARTUPINFOA startup_infoA; +BOOL is_wow64;
/*********************************************************************** * set_entry_point @@ -161,6 +162,7 @@ BOOL WINAPI DllMain( HINSTANCE hinst, DWORD reason, LPVOID reserved ) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls( hinst ); + IsWow64Process( GetCurrentProcess(), &is_wow64 ); return process_attach( hinst ); case DLL_PROCESS_DETACH: WritePrivateProfileSectionW( NULL, NULL, NULL ); diff --git a/dlls/kernel32/kernel_private.h b/dlls/kernel32/kernel_private.h index 3c35a4b1bba..c6f81992f29 100644 --- a/dlls/kernel32/kernel_private.h +++ b/dlls/kernel32/kernel_private.h @@ -44,5 +44,6 @@ extern SYSTEM_BASIC_INFORMATION system_info;
extern WCHAR *FILE_name_AtoW( LPCSTR name, BOOL alloc ); extern DWORD FILE_name_WtoA( LPCWSTR src, INT srclen, LPSTR dest, INT destlen ); +extern BOOL is_wow64;
#endif diff --git a/dlls/kernel32/tests/toolhelp.c b/dlls/kernel32/tests/toolhelp.c index 2f5358e7ef1..b459e1092ba 100644 --- a/dlls/kernel32/tests/toolhelp.c +++ b/dlls/kernel32/tests/toolhelp.c @@ -474,13 +474,7 @@ static void test_module32_only(DWORD pid) MODULEENTRY32 me;
hSnapshot = create_toolhelp_snapshot( TH32CS_SNAPMODULE32, pid ); - todo_wine ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot\n"); - if (hSnapshot == INVALID_HANDLE_VALUE && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) - { - skip("Cannot create snapshot handle\n"); - return; - } - + ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot\n"); ok(!pModule32First( hSnapshot, &me ), "Got unexpected module entry\n"); CloseHandle( hSnapshot ); } @@ -509,11 +503,6 @@ static void test_module(DWORD pid, struct expected_module expected[], unsigned n
hSnapshot = create_toolhelp_snapshot( snapshot_flags, pid ); ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot %#lx\n", GetLastError()); - if (hSnapshot == INVALID_HANDLE_VALUE && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) - { - skip("CreateToolhelp32Snapshot doesn't support requested flags\n"); - return; - }
for (i = 0; i < num_expected; i++) found[i] = 0; me.dwSize = sizeof(me); @@ -529,10 +518,9 @@ static void test_module(DWORD pid, struct expected_module expected[], unsigned n num++; } while (pModule32Next( hSnapshot, &me )); } - todo_if(winetest_platform_is_wine && module32 && is_win64) ok(found[0] == expected_main_exe_count, "Main exe was found %d time(s)\n", found[0]); for (i = 1; i < num_expected; i++) - todo_if((winetest_platform_is_wine && expected[i].wow64 == 1) || (is_old_wow && !expected[i].wow64)) + todo_if(is_old_wow && !expected[i].wow64) ok(found[i] == 1, "Module %s is %s\n", expected[i].module, found[i] ? "listed more than once" : "not listed");
@@ -550,10 +538,9 @@ static void test_module(DWORD pid, struct expected_module expected[], unsigned n num--; } while (pModule32Next( hSnapshot, &me )); } - todo_if(winetest_platform_is_wine && module32 && is_win64) ok(found[0] == expected_main_exe_count, "Main exe was found %d time(s)\n", found[0]); for (i = 1; i < num_expected; i++) - todo_if((winetest_platform_is_wine && expected[i].wow64 == 1) || (is_old_wow && !expected[i].wow64)) + todo_if(is_old_wow && !expected[i].wow64) ok(found[i] == 1, "Module %s is %s\n", expected[i].module, found[i] ? "listed more than once" : "not listed"); ok(!num, "mismatch in counting\n"); diff --git a/dlls/kernel32/toolhelp.c b/dlls/kernel32/toolhelp.c index 28971dbd07e..e6275fb751b 100644 --- a/dlls/kernel32/toolhelp.c +++ b/dlls/kernel32/toolhelp.c @@ -69,18 +69,56 @@ static WCHAR *fetch_string( HANDLE hProcess, UNICODE_STRING* us) return local; }
+typedef struct _LDR_DATA_TABLE_ENTRY32 +{ + LIST_ENTRY32 InLoadOrderLinks; + LIST_ENTRY32 InMemoryOrderLinks; + LIST_ENTRY32 InInitializationOrderLinks; + DWORD DllBase; + DWORD EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING32 FullDllName; + UNICODE_STRING32 BaseDllName; +} LDR_DATA_TABLE_ENTRY32; + +typedef LIST_ENTRY32 *PLIST_ENTRY32; + +static inline void ldr_data_table_entry_32to64(LDR_DATA_TABLE_ENTRY *dst, LDR_DATA_TABLE_ENTRY32 *src) +{ + dst->BaseDllName.Buffer = (PWSTR)(DWORD_PTR)src->BaseDllName.Buffer; + dst->BaseDllName.Length = src->BaseDllName.Length; + dst->FullDllName.Buffer = (PWSTR)(DWORD_PTR)src->FullDllName.Buffer; + dst->FullDllName.Length = src->FullDllName.Length; + dst->DllBase = (void *)(DWORD_PTR)src->DllBase; + dst->SizeOfImage = src->SizeOfImage; +} + static BOOL fetch_module( DWORD process, DWORD flags, LDR_DATA_TABLE_ENTRY **ldr_mod, ULONG *num ) { + static const BOOL is_win64 = (sizeof(void *) > sizeof(int)); HANDLE hProcess; PROCESS_BASIC_INFORMATION pbi; PPEB_LDR_DATA pLdrData; PLIST_ENTRY head, curr; + PLIST_ENTRY32 head32, curr32; BOOL ret = FALSE; + BOOL target_wow64; + PEB32* peb32; + DWORD pLdrData32, tmp; + WCHAR system32[MAX_PATH], syswow64[MAX_PATH]; + int system32_len = 0, syswow64_len = 0;
*num = 0;
if (!(flags & TH32CS_SNAPMODULE)) return TRUE;
+ if (flags & TH32CS_SNAPMODULE32) + { + /* need system directory paths for path rewrites */ + if (!(system32_len = GetSystemDirectoryW(system32, ARRAY_SIZE(system32)))) return FALSE; + if (!(syswow64_len = GetSystemWow64DirectoryW(syswow64, ARRAY_SIZE(syswow64)))) return FALSE; + } + if (process) { hProcess = OpenProcess( PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, process ); @@ -90,6 +128,18 @@ static BOOL fetch_module( DWORD process, DWORD flags, LDR_DATA_TABLE_ENTRY **ldr else hProcess = GetCurrentProcess();
+ if (hProcess != GetCurrentProcess()) + { + if (!IsWow64Process( hProcess, &target_wow64 )) return FALSE; + } + else target_wow64 = is_wow64; + + if (is_wow64 && !target_wow64) + { + SetLastError( ERROR_PARTIAL_COPY ); + goto out; + } + if (set_ntstatus( NtQueryInformationProcess( hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL ))) { @@ -101,7 +151,7 @@ static BOOL fetch_module( DWORD process, DWORD flags, LDR_DATA_TABLE_ENTRY **ldr goto out; }
- /* pLdrData is NULL on "old" wow64. Don't fail, just return an empty modules list. */ + /* pLdrData can be NULL on "old" wow64 configuration. Don't fail. */ if (pLdrData) { head = &pLdrData->InLoadOrderModuleList; @@ -131,8 +181,78 @@ static BOOL fetch_module( DWORD process, DWORD flags, LDR_DATA_TABLE_ENTRY **ldr HeapFree( GetProcessHeap(), 0, (*ldr_mod)[*num].BaseDllName.Buffer ); } } + } + + if (!is_win64 || !target_wow64 || !(flags & TH32CS_SNAPMODULE32)) + { ret = TRUE; + goto out; + } + if (!set_ntstatus( NtQueryInformationProcess( hProcess, ProcessWow64Information, + &peb32, sizeof(peb32), NULL )) || + !ReadProcessMemory( hProcess, &peb32->LdrData, &pLdrData32, sizeof(pLdrData32), NULL ) || + !ReadProcessMemory( hProcess, + &((PPEB_LDR_DATA32)(DWORD_PTR)pLdrData32)->InLoadOrderModuleList.Flink, + &tmp, sizeof(tmp), NULL )) + { + goto out; + } + + curr32 = (PLIST_ENTRY32)(DWORD_PTR)tmp; + head32 = &((PPEB_LDR_DATA32)(DWORD_PTR)pLdrData32)->InLoadOrderModuleList; + while (curr32 != head32) + { + LDR_DATA_TABLE_ENTRY32 entry32; + LDR_DATA_TABLE_ENTRY* out_entry; + int full_dll_name_len; + if (!*num) + *ldr_mod = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(LDR_DATA_TABLE_ENTRY) ); + else + *ldr_mod = HeapReAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, *ldr_mod, + (*num + 1) * sizeof(LDR_DATA_TABLE_ENTRY) ); + out_entry = &(*ldr_mod)[*num]; + if (!ReadProcessMemory( hProcess, + CONTAINING_RECORD(curr32, LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks), + &entry32, sizeof(entry32), NULL )) + break; + + curr32 = (PLIST_ENTRY32)(DWORD_PTR)entry32.InLoadOrderLinks.Flink; + ldr_data_table_entry_32to64(out_entry, &entry32); + if (!fetch_string( hProcess, &out_entry->BaseDllName )) continue; + if (!fetch_string( hProcess, &out_entry->FullDllName )) + { + HeapFree( GetProcessHeap(), 0, out_entry->BaseDllName.Buffer ); + continue; + } + + /* rewrite path in system32 into syswow64 for 32bit modules */ + full_dll_name_len = out_entry->FullDllName.Length / sizeof(WCHAR); + if (full_dll_name_len >= system32_len && + CompareStringW( LOCALE_INVARIANT, NORM_IGNORECASE, + system32, system32_len, + out_entry->FullDllName.Buffer, system32_len ) == CSTR_EQUAL) + { + int new_len = full_dll_name_len - system32_len + syswow64_len + 1; + WCHAR *new_path = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, new_len * sizeof(WCHAR) ); + + if (!new_path) + { + HeapFree( GetProcessHeap(), 0, out_entry->BaseDllName.Buffer ); + HeapFree( GetProcessHeap(), 0, out_entry->FullDllName.Buffer ); + continue; + } + + lstrcpyW( new_path, syswow64 ); + memcpy( new_path + syswow64_len, out_entry->FullDllName.Buffer + system32_len, + out_entry->FullDllName.Length - system32_len * sizeof(WCHAR) ); + HeapFree( GetProcessHeap(), 0, out_entry->FullDllName.Buffer ); + out_entry->FullDllName.Buffer = new_path; + out_entry->FullDllName.Length = new_len * sizeof(WCHAR); + } + + (*num)++; } + ret = TRUE;
out: if (process) CloseHandle( hProcess ); @@ -298,7 +418,7 @@ HANDLE WINAPI CreateToolhelp32Snapshot( DWORD flags, DWORD process ) HANDLE hSnapShot = 0;
TRACE("%lx,%lx\n", flags, process ); - if (!(flags & (TH32CS_SNAPPROCESS|TH32CS_SNAPTHREAD|TH32CS_SNAPMODULE))) + if (!(flags & (TH32CS_SNAPPROCESS|TH32CS_SNAPTHREAD|TH32CS_SNAPMODULE|TH32CS_SNAPMODULE32))) { FIXME("flags %lx not implemented\n", flags ); SetLastError( ERROR_CALL_NOT_IMPLEMENTED );
On Tue Dec 2 16:25:53 2025 +0000, eric pouech wrote:
IIRC, this is likely only the case when the calling process is a 32bit querying 64 bit modules into a 64 bit process so the comment is a bit misleading and as this case doesn't exist on Windows, we can decide which behavior to adopt; I'd suggest to return the same value as on new wow (didn't check if it's an error or an empty list) -- coming back to this one, I'm not sure this is correct (cf top comments)
no, 32bit querying 64bit will fail with [ERROR_PARTIAL_COPY](https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-cre...)
this case only exist when querying a 32bit process from a 64bit process on old wow.
since you don't test MODULE32 on old wow in this serie
but i did.