Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make backtraces work on Windows GNU targets again. #39234

Merged
merged 4 commits into from
Jan 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to validate that the reverse conversion with MultiByteToWideChar gives the original u16 string, otherwise the conversion was lossy and libbacktrace will try to open some other file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the lpUsedDefaultChar parameter of WideCharToMultiByte, it can save the need to convert twice assuming that it's not broken in some way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually there's still ways in which data loss can occur other than default char replacement. You'll want to pass WC_NO_BEST_FIT_CHARS.

For strings that require validation, such as file, resource, and user names, the application should always use the WC_NO_BEST_FIT_CHARS flag. This flag prevents the function from mapping characters to characters that appear similar but have very different semantics. In some cases, the semantic change can be extreme. For example, the symbol for "∞" (infinity) maps to 8 (eight) in some code pages.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally the set of flags should mirror whatever is used inside of (most probably) CreateFileA which is used inside posix open in CRT which is used in libbacktrace.

Copy link
Member

@retep998 retep998 Jan 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function which is actually used to convert strings inside A functions is RtlAnsiStringToUnicodeString which in turn calls RtlMultiByteToUnicodeN, not MultiByteToWideChar.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also another point is that we're going in the opposite direction here, so we can't match the flags anyway.

Copy link
Contributor

@petrochenkov petrochenkov Jan 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, if we do want to "fix" this, then this will work 😄

    let image_name1 = query_full_process_image_name()?;
    let ansi_image_name1 = WideCharToMultiByte(image_name1, TRY_TO_GUESS_FLAGS);
    let file = CreateFileA(ansi_image_name1)?; // instead of OpenOptions::new().blah().open(&image_name1)
    let image_name2 = query_full_process_image_name()?;
    if image_name1 != image_name2 {
        // Simultaneously make sure that the file is not moved and that encoding conversion was correct
        return Err
    }
    return ansi_image_name1;

Copy link
Contributor Author

@segevfiner segevfiner Jan 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MSDN documents that WC_NO_BEST_FIT_CHARS should actually make the conversion reversible, which is what you wanted (assuming no surprises). So I added it as suggested.

}
}

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