Skip to content

Commit

Permalink
Auto merge of #39234 - segevfiner:fix-backtraces-on-windows-gnu, r=pe…
Browse files Browse the repository at this point in the history
…trochenkov

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.
  • Loading branch information
bors committed Jan 28, 2017
2 parents c81c1d6 + ab21314 commit 1491e04
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 4 deletions.
11 changes: 11 additions & 0 deletions src/libstd/sys/unix/backtrace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
}
4 changes: 4 additions & 0 deletions src/libstd/sys/windows/backtrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
62 changes: 62 additions & 0 deletions src/libstd/sys/windows/backtrace_gnu.rs
Original file line number Diff line number Diff line change
@@ -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))
}
40 changes: 40 additions & 0 deletions src/libstd/sys/windows/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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::*;
47 changes: 47 additions & 0 deletions src/libstd/sys/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand Down
17 changes: 16 additions & 1 deletion src/libstd/sys_common/gnu/libbacktrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -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
}
Expand Down
3 changes: 1 addition & 2 deletions src/test/run-pass/backtrace-debuginfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -173,4 +173,3 @@ fn main() {
run_test(&args[0]);
}
}

2 changes: 1 addition & 1 deletion src/test/run-pass/backtrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 1491e04

Please sign in to comment.