]> git.lizzy.rs Git - rust.git/commitdiff
Print stack overflow messages for Windows, Linux and OS X
authorJohn Kåre Alsaker <john.kare.alsaker@gmail.com>
Thu, 23 Oct 2014 05:29:41 +0000 (07:29 +0200)
committerJohn Kåre Alsaker <john.kare.alsaker@gmail.com>
Fri, 24 Oct 2014 12:36:29 +0000 (14:36 +0200)
Fixes #17562

14 files changed:
src/libgreen/simple.rs
src/libgreen/stack.rs
src/libgreen/task.rs
src/libnative/lib.rs
src/libnative/task.rs
src/librustrt/lib.rs
src/librustrt/stack.rs
src/librustrt/stack_overflow.rs [new file with mode: 0644]
src/librustrt/task.rs
src/librustrt/thread.rs
src/test/run-pass/out-of-stack-new-thread-no-split.rs [new file with mode: 0644]
src/test/run-pass/out-of-stack-no-split.rs [new file with mode: 0644]
src/test/run-pass/out-of-stack.rs
src/test/run-pass/segfault-no-out-of-stack.rs [new file with mode: 0644]

index 058a00bcd4bc0960c08277811c4e0ae40bd83c98..686a039d6d600758d9b348b95b1bac12e6d74892 100644 (file)
@@ -81,6 +81,7 @@ fn spawn_sibling(self: Box<SimpleTask>,
     }
     fn local_io<'a>(&'a mut self) -> Option<rtio::LocalIo<'a>> { None }
     fn stack_bounds(&self) -> (uint, uint) { fail!() }
+    fn stack_guard(&self) -> Option<uint> { fail!() }
     fn can_block(&self) -> bool { true }
     fn wrap(self: Box<SimpleTask>) -> Box<Any+'static> { fail!() }
 }
index 6a5772ff6282a5c764f0a3a92695ff9337591fd4..cccf0ec698779af0e0962f5b395e9464e36a7d5f 100644 (file)
@@ -82,6 +82,11 @@ pub unsafe fn dummy_stack() -> Stack {
         }
     }
 
+    /// Point to the last writable byte of the stack
+    pub fn guard(&self) -> *const uint {
+        (self.start() as uint + page_size()) as *const uint
+    }
+
     /// Point to the low end of the allocated stack
     pub fn start(&self) -> *const uint {
         self.buf.as_ref().map(|m| m.data() as *const uint)
index b6bd8c3cec4a735d315cb425cab6450cb2417956..f151e00f56d567c1c2febab9f4929d31a988ed58 100644 (file)
@@ -486,6 +486,13 @@ fn stack_bounds(&self) -> (uint, uint) {
          c.current_stack_segment.end() as uint)
     }
 
+    fn stack_guard(&self) -> Option<uint> {
+        let c = self.coroutine.as_ref()
+            .expect("GreenTask.stack_guard called without a coroutine");
+
+        Some(c.current_stack_segment.guard() as uint)
+    }
+
     fn can_block(&self) -> bool { false }
 
     fn wrap(self: Box<GreenTask>) -> Box<Any+'static> {
