Skip to content

Commit 7fa4fee

Browse files
Merge #584 from michaelwoerister/dbghelp-search-path
Make dbghelp look for PDBs next to their exe/dll.
2 parents 0249531 + b3a2a03 commit 7fa4fee

File tree

3 files changed

+173
-0
lines changed

3 files changed

+173
-0
lines changed

.github/workflows/main.yml

+11
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ jobs:
110110
- run: ./ci/debuglink-docker.sh
111111
if: contains(matrix.os, 'ubuntu')
112112

113+
# Test that backtraces are still symbolicated if we don't embed an absolute
114+
# path to the PDB file in the binary.
115+
# Add -Cforce-frame-pointers for stability. The test otherwise fails
116+
# non-deterministically on i686-pc-windows-msvc because the stack cannot be
117+
# unwound reliably. This failure is not related to the feature being tested.
118+
- run: cargo clean && cargo test
119+
if: contains(matrix.rust, 'msvc')
120+
name: "Test that backtraces are symbolicated without absolute PDB path"
121+
env:
122+
RUSTFLAGS: "-Clink-arg=/PDBALTPATH:%_PDB% -Cforce-frame-pointers"
123+
113124
# Test that including as a submodule will still work, both with and without
114125
# the `backtrace` feature enabled.
115126
- run: cargo build --manifest-path crates/as-if-std/Cargo.toml

src/dbghelp.rs

+150
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
2424
#![allow(non_snake_case)]
2525

26+
use alloc::vec::Vec;
27+
2628
use super::windows::*;
2729
use core::mem;
2830
use core::ptr;
31+
use core::slice;
2932

3033
// Work around `SymGetOptions` and `SymSetOptions` not being present in winapi
3134
// itself. Otherwise this is only used when we're double-checking types against
@@ -65,6 +68,17 @@ mod dbghelp {
6568
CurContext: LPDWORD,
6669
CurFrameIndex: LPDWORD,
6770
) -> BOOL;
71+
pub fn SymGetSearchPathW(
72+
hprocess: HANDLE,
73+
searchpatha: PWSTR,
74+
searchpathlength: DWORD,
75+
) -> BOOL;
76+
pub fn SymSetSearchPathW(hprocess: HANDLE, searchpatha: PCWSTR) -> BOOL;
77+
pub fn EnumerateLoadedModulesW64(
78+
hprocess: HANDLE,
79+
enumloadedmodulescallback: PENUMLOADED_MODULES_CALLBACKW64,
80+
usercontext: PVOID,
81+
) -> BOOL;
6882
}
6983

