Skip to content

Catching segmentation faults on Windows, proof of concept #118

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

Merged
merged 4 commits into from
Aug 6, 2018
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
119 changes: 63 additions & 56 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ path = "iguana/exchanges/mm2.rs"
[dependencies]
# `etomiclibrs` pulls https://github.com/briansmith/ring, to build which on Windows we need `perl` and `yasm` in the PATH.
# Download http://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe and rename to `yasm.exe`.
backtrace = "0.3.9"
etomiclibrs = { path = "iguana/exchanges/etomicswap" }
lazy_static = "1.1.0"
winapi = "0.3.5"

[build-dependencies]
bindgen = "0.37.4"
cc = "1.0.18"
gstuff = { git = "https://github.com/ArtemGr/gstuff.rs" }

# ring 0.12 fails to build on Windows, this patch replaces it with ring 0.13.
Expand Down
109 changes: 109 additions & 0 deletions OSlibs/crash_reports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use backtrace;
#[cfg(not(test))] use std::process::abort;
#[cfg(test)] use std::sync::Mutex;
#[cfg(test)] use winapi::um::minwinbase::EXCEPTION_ACCESS_VIOLATION;

type StackTrace = String;
/// https://docs.microsoft.com/en-us/windows/desktop/debug/getexceptioncode
#[cfg(test)] type ExceptionCode = u32;
#[cfg(test)] lazy_static! {static ref SEH_CAUGHT: Mutex<Option<(ExceptionCode, StackTrace)>> = Mutex::new (None);}

#[allow(dead_code)]
fn stack_trace_frame (buf: &mut String, symbol: &backtrace::Symbol) {
let filename = match symbol.filename() {Some (path) => path, None => return};
let filename = match filename.components().rev().next() {Some (c) => c.as_os_str().to_string_lossy(), None => return};
let lineno = match symbol.lineno() {Some (lineno) => lineno, None => return};
let name = match symbol.name() {Some (name) => name, None => return};
let name = format! ("{}", name); // NB: `fmt` is different from `SymbolName::as_str`.

// Skip common and less than informative frames.

if name.starts_with ("backtrace::") {return}
if name.starts_with ("core::") {return}
if name.starts_with ("alloc::") {return}
if name.starts_with ("panic_unwind::") {return}
if name.starts_with ("std::") {return}
if name == "mm2::crash_reports::rust_seh_handler" {return}
if name.starts_with ("mm2::crash_reports::stack_trace") {return}
// seh.c
if name == "ExpFilter" {return}
if name == "with_seh$filt$0" {return}

if !buf.is_empty() {buf.push ('\n')}
use std::fmt::Write;
let _ = write! (buf, " {}:{}] {}", filename, lineno, name);
}

/// Generates a string with the current stack trace.
pub fn stack_trace (format: &mut FnMut (&mut String, &backtrace::Symbol)) -> StackTrace {
let mut buf = String::with_capacity (128);

backtrace::trace (|frame| {
backtrace::resolve (frame.ip(), |symbol| format (&mut buf, symbol));
true
});

buf
}

#[cfg(test)]
#[no_mangle]
pub extern fn rust_seh_handler (exception_code: ExceptionCode) {
let mut seh_caught = SEH_CAUGHT.lock().expect("!SEH_CAUGHT");
*seh_caught = Some ((exception_code, stack_trace(&mut stack_trace_frame)));
}

#[cfg(not(test))]
#[no_mangle]
pub extern fn rust_seh_handler (exception_code: u32) {
println! ("SEH caught! ExceptionCode: {}\n", exception_code);
abort()
}

#[cfg(windows)]
#[cfg(test)]
extern "C" {
fn with_seh (cb: extern fn()->());
fn c_access_violation();
}

#[cfg(windows)]
#[cfg(test)]
extern fn call_access_violation() {
access_violation()
}

#[cfg(windows)]
#[cfg(test)]
#[inline(never)]
fn access_violation() {
let ptr: *mut i32 = 0 as *mut i32;
unsafe {*ptr = 123};
}

#[cfg(windows)]
#[cfg(test)]
#[inline(never)]
extern fn call_c_access_violation() {
unsafe {c_access_violation()}
}

