Skip to content

Commit 7d26334

Browse files
authored
Reduce panics in dbghelp (#608)
According to the docs for `resolve`: > The closure may not be called if resolution could not be performed > This function strives to never panic. So we should ideally not panic and instead simply return without calling the closure. Therefore, I've changed the panicky FFI calls to instead simply return. I also replaced our custom UTF-16 to UTF-8 converter with a call to `WideCharToMultiByte`. This function already has the semantics we want and doesn't include panicking code. These changes should have a small but notable effect on binary size.
1 parent f63b581 commit 7d26334

File tree

4 files changed

+62
-46
lines changed

4 files changed

+62
-46
lines changed

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ verify-winapi = [
9393
'winapi/tlhelp32',
9494
'winapi/winbase',
9595
'winapi/winnt',
96+
'winapi/winnls',
97+
'winapi/stringapiset',
9698
]
9799

98100
[[example]]

src/dbghelp.rs

+15-12
Original file line numberDiff line numberDiff line change
@@ -376,16 +376,21 @@ pub fn init() -> Result<Init, ()> {
376376
DBGHELP.ensure_open()?;
377377

378378
static mut INITIALIZED: bool = false;
379-
if INITIALIZED {
380-
return Ok(ret);
379+
if !INITIALIZED {
380+
set_optional_options();
381+
INITIALIZED = true;
381382
}
382-
383-
let orig = DBGHELP.SymGetOptions().unwrap()();
383+
Ok(ret)
384+
}
385+
}
386+
fn set_optional_options() -> Option<()> {
387+
unsafe {
388+
let orig = DBGHELP.SymGetOptions()?();
384389

385390
// Ensure that the `SYMOPT_DEFERRED_LOADS` flag is set, because
386391
// according to MSVC's own docs about this: "This is the fastest, most
387392
// efficient way to use the symbol handler.", so let's do that!
388-
DBGHELP.SymSetOptions().unwrap()(orig | SYMOPT_DEFERRED_LOADS);
393+
DBGHELP.SymSetOptions()?(orig | SYMOPT_DEFERRED_LOADS);
389394

390395
// Actually initialize symbols with MSVC. Note that this can fail, but we
391396
// ignore it. There's not a ton of prior art for this per se, but LLVM
@@ -399,7 +404,7 @@ pub fn init() -> Result<Init, ()> {
399404
// the time, but now that it's using this crate it means that someone will
400405
// get to initialization first and the other will pick up that
401406
// initialization.
402-
DBGHELP.SymInitializeW().unwrap()(GetCurrentProcess(), ptr::null_mut(), TRUE);
407+
DBGHELP.SymInitializeW()?(GetCurrentProcess(), ptr::null_mut(), TRUE);
403408

404409
// The default search path for dbghelp will only look in the current working
405410
// directory and (possibly) `_NT_SYMBOL_PATH` and `_NT_ALT_SYMBOL_PATH`.
@@ -413,7 +418,7 @@ pub fn init() -> Result<Init, ()> {
413418
search_path_buf.resize(1024, 0);
414419

415420
// Prefill the buffer with the current search path.
416-
if DBGHELP.SymGetSearchPathW().unwrap()(
421+
if DBGHELP.SymGetSearchPathW()?(
417422
GetCurrentProcess(),
418423
search_path_buf.as_mut_ptr(),
419424
search_path_buf.len() as _,
@@ -433,7 +438,7 @@ pub fn init() -> Result<Init, ()> {
433438
let mut search_path = SearchPath::new(search_path_buf);
434439

435440
// Update the search path to include the directory of the executable and each DLL.
436-
DBGHELP.EnumerateLoadedModulesW64().unwrap()(
441+
DBGHELP.EnumerateLoadedModulesW64()?(
437442
GetCurrentProcess(),
438443
Some(enum_loaded_modules_callback),
439444
((&mut search_path) as *mut SearchPath) as *mut c_void,
@@ -442,11 +447,9 @@ pub fn init() -> Result<Init, ()> {
442447
let new_search_path = search_path.finalize();
443448

444449
// Set the new search path.
445-
DBGHELP.SymSetSearchPathW().unwrap()(GetCurrentProcess(), new_search_path.as_ptr());
446-
447-
INITIALIZED = true;
448-
Ok(ret)
450+
DBGHELP.SymSetSearchPathW()?(GetCurrentProcess(), new_search_path.as_ptr());
449451
}
452+
Some(())
450453
}
451454

452455
struct SearchPath {

src/symbolize/dbghelp.rs

+32-34
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
use super::super::{dbghelp, windows::*};
2121
use super::{BytesOrWideString, ResolveWhat, SymbolName};
22-
use core::char;
2322
use core::ffi::c_void;
2423
use core::marker;
2524
use core::mem;
@@ -91,7 +90,7 @@ pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol))
9190
ResolveWhat::Frame(frame) => {
9291
resolve_with_inline(&dbghelp, frame.ip(), frame.inner.inline_context(), cb)
9392
}
94-
}
93+
};
9594
}
9695

9796
#[cfg(target_vendor = "win7")]
@@ -116,7 +115,7 @@ pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol))
116115
ResolveWhat::Frame(frame) => {
117116
resolve_inner(&dbghelp, frame.ip(), frame.inner.inline_context(), cb)
118117
}
119-
}
118+
};
120119
}
121120

