Skip to content

Commit

Permalink
Make dbghelp look for PDBs next to their exe/dll.
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelwoerister committed Feb 16, 2024
1 parent 7b7c103 commit 101a1b7
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ jobs:
- run: ./ci/debuglink-docker.sh
if: contains(matrix.os, 'ubuntu')

# Test that backtraces are still symbolicated if we don't embed an absolute
# path to the PDB file in the binary.
- run: cargo test
if: contains(matrix.rust, 'msvc')
env:
RUSTFLAGS: "-C link-arg=/PDBALTPATH:%_PDB%"

# Test that including as a submodule will still work, both with and without
# the `backtrace` feature enabled.
- run: cargo build --manifest-path crates/as-if-std/Cargo.toml
Expand Down
162 changes: 162 additions & 0 deletions src/dbghelp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@

#![allow(non_snake_case)]

use alloc::collections::BTreeSet;
use alloc::vec::Vec;

use super::windows::*;
use core::mem;
use core::ptr;
use core::slice;

// Work around `SymGetOptions` and `SymSetOptions` not being present in winapi
// itself. Otherwise this is only used when we're double-checking types against
Expand Down Expand Up @@ -65,6 +69,17 @@ mod dbghelp {
CurContext: LPDWORD,
CurFrameIndex: LPDWORD,
) -> BOOL;
pub fn SymGetSearchPathW(
hprocess: HANDLE,
searchpatha: PWSTR,
searchpathlength: u32,
) -> BOOL;
pub fn SymSetSearchPathW(hprocess: HANDLE, searchpatha: PCWSTR) -> BOOL;
pub fn EnumerateLoadedModulesW64(
hprocess: HANDLE,
enumloadedmodulescallback: PENUMLOADED_MODULES_CALLBACKW64,
usercontext: *const c_void,
) -> BOOL;
}

