Skip to content

Commit 1491e04

Browse files
committed
Auto merge of #39234 - segevfiner:fix-backtraces-on-windows-gnu, r=petrochenkov
Make backtraces work on Windows GNU targets again. This is done by adding a function that can return a filename to pass to backtrace_create_state. The filename is obtained in a safe way by first getting the filename, locking the file so it can't be moved, and then getting the filename again and making sure it's the same. See: #37359 (comment) Issue: #33985 Note though that this isn't that pretty... I had to implement a `WideCharToMultiByte` wrapper function to convert to the ANSI code page. This will work better than only allowing ASCII provided that the ANSI code page is set to the user's local language, which is often the case. Also, please make sure that I didn't break the Unix build.
2 parents c81c1d6 + ab21314 commit 1491e04

File tree

8 files changed

+182
-4
lines changed

8 files changed

+182
-4
lines changed

src/libstd/sys/unix/backtrace/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,14 @@ pub use self::tracing::write;
8989
mod tracing;
9090
// symbol resolvers:
9191
mod printing;
92+
93+
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "emscripten")))]
94+
pub mod gnu {
95+
use io;
96+
use fs;
97+
use libc::c_char;
98+
99+
pub fn get_executable_filename() -> io::Result<(Vec<c_char>, fs::File)> {
100+
Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
101+
}
102+
}

src/libstd/sys/windows/backtrace.rs

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ mod printing;
5151
#[path = "printing/gnu.rs"]
5252
mod printing;
5353

54+
#[cfg(target_env = "gnu")]
55+
#[path = "backtrace_gnu.rs"]
56+
pub mod gnu;
57+
5458
type SymInitializeFn =
5559
unsafe extern "system" fn(c::HANDLE, *mut c_void,
5660
c::BOOL) -> c::BOOL;
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use io;
12+
use sys::c;
13+
use libc::c_char;
14+
use path::PathBuf;
15+
use fs::{OpenOptions, File};
16+
use sys::ext::fs::OpenOptionsExt;
17+
use sys::handle::Handle;
18+
use super::super::{fill_utf16_buf, os2path, to_u16s, wide_char_to_multi_byte};
19+
20+
fn query_full_process_image_name() -> io::Result<PathBuf> {
21+
unsafe {
22+
let process_handle = Handle::new(c::OpenProcess(c::PROCESS_QUERY_INFORMATION,
23+
c::FALSE,
24+
c::GetCurrentProcessId()));
25+
fill_utf16_buf(|buf, mut sz| {
26+
if c::QueryFullProcessImageNameW(process_handle.raw(), 0, buf, &mut sz) == 0 {
27+
0
28+
} else {
29+
sz
30+
}
31+
}, os2path)
32+
}
33+
}
34+
35+
fn lock_and_get_executable_filename() -> io::Result<(PathBuf, File)> {
36+
// We query the current image name, open the file without FILE_SHARE_DELETE so it
37+
// can't be moved and then get the current image name again. If the names are the
38+
// same than we have successfully locked the file
39+
let image_name1 = query_full_process_image_name()?;
40+
let file = OpenOptions::new()
41+
.read(true)
42+
.share_mode(c::FILE_SHARE_READ | c::FILE_SHARE_WRITE)
43+
.open(&image_name1)?;
44+
let image_name2 = query_full_process_image_name()?;
45+
46+
if image_name1 != image_name2 {
47+
return Err(io::Error::new(io::ErrorKind::Other,
48+
"executable moved while trying to lock it"));
49+
}
50+
51+
Ok((image_name1, file))
52+
}
53+
54+
// Get the executable filename for libbacktrace
55+
// This returns the path in the ANSI code page and a File which should remain open
56+
// for as long as the path should remain valid
57+
pub fn get_executable_filename() -> io::Result<(Vec<c_char>, File)> {
58+
let (executable, file) = lock_and_get_executable_filename()?;
59+
let u16_executable = to_u16s(executable.into_os_string())?;
60+
Ok((wide_char_to_multi_byte(c::CP_ACP, c::WC_NO_BEST_FIT_CHARS,
61+
&u16_executable, true)?, file))
62+
}

src/libstd/sys/windows/c.rs

+40
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub type LPWCH = *mut WCHAR;
6969
pub type LPWIN32_FIND_DATAW = *mut WIN32_FIND_DATAW;
7070
pub type LPWSADATA = *mut WSADATA;
7171
pub type LPWSAPROTOCOL_INFO = *mut WSAPROTOCOL_INFO;
72+
pub type LPSTR = *mut CHAR;
7273
pub type LPWSTR = *mut WCHAR;
7374
pub type LPFILETIME = *mut FILETIME;
7475

@@ -973,6 +974,14 @@ extern "system" {
973974
pub fn DeleteFileW(lpPathName: LPCWSTR) -> BOOL;
974975
pub fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: LPWSTR) -> DWORD;
975976
pub fn SetCurrentDirectoryW(lpPathName: LPCWSTR) -> BOOL;
977+
pub fn WideCharToMultiByte(CodePage: UINT,
978+
dwFlags: DWORD,
979+
lpWideCharStr: LPCWSTR,
980+
cchWideChar: c_int,
981+
lpMultiByteStr: LPSTR,
982+
cbMultiByte: c_int,
983+
lpDefaultChar: LPCSTR,
984+
lpUsedDefaultChar: LPBOOL) -> c_int;
976985