122121
/// Resolve the address using the legacy dbghelp API.
@@ -147,22 +146,28 @@ unsafe fn resolve_with_inline(
147146
addr: *mut c_void,
148147
inline_context: Option<DWORD>,
149148
cb: &mut dyn FnMut(&super::Symbol),
150-
) {
149+
) -> Option<()> {
151150
let current_process = GetCurrentProcess();
151+
// Ensure we have the functions we need. Return if any aren't found.
152+
let SymFromInlineContextW = (*dbghelp.dbghelp()).SymFromInlineContextW()?;
153+
let SymGetLineFromInlineContextW = (*dbghelp.dbghelp()).SymGetLineFromInlineContextW()?;
152154

153155
let addr = super::adjust_ip(addr) as DWORD64;
154156

155157
let (inlined_frame_count, inline_context) = if let Some(ic) = inline_context {
156158
(0, ic)
157159
} else {
158-
let mut inlined_frame_count = dbghelp.SymAddrIncludeInlineTrace()(current_process, addr);
160+
let SymAddrIncludeInlineTrace = (*dbghelp.dbghelp()).SymAddrIncludeInlineTrace()?;
161+
let SymQueryInlineTrace = (*dbghelp.dbghelp()).SymQueryInlineTrace()?;
162+
163+
let mut inlined_frame_count = SymAddrIncludeInlineTrace(current_process, addr);
159164

160165
let mut inline_context = 0;
161166

162167
// If there is are inlined frames but we can't load them for some reason OR if there are no
163168
// inlined frames, then we disregard inlined_frame_count and inline_context.
164169
if (inlined_frame_count > 0
165-
&& dbghelp.SymQueryInlineTrace()(
170+
&& SymQueryInlineTrace(
166171
current_process,
167172
addr,
168173
0,
@@ -184,22 +189,14 @@ unsafe fn resolve_with_inline(
184189

185190
for inline_context in inline_context..last_inline_context {
186191
do_resolve(
187-
|info| {
188-
dbghelp.SymFromInlineContextW()(current_process, addr, inline_context, &mut 0, info)
189-
},
192+
|info| SymFromInlineContextW(current_process, addr, inline_context, &mut 0, info),
190193
|line| {
191-
dbghelp.SymGetLineFromInlineContextW()(
192-
current_process,
193-
addr,
194-
inline_context,
195-
0,
196-
&mut 0,
197-
line,
198-
)
194+
SymGetLineFromInlineContextW(current_process, addr, inline_context, 0, &mut 0, line)
199195
},
200196
cb,
201197
);
202198
}
199+
Some(())
203200
}
204201

205202
unsafe fn do_resolve(
@@ -225,26 +222,27 @@ unsafe fn do_resolve(
225222
// the real value.
226223
let name_len = ::core::cmp::min(info.NameLen as usize, info.MaxNameLen as usize - 1);
227224
let name_ptr = info.Name.as_ptr().cast::<u16>();
228-
let name = slice::from_raw_parts(name_ptr, name_len);
229225

230226
// Reencode the utf-16 symbol to utf-8 so we can use `SymbolName::new` like
231227
// all other platforms
232-
let mut name_len = 0;
233-
let mut name_buffer = [0; 256];
234-
{
235-
let mut remaining = &mut name_buffer[..];
236-
for c in char::decode_utf16(name.iter().cloned()) {
237-
let c = c.unwrap_or(char::REPLACEMENT_CHARACTER);
238-
let len = c.len_utf8();
239-
if len < remaining.len() {
240-
c.encode_utf8(remaining);
241-
let tmp = remaining;
242-
remaining = &mut tmp[len..];
243-
name_len += len;
244-
} else {
245-
break;
246-
}
247-
}
228+
let mut name_buffer = [0_u8; 256];
229+
let mut name_len = WideCharToMultiByte(
230+
CP_UTF8,
231+
0,
232+
name_ptr,
233+
name_len as i32,
234+
name_buffer.as_mut_ptr().cast::<i8>(),
235+
name_buffer.len() as i32,
236+
core::ptr::null_mut(),
237+
core::ptr::null_mut(),
238+
) as usize;
239+
if name_len == 0 {
240+
// If the returned length is zero that means the buffer wasn't big enough.
241+
// However, the buffer will be filled with as much as will fit.
242+
name_len = name_buffer.len();
243+
} else if name_len > name_buffer.len() {
244+
// This can't happen.
245+
return;
248246
}
249247
let name = ptr::addr_of!(name_buffer[..name_len]);
250248

src/windows.rs

+13
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ cfg_if::cfg_if! {
3838
pub use winapi::um::tlhelp32::*;
3939
pub use winapi::um::winbase::*;
4040
pub use winapi::um::winnt::*;
41+
pub use winapi::um::winnls::*;
42+
pub use winapi::um::stringapiset::*;
4143

4244
// Work around winapi not having this function on aarch64.
4345
#[cfg(target_arch = "aarch64")]
@@ -379,6 +381,7 @@ ffi! {
379381
pub const INVALID_HANDLE_VALUE: HANDLE = -1isize as HANDLE;
380382
pub const MAX_MODULE_NAME32: usize = 255;
381383
pub const MAX_PATH: usize = 260;
384+
pub const CP_UTF8: u32 = 65001;
382385

383386
pub type DWORD = u32;
384387
pub type PDWORD = *mut u32;
@@ -456,6 +459,16 @@ ffi! {
456459
lpme: LPMODULEENTRY32W,
457460
) -> BOOL;
458461
pub fn lstrlenW(lpstring: PCWSTR) -> i32;
462+
pub fn WideCharToMultiByte(
463+
codepage: u32,
464+
dwflags: u32,
465+
lpwidecharstr: PCWSTR,
466+
cchwidechar: i32,
467+
lpmultibytestr: *mut i8,
468+
cbmultibyte: i32,
469+
lpdefaultchar: *const i8,
470+
lpuseddefaultchar: *mut BOOL
471+
) -> i32;
459472
}
460473
}
461474

0 commit comments

Comments
 (0)