#[cfg(windows)]
#[test]
fn test_seh_handler() {
*SEH_CAUGHT.lock().expect("!SEH_CAUGHT") = None;
unsafe {with_seh (call_access_violation)};
let seh = SEH_CAUGHT.lock().expect("!SEH_CAUGHT").take().expect("!trace");
println! ("ExceptionCode: {}\n{}", seh.0, seh.1);
assert_eq! (seh.0, EXCEPTION_ACCESS_VIOLATION);
assert! (seh.1.contains ("with_seh"));
assert! (seh.1.contains ("mm2::crash_reports::call_access_violation"));
assert! (seh.1.contains ("mm2::crash_reports::access_violation"));

unsafe {with_seh (call_c_access_violation)};
let seh = SEH_CAUGHT.lock().expect("!SEH_CAUGHT").take().expect("!trace");
println! ("ExceptionCode: {}\n{}", seh.0, seh.1);
assert! (seh.1.contains ("with_seh"));
assert! (seh.1.contains ("mm2::crash_reports::call_c_access_violation"));
assert! (seh.1.contains ("c_access_violation"));
}
31 changes: 31 additions & 0 deletions OSlibs/win/seh.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include <windows.h>

void rust_seh_handler (u32);

void c_access_violation()
{
// Straight from the https://docs.microsoft.com/en-us/windows/desktop/Debug/using-a-vectored-exception-handler.
char *ptr = 0;
*ptr = 0;
}

// https://github.com/JochenKalmbach/StackWalker#displaying-the-callstack-of-an-exception
LONG WINAPI ExpFilter(DWORD exception_code)
{
rust_seh_handler (exception_code);
return EXCEPTION_EXECUTE_HANDLER;
}

/// Runs the given callback withing the __try/__except block, allowing us to catch segmentation faults on Windows.
///
/// Invokes `rust_seh_handler` whenever a Structured Exception is caught.
void with_seh (void (*cb)())
{
__try
{
cb();
}
__except (ExpFilter(GetExceptionCode()))
{
}
}
20 changes: 19 additions & 1 deletion build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
// On build.rs: https://doc.rust-lang.org/cargo/reference/build-scripts.html

extern crate bindgen;
extern crate cc;
extern crate gstuff;

use gstuff::last_modified_sec;
use std::env;
use std::fs;
use std::io::Read;

Expand Down Expand Up @@ -69,7 +71,23 @@ fn mm_version() {
println!("cargo:rustc-env=MM_VERSION={}", version);
}

/// Build helper C code.
///
/// I think "git clone ... && cargo build" should be enough to start hacking on the Rust code.
///
/// For now we're building the Structured Exception Handling code here,
/// but in the future we might subsume the rest of the C build under build.rs.
fn build_c_code() {
if cfg!(windows) {
// TODO: Only (re)build the library when the source code or the build script changes.
cc::Build::new().file("OSlibs/win/seh.c").warnings(true).compile("seh");
println! ("cargo:rustc-link-lib=static=seh");
println! ("cargo:rustc-link-search=native={}", env::var("OUT_DIR").expect("!OUT_DIR"));
}
}

fn main() {
generate_bindings();
build_c_code();
mm_version();
generate_bindings();
}
4 changes: 2 additions & 2 deletions iguana/exchanges/etomicswap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ ethcore-transaction = { git = "https://github.com/paritytech/parity.git" }
ethkey = { git = "https://github.com/paritytech/parity.git" }
rlp = { git = "https://github.com/paritytech/parity-common" }
ethereum-types = "0.3.1"
web3 = { git = "https://github.com/ArtemGr/rust-web3" }
ethabi = { git = "https://github.com/ArtemGr/ethabi" }
web3 = { git = "https://github.com/artemii235/rust-web3" }
ethabi = { git = "https://github.com/artemii235/ethabi" }
hex = "0.3.2"
regex = "1"
libc = "0.2.42"
Expand Down
16 changes: 14 additions & 2 deletions iguana/exchanges/mm2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@
// Copyright © 2017-2018 SuperNET. All rights reserved.
//

extern crate backtrace;
extern crate etomiclibrs;

#[allow(unused_imports)]
#[macro_use]
extern crate lazy_static;

extern crate winapi;

// Re-export preserves the functions that are temporarily accessed from C during the gradual port.
pub use etomiclibrs::*;

pub mod crash_reports {include! ("../../OSlibs/crash_reports.rs");}

/* The original C code will be replaced with the corresponding Rust code in small increments,
allowing Git history to catch up and show the function-level diffs.

Expand All @@ -35,8 +49,6 @@ void PNACL_message(char *arg,...)
#include "OS_portable.h"
#else
*/
extern crate etomiclibrs;
pub use etomiclibrs::*;

#[allow(dead_code,non_upper_case_globals,non_camel_case_types,non_snake_case)]
mod os_portable {include! ("../../crypto777/OS_portable.rs");}
Expand Down