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