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
23 #include "wine/test.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
);
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
;
50 thread
= CreateThread(NULL
, 0, stack_walk_thread
, NULL
, 0, NULL
);
52 /* wait for the thread to suspend itself */
56 count
= SuspendThread(thread
);
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
;
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
;
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
)
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];
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
)
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
);
210 mapping
= MapViewOfFile(hMap
, FILE_MAP_READ
, 0, 0, 0);
213 nthdr
= RtlImageNtHeader(mapping
);
214 if (nthdr
!= NULL
) machine
= nthdr
->FileHeader
.Machine
;
215 UnmapViewOfFile(mapping
);
224 static BOOL
skip_too_old_dbghelp(HANDLE proc
, DWORD64 base
)
226 IMAGEHLP_MODULE im0
= {sizeof(im0
)};
229 /* test if get module info succeeds with oldest structure format */
230 ret
= SymGetModuleInfo(proc
, base
, &im0
);
233 skip("Too old dbghelp. Skipping module tests.\n");
234 ret
= SymCleanup(proc
);
235 ok(ret
, "SymCleanup failed: %lu\n", GetLastError());
238 ok(ret
, "SymGetModuleInfo failed: %lu\n", GetLastError());
242 static BOOL
test_modules(void)
245 char file_system
[MAX_PATH
];
246 char file_wow64
[MAX_PATH
];
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());
304 struct loaded_module_aggregation
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
;
317 memset(&im
, 0, sizeof(im
));
318 im
.SizeOfStruct
= sizeof(im
);
320 image_len
= wcslen(imagename
);
322 ret
= SymGetModuleInfoW64(aggregation
->proc
, base
, &im
);
324 ok(aggregation
->count_exe
&& image_len
>= 4 && !wcscmp(imagename
+ image_len
- 4, L
".exe"),
325 "%ls shouldn't already be loaded\n", imagename
);
328 ok(!ret
, "Module %ls shouldn't be loaded\n", imagename
);
329 ret
= SymLoadModuleExW(aggregation
->proc
, NULL
, imagename
, NULL
, base
, sz
, NULL
, 0);
331 ok(ret
, "SymLoadModuleExW failed on %ls: %lu\n", imagename
, GetLastError());
332 ret
= SymGetModuleInfoW64(aggregation
->proc
, base
, &im
);
334 ok(ret
, "SymGetModuleInfoW64 failed: %lu\n", GetLastError());
336 if (image_len
>= 4 && !wcsicmp(imagename
+ image_len
- 4, L
".exe"))
337 aggregation
->count_exe
++;
343 static void test_loaded_modules(void)
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);
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
);
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);
400 if (GetLastError() == ERROR_FILE_NOT_FOUND
)
401 skip("Skip wow64 test on non compatible platform\n");
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
);
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);
428 if (GetLastError() == ERROR_FILE_NOT_FOUND
)
429 skip("Skip wow64 test on non compatible platform\n");
431 ok(ret
, "CreateProcess failed: %lu\n", GetLastError());
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());
450 ret
= SymCleanup(GetCurrentProcess());
451 ok(ret
, "got error %lu\n", GetLastError());
455 test_loaded_modules();