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?
-- v5: kernel32: Implement TH32CS_SNAPMODULE32 support for CreateToolhelp32Snapshot. kernel32: Use GetCurrentProcess() handle in CreateToolheap32Snapshot if possible. kernel32: Test CreateToolhelp32Snapshot with TH32CS_SNAPMODULE32. include: Add TH32CS_SNAPMODULE32. kernel32/tests: Fix CreateToolhelp32Snapshot failure check.
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
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
--- dlls/kernel32/tests/toolhelp.c | 236 +++++++++++++++++++++++++++++---- 1 file changed, 212 insertions(+), 24 deletions(-)
diff --git a/dlls/kernel32/tests/toolhelp.c b/dlls/kernel32/tests/toolhelp.c index 79dbb04ac88..f2f4916cc3b 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,35 +361,134 @@ 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", - "kernel32.dll", - "ntdll.dll" + {"kernel32_test.exe", 2}, + {"kernel32.dll"}, + {"ntdll.dll"}, };
-static const char* sub_expected_modules[] = +static struct expected_module sub_expected_modules[] = { - "kernel32_test.exe", - "kernel32.dll", - "shell32.dll", - "ntdll.dll" + {"kernel32_test.exe", 2}, + {"kernel32.dll"}, + {"shell32.dll"}, + {"ntdll.dll"}, };
-static void test_module(DWORD pid, const char* expected[], unsigned num_expected) +static struct expected_module msinfo32_32_expected_modules[] = { + {"msinfo32.exe", 1}, + {"kernel32.dll", 1}, + {"shell32.dll", 1}, + {"ntdll.dll", 1}, + {"ntdll.dll"}, + {"wow64.dll"}, + {"wow64win.dll"}, + {"wow64cpu.dll"}, +}; + +static struct expected_module msinfo32_64_expected_modules[] = +{ + {"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 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(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; + if (status != STATUS_SUCCESS) 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 = pCreateToolhelp32Snapshot( TH32CS_SNAPMODULE32, pid ); + todo_wine ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot\n"); + if (hSnapshot == INVALID_HANDLE_VALUE) + { + 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 int expected_main_exe_count = (is_win64 && module32) ? 2 : 1; HANDLE hSnapshot; PROCESSENTRY32 pe; THREADENTRY32 te; MODULEENTRY32 me; + DWORD snapshot_flags = TH32CS_SNAPMODULE | (module32 ? TH32CS_SNAPMODULE32 : 0); unsigned found[32]; unsigned i; int num = 0;
ok(ARRAY_SIZE(found) >= num_expected, "Internal: bump found[] size\n");
- hSnapshot = pCreateToolhelp32Snapshot( TH32CS_SNAPMODULE, pid ); + hSnapshot = pCreateToolhelp32Snapshot( snapshot_flags, pid ); + ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot\n"); + 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); @@ -400,13 +500,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 && expected_main_exe_count == 2) + 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) 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; @@ -418,13 +521,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 && expected_main_exe_count == 2) + 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) 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); @@ -437,6 +543,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(); @@ -444,12 +586,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"); @@ -495,15 +646,52 @@ 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"); + if (!winetest_platform_is_wine || !is_old_wow(info32.dwProcessId)) + { + 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); + } + else skip("Skipping TH32CS_SNAPMODULE32 tests on old wow64\n"); + 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 5e1ec84d100..838ce1b69f1 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 | 11 --- dlls/kernel32/toolhelp.c | 126 ++++++++++++++++++++++++++++++++- 4 files changed, 127 insertions(+), 13 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 f2f4916cc3b..1c7e6a6a1fb 100644 --- a/dlls/kernel32/tests/toolhelp.c +++ b/dlls/kernel32/tests/toolhelp.c @@ -456,7 +456,6 @@ static void test_module32_only(DWORD pid) MODULEENTRY32 me;
hSnapshot = pCreateToolhelp32Snapshot( TH32CS_SNAPMODULE32, pid ); - todo_wine ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot\n"); if (hSnapshot == INVALID_HANDLE_VALUE) { skip("Cannot create snapshot handle\n"); @@ -482,13 +481,7 @@ static void test_module(DWORD pid, struct expected_module expected[], unsigned n ok(ARRAY_SIZE(found) >= num_expected, "Internal: bump found[] size\n");
hSnapshot = pCreateToolhelp32Snapshot( snapshot_flags, pid ); - ok(hSnapshot != INVALID_HANDLE_VALUE, "Cannot create snapshot\n"); - 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); @@ -504,10 +497,8 @@ static void test_module(DWORD pid, struct expected_module expected[], unsigned n num++; } while (pModule32Next( hSnapshot, &me )); } - todo_if(winetest_platform_is_wine && expected_main_exe_count == 2) 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) ok(found[i] == 1, "Module %s is %s\n", expected[i].module, found[i] ? "listed more than once" : "not listed");
@@ -525,10 +516,8 @@ static void test_module(DWORD pid, struct expected_module expected[], unsigned n num--; } while (pModule32Next( hSnapshot, &me )); } - todo_if(winetest_platform_is_wine && expected_main_exe_count == 2) 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) 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 838ce1b69f1..585d61517e5 100644 --- a/dlls/kernel32/toolhelp.c +++ b/dlls/kernel32/toolhelp.c @@ -69,18 +69,57 @@ 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; + PPEB_LDR_DATA32 pLdrData32; + PEB32* peb32; + DWORD 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 +129,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 ))) { @@ -125,10 +176,81 @@ static BOOL fetch_module( DWORD process, DWORD flags, LDR_DATA_TABLE_ENTRY **ldr else HeapFree( GetProcessHeap(), 0, (*ldr_mod)[*num].BaseDllName.Buffer ); } - ret = TRUE; } }
+ if (!is_win64 || !target_wow64 || !(flags & TH32CS_SNAPMODULE32)) + { + ret = TRUE; + goto out; + } + if (!set_ntstatus( NtQueryInformationProcess( hProcess, ProcessWow64Information, + &peb32, sizeof(peb32), NULL ))) + goto out; + if (!ReadProcessMemory( hProcess, &peb32->LdrData, &tmp, sizeof(tmp), NULL )) + goto out; + pLdrData32 = (PPEB_LDR_DATA32)(DWORD_PTR)tmp; + if (!ReadProcessMemory( hProcess, &pLdrData32->InLoadOrderModuleList.Flink, + &tmp, sizeof(tmp), NULL )) + goto out; + + curr32 = (PLIST_ENTRY32)(DWORD_PTR)tmp; + head32 = &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 ); return ret; } @@ -292,7 +414,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 Nov 25 12:50:50 2025 +0000, eric pouech wrote:
the provided tests generate failures when run on old wow, and 32bit only system configurations
the old wow configuration is unsupportable (same as in [`EnumProcessModules`](https://gitlab.winehq.org/wine/wine/-/blob/8bbc65df51c7c62c9b62a7b92bfd288ba...)). I add a check to skip these tests on old wow.
On Tue Nov 25 10:30:32 2025 +0000, Yuxuan Shui wrote:
I am currently working on winegstreamer because @huw wants more of my time allocated that way. Once that gets to a reasonable point I'll come back to this.
Hi, I have updated the patch. I think most of the suggestions have been addressed now.
On Tue Nov 25 12:50:50 2025 +0000, Yuxuan Shui wrote:
the old wow configuration is unsupportable (same as in [`EnumProcessModules`](https://gitlab.winehq.org/wine/wine/-/blob/8bbc65df51c7c62c9b62a7b92bfd288ba...)). I add a check to skip these tests on old wow.
AFAIU this is not some specific corner case but the thing is not going to work at all on old wow64? If that is the case I think it is a bit premature to leave internal functionality broken on old wow. It should probably be possible to detect that in the implementation and get loader data from old wow peb?
On Tue Nov 25 16:38:10 2025 +0000, Paul Gofman wrote:
AFAIU this is not some specific corner case but the thing is not going to work at all on old wow64? If that is the case I think it is a bit premature to leave internal functionality broken on old wow. It should probably be possible to detect that in the implementation and get loader data from old wow peb?
what's not supported on the old wow is getting from a (caller) 64bit process the 64bit modules out of a 32bit child
(EnumProcess return the module of same bitness as caller)
but getting the 32bit modules (in the same configuration) (with EnumProcessEx + 32bit flag) does properly work and should be supported in toolhelp
kernelbase APIs (EnumProcessModulesEx), dbghelp (EnumLoadedModules) do support this properly
didn't fully review the update, but AFAICS this MR skips the configuration {64bit caller => 32bit target, listing 32bit modules}, while it shouldn't (and it should work)
what's not supported on the old wow is getting from a (caller) 64bit process the 64bit modules out of a 32bit child
Ah that's very specific to true wow64 indeed, thanks.
I am just worried that EnumProcessModules and similar things work between 32 and 64 bit processes both ways WRT 32 modules in 32 bit process and that is not regressed or new implemented API doesn't support that with new wow64 only.
On Tue Nov 25 17:57:43 2025 +0000, Paul Gofman wrote:
what's not supported on the old wow is getting from a (caller) 64bit
process the 64bit modules out of a 32bit child Ah that's very specific to true wow64 indeed, thanks. I am just worried that EnumProcessModules and similar things work between 32 and 64 bit processes both ways WRT 32 modules in 32 bit process and that is not regressed or new implemented API doesn't support that with new wow64 only.
Yes, on old wow, `TH32CS_SNAPMODULE32` from a 64bit process against a 32bit process will list all 32bit modules and none of the 64bit modules. this is the same behavior `EnumProcessModules` have.
I skipped tests involving `TH32CS_SNAPMODULE32` on old wow altogether, so this "only 32bit modules are listed" is not tested, because it's a behavior only old wow wine has, and is different from native Windows.
On Tue Nov 25 19:51:37 2025 +0000, Yuxuan Shui wrote:
Yes, on old wow, `TH32CS_SNAPMODULE32` from a 64bit process against a 32bit process will list all 32bit modules and none of the 64bit modules. this is the same behavior `EnumProcessModules` have. I skipped tests involving `TH32CS_SNAPMODULE32` on old wow altogether, so this "only 32bit modules are listed" is not tested, because it's a behavior only old wow wine has, and is different from native Windows.
yes it cannot be tested under Windows, but it doesn't mean we cannot test it under Wine only and expect some results
that's what the tests in psapi and dbghelp do.
so we can either add the tests in this MR, or run only the tests (for this configuration) in dbghelp, but the later will require to review the two MRs at once