pub fn assert_equal_types<T>(a: T, _b: T) -> T {
Expand Down Expand Up @@ -174,6 +189,20 @@ dbghelp! {
path: PCWSTR,
invade: BOOL
) -> BOOL;
fn SymGetSearchPathW(
hprocess: HANDLE,
searchpatha: PWSTR,
searchpathlength: u32
) -> BOOL;
fn SymSetSearchPathW(
hprocess: HANDLE,
searchpatha: PCWSTR
) -> BOOL;
fn EnumerateLoadedModulesW64(
hprocess: HANDLE,
enumloadedmodulescallback: PENUMLOADED_MODULES_CALLBACKW64,
usercontext: *const c_void
) -> BOOL;
fn StackWalk64(
MachineType: DWORD,
hProcess: HANDLE,
Expand Down Expand Up @@ -372,11 +401,144 @@ pub fn init() -> Result<Init, ()> {
// get to initialization first and the other will pick up that
// initialization.
DBGHELP.SymInitializeW().unwrap()(GetCurrentProcess(), ptr::null_mut(), TRUE);

// The default search path for dbghelp will only look in the current working
// directory and (possibly) `_NT_SYMBOL_PATH` and `_NT_ALT_SYMBOL_PATH`.
// However, we also want to look in the directory of the executable
// and each DLL that is loaded. To do this, we need to update the search path
// to include these directories.
//
// See https://learn.microsoft.com/cpp/build/reference/pdbpath for an
// example of where symbols are usually searched for.
let mut search_path_buf = Vec::new();
search_path_buf.resize(1024, 0);

// Prefill the buffer with the current search path.
if DBGHELP.SymGetSearchPathW().unwrap()(
GetCurrentProcess(),
search_path_buf.as_mut_ptr(),
search_path_buf.len() as _,
) == TRUE
{
// Trim the buffer to the actual length of the string.
let len = lstrlenW(search_path_buf.as_mut_ptr());
assert!(len >= 0);
search_path_buf.truncate(len as usize);
} else {
// If getting the search path fails, at least include the current directory.
search_path_buf.clear();
search_path_buf.push(utf16_char('.'));
search_path_buf.push(utf16_char(';'));
}

let mut search_path = SearchPath::new(search_path_buf);

// Update the search path to include the directory of the executable and each DLL.
DBGHELP.EnumerateLoadedModulesW64().unwrap()(
GetCurrentProcess(),
Some(enum_loaded_modules_callback),
&mut search_path as *mut _ as *mut _,
);

let new_search_path = search_path.finalize();

// Set the new search path.
DBGHELP.SymSetSearchPathW().unwrap()(GetCurrentProcess(), new_search_path.as_ptr());

INITIALIZED = true;
Ok(ret)
}
}

struct SearchPath {
search_path_utf16: Vec<u16>,
dedup: BTreeSet<Vec<u16>>,
}

fn utf16_char(c: char) -> u16 {
let buf = &mut [0u16; 2];
let buf = c.encode_utf16(buf);
assert!(buf.len() == 1);
buf[0]
}

fn utf16_str_dedup_string(s: &[u16]) -> Vec<u16> {
// We could deduplicate in a case-insensitive way, but case-sensitivity
// can be configured by directory on Windows, so let's not do that.
// https://learn.microsoft.com/windows/wsl/case-sensitivity
s.to_vec()
}

impl SearchPath {
fn new(initial_search_path: Vec<u16>) -> Self {
let sep = utf16_char(';');

let paths = initial_search_path.split(|&c| c == sep);

let mut dedup = BTreeSet::new();

for path in paths {
dedup.insert(utf16_str_dedup_string(path));
}

Self {
search_path_utf16: initial_search_path,
dedup,
}
}

/// Add a path to the search path if it is not already present.
fn add(&mut self, path: &[u16]) {
if self.dedup.insert(utf16_str_dedup_string(path)) {
let sep = utf16_char(';');
if self.search_path_utf16.last() != Some(&sep) {
self.search_path_utf16.push(sep);
}
self.search_path_utf16.extend_from_slice(path);
}
}

fn finalize(mut self) -> Vec<u16> {
// Add a null terminator.
self.search_path_utf16.push(0);
self.search_path_utf16
}
}

extern "system" fn enum_loaded_modules_callback(
module_name: PCWSTR,
_: u64,
_: u32,
user_context: *const c_void,
) -> BOOL {
// `module_name` is an absolute path like `C:\path\to\module.dll`
// or `C:\path\to\module.exe`
let len: usize = unsafe { lstrlenW(module_name).try_into().unwrap() };

if len == 0 {
// This should not happen, but if it does, we can just ignore it.
return TRUE;
}

let module_name = unsafe { slice::from_raw_parts(module_name, len) };
let path_sep = utf16_char('\\');
let alt_path_sep = utf16_char('/');

let Some(end_of_directory) = module_name
.iter()
.rposition(|&c| c == path_sep || c == alt_path_sep)
else {
// `module_name` being an absolute path, it should always contain at least one
// path separator. If not, there is nothing we can do.
return TRUE;
};

let search_path = unsafe { &mut *(user_context as *mut SearchPath) };
search_path.add(&module_name[..end_of_directory]);

TRUE
}

impl Drop for Init {
fn drop(&mut self) {
unsafe {
Expand Down
12 changes: 12 additions & 0 deletions src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ cfg_if::cfg_if! {
ContextPointers: PKNONVOLATILE_CONTEXT_POINTERS
) -> PEXCEPTION_ROUTINE;
}

// winapi doesn't have this type
pub type PENUMLOADED_MODULES_CALLBACKW64 = Option<
unsafe extern "system" fn(
modulename: PCWSTR,
modulebase: u64,
modulesize: u32,
usercontext: *const c_void,
) -> BOOL,
>;
}
} else {
pub use core::ffi::c_void;
Expand Down Expand Up @@ -291,6 +301,7 @@ ffi! {
pub type PTRANSLATE_ADDRESS_ROUTINE64 = Option<
unsafe extern "system" fn(hProcess: HANDLE, hThread: HANDLE, lpaddr: LPADDRESS64) -> DWORD64,
>;
pub type PENUMLOADED_MODULES_CALLBACKW64 = Option<unsafe extern "system" fn(modulename: PCWSTR, modulebase: u64, modulesize: u32, usercontext: *const c_void) -> BOOL>;
pub type PGET_MODULE_BASE_ROUTINE64 =
Option<unsafe extern "system" fn(hProcess: HANDLE, Address: DWORD64) -> DWORD64>;
pub type PFUNCTION_TABLE_ACCESS_ROUTINE64 =
Expand Down Expand Up @@ -444,6 +455,7 @@ ffi! {
hSnapshot: HANDLE,
lpme: LPMODULEENTRY32W,
) -> BOOL;
pub fn lstrlenW(lpstring: PCWSTR) -> i32;
}
}

Expand Down

0 comments on commit 101a1b7

Please sign in to comment.