index 656c7e4103c6eb29d0281b3de3625153f3addca8..c99143f0a5d7b05d844f011ea5510c35dafb26a8 100644 (file)
@@ -132,7 +132,8 @@ pub fn start(argc: int, argv: *const *const u8, main: proc()) -> int {
     rt::init(argc, argv);
     let mut exit_code = None;
     let mut main = Some(main);
-    let mut task = task::new((my_stack_bottom, my_stack_top));
+    let mut task = task::new((my_stack_bottom, my_stack_top),
+                             rt::thread::main_guard_page());
     task.name = Some(str::Slice("<main>"));
     drop(task.run(|| {
         unsafe {
index d90535428dac6cdef2f87269ffdf4c3676530264..455656c09d493279a45975b40c99c53e6771f094 100644 (file)
 use std::task::{TaskBuilder, Spawner};
 
 /// Creates a new Task which is ready to execute as a 1:1 task.
-pub fn new(stack_bounds: (uint, uint)) -> Box<Task> {
+pub fn new(stack_bounds: (uint, uint), stack_guard: uint) -> Box<Task> {
     let mut task = box Task::new();
     let mut ops = ops();
     ops.stack_bounds = stack_bounds;
+    ops.stack_guard = stack_guard;
     task.put_runtime(ops);
     return task;
 }
@@ -44,6 +45,7 @@ fn ops() -> Box<Ops> {
         io: io::IoFactory::new(),
         // these *should* get overwritten
         stack_bounds: (0, 0),
+        stack_guard: 0
     }
 }
 
@@ -82,6 +84,7 @@ fn spawn(self, opts: TaskOpts, f: proc():Send) {
                                                       my_stack);
             }
             let mut ops = ops;
+            ops.stack_guard = rt::thread::current_guard_page();
             ops.stack_bounds = (my_stack - stack + 1024, my_stack);
 
             let mut f = Some(f);
@@ -115,6 +118,8 @@ struct Ops {
     // native tasks necessarily know their precise bounds, hence this is
     // optional.
     stack_bounds: (uint, uint),
+
+    stack_guard: uint
 }
 
 impl rt::Runtime for Ops {
@@ -138,6 +143,14 @@ fn wrap(self: Box<Ops>) -> Box<Any+'static> {
 
     fn stack_bounds(&self) -> (uint, uint) { self.stack_bounds }
 
+    fn stack_guard(&self) -> Option<uint> {
+        if self.stack_guard != 0 {
+            Some(self.stack_guard)
+        } else {
+            None
+        }
+    }
+
     fn can_block(&self) -> bool { true }
 
     // This function gets a little interesting. There are a few safety and
index e45167565ea489bac1a940a28f61b3a634d73ffc..972497f98188309f3aa0ec481e9c053f683966ff 100644 (file)
@@ -51,6 +51,7 @@
 mod thread_local_storage;
 mod util;
 mod libunwind;
+mod stack_overflow;
 
 pub mod args;
 pub mod bookkeeping;
@@ -92,6 +93,8 @@ fn spawn_sibling(self: Box<Self>,
     fn local_io<'a>(&'a mut self) -> Option<rtio::LocalIo<'a>>;
     /// The (low, high) edges of the current stack.
     fn stack_bounds(&self) -> (uint, uint); // (lo, hi)
+    /// The last writable byte of the stack next to the guard page
+    fn stack_guard(&self) -> Option<uint>;
     fn can_block(&self) -> bool;
 
     // FIXME: This is a serious code smell and this should not exist at all.
@@ -113,6 +116,7 @@ pub fn init(argc: int, argv: *const *const u8) {
         args::init(argc, argv);
         local_ptr::init();
         at_exit_imp::init();
+        thread::init();
     }
 
     // FIXME(#14344) this shouldn't be necessary
@@ -151,6 +155,7 @@ pub unsafe fn cleanup() {
     bookkeeping::wait_for_other_tasks();
     at_exit_imp::run();
     args::cleanup();
+    thread::cleanup();
     local_ptr::cleanup();
 }
 
index 4034000e28f33e22adbfad9af7f14f028bb61a47..4874f642a93afac63e627a4e09b5a7e89ab322b7 100644 (file)
 #[cfg(not(test))] // in testing, use the original libstd's version
 #[lang = "stack_exhausted"]
 extern fn stack_exhausted() {
-    use core::prelude::*;
-    use alloc::boxed::Box;
-    use local::Local;
-    use task::Task;
     use core::intrinsics;
 
     unsafe {
         //  #9854 - unwinding on windows through __morestack has never worked
         //  #2361 - possible implementation of not using landing pads
 
-        let task: Option<Box<Task>> = Local::try_take();
-        let name = match task {
-            Some(ref task) => {
-                task.name.as_ref().map(|n| n.as_slice())
-            }
-            None => None
-        };
-        let name = name.unwrap_or("<unknown>");
-
-        // See the message below for why this is not emitted to the
-        // task's logger. This has the additional conundrum of the
-        // logger may not be initialized just yet, meaning that an FFI
-        // call would happen to initialized it (calling out to libuv),
-        // and the FFI call needs 2MB of stack when we just ran out.
-        rterrln!("task '{}' has overflowed its stack", name);
+        ::stack_overflow::report();
 
         intrinsics::abort();
     }
diff --git a/src/librustrt/stack_overflow.rs b/src/librustrt/stack_overflow.rs
new file mode 100644 (file)
index 0000000..aaaeb88
--- /dev/null
@@ -0,0 +1,412 @@
+// 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.
+
+#![allow(non_camel_case_types)]
+
+use core::prelude::*;
+use libc;
+use local::Local;
+use task::Task;
+
+pub unsafe fn init() {
+    imp::init();
+}
+
+pub unsafe fn cleanup() {
+    imp::cleanup();
+}
+
+pub struct Handler {
+    _data: *mut libc::c_void
+}
+
+impl Handler {
+    pub unsafe fn new() -> Handler {
+        imp::make_handler()
+    }
+}
+
+impl Drop for Handler {
+    fn drop(&mut self) {
+        unsafe {
+            imp::drop_handler(self);
+        }
+    }
+}
+
+pub unsafe fn report() {
+    // See the message below for why this is not emitted to the
+    // ^ Where did the message below go?
+    // task's logger. This has the additional conundrum of the
+    // logger may not be initialized just yet, meaning that an FFI
+    // call would happen to initialized it (calling out to libuv),
+    // and the FFI call needs 2MB of stack when we just ran out.
+
+    let task: Option<*mut Task> = Local::try_unsafe_borrow();
+
+    let name = task.and_then(|task| {
+        (*task).name.as_ref().map(|n| n.as_slice())
+    });
+
+    rterrln!("\ntask '{}' has overflowed its stack", name.unwrap_or("<unknown>"));
+}
+
+// get_task_info is called from an exception / signal handler.
+// It returns the guard page of the current task or 0 if that
+// guard page doesn't exist. None is returned if there's currently
+// no local task.
+#[cfg(any(windows, target_os = "linux", target_os = "macos"))]
+unsafe fn get_task_guard_page() -> Option<uint> {
+    let task: Option<*mut Task> = Local::try_unsafe_borrow();
+
+    task.map(|task| {
+        let runtime = (*task).take_runtime();
+        let guard = runtime.stack_guard();
+        (*task).put_runtime(runtime);
+
+        guard.unwrap_or(0)
+    })
+}
+
+#[cfg(windows)]
+#[allow(non_snake_case)]
+mod imp {
+    use core::ptr;
+    use core::mem;
+    use libc;
+    use libc::types::os::arch::extra::{LPVOID, DWORD, LONG, BOOL};
+    use stack;
+    use super::{Handler, get_task_guard_page, report};
+
+    // This is initialized in init() and only read from after
+    static mut PAGE_SIZE: uint = 0;
+
+    #[no_stack_check]
+    extern "system" fn vectored_handler(ExceptionInfo: *mut EXCEPTION_POINTERS) -> LONG {
+        unsafe {
+            let rec = &(*(*ExceptionInfo).ExceptionRecord);
+            let code = rec.ExceptionCode;
+
+            if code != EXCEPTION_STACK_OVERFLOW {
+                return EXCEPTION_CONTINUE_SEARCH;
+            }
+
+            // We're calling into functions with stack checks,
+            // however stack checks by limit should be disabled on Windows
+            stack::record_sp_limit(0);
+
+            if get_task_guard_page().is_some() {
+               report();
+            }
+
+            EXCEPTION_CONTINUE_SEARCH
+        }
+    }
+
+    pub unsafe fn init() {
+        let mut info = mem::zeroed();
+        libc::GetSystemInfo(&mut info);
+        PAGE_SIZE = info.dwPageSize as uint;
+
+        if AddVectoredExceptionHandler(0, vectored_handler) == ptr::null_mut() {
+            fail!("failed to install exception handler");
+        }
+
+        mem::forget(make_handler());
+    }
+
+    pub unsafe fn cleanup() {
+    }
+
+    pub unsafe fn make_handler() -> Handler {
+        if SetThreadStackGuarantee(&mut 0x5000) == 0 {
+            fail!("failed to reserve stack space for exception handling");
+        }
+
+        super::Handler { _data: 0i as *mut libc::c_void }
+    }
+
+    pub unsafe fn drop_handler(_handler: &mut Handler) {
+    }
+
+    pub struct EXCEPTION_RECORD {
+        pub ExceptionCode: DWORD,
+        pub ExceptionFlags: DWORD,
+        pub ExceptionRecord: *mut EXCEPTION_RECORD,
+        pub ExceptionAddress: LPVOID,
+        pub NumberParameters: DWORD,
+        pub ExceptionInformation: [LPVOID, ..EXCEPTION_MAXIMUM_PARAMETERS]
+    }
+
+    pub struct EXCEPTION_POINTERS {
+        pub ExceptionRecord: *mut EXCEPTION_RECORD,
+        pub ContextRecord: LPVOID
+    }
+
+    pub type PVECTORED_EXCEPTION_HANDLER = extern "system"
+            fn(ExceptionInfo: *mut EXCEPTION_POINTERS) -> LONG;
+
+    pub type ULONG = libc::c_ulong;
+
+    const EXCEPTION_CONTINUE_SEARCH: LONG = 0;
+    const EXCEPTION_MAXIMUM_PARAMETERS: uint = 15;
+    const EXCEPTION_STACK_OVERFLOW: DWORD = 0xc00000fd;
+
+    extern "system" {
+        fn AddVectoredExceptionHandler(FirstHandler: ULONG,
+                                       VectoredHandler: PVECTORED_EXCEPTION_HANDLER)
+                                      -> LPVOID;
+        fn SetThreadStackGuarantee(StackSizeInBytes: *mut ULONG) -> BOOL;
+    }
+}
+
+#[cfg(any(target_os = "linux", target_os = "macos"))]
+mod imp {
+    use core::prelude::*;
+    use stack;
+
+    use super::{Handler, get_task_guard_page, report};
+    use core::mem;
+    use core::ptr;
+    use core::intrinsics;
+    use self::signal::{siginfo, sigaction, SIGBUS, SIG_DFL,
+                       SA_SIGINFO, SA_ONSTACK, sigaltstack,
+                       SIGSTKSZ};
+    use libc;
+    use libc::funcs::posix88::mman::{mmap, munmap};
+    use libc::consts::os::posix88::{SIGSEGV,
+                                    PROT_READ,
+                                    PROT_WRITE,
+                                    MAP_PRIVATE,
+                                    MAP_ANON,
+                                    MAP_FAILED};
+
+
+    // This is initialized in init() and only read from after
+    static mut PAGE_SIZE: uint = 0;
+
+    #[no_stack_check]
+    unsafe extern fn signal_handler(signum: libc::c_int,
+                                     info: *mut siginfo,
+                                     _data: *mut libc::c_void) {
+
+        // We can not return from a SIGSEGV or SIGBUS signal.
+        // See: https://www.gnu.org/software/libc/manual/html_node/Handler-Returns.html
+
+        unsafe fn term(signum: libc::c_int) -> ! {
+            use core::mem::transmute;
+
+            signal(signum, transmute(SIG_DFL));
+            raise(signum);
+            intrinsics::abort();
+        }
+
+        // We're calling into functions with stack checks
+        stack::record_sp_limit(0);
+
+        match get_task_guard_page() {
+            Some(guard) => {
+                let addr = (*info).si_addr as uint;
+
+                if guard == 0 || addr < guard - PAGE_SIZE || addr >= guard {
+                    term(signum);
+                }
+
+                report();
+
+                intrinsics::abort()
+            }
+            None => term(signum)
+        }
+    }
+
+    static mut MAIN_ALTSTACK: *mut libc::c_void = 0 as *mut libc::c_void;
+
+    pub unsafe fn init() {
+        let psize = libc::sysconf(libc::consts::os::sysconf::_SC_PAGESIZE);
+        if psize == -1 {
+            fail!("failed to get page size");
+        }
+
+        PAGE_SIZE = psize as uint;
+
+        let mut action: sigaction = mem::zeroed();
+        action.sa_flags = SA_SIGINFO | SA_ONSTACK;
+        action.sa_sigaction = signal_handler as sighandler_t;
+        sigaction(SIGSEGV, &action, ptr::null_mut());
+        sigaction(SIGBUS, &action, ptr::null_mut());
+
+        let handler = make_handler();
+        MAIN_ALTSTACK = handler._data;
+        mem::forget(handler);
+    }
+
+    pub unsafe fn cleanup() {
+        Handler { _data: MAIN_ALTSTACK };
+    }
+
+    pub unsafe fn make_handler() -> Handler {
+        let alt_stack = mmap(ptr::null_mut(),
+                             signal::SIGSTKSZ,
+                             PROT_READ | PROT_WRITE,
+                             MAP_PRIVATE | MAP_ANON,
+                             -1,
+                             0);
+        if alt_stack == MAP_FAILED {
+            fail!("failed to allocate an alternative stack");
+        }
+
+        let mut stack: sigaltstack = mem::zeroed();
+
+        stack.ss_sp = alt_stack;
+        stack.ss_flags = 0;
+        stack.ss_size = SIGSTKSZ;
+
+        sigaltstack(&stack, ptr::null_mut());
+
+        Handler { _data: alt_stack }
+    }
+
+    pub unsafe fn drop_handler(handler: &mut Handler) {
+        munmap(handler._data, SIGSTKSZ);
+    }
+
+    type sighandler_t = *mut libc::c_void;
+
+    #[cfg(any(all(target_os = "linux", target_arch = "x86"), // may not match
+              all(target_os = "linux", target_arch = "x86_64"),
+              all(target_os = "linux", target_arch = "arm"), // may not match
+              target_os = "android"))] // may not match
+    mod signal {
+        use libc;
+        use super::sighandler_t;
+
+        pub static SA_ONSTACK: libc::c_int = 0x08000000;
+        pub static SA_SIGINFO: libc::c_int = 0x00000004;
+        pub static SIGBUS: libc::c_int = 7;
+
+        pub static SIGSTKSZ: libc::size_t = 8192;
+
+        pub static SIG_DFL: sighandler_t = 0i as sighandler_t;
+
+        // This definition is not as accurate as it could be, {si_addr} is
+        // actually a giant union. Currently we're only interested in that field,
+        // however.
+        #[repr(C)]
+        pub struct siginfo {
+            si_signo: libc::c_int,
+            si_errno: libc::c_int,
+            si_code: libc::c_int,
+            pub si_addr: *mut libc::c_void
+        }
+
+        #[repr(C)]
+        pub struct sigaction {
+            pub sa_sigaction: sighandler_t,
+            pub sa_mask: sigset_t,
+            pub sa_flags: libc::c_int,
+            sa_restorer: *mut libc::c_void,
+        }
+
+        #[cfg(target_word_size = "32")]
+        #[repr(C)]
+        pub struct sigset_t {
+            __val: [libc::c_ulong, ..32],
+        }
+        #[cfg(target_word_size = "64")]
+        #[repr(C)]
+        pub struct sigset_t {
+            __val: [libc::c_ulong, ..16],
+        }
+
+        #[repr(C)]
+        pub struct sigaltstack {
+            pub ss_sp: *mut libc::c_void,
+            pub ss_flags: libc::c_int,
+            pub ss_size: libc::size_t
+        }
+
+    }
+
+    #[cfg(target_os = "macos")]
+    mod signal {
+        use libc;
+        use super::sighandler_t;
+
+        pub const SA_ONSTACK: libc::c_int = 0x0001;
+        pub const SA_SIGINFO: libc::c_int = 0x0040;
+        pub const SIGBUS: libc::c_int = 10;
+
+        pub const SIGSTKSZ: libc::size_t = 131072;
+
+        pub const SIG_DFL: sighandler_t = 0i as sighandler_t;
+
+        pub type sigset_t = u32;
+
+        // This structure has more fields, but we're not all that interested in
+        // them.
+        #[repr(C)]
+        pub struct siginfo {
+            pub si_signo: libc::c_int,
+            pub si_errno: libc::c_int,
+            pub si_code: libc::c_int,
+            pub pid: libc::pid_t,
+            pub uid: libc::uid_t,
+            pub status: libc::c_int,
+            pub si_addr: *mut libc::c_void
+        }
+
+        #[repr(C)]
+        pub struct sigaltstack {
+            pub ss_sp: *mut libc::c_void,
+            pub ss_size: libc::size_t,
+            pub ss_flags: libc::c_int
+        }
+
+        #[repr(C)]
+        pub struct sigaction {
+            pub sa_sigaction: sighandler_t,
+            pub sa_mask: sigset_t,
+            pub sa_flags: libc::c_int,
+        }
+    }
+
+    extern {
+        pub fn signal(signum: libc::c_int, handler: sighandler_t) -> sighandler_t;
+        pub fn raise(signum: libc::c_int) -> libc::c_int;
+
+        pub fn sigaction(signum: libc::c_int,
+                         act: *const sigaction,
+                         oldact: *mut sigaction) -> libc::c_int;
+
+        pub fn sigaltstack(ss: *const sigaltstack,
+                           oss: *mut sigaltstack) -> libc::c_int;
+    }
+}
+
+#[cfg(not(any(target_os = "linux",
+              target_os = "macos",
+              windows)))]
+mod imp {
+    use libc;
+
+    pub unsafe fn init() {
+    }
+
+    pub unsafe fn cleanup() {
+    }
+
+    pub unsafe fn make_handler() -> super::Handler {
+        super::Handler { _data: 0i as *mut libc::c_void }
+    }
+
+    pub unsafe fn drop_handler(_handler: &mut super::Handler) {
+    }
+}
index ca5f76cf0d45a5122cdea9f84f877fe1d8c388b3..5eb28412abdab2dccf6d85c90f775f05426dec8f 100644 (file)
@@ -72,7 +72,7 @@
 /// # fn main() {
 ///
 /// // Create a task using a native runtime
