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: https://github.com/rust-lang/rust/pull/37359#issuecomment-260123399
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.
This commit is contained in:
bors 2017-01-28 20:32:56 +00:00
commit 1491e04259
8 changed files with 182 additions and 4 deletions

View File

@ -89,3 +89,14 @@ pub use self::tracing::write;
mod tracing;
// symbol resolvers:
mod printing;
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "emscripten")))]
pub mod gnu {
use io;
use fs;
use libc::c_char;
pub fn get_executable_filename() -> io::Result<(Vec<c_char>, fs::File)> {
Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
}
}

View File

@ -51,6 +51,10 @@ mod printing;
#[path = "printing/gnu.rs"]
mod printing;
#[cfg(target_env = "gnu")]
#[path = "backtrace_gnu.rs"]
pub mod gnu;
type SymInitializeFn =
unsafe extern "system" fn(c::HANDLE, *mut c_void,
c::BOOL) -> c::BOOL;

View File

@ -0,0 +1,62 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use io;
use sys::c;
use libc::c_char;
use path::PathBuf;
use fs::{OpenOptions, File};
use sys::ext::fs::OpenOptionsExt;
use sys::handle::Handle;
use super::super::{fill_utf16_buf, os2path, to_u16s, wide_char_to_multi_byte};
fn query_full_process_image_name() -> io::Result<PathBuf> {
unsafe {
let process_handle = Handle::new(c::OpenProcess(c::PROCESS_QUERY_INFORMATION,
c::FALSE,
c::GetCurrentProcessId()));
fill_utf16_buf(|buf, mut sz| {
if c::QueryFullProcessImageNameW(process_handle.raw(), 0, buf, &mut sz) == 0 {
0
} else {
sz
}
}, os2path)
}
}
fn lock_and_get_executable_filename() -> io::Result<(PathBuf, File)> {
// We query the current image name, open the file without FILE_SHARE_DELETE so it
// can't be moved and then get the current image name again. If the names are the
// same than we have successfully locked the file
let image_name1 = query_full_process_image_name()?;
let file = OpenOptions::new()
.read(true)
.share_mode(c::FILE_SHARE_READ | c::FILE_SHARE_WRITE)
.open(&image_name1)?;
let image_name2 = query_full_process_image_name()?;
if image_name1 != image_name2 {
return Err(io::Error::new(io::ErrorKind::Other,
"executable moved while trying to lock it"));
}
Ok((image_name1, file))
}
// Get the executable filename for libbacktrace
// This returns the path in the ANSI code page and a File which should remain open
// for as long as the path should remain valid
pub fn get_executable_filename() -> io::Result<(Vec<c_char>, File)> {
let (executable, file) = lock_and_get_executable_filename()?;
let u16_executable = to_u16s(executable.into_os_string())?;
Ok((wide_char_to_multi_byte(c::CP_ACP, c::WC_NO_BEST_FIT_CHARS,
&u16_executable, true)?, file))
}

View File

@ -69,6 +69,7 @@ pub type LPWCH = *mut WCHAR;
pub type LPWIN32_FIND_DATAW = *mut WIN32_FIND_DATAW;
pub type LPWSADATA = *mut WSADATA;
pub type LPWSAPROTOCOL_INFO = *mut WSAPROTOCOL_INFO;
pub type LPSTR = *mut CHAR;
pub type LPWSTR = *mut WCHAR;
pub type LPFILETIME = *mut FILETIME;
@ -973,6 +974,14 @@ extern "system" {
pub fn DeleteFileW(lpPathName: LPCWSTR) -> BOOL;
pub fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: LPWSTR) -> DWORD;
pub fn SetCurrentDirectoryW(lpPathName: LPCWSTR) -> BOOL;
pub fn WideCharToMultiByte(CodePage: UINT,
dwFlags: DWORD,
lpWideCharStr: LPCWSTR,
cchWideChar: c_int,
lpMultiByteStr: LPSTR,
cbMultiByte: c_int,
lpDefaultChar: LPCSTR,
lpUsedDefaultChar: LPBOOL) -> c_int;
pub fn closesocket(socket: SOCKET) -> c_int;
pub fn recv(socket: SOCKET, buf: *mut c_void, len: c_int,
@ -1178,3 +1187,34 @@ compat_fn! {
panic!("rwlocks not available")
}
}
#[cfg(target_env = "gnu")]
mod gnu {
use super::*;
pub const PROCESS_QUERY_INFORMATION: DWORD = 0x0400;
pub const CP_ACP: UINT = 0;
pub const WC_NO_BEST_FIT_CHARS: DWORD = 0x00000400;
extern "system" {
pub fn OpenProcess(dwDesiredAccess: DWORD,
bInheritHandle: BOOL,
dwProcessId: DWORD) -> HANDLE;
}
compat_fn! {
kernel32:
pub fn QueryFullProcessImageNameW(_hProcess: HANDLE,
_dwFlags: DWORD,
_lpExeName: LPWSTR,
_lpdwSize: LPDWORD) -> BOOL {
SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); 0
}
}
}
#[cfg(target_env = "gnu")]
pub use self::gnu::*;

