From 70cef9474a3307ec763efc01fe6969e542083823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Thu, 23 Oct 2014 07:29:41 +0200 Subject: [PATCH] Print stack overflow messages for Windows, Linux and OS X Fixes #17562 --- src/libgreen/simple.rs | 1 + src/libgreen/stack.rs | 5 + src/libgreen/task.rs | 7 + src/libnative/lib.rs | 3 +- src/libnative/task.rs | 15 +- src/librustrt/lib.rs | 5 + src/librustrt/stack.rs | 20 +- src/librustrt/stack_overflow.rs | 412 ++++++++++++++++++ src/librustrt/task.rs | 4 +- src/librustrt/thread.rs | 205 ++++++++- .../out-of-stack-new-thread-no-split.rs | 48 ++ src/test/run-pass/out-of-stack-no-split.rs | 47 ++ src/test/run-pass/out-of-stack.rs | 10 +- src/test/run-pass/segfault-no-out-of-stack.rs | 25 ++ 14 files changed, 774 insertions(+), 33 deletions(-) create mode 100644 src/librustrt/stack_overflow.rs create mode 100644 src/test/run-pass/out-of-stack-new-thread-no-split.rs create mode 100644 src/test/run-pass/out-of-stack-no-split.rs create mode 100644 src/test/run-pass/segfault-no-out-of-stack.rs diff --git a/src/libgreen/simple.rs b/src/libgreen/simple.rs index 058a00bcd4bc0..686a039d6d600 100644 --- a/src/libgreen/simple.rs +++ b/src/libgreen/simple.rs @@ -81,6 +81,7 @@ impl Runtime for SimpleTask { } fn local_io<'a>(&'a mut self) -> Option> { None } fn stack_bounds(&self) -> (uint, uint) { fail!() } + fn stack_guard(&self) -> Option { fail!() } fn can_block(&self) -> bool { true } fn wrap(self: Box) -> Box { fail!() } } diff --git a/src/libgreen/stack.rs b/src/libgreen/stack.rs index 6a5772ff6282a..cccf0ec698779 100644 --- a/src/libgreen/stack.rs +++ b/src/libgreen/stack.rs @@ -82,6 +82,11 @@ impl 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) diff --git a/src/libgreen/task.rs b/src/libgreen/task.rs index b6bd8c3cec4a7..f151e00f56d56 100644 --- a/src/libgreen/task.rs +++ b/src/libgreen/task.rs @@ -486,6 +486,13 @@ impl Runtime for GreenTask { c.current_stack_segment.end() as uint) } + fn stack_guard(&self) -> Option { + 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) -> Box { diff --git a/src/libnative/lib.rs b/src/libnative/lib.rs index 656c7e4103c6e..c99143f0a5d7b 100644 --- a/src/libnative/lib.rs +++ b/src/libnative/lib.rs @@ -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("
")); drop(task.run(|| { unsafe { diff --git a/src/libnative/task.rs b/src/libnative/task.rs index d90535428dac6..455656c09d493 100644 --- a/src/libnative/task.rs +++ b/src/libnative/task.rs @@ -29,10 +29,11 @@ use io; 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 { +pub fn new(stack_bounds: (uint, uint), stack_guard: uint) -> Box { 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 { io: io::IoFactory::new(), // these *should* get overwritten stack_bounds: (0, 0), + stack_guard: 0 } } @@ -82,6 +84,7 @@ impl Spawner for NativeSpawner { 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 @@ impl rt::Runtime for Ops { fn stack_bounds(&self) -> (uint, uint) { self.stack_bounds } + fn stack_guard(&self) -> Option { + 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 diff --git a/src/librustrt/lib.rs b/src/librustrt/lib.rs index e45167565ea48..972497f981883 100644 --- a/src/librustrt/lib.rs +++ b/src/librustrt/lib.rs @@ -51,6 +51,7 @@ mod local_ptr; mod thread_local_storage; mod util; mod libunwind; +mod stack_overflow; pub mod args; pub mod bookkeeping; @@ -92,6 +93,8 @@ pub trait Runtime { fn local_io<'a>(&'a mut self) -> Option>; /// 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; 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(); } diff --git a/src/librustrt/stack.rs b/src/librustrt/stack.rs index 4034000e28f33..4874f642a93af 100644 --- a/src/librustrt/stack.rs +++ b/src/librustrt/stack.rs @@ -55,10 +55,6 @@ pub const RED_ZONE: uint = 20 * 1024; #[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 { @@ -104,21 +100,7 @@ extern fn stack_exhausted() { // #9854 - unwinding on windows through __morestack has never worked // #2361 - possible implementation of not using landing pads - let task: Option> = 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(""); - - // 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 index 0000000000000..aaaeb8846ccaf --- /dev/null +++ b/src/librustrt/stack_overflow.rs @@ -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 or the MIT license +// , 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("")); +} + +// 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 { + 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) { + } +} diff --git a/src/librustrt/task.rs b/src/librustrt/task.rs index ca5f76cf0d45a..5eb28412abdab 100644 --- a/src/librustrt/task.rs +++ b/src/librustrt/task.rs @@ -72,7 +72,7 @@ use collections::str::SendStr; /// # 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 @@ impl 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(|| { diff --git a/src/librustrt/thread.rs b/src/librustrt/thread.rs index 9a67e5c72acec..50b570091ada1 100644 --- a/src/librustrt/thread.rs +++ b/src/librustrt/thread.rs @@ -24,7 +24,21 @@ use core::uint; 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 @@ static DEFAULT_STACK_SIZE: uint = 1024 * 1024; // 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 = 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 @@ impl Drop for Thread { } #[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) -> 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) -> rust_thread { let mut native: libc::pthread_t = mem::zeroed(); let mut attr: libc::pthread_attr_t = mem::zeroed(); @@ -307,6 +489,25 @@ mod imp { 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 @@ mod imp { 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 index 0000000000000..e4a4216132214 --- /dev/null +++ b/src/test/run-pass/out-of-stack-new-thread-no-split.rs @@ -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 or the MIT license +// , 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(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 index 0000000000000..ecb93cc6f8c7e --- /dev/null +++ b/src/test/run-pass/out-of-stack-no-split.rs @@ -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 or the MIT license +// , 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(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")); + } +} diff --git a/src/test/run-pass/out-of-stack.rs b/src/test/run-pass/out-of-stack.rs index bbaa09bfac310..7f2f9f9ece83d 100644 --- a/src/test/run-pass/out-of-stack.rs +++ b/src/test/run-pass/out-of-stack.rs @@ -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 index 0000000000000..6ef33c1a11211 --- /dev/null +++ b/src/test/run-pass/segfault-no-out-of-stack.rs @@ -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 or the MIT license +// , 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")); + } +}