|
23 | 23 |
|
24 | 24 | #![allow(non_snake_case)]
|
25 | 25 |
|
| 26 | +use alloc::collections::BTreeSet; |
| 27 | +use alloc::vec::Vec; |
| 28 | + |
26 | 29 | use super::windows::*;
|
27 | 30 | use core::mem;
|
28 | 31 | use core::ptr;
|
| 32 | +use core::slice; |
29 | 33 |
|
30 | 34 | // Work around `SymGetOptions` and `SymSetOptions` not being present in winapi
|
31 | 35 | // itself. Otherwise this is only used when we're double-checking types against
|
@@ -65,6 +69,17 @@ mod dbghelp {
|
65 | 69 | CurContext: LPDWORD,
|
66 | 70 | CurFrameIndex: LPDWORD,
|
67 | 71 | ) -> 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; |
68 | 83 | }
|
69 | 84 |
|
70 | 85 | pub fn assert_equal_types<T>(a: T, _b: T) -> T {
|
@@ -174,6 +189,20 @@ dbghelp! {
|
174 | 189 | path: PCWSTR,
|
175 | 190 | invade: BOOL
|
176 | 191 | ) -> 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; |
177 | 206 | fn StackWalk64(
|
178 | 207 | MachineType: DWORD,
|
179 | 208 | hProcess: HANDLE,
|
@@ -372,11 +401,144 @@ pub fn init() -> Result<Init, ()> {
|
372 | 401 | // get to initialization first and the other will pick up that
|
373 | 402 | // initialization.
|
374 | 403 | 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 | + |
375 | 448 | INITIALIZED = true;
|
376 | 449 | Ok(ret)
|
377 | 450 | }
|
378 | 451 | }
|
379 | 452 |
|
| 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 | + |
380 | 542 | impl Drop for Init {
|
381 | 543 | fn drop(&mut self) {
|
382 | 544 | unsafe {
|
|
0 commit comments