dbghelp/tests: Add tests for 'module' name in EnumLoadedModules() callback.
[wine.git] / dlls / dbghelp / tests / dbghelp.c
blob141c797abfc5ecff9bd13ab38ce6bed140c85354
1 /*
2 * Copyright 2018 Zebediah Figura
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 #include "windows.h"
20 #include "psapi.h"
21 #include "verrsrc.h"
22 #include "dbghelp.h"
23 #include "wine/test.h"
24 #include "winternl.h"
26 static const BOOL is_win64 = sizeof(void*) > sizeof(int);
28 #if defined(__i386__) || defined(__x86_64__)
30 static DWORD CALLBACK stack_walk_thread(void *arg)
32 DWORD count = SuspendThread(GetCurrentThread());
33 ok(!count, "got %ld\n", count);
34 return 0;
37 static void test_stack_walk(void)
39 char si_buf[sizeof(SYMBOL_INFO) + 200];
40 SYMBOL_INFO *si = (SYMBOL_INFO *)si_buf;
41 STACKFRAME64 frame = {{0}}, frame0;
42 BOOL found_our_frame = FALSE;
43 DWORD machine;
44 HANDLE thread;
45 DWORD64 disp;
46 CONTEXT ctx;
47 DWORD count;
48 BOOL ret;
50 thread = CreateThread(NULL, 0, stack_walk_thread, NULL, 0, NULL);
52 /* wait for the thread to suspend itself */
55 Sleep(50);
56 count = SuspendThread(thread);
57 ResumeThread(thread);
59 while (!count);
61 ctx.ContextFlags = CONTEXT_CONTROL;
62 ret = GetThreadContext(thread, &ctx);
63 ok(ret, "got error %u\n", ret);
65 frame.AddrPC.Mode = AddrModeFlat;
66 frame.AddrFrame.Mode = AddrModeFlat;
67 frame.AddrStack.Mode = AddrModeFlat;
69 #ifdef __i386__
70 machine = IMAGE_FILE_MACHINE_I386;
72 frame.AddrPC.Segment = ctx.SegCs;
73 frame.AddrPC.Offset = ctx.Eip;
74 frame.AddrFrame.Segment = ctx.SegSs;
75 frame.AddrFrame.Offset = ctx.Ebp;
76 frame.AddrStack.Segment = ctx.SegSs;
77 frame.AddrStack.Offset = ctx.Esp;
78 #elif defined(__x86_64__)
79 machine = IMAGE_FILE_MACHINE_AMD64;
81 frame.AddrPC.Segment = ctx.SegCs;
82 frame.AddrPC.Offset = ctx.Rip;
83 frame.AddrFrame.Segment = ctx.SegSs;
84 frame.AddrFrame.Offset = ctx.Rbp;
85 frame.AddrStack.Segment = ctx.SegSs;
86 frame.AddrStack.Offset = ctx.Rsp;
87 #endif
88 frame0 = frame;
90 /* first invocation just calculates the return address */
91 ret = StackWalk64(machine, GetCurrentProcess(), thread, &frame, &ctx, NULL,
92 SymFunctionTableAccess64, SymGetModuleBase64, NULL);
93 ok(ret, "StackWalk64() failed: %lu\n", GetLastError());
94 ok(frame.AddrPC.Offset == frame0.AddrPC.Offset, "expected %s, got %s\n",
95 wine_dbgstr_longlong(frame0.AddrPC.Offset),
96 wine_dbgstr_longlong(frame.AddrPC.Offset));
97 ok(frame.AddrStack.Offset == frame0.AddrStack.Offset, "expected %s, got %s\n",
98 wine_dbgstr_longlong(frame0.AddrStack.Offset),
99 wine_dbgstr_longlong(frame.AddrStack.Offset));
100 ok(frame.AddrReturn.Offset && frame.AddrReturn.Offset != frame.AddrPC.Offset,
101 "got bad return address %s\n", wine_dbgstr_longlong(frame.AddrReturn.Offset));
103 while (frame.AddrReturn.Offset)
105 char *addr;
107 ret = StackWalk64(machine, GetCurrentProcess(), thread, &frame, &ctx, NULL,
108 SymFunctionTableAccess64, SymGetModuleBase64, NULL);
109 ok(ret, "StackWalk64() failed: %lu\n", GetLastError());
111 addr = (void *)(DWORD_PTR)frame.AddrPC.Offset;
113 if (!found_our_frame && addr > (char *)stack_walk_thread && addr < (char *)stack_walk_thread + 0x100)
115 found_our_frame = TRUE;
117 si->SizeOfStruct = sizeof(SYMBOL_INFO);
118 si->MaxNameLen = 200;
119 if (SymFromAddr(GetCurrentProcess(), frame.AddrPC.Offset, &disp, si))
120 ok(!strcmp(si->Name, "stack_walk_thread"), "got wrong name %s\n", si->Name);
124 ret = StackWalk64(machine, GetCurrentProcess(), thread, &frame, &ctx, NULL,
125 SymFunctionTableAccess64, SymGetModuleBase64, NULL);
126 ok(!ret, "StackWalk64() should have failed\n");
128 ok(found_our_frame, "didn't find stack_walk_thread frame\n");
131 #else /* __i386__ || __x86_64__ */
133 static void test_stack_walk(void)
137 #endif /* __i386__ || __x86_64__ */
139 static void test_search_path(void)
141 char search_path[128];
142 BOOL ret;
144 /* The default symbol path is ".[;%_NT_SYMBOL_PATH%][;%_NT_ALT_SYMBOL_PATH%]".
145 * We unset both variables earlier so should simply get "." */
146 ret = SymGetSearchPath(GetCurrentProcess(), search_path, ARRAY_SIZE(search_path));
147 ok(ret == TRUE, "ret = %d\n", ret);
148 ok(!strcmp(search_path, "."), "Got search path '%s', expected '.'\n", search_path);
150 /* Set an arbitrary search path */
151 ret = SymSetSearchPath(GetCurrentProcess(), "W:\\");
152 ok(ret == TRUE, "ret = %d\n", ret);
153 ret = SymGetSearchPath(GetCurrentProcess(), search_path, ARRAY_SIZE(search_path));
154 ok(ret == TRUE, "ret = %d\n", ret);
155 ok(!strcmp(search_path, "W:\\"), "Got search path '%s', expected 'W:\\'\n", search_path);
157 /* Setting to NULL resets to the default */
158 ret = SymSetSearchPath(GetCurrentProcess(), NULL);
159 ok(ret == TRUE, "ret = %d\n", ret);
160 ret = SymGetSearchPath(GetCurrentProcess(), search_path, ARRAY_SIZE(search_path));
161 ok(ret == TRUE, "ret = %d\n", ret);
162 ok(!strcmp(search_path, "."), "Got search path '%s', expected '.'\n", search_path);
164 /* With _NT_SYMBOL_PATH */
165 SetEnvironmentVariableA("_NT_SYMBOL_PATH", "X:\\");
166 ret = SymSetSearchPath(GetCurrentProcess(), NULL);
167 ok(ret == TRUE, "ret = %d\n", ret);
168 ret = SymGetSearchPath(GetCurrentProcess(), search_path, ARRAY_SIZE(search_path));
169 ok(ret == TRUE, "ret = %d\n", ret);
170 ok(!strcmp(search_path, ".;X:\\"), "Got search path '%s', expected '.;X:\\'\n", search_path);
172 /* With both _NT_SYMBOL_PATH and _NT_ALT_SYMBOL_PATH */
173 SetEnvironmentVariableA("_NT_ALT_SYMBOL_PATH", "Y:\\");
174 ret = SymSetSearchPath(GetCurrentProcess(), NULL);
175 ok(ret == TRUE, "ret = %d\n", ret);
176 ret = SymGetSearchPath(GetCurrentProcess(), search_path, ARRAY_SIZE(search_path));
177 ok(ret == TRUE, "ret = %d\n", ret);
178 ok(!strcmp(search_path, ".;X:\\;Y:\\"), "Got search path '%s', expected '.;X:\\;Y:\\'\n", search_path);
180 /* With just _NT_ALT_SYMBOL_PATH */
181 SetEnvironmentVariableA("_NT_SYMBOL_PATH", NULL);
182 ret = SymSetSearchPath(GetCurrentProcess(), NULL);
183 ok(ret == TRUE, "ret = %d\n", ret);
184 ret = SymGetSearchPath(GetCurrentProcess(), search_path, ARRAY_SIZE(search_path));
185 ok(ret == TRUE, "ret = %d\n", ret);
186 ok(!strcmp(search_path, ".;Y:\\"), "Got search path '%s', expected '.;Y:\\'\n", search_path);
188 /* Restore original search path */
189 SetEnvironmentVariableA("_NT_ALT_SYMBOL_PATH", NULL);
190 ret = SymSetSearchPath(GetCurrentProcess(), NULL);
191 ok(ret == TRUE, "ret = %d\n", ret);
192 ret = SymGetSearchPath(GetCurrentProcess(), search_path, ARRAY_SIZE(search_path));
193 ok(ret == TRUE, "ret = %d\n", ret);
194 ok(!strcmp(search_path, "."), "Got search path '%s', expected '.'\n", search_path);
197 static USHORT get_module_machine(const char* path)
199 HANDLE hFile, hMap;
200 void* mapping;
201 IMAGE_NT_HEADERS *nthdr;
202 USHORT machine = IMAGE_FILE_MACHINE_UNKNOWN;
204 hFile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
205 if (hFile != INVALID_HANDLE_VALUE)
207 hMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
208 if (hMap != NULL)
210 mapping = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
211 if (mapping != NULL)
213 nthdr = RtlImageNtHeader(mapping);
214 if (nthdr != NULL) machine = nthdr->FileHeader.Machine;
215 UnmapViewOfFile(mapping);
217 CloseHandle(hMap);
219 CloseHandle(hFile);
221 return machine;
224 static BOOL skip_too_old_dbghelp(HANDLE proc, DWORD64 base)
226 IMAGEHLP_MODULE im0 = {sizeof(im0)};
227 BOOL ret;
229 /* test if get module info succeeds with oldest structure format */
230 ret = SymGetModuleInfo(proc, base, &im0);
231 if (ret)
233 skip("Too old dbghelp. Skipping module tests.\n");
234 ret = SymCleanup(proc);
235 ok(ret, "SymCleanup failed: %lu\n", GetLastError());
236 return TRUE;
238 ok(ret, "SymGetModuleInfo failed: %lu\n", GetLastError());
239 return FALSE;
242 static BOOL test_modules(void)
244 BOOL ret;
245 char file_system[MAX_PATH];
246 char file_wow64[MAX_PATH];
247 DWORD64 base;
248 const DWORD64 base1 = 0x00010000;
249 const DWORD64 base2 = 0x08010000;
250 IMAGEHLP_MODULEW64 im;
251 USHORT machine_wow, machine2;
253 im.SizeOfStruct = sizeof(im);
255 /* can sym load an exec of different bitness even if 32Bit flag not set */
257 SymSetOptions(SymGetOptions() & ~SYMOPT_INCLUDE_32BIT_MODULES);
258 ret = SymInitialize(GetCurrentProcess(), 0, FALSE);
259 ok(ret, "SymInitialize failed: %lu\n", GetLastError());
261 GetSystemWow64DirectoryA(file_wow64, MAX_PATH);
262 strcat(file_wow64, "\\notepad.exe");
264 /* not always present */
265 machine_wow = get_module_machine(file_wow64);
266 if (machine_wow != IMAGE_FILE_MACHINE_UNKNOWN)
268 base = SymLoadModule(GetCurrentProcess(), NULL, file_wow64, NULL, base2, 0);
269 ok(base == base2, "SymLoadModule failed: %lu\n", GetLastError());
270 ret = SymGetModuleInfoW64(GetCurrentProcess(), base2, &im);
271 if (!ret && skip_too_old_dbghelp(GetCurrentProcess(), base2)) return FALSE;
272 ok(ret, "SymGetModuleInfoW64 failed: %lu\n", GetLastError());
273 ok(im.BaseOfImage == base2, "Wrong base address\n");
274 ok(im.MachineType == machine_wow, "Wrong machine %lx (expecting %u)\n", im.MachineType, machine_wow);
277 GetSystemDirectoryA(file_system, MAX_PATH);
278 strcat(file_system, "\\notepad.exe");
280 base = SymLoadModule(GetCurrentProcess(), NULL, file_system, NULL, base1, 0);
281 ok(base == base1, "SymLoadModule failed: %lu\n", GetLastError());
282 ret = SymGetModuleInfoW64(GetCurrentProcess(), base1, &im);
283 if (!ret && skip_too_old_dbghelp(GetCurrentProcess(), base1)) return FALSE;
284 ok(ret, "SymGetModuleInfoW64 failed: %lu\n", GetLastError());
285 ok(im.BaseOfImage == base1, "Wrong base address\n");
286 machine2 = get_module_machine(file_system);
287 ok(machine2 != IMAGE_FILE_MACHINE_UNKNOWN, "Unexpected machine %u\n", machine2);
288 ok(im.MachineType == machine2, "Wrong machine %lx (expecting %u)\n", im.MachineType, machine2);
290 /* still can access first module after loading second */
291 if (machine_wow != IMAGE_FILE_MACHINE_UNKNOWN)
293 ret = SymGetModuleInfoW64(GetCurrentProcess(), base2, &im);
294 ok(ret, "SymGetModuleInfoW64 failed: %lu\n", GetLastError());
295 ok(im.BaseOfImage == base2, "Wrong base address\n");
296 ok(im.MachineType == machine_wow, "Wrong machine %lx (expecting %u)\n", im.MachineType, machine_wow);
299 ret = SymCleanup(GetCurrentProcess());
300 ok(ret, "SymCleanup failed: %lu\n", GetLastError());
301 return TRUE;
304 struct loaded_module_aggregation
306 HANDLE proc;
307 unsigned int count_exe;
310 static BOOL CALLBACK aggregate_cb(PCWSTR imagename, DWORD64 base, ULONG sz, PVOID usr)
312 struct loaded_module_aggregation* aggregation = usr;
313 IMAGEHLP_MODULEW64 im;
314 size_t image_len;
315 BOOL ret;
317 memset(&im, 0, sizeof(im));
318 im.SizeOfStruct = sizeof(im);
320 image_len = wcslen(imagename);
322 ret = SymGetModuleInfoW64(aggregation->proc, base, &im);
323 if (ret)
324 ok(aggregation->count_exe && image_len >= 4 && !wcscmp(imagename + image_len - 4, L".exe"),
325 "%ls shouldn't already be loaded\n", imagename);
326 else
328 ok(!ret, "Module %ls shouldn't be loaded\n", imagename);
329 ret = SymLoadModuleExW(aggregation->proc, NULL, imagename, NULL, base, sz, NULL, 0);
330 todo_wine
331 ok(ret, "SymLoadModuleExW failed on %ls: %lu\n", imagename, GetLastError());
332 ret = SymGetModuleInfoW64(aggregation->proc, base, &im);
333 todo_wine
334 ok(ret, "SymGetModuleInfoW64 failed: %lu\n", GetLastError());
336 if (image_len >= 4 && !wcsicmp(imagename + image_len - 4, L".exe"))
337 aggregation->count_exe++;
339 return TRUE;
343 static void test_loaded_modules(void)
345 BOOL ret;
346 char buffer[200];
347 PROCESS_INFORMATION pi = {0};
348 STARTUPINFOA si = {0};
349 struct loaded_module_aggregation aggregation = {0};
351 ret = GetSystemDirectoryA(buffer, sizeof(buffer));
352 ok(ret, "got error %lu\n", GetLastError());
353 strcat(buffer, "\\notepad.exe");
355 /* testing with child process of different machines */
356 ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
357 ok(ret, "CreateProcess failed: %lu\n", GetLastError());
359 ret = WaitForInputIdle(pi.hProcess, 5000);
360 ok(!ret, "wait timed out\n");
362 ret = SymInitialize(pi.hProcess, NULL, FALSE);
363 ok(ret, "SymInitialize failed: %lu\n", GetLastError());
364 memset(&aggregation, 0, sizeof(aggregation));
365 aggregation.proc = pi.hProcess;
367 ret = EnumerateLoadedModulesW64(pi.hProcess, aggregate_cb, &aggregation);
368 ok(ret, "EnumerateLoadedModulesW64 failed: %lu\n", GetLastError());
370 SymCleanup(pi.hProcess);
371 TerminateProcess(pi.hProcess, 0);
373 if (is_win64)
375 ret = GetSystemWow64DirectoryA(buffer, sizeof(buffer));
376 ok(ret, "got error %lu\n", GetLastError());
377 strcat(buffer, "\\notepad.exe");
379 SymSetOptions(SymGetOptions() & ~SYMOPT_INCLUDE_32BIT_MODULES);
381 ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
382 if (ret)
384 ret = WaitForInputIdle(pi.hProcess, 5000);
385 ok(!ret, "wait timed out\n");
387 ret = SymInitialize(pi.hProcess, NULL, FALSE);
388 ok(ret, "SymInitialize failed: %lu\n", GetLastError());
389 memset(&aggregation, 0, sizeof(aggregation));
390 aggregation.proc = pi.hProcess;
392 ret = EnumerateLoadedModulesW64(pi.hProcess, aggregate_cb, &aggregation);
393 ok(ret, "EnumerateLoadedModulesW64 failed: %lu\n", GetLastError());
395 SymCleanup(pi.hProcess);
396 TerminateProcess(pi.hProcess, 0);
398 else
400 if (GetLastError() == ERROR_FILE_NOT_FOUND)
401 skip("Skip wow64 test on non compatible platform\n");
402 else
403 ok(ret, "CreateProcess failed: %lu\n", GetLastError());
406 SymSetOptions(SymGetOptions() | SYMOPT_INCLUDE_32BIT_MODULES);
408 ret = CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
409 if (ret)
411 struct loaded_module_aggregation aggregation2 = {0};
413 ret = WaitForInputIdle(pi.hProcess, 5000);
414 ok(!ret, "wait timed out\n");
416 ret = SymInitialize(pi.hProcess, NULL, FALSE);
417 ok(ret, "SymInitialize failed: %lu\n", GetLastError());
418 memset(&aggregation2, 0, sizeof(aggregation2));
419 aggregation2.proc = pi.hProcess;
420 ret = EnumerateLoadedModulesW64(pi.hProcess, aggregate_cb, &aggregation2);
421 ok(ret, "EnumerateLoadedModulesW64 failed: %lu\n", GetLastError());
423 SymCleanup(pi.hProcess);
424 TerminateProcess(pi.hProcess, 0);
426 else
428 if (GetLastError() == ERROR_FILE_NOT_FOUND)
429 skip("Skip wow64 test on non compatible platform\n");
430 else
431 ok(ret, "CreateProcess failed: %lu\n", GetLastError());
436 START_TEST(dbghelp)
438 BOOL ret;
440 /* Don't let the user's environment influence our symbol path */
441 SetEnvironmentVariableA("_NT_SYMBOL_PATH", NULL);
442 SetEnvironmentVariableA("_NT_ALT_SYMBOL_PATH", NULL);
444 ret = SymInitialize(GetCurrentProcess(), NULL, TRUE);
445 ok(ret, "got error %lu\n", GetLastError());
447 test_stack_walk();
448 test_search_path();
450 ret = SymCleanup(GetCurrentProcess());
451 ok(ret, "got error %lu\n", GetLastError());
453 if (test_modules())
455 test_loaded_modules();