Skip to content

Commit 101a1b7

Browse files
Make dbghelp look for PDBs next to their exe/dll.
1 parent 7b7c103 commit 101a1b7

File tree

3 files changed

+181
-0
lines changed

3 files changed

+181
-0
lines changed

.github/workflows/main.yml

+7
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ 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+
- run: cargo test
116+
if: contains(matrix.rust, 'msvc')
117+
env:
118+
RUSTFLAGS: "-C link-arg=/PDBALTPATH:%_PDB%"
119+
113120
# Test that including as a submodule will still work, both with and without
114121
# the `backtrace` feature enabled.
115122
- run: cargo build --manifest-path crates/as-if-std/Cargo.toml

src/dbghelp.rs

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

26+
use alloc::collections::BTreeSet;
27+
use alloc::vec::Vec;
28+
2629
use super::windows::*;
2730
use core::mem;
2831
use core::ptr;
32+
use core::slice;
2933

3034
// Work around `SymGetOptions` and `SymSetOptions` not being present in winapi
3135
// itself. Otherwise this is only used when we're double-checking types against
@@ -65,6 +69,17 @@ mod dbghelp {
6569
CurContext: LPDWORD,
6670
CurFrameIndex: LPDWORD,
6771
) -> BOOL;
72+
pub fn SymGetSearchPathW(
73+
hprocess: HANDLE,
74+
searchpatha: PWSTR,
75+
searchpathlength: u32,
76+
) -> BOOL;
77+
pub fn SymSetSearchPathW(hprocess: HANDLE, searchpatha: PCWSTR) -> BOOL;
78+
pub fn EnumerateLoadedModulesW64(
79+
hprocess: HANDLE,
80+
enumloadedmodulescallback: PENUMLOADED_MODULES_CALLBACKW64,
81+
usercontext: *const c_void,
82+
) -> BOOL;
6883
}
6984

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

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