View File

@ -10,6 +10,7 @@
#![allow(missing_docs, bad_style)]
use ptr;
use ffi::{OsStr, OsString};
use io::{self, ErrorKind};
use os::windows::ffi::{OsStrExt, OsStringExt};
@ -171,6 +172,52 @@ fn os2path(s: &[u16]) -> PathBuf {
PathBuf::from(OsString::from_wide(s))
}
#[allow(dead_code)] // Only used in backtrace::gnu::get_executable_filename()
fn wide_char_to_multi_byte(code_page: u32,
flags: u32,
s: &[u16],
no_default_char: bool)
-> io::Result<Vec<i8>> {
unsafe {
let mut size = c::WideCharToMultiByte(code_page,
flags,
s.as_ptr(),
s.len() as i32,
ptr::null_mut(),
0,
ptr::null(),
ptr::null_mut());
if size == 0 {
return Err(io::Error::last_os_error());
}
let mut buf = Vec::with_capacity(size as usize);
buf.set_len(size as usize);
let mut used_default_char = c::FALSE;
size = c::WideCharToMultiByte(code_page,
flags,
s.as_ptr(),
s.len() as i32,
buf.as_mut_ptr(),
buf.len() as i32,
ptr::null(),
if no_default_char { &mut used_default_char }
else { ptr::null_mut() });
if size == 0 {
return Err(io::Error::last_os_error());
}
if no_default_char && used_default_char == c::TRUE {
return Err(io::Error::new(io::ErrorKind::InvalidData,
"string cannot be converted to requested code page"));
}
buf.set_len(size as usize);
Ok(buf)
}
}
pub fn truncate_utf16_at_nul<'a>(v: &'a [u16]) -> &'a [u16] {
match v.iter().position(|c| *c == 0) {
// don't include the 0

View File

@ -16,6 +16,7 @@ use sys_common::backtrace::{output, output_fileline};
pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
symaddr: *mut libc::c_void) -> io::Result<()> {
use ffi::CStr;
use mem;
use ptr;
////////////////////////////////////////////////////////////////////////
@ -124,7 +125,21 @@ pub fn print(w: &mut Write, idx: isize, addr: *mut libc::c_void,
unsafe fn init_state() -> *mut backtrace_state {
static mut STATE: *mut backtrace_state = ptr::null_mut();
if !STATE.is_null() { return STATE }
STATE = backtrace_create_state(ptr::null(), 0, error_cb,
let filename = match ::sys::backtrace::gnu::get_executable_filename() {
Ok((filename, file)) => {
// filename is purposely leaked here since libbacktrace requires
// it to stay allocated permanently, file is also leaked so that
// the file stays locked
let filename_ptr = filename.as_ptr();
mem::forget(filename);
mem::forget(file);
filename_ptr
},
Err(_) => ptr::null(),
};
STATE = backtrace_create_state(filename, 0, error_cb,
ptr::null_mut());
STATE
}

View File

@ -37,7 +37,7 @@ macro_rules! dump_and_die {
target_os = "ios",
target_os = "android",
all(target_os = "linux", target_arch = "arm"),
target_os = "windows",
all(target_os = "windows", target_pointer_width = "32"),
target_os = "freebsd",
target_os = "dragonfly",
target_os = "bitrig",
@ -173,4 +173,3 @@ fn main() {
run_test(&args[0]);
}
}

View File

@ -104,7 +104,7 @@ fn runtest(me: &str) {
}
fn main() {
if cfg!(windows) && cfg!(target_env = "gnu") {
if cfg!(windows) && cfg!(target_env = "gnu") && cfg!(target_pointer_width = "32") {
return
}