-/// let task = native::task::new((0, uint::MAX));
+/// let task = native::task::new((0, uint::MAX), 0);
 ///
 /// // Run some code, catching any possible failures
 /// let task = task.run(|| {
@@ -197,7 +197,7 @@ pub fn new() -> Task {
     /// # fn main() {
     ///
     /// // Create a new native task
-    /// let task = native::task::new((0, uint::MAX));
+    /// let task = native::task::new((0, uint::MAX), 0);
     ///
     /// // Run some code once and then destroy this task
     /// task.run(|| {
index 9a67e5c72acec1f9453b13b939e108f84e9c0546..50b570091ada146246270bb266165966bb440db5 100644 (file)
 use libc;
 
 use stack;
+use stack_overflow;
 
+pub unsafe fn init() {
+    imp::guard::init();
+    stack_overflow::init();
+}
+
+pub unsafe fn cleanup() {
+    stack_overflow::cleanup();
+}
+
+#[cfg(target_os = "windows")]
+type StartFn = extern "system" fn(*mut libc::c_void) -> imp::rust_thread_return;
+
+#[cfg(not(target_os = "windows"))]
 type StartFn = extern "C" fn(*mut libc::c_void) -> imp::rust_thread_return;
 
 /// This struct represents a native thread's state. This is used to join on an
@@ -42,15 +56,45 @@ pub struct Thread<T> {
 // no_stack_check annotation), and then we extract the main function
 // and invoke it.
 #[no_stack_check]
-extern fn thread_start(main: *mut libc::c_void) -> imp::rust_thread_return {
+fn start_thread(main: *mut libc::c_void) -> imp::rust_thread_return {
     unsafe {
         stack::record_os_managed_stack_bounds(0, uint::MAX);
+        let handler = stack_overflow::Handler::new();
         let f: Box<proc()> = mem::transmute(main);
         (*f)();
+        drop(handler);
         mem::transmute(0 as imp::rust_thread_return)
     }
 }
 
+#[no_stack_check]
+#[cfg(target_os = "windows")]
+extern "system" fn thread_start(main: *mut libc::c_void) -> imp::rust_thread_return {
+    return start_thread(main);
+}
+
+#[no_stack_check]
+#[cfg(not(target_os = "windows"))]
+extern fn thread_start(main: *mut libc::c_void) -> imp::rust_thread_return {
+    return start_thread(main);
+}
+
+/// Returns the last writable byte of the main thread's stack next to the guard
+/// page. Must be called from the main thread.
+pub fn main_guard_page() -> uint {
+    unsafe {
+        imp::guard::main()
+    }
+}
+
+/// Returns the last writable byte of the current thread's stack next to the
+/// guard page. Must not be called from the main thread.
+pub fn current_guard_page() -> uint {
+    unsafe {
+        imp::guard::current()
+    }
+}
+
 // There are two impl blocks b/c if T were specified at the top then it's just a
 // pain to specify a type parameter on Thread::spawn (which doesn't need the
 // type parameter).
@@ -144,6 +188,7 @@ fn drop(&mut self) {
 }
 
 #[cfg(windows)]
+#[allow(non_snake_case)]
 mod imp {
     use core::prelude::*;
 
@@ -159,6 +204,19 @@ mod imp {
     pub type rust_thread = HANDLE;
     pub type rust_thread_return = DWORD;
 
+    pub mod guard {
+        pub unsafe fn main() -> uint {
+            0
+        }
+
+        pub unsafe fn current() -> uint {
+            0
+        }
+
+        pub unsafe fn init() {
+        }
+    }
+
     pub unsafe fn create(stack: uint, p: Box<proc():Send>) -> rust_thread {
         let arg: *mut libc::c_void = mem::transmute(p);
         // FIXME On UNIX, we guard against stack sizes that are too small but
@@ -227,6 +285,130 @@ mod imp {
     pub type rust_thread = libc::pthread_t;
     pub type rust_thread_return = *mut u8;
 
+    #[cfg(all(not(target_os = "linux"), not(target_os = "macos")))]
+    pub mod guard {
+        pub unsafe fn current() -> uint {
+            0
+        }
+
+        pub unsafe fn main() -> uint {
+            0
+        }
+
+        pub unsafe fn init() {
+        }
+    }
+
+    #[cfg(any(target_os = "linux", target_os = "macos"))]
+    pub mod guard {
+        use super::*;
+        #[cfg(any(target_os = "linux", target_os = "android"))]
+        use core::mem;
+        #[cfg(any(target_os = "linux", target_os = "android"))]
+        use core::ptr;
+        use libc;
+        use libc::funcs::posix88::mman::{mmap};
+        use libc::consts::os::posix88::{PROT_NONE,
+                                        MAP_PRIVATE,
+                                        MAP_ANON,
+                                        MAP_FAILED,
+                                        MAP_FIXED};
+
+        // These are initialized in init() and only read from after
+        static mut PAGE_SIZE: uint = 0;
+        static mut GUARD_PAGE: uint = 0;
+
+        #[cfg(target_os = "macos")]
+        unsafe fn get_stack_start() -> *mut libc::c_void {
+            current() as *mut libc::c_void
+        }
+
+        #[cfg(any(target_os = "linux", target_os = "android"))]
+        unsafe fn get_stack_start() -> *mut libc::c_void {
+            let mut attr: libc::pthread_attr_t = mem::zeroed();
+            if pthread_getattr_np(pthread_self(), &mut attr) != 0 {
+                fail!("failed to get thread attributes");
+            }
+            let mut stackaddr = ptr::null_mut();
+            let mut stacksize = 0;
+            if pthread_attr_getstack(&attr, &mut stackaddr, &mut stacksize) != 0 {
+                fail!("failed to get stack information");
+            }
+            if pthread_attr_destroy(&mut attr) != 0 {
+                fail!("failed to destroy thread attributes");
+            }
+            stackaddr
+        }
+
+        pub unsafe fn init() {
+            let psize = libc::sysconf(libc::consts::os::sysconf::_SC_PAGESIZE);
+            if psize == -1 {
+                fail!("failed to get page size");
+            }
+
+            PAGE_SIZE = psize as uint;
+
+            let stackaddr = get_stack_start();
+
+            // Rellocate the last page of the stack.
+            // This ensures SIGBUS will be raised on
+            // stack overflow.
+            let result = mmap(stackaddr,
+                              PAGE_SIZE as libc::size_t,
+                              PROT_NONE,
+                              MAP_PRIVATE | MAP_ANON | MAP_FIXED,
+                              -1,
+                              0);
+
+            if result != stackaddr || result == MAP_FAILED {
+                fail!("failed to allocate a guard page");
+            }
+
+            let offset = if cfg!(target_os = "linux") {
+                2
+            } else {
+                1
+            };
+
+            GUARD_PAGE = stackaddr as uint + offset * PAGE_SIZE;
+        }
+
+        pub unsafe fn main() -> uint {
+            GUARD_PAGE
+        }
+
+        #[cfg(target_os = "macos")]
+        pub unsafe fn current() -> uint {
+            (pthread_get_stackaddr_np(pthread_self()) as libc::size_t -
+             pthread_get_stacksize_np(pthread_self())) as uint
+        }
+
+        #[cfg(any(target_os = "linux", target_os = "android"))]
+        pub unsafe fn current() -> uint {
+            let mut attr: libc::pthread_attr_t = mem::zeroed();
+            if pthread_getattr_np(pthread_self(), &mut attr) != 0 {
+                fail!("failed to get thread attributes");
+            }
+            let mut guardsize = 0;
+            if pthread_attr_getguardsize(&attr, &mut guardsize) != 0 {
+                fail!("failed to get stack guard page");
+            }
+            if guardsize == 0 {
+                fail!("there is no guard page");
+            }
+            let mut stackaddr = ptr::null_mut();
+            let mut stacksize = 0;
+            if pthread_attr_getstack(&attr, &mut stackaddr, &mut stacksize) != 0 {
+                fail!("failed to get stack information");
+            }
+            if pthread_attr_destroy(&mut attr) != 0 {
+                fail!("failed to destroy thread attributes");
+            }
+
+            stackaddr as uint + guardsize as uint
+        }
+    }
+
     pub unsafe fn create(stack: uint, p: Box<proc():Send>) -> rust_thread {
         let mut native: libc::pthread_t = mem::zeroed();
         let mut attr: libc::pthread_attr_t = mem::zeroed();
@@ -307,6 +489,25 @@ fn min_stack_size(_: *const libc::pthread_attr_t) -> libc::size_t {
         PTHREAD_STACK_MIN
     }
 
+    #[cfg(any(target_os = "linux"))]
+    extern {
+        pub fn pthread_self() -> libc::pthread_t;
+        pub fn pthread_getattr_np(native: libc::pthread_t,
+                                  attr: *mut libc::pthread_attr_t) -> libc::c_int;
+        pub fn pthread_attr_getguardsize(attr: *const libc::pthread_attr_t,
+                                         guardsize: *mut libc::size_t) -> libc::c_int;
+        pub fn pthread_attr_getstack(attr: *const libc::pthread_attr_t,
+                                     stackaddr: *mut *mut libc::c_void,
+                                     stacksize: *mut libc::size_t) -> libc::c_int;
+    }
+
+    #[cfg(target_os = "macos")]
+    extern {
+        pub fn pthread_self() -> libc::pthread_t;
+        pub fn pthread_get_stackaddr_np(thread: libc::pthread_t) -> *mut libc::c_void;
+        pub fn pthread_get_stacksize_np(thread: libc::pthread_t) -> libc::size_t;
+    }
+
     extern {
         fn pthread_create(native: *mut libc::pthread_t,
                           attr: *const libc::pthread_attr_t,
@@ -315,7 +516,7 @@ fn pthread_create(native: *mut libc::pthread_t,
         fn pthread_join(native: libc::pthread_t,
                         value: *mut *mut libc::c_void) -> libc::c_int;
         fn pthread_attr_init(attr: *mut libc::pthread_attr_t) -> libc::c_int;
-        fn pthread_attr_destroy(attr: *mut libc::pthread_attr_t) -> libc::c_int;
+        pub fn pthread_attr_destroy(attr: *mut libc::pthread_attr_t) -> libc::c_int;
         fn pthread_attr_setstacksize(attr: *mut libc::pthread_attr_t,
                                      stack_size: libc::size_t) -> libc::c_int;
         fn pthread_attr_setdetachstate(attr: *mut libc::pthread_attr_t,
diff --git a/src/test/run-pass/out-of-stack-new-thread-no-split.rs b/src/test/run-pass/out-of-stack-new-thread-no-split.rs
new file mode 100644 (file)
index 0000000..e4a4216
--- /dev/null
@@ -0,0 +1,48 @@
+// Copyright 2012-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.
+
+//ignore-android
+//ignore-freebsd
+//ignore-ios
+//ignore-dragonfly
+
+#![feature(asm)]
+
+use std::io::process::Command;
+use std::os;
+
+// lifted from the test module
+// Inlining to avoid llvm turning the recursive functions into tail calls,
+// which doesn't consume stack.
+#[inline(always)]
+#[no_stack_check]
+pub fn black_box<T>(dummy: T) { unsafe { asm!("" : : "r"(&dummy)) } }
+
+#[no_stack_check]
+fn recurse() {
+    let buf = [0i, ..10];
+    black_box(buf);
+    recurse();
+}
+
+fn main() {
+    let args = os::args();
+    let args = args.as_slice();
+    if args.len() > 1 && args[1].as_slice() == "recurse" {
+        spawn(proc() {
+            recurse();
+        });
+    } else {
+        let recurse = Command::new(args[0].as_slice()).arg("recurse").output().unwrap();
+        assert!(!recurse.status.success());
+        let error = String::from_utf8_lossy(recurse.error.as_slice());
+        assert!(error.as_slice().contains("has overflowed its stack"));
+    }
+}
diff --git a/src/test/run-pass/out-of-stack-no-split.rs b/src/test/run-pass/out-of-stack-no-split.rs
new file mode 100644 (file)
index 0000000..ecb93cc
--- /dev/null
@@ -0,0 +1,47 @@
+// Copyright 2012-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.
+
+//ignore-android
+//ignore-linux
+//ignore-freebsd
+//ignore-ios
+//ignore-dragonfly
+
+#![feature(asm)]
+
+use std::io::process::Command;
+use std::os;
+
+// lifted from the test module
+// Inlining to avoid llvm turning the recursive functions into tail calls,
+// which doesn't consume stack.
+#[inline(always)]
+#[no_stack_check]
+pub fn black_box<T>(dummy: T) { unsafe { asm!("" : : "r"(&dummy)) } }
+
+#[no_stack_check]
+fn recurse() {
+    let buf = [0i, ..10];
+    black_box(buf);
+    recurse();
+}
+
+fn main() {
+    let args = os::args();
+    let args = args.as_slice();
+    if args.len() > 1 && args[1].as_slice() == "recurse" {
+        recurse();
+    } else {
+        let recurse = Command::new(args[0].as_slice()).arg("recurse").output().unwrap();
+        assert!(!recurse.status.success());
+        let error = String::from_utf8_lossy(recurse.error.as_slice());
+        assert!(error.as_slice().contains("has overflowed its stack"));
+    }
+}
index bbaa09bfac3100d5a4f110f7c73395d2522a99f4..7f2f9f9ece83de1ffc9398d49b99b99db0be3ea2 100644 (file)
@@ -42,17 +42,11 @@ fn main() {
         let silent = Command::new(args[0].as_slice()).arg("silent").output().unwrap();
         assert!(!silent.status.success());
         let error = String::from_utf8_lossy(silent.error.as_slice());
-        // FIXME #17562: Windows is using stack probes and isn't wired up to print an error
-        if !cfg!(windows) {
-            assert!(error.as_slice().contains("has overflowed its stack"));
-        }
+        assert!(error.as_slice().contains("has overflowed its stack"));
 
         let loud = Command::new(args[0].as_slice()).arg("loud").output().unwrap();
         assert!(!loud.status.success());
         let error = String::from_utf8_lossy(silent.error.as_slice());
-        // FIXME #17562: Windows is using stack probes and isn't wired up to print an error
-        if !cfg!(windows) {
-            assert!(error.as_slice().contains("has overflowed its stack"));
-        }
+        assert!(error.as_slice().contains("has overflowed its stack"));
     }
 }
diff --git a/src/test/run-pass/segfault-no-out-of-stack.rs b/src/test/run-pass/segfault-no-out-of-stack.rs
new file mode 100644 (file)
index 0000000..6ef33c1
--- /dev/null
@@ -0,0 +1,25 @@
+// Copyright 2012-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 std::io::process::Command;
+use std::os;
+
+fn main() {
+    let args = os::args();
+    let args = args.as_slice();
+    if args.len() > 1 && args[1].as_slice() == "segfault" {
+        unsafe { *(0 as *mut int) = 1 }; // trigger a segfault
+    } else {
+        let segfault = Command::new(args[0].as_slice()).arg("segfault").output().unwrap();
+        assert!(!segfault.status.success());
+        let error = String::from_utf8_lossy(segfault.error.as_slice());
+        assert!(!error.as_slice().contains("has overflowed its stack"));
+    }
+}