977986
pub fn closesocket(socket: SOCKET) -> c_int;
978987
pub fn recv(socket: SOCKET, buf: *mut c_void, len: c_int,
@@ -1178,3 +1187,34 @@ compat_fn! {
11781187
panic!("rwlocks not available")
11791188
}
11801189
}
1190+
1191+
#[cfg(target_env = "gnu")]
1192+
mod gnu {
1193+
use super::*;
1194+
1195+
pub const PROCESS_QUERY_INFORMATION: DWORD = 0x0400;
1196+
1197+
pub const CP_ACP: UINT = 0;
1198+
1199+
pub const WC_NO_BEST_FIT_CHARS: DWORD = 0x00000400;
1200+
1201+
extern "system" {
1202+
pub fn OpenProcess(dwDesiredAccess: DWORD,
1203+
bInheritHandle: BOOL,
1204+
dwProcessId: DWORD) -> HANDLE;
1205+
}
1206+
1207+
compat_fn! {
1208+
kernel32:
1209+
1210+
pub fn QueryFullProcessImageNameW(_hProcess: HANDLE,
1211+
_dwFlags: DWORD,
1212+
_lpExeName: LPWSTR,
1213+
_lpdwSize: LPDWORD) -> BOOL {
1214+
SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); 0
1215+
}
1216+
}
1217+
}
1218+
1219+
#[cfg(target_env = "gnu")]
1220+
pub use self::gnu::*;

src/libstd/sys/windows/mod.rs

+47
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#![allow(missing_docs, bad_style)]
1212

13+
use ptr;
1314
use ffi::{OsStr, OsString};
1415
use io::{self, ErrorKind};
1516
use os::windows::ffi::{OsStrExt, OsStringExt};
@@ -171,6 +172,52 @@ fn os2path(s: &[u16]) -> PathBuf {
171172
PathBuf::from(OsString::from_wide(s))
172173
}
173174

175+
#[allow(dead_code)] // Only used in backtrace::gnu::get_executable_filename()
176+
fn wide_char_to_multi_byte(code_page: u32,
177+
flags: u32,
178+
s: &[u16],
179+
no_default_char: bool)
180+
-> io::Result<Vec<i8>> {
181+
unsafe {
182+
let mut size = c::WideCharToMultiByte(code_page,
183+
flags,
184+
s.as_ptr(),
185+
s.len() as i32,
186+
ptr::null_mut(),
187+
0,
188+
ptr::null(),
189+
ptr::null_mut());
190+
if size == 0 {
191+
return Err(io::Error::last_os_error());
192+
}
193+
194+
let mut buf = Vec::with_capacity(size as usize);
195+
buf.set_len(size as usize);
196+
197+
let mut used_default_char = c::FALSE;
198+
size = c::WideCharToMultiByte(code_page,
199+
flags,
200+
s.as_ptr(),
201+
s.len() as i32,
202+
buf.as_mut_ptr(),
203+
buf.len() as i32,
204+
ptr::null(),
205+
if no_default_char { &mut used_default_char }
206+
else { ptr::null_mut() });
207+
if size == 0 {
208+
return Err(io::Error::last_os_error());
209+
}
210+
if no_default_char && used_default_char == c::TRUE {
211+
return Err(io::Error::new(io::ErrorKind::InvalidData,
212+
"string cannot be converted to requested code page"));
213+
}
214+
215+
buf.set_len(size as usize);
216+
217+
Ok(buf)
218+
}
219+
}
220+
174221
pub fn truncate_utf16_at_nul<'a>(v: &'a [u16]) -> &'a [u16] {
175222
match v.iter().position(|c| *c == 0) {
176223
// don't include the 0

src/libstd/sys_common/gnu/libbacktrace.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use sys_common::backtrace::{output, output_fileline};
1616
pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
1717
symaddr: *mut libc::c_void) -> io::Result<()> {
1818
use ffi::CStr;
19+
use mem;
1920
use ptr;
2021

2122
////////////////////////////////////////////////////////////////////////
@@ -124,7 +125,21 @@ pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
124125
unsafe fn init_state() -> *mut backtrace_state {
125126
static mut STATE: *mut backtrace_state = ptr::null_mut();
126127
if !STATE.is_null() { return STATE }
127-
STATE = backtrace_create_state(ptr::null(), 0, error_cb,
128+
129+
let filename = match ::sys::backtrace::gnu::get_executable_filename() {
130+
Ok((filename, file)) => {
131+
// filename is purposely leaked here since libbacktrace requires
132+
// it to stay allocated permanently, file is also leaked so that
133+
// the file stays locked
134+
let filename_ptr = filename.as_ptr();
135+
mem::forget(filename);
136+
mem::forget(file);
137+
filename_ptr
138+
},
139+
Err(_) => ptr::null(),
140+
};
141+
142+
STATE = backtrace_create_state(filename, 0, error_cb,
128143
ptr::null_mut());
129144
STATE
130145
}

src/test/run-pass/backtrace-debuginfo.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ macro_rules! dump_and_die {
3737
target_os = "ios",
3838
target_os = "android",
3939
all(target_os = "linux", target_arch = "arm"),
40-
target_os = "windows",
40+
all(target_os = "windows", target_pointer_width = "32"),
4141
target_os = "freebsd",
4242
target_os = "dragonfly",
4343
target_os = "bitrig",
@@ -173,4 +173,3 @@ fn main() {
173173
run_test(&args[0]);
174174
}
175175
}
176-

src/test/run-pass/backtrace.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ fn runtest(me: &str) {
104104
}
105105

106106
fn main() {
107-
if cfg!(windows) && cfg!(target_env = "gnu") {
107+
if cfg!(windows) && cfg!(target_env = "gnu") && cfg!(target_pointer_width = "32") {
108108
return
109109
}
110110

0 commit comments

Comments
 (0)