7084
pub fn assert_equal_types<T>(a: T, _b: T) -> T {
@@ -174,6 +188,20 @@ dbghelp! {
174188
path: PCWSTR,
175189
invade: BOOL
176190
) -> BOOL;
191+
fn SymGetSearchPathW(
192+
hprocess: HANDLE,
193+
searchpatha: PWSTR,
194+
searchpathlength: DWORD
195+
) -> BOOL;
196+
fn SymSetSearchPathW(
197+
hprocess: HANDLE,
198+
searchpatha: PCWSTR
199+
) -> BOOL;
200+
fn EnumerateLoadedModulesW64(
201+
hprocess: HANDLE,
202+
enumloadedmodulescallback: PENUMLOADED_MODULES_CALLBACKW64,
203+
usercontext: PVOID
204+
) -> BOOL;
177205
fn StackWalk64(
178206
MachineType: DWORD,
179207
hProcess: HANDLE,
@@ -372,11 +400,133 @@ pub fn init() -> Result<Init, ()> {
372400
// get to initialization first and the other will pick up that
373401
// initialization.
374402
DBGHELP.SymInitializeW().unwrap()(GetCurrentProcess(), ptr::null_mut(), TRUE);
403+
404+
// The default search path for dbghelp will only look in the current working
405+
// directory and (possibly) `_NT_SYMBOL_PATH` and `_NT_ALT_SYMBOL_PATH`.
406+
// However, we also want to look in the directory of the executable
407+
// and each DLL that is loaded. To do this, we need to update the search path
408+
// to include these directories.
409+
//
410+
// See https://learn.microsoft.com/cpp/build/reference/pdbpath for an
411+
// example of where symbols are usually searched for.
412+
let mut search_path_buf = Vec::new();
413+
search_path_buf.resize(1024, 0);
414+
415+
// Prefill the buffer with the current search path.
416+
if DBGHELP.SymGetSearchPathW().unwrap()(
417+
GetCurrentProcess(),
418+
search_path_buf.as_mut_ptr(),
419+
search_path_buf.len() as _,
420+
) == TRUE
421+
{
422+
// Trim the buffer to the actual length of the string.
423+
let len = lstrlenW(search_path_buf.as_mut_ptr());
424+
assert!(len >= 0);
425+
search_path_buf.truncate(len as usize);
426+
} else {
427+
// If getting the search path fails, at least include the current directory.
428+
search_path_buf.clear();
429+
search_path_buf.push(utf16_char('.'));
430+
search_path_buf.push(utf16_char(';'));
431+
}
432+
433+
let mut search_path = SearchPath::new(search_path_buf);
434+
435+
// Update the search path to include the directory of the executable and each DLL.
436+
DBGHELP.EnumerateLoadedModulesW64().unwrap()(
437+
GetCurrentProcess(),
438+
Some(enum_loaded_modules_callback),
439+
((&mut search_path) as *mut SearchPath) as *mut c_void,
440+
);
441+
442+
let new_search_path = search_path.finalize();
443+
444+
// Set the new search path.
445+
DBGHELP.SymSetSearchPathW().unwrap()(GetCurrentProcess(), new_search_path.as_ptr());
446+
375447
INITIALIZED = true;
376448
Ok(ret)
377449
}
378450
}
379451

452+
struct SearchPath {
453+
search_path_utf16: Vec<u16>,
454+
}
455+
456+
fn utf16_char(c: char) -> u16 {
457+
let buf = &mut [0u16; 2];
458+
let buf = c.encode_utf16(buf);
459+
assert!(buf.len() == 1);
460+
buf[0]
461+
}
462+
463+
impl SearchPath {
464+
fn new(initial_search_path: Vec<u16>) -> Self {
465+
Self {
466+
search_path_utf16: initial_search_path,
467+
}
468+
}
469+
470+
/// Add a path to the search path if it is not already present.
471+
fn add(&mut self, path: &[u16]) {
472+
let sep = utf16_char(';');
473+
474+
// We could deduplicate in a case-insensitive way, but case-sensitivity
475+
// can be configured by directory on Windows, so let's not do that.
476+
// https://learn.microsoft.com/windows/wsl/case-sensitivity
477+
if !self
478+
.search_path_utf16
479+
.split(|&c| c == sep)
480+
.any(|p| p == path)
481+
{
482+
if self.search_path_utf16.last() != Some(&sep) {
483+
self.search_path_utf16.push(sep);
484+
}
485+
self.search_path_utf16.extend_from_slice(path);
486+
}
487+
}
488+
489+
fn finalize(mut self) -> Vec<u16> {
490+
// Add a null terminator.
491+
self.search_path_utf16.push(0);
492+
self.search_path_utf16
493+
}
494+
}
495+
496+
extern "system" fn enum_loaded_modules_callback(
497+
module_name: PCWSTR,
498+
_: DWORD64,
499+
_: ULONG,
500+
user_context: PVOID,
501+
) -> BOOL {
502+
// `module_name` is an absolute path like `C:\path\to\module.dll`
503+
// or `C:\path\to\module.exe`
504+
let len: usize = unsafe { lstrlenW(module_name).try_into().unwrap() };
505+
506+
if len == 0 {
507+
// This should not happen, but if it does, we can just ignore it.
508+
return TRUE;
509+
}
510+
511+
let module_name = unsafe { slice::from_raw_parts(module_name, len) };
512+
let path_sep = utf16_char('\\');
513+
let alt_path_sep = utf16_char('/');
514+
515+
let Some(end_of_directory) = module_name
516+
.iter()
517+
.rposition(|&c| c == path_sep || c == alt_path_sep)
518+
else {
519+
// `module_name` being an absolute path, it should always contain at least one
520+
// path separator. If not, there is nothing we can do.
521+
return TRUE;
522+
};
523+
524+
let search_path = unsafe { &mut *(user_context as *mut SearchPath) };
525+
search_path.add(&module_name[..end_of_directory]);
526+
527+
TRUE
528+
}
529+
380530
impl Drop for Init {
381531
fn drop(&mut self) {
382532
unsafe {

src/windows.rs

+12
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ cfg_if::cfg_if! {
5454
ContextPointers: PKNONVOLATILE_CONTEXT_POINTERS
5555
) -> PEXCEPTION_ROUTINE;
5656
}
57+
58+
// winapi doesn't have this type
59+
pub type PENUMLOADED_MODULES_CALLBACKW64 = Option<
60+
unsafe extern "system" fn(
61+
modulename: PCWSTR,
62+
modulebase: DWORD64,
63+
modulesize: ULONG,
64+
usercontext: PVOID,
65+
) -> BOOL,
66+
>;
5767
}
5868
} else {
5969
pub use core::ffi::c_void;
@@ -291,6 +301,7 @@ ffi! {
291301
pub type PTRANSLATE_ADDRESS_ROUTINE64 = Option<
292302
unsafe extern "system" fn(hProcess: HANDLE, hThread: HANDLE, lpaddr: LPADDRESS64) -> DWORD64,
293303
>;
304+
pub type PENUMLOADED_MODULES_CALLBACKW64 = Option<unsafe extern "system" fn(modulename: PCWSTR, modulebase: DWORD64, modulesize: ULONG, usercontext: PVOID) -> BOOL>;
294305
pub type PGET_MODULE_BASE_ROUTINE64 =
295306
Option<unsafe extern "system" fn(hProcess: HANDLE, Address: DWORD64) -> DWORD64>;
296307
pub type PFUNCTION_TABLE_ACCESS_ROUTINE64 =
@@ -444,6 +455,7 @@ ffi! {
444455
hSnapshot: HANDLE,
445456
lpme: LPMODULEENTRY32W,
446457
) -> BOOL;
458+
pub fn lstrlenW(lpstring: PCWSTR) -> i32;
447459
}
448460
}
449461

0 commit comments

Comments
 (0)