1 // Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Rust stack-limit management
13 //! Currently Rust uses a segmented-stack-like scheme in order to detect stack
14 //! overflow for rust tasks. In this scheme, the prologue of all functions are
15 //! preceded with a check to see whether the current stack limits are being
18 //! This module provides the functionality necessary in order to manage these
19 //! stack limits (which are stored in platform-specific locations). The
20 //! functions here are used at the borders of the task lifetime in order to
21 //! manage these limits.
23 //! This function is an unstable module because this scheme for stack overflow
24 //! detection is not guaranteed to continue in the future. Usage of this module
25 //! is discouraged unless absolutely necessary.
29 // It is possible to implement it using idea from
30 // http://www.opensource.apple.com/source/Libc/Libc-825.40.1/pthreads/pthread_machdep.h
32 // In short: _pthread_{get,set}_specific_direct allows extremely fast
33 // access, exactly what is required for segmented stack
34 // There is a pool of reserved slots for Apple internal use (0..119)
35 // First dynamic allocated pthread key starts with 257 (on iOS7)
36 // So using slot 149 should be pretty safe ASSUMING space is reserved
37 // for every key < first dynamic key
39 // There is also an opportunity to steal keys reserved for Garbage Collection
40 // ranges 80..89 and 110..119, especially considering the fact Garbage Collection
41 // never supposed to work on iOS. But as everybody knows it - there is a chance
42 // that those slots will be re-used, like it happened with key 95 (moved from
43 // JavaScriptCore to CoreText)
45 // Unfortunately Apple rejected patch to LLVM which generated
46 // corresponding prolog, decision was taken to disable segmented
47 // stack support on iOS.
49 pub static RED_ZONE: uint = 20 * 1024;
51 /// This function is invoked from rust's current __morestack function. Segmented
52 /// stacks are currently not enabled as segmented stacks, but rather one giant
53 /// stack segment. This means that whenever we run out of stack, we want to
54 /// truly consider it to be stack overflow rather than allocating a new stack.
55 #[cfg(not(test))] // in testing, use the original libstd's version
56 #[lang = "stack_exhausted"]
57 extern fn stack_exhausted() {
59 use alloc::boxed::Box;
65 // We're calling this function because the stack just ran out. We need
66 // to call some other rust functions, but if we invoke the functions
67 // right now it'll just trigger this handler being called again. In
68 // order to alleviate this, we move the stack limit to be inside of the
69 // red zone that was allocated for exactly this reason.
70 let limit = get_sp_limit();
71 record_sp_limit(limit - RED_ZONE / 2);
73 // This probably isn't the best course of action. Ideally one would want
74 // to unwind the stack here instead of just aborting the entire process.
75 // This is a tricky problem, however. There's a few things which need to
78 // 1. We're here because of a stack overflow, yet unwinding will run
79 // destructors and hence arbitrary code. What if that code overflows
80 // the stack? One possibility is to use the above allocation of an
81 // extra 10k to hope that we don't hit the limit, and if we do then
82 // abort the whole program. Not the best, but kind of hard to deal
83 // with unless we want to switch stacks.
85 // 2. LLVM will optimize functions based on whether they can unwind or
86 // not. It will flag functions with 'nounwind' if it believes that
87 // the function cannot trigger unwinding, but if we do unwind on
88 // stack overflow then it means that we could unwind in any function
89 // anywhere. We would have to make sure that LLVM only places the
90 // nounwind flag on functions which don't call any other functions.
92 // 3. The function that overflowed may have owned arguments. These
93 // arguments need to have their destructors run, but we haven't even
94 // begun executing the function yet, so unwinding will not run the
95 // any landing pads for these functions. If this is ignored, then
96 // the arguments will just be leaked.
98 // Exactly what to do here is a very delicate topic, and is possibly
99 // still up in the air for what exactly to do. Some relevant issues:
101 // #3555 - out-of-stack failure leaks arguments
102 // #3695 - should there be a stack limit?
103 // #9855 - possible strategies which could be taken
104 // #9854 - unwinding on windows through __morestack has never worked
105 // #2361 - possible implementation of not using landing pads
107 let task: Option<Box<Task>> = Local::try_take();
108 let name = match task {
110 task.name.as_ref().map(|n| n.as_slice())
114 let name = name.unwrap_or("<unknown>");
116 // See the message below for why this is not emitted to the
117 // task's logger. This has the additional conundrum of the
118 // logger may not be initialized just yet, meaning that an FFI
119 // call would happen to initialized it (calling out to libuv),
120 // and the FFI call needs 2MB of stack when we just ran out.
121 rterrln!("task '{}' has overflowed its stack", name);
127 // Windows maintains a record of upper and lower stack bounds in the Thread Information
128 // Block (TIB), and some syscalls do check that addresses which are supposed to be in
129 // the stack, indeed lie between these two values.
130 // (See https://github.com/rust-lang/rust/issues/3445#issuecomment-26114839)
132 // When using Rust-managed stacks (libgreen), we must maintain these values accordingly.
133 // For OS-managed stacks (libnative), we let the OS manage them for us.
135 // On all other platforms both variants behave identically.
138 pub unsafe fn record_os_managed_stack_bounds(stack_lo: uint, _stack_hi: uint) {
139 record_sp_limit(stack_lo + RED_ZONE);
143 pub unsafe fn record_rust_managed_stack_bounds(stack_lo: uint, stack_hi: uint) {
144 // When the old runtime had segmented stacks, it used a calculation that was
145 // "limit + RED_ZONE + FUDGE". The red zone was for things like dynamic
146 // symbol resolution, llvm function calls, etc. In theory this red zone
147 // value is 0, but it matters far less when we have gigantic stacks because
148 // we don't need to be so exact about our stack budget. The "fudge factor"
149 // was because LLVM doesn't emit a stack check for functions < 256 bytes in
150 // size. Again though, we have giant stacks, so we round all these
151 // calculations up to the nice round number of 20k.
152 record_sp_limit(stack_lo + RED_ZONE);
154 return target_record_stack_bounds(stack_lo, stack_hi);
156 #[cfg(not(windows))] #[inline(always)]
157 unsafe fn target_record_stack_bounds(_stack_lo: uint, _stack_hi: uint) {}
159 #[cfg(windows, target_arch = "x86")] #[inline(always)]
160 unsafe fn target_record_stack_bounds(stack_lo: uint, stack_hi: uint) {
161 // stack range is at TIB: %fs:0x04 (top) and %fs:0x08 (bottom)
162 asm!("mov $0, %fs:0x04" :: "r"(stack_hi) :: "volatile");
163 asm!("mov $0, %fs:0x08" :: "r"(stack_lo) :: "volatile");
165 #[cfg(windows, target_arch = "x86_64")] #[inline(always)]
166 unsafe fn target_record_stack_bounds(stack_lo: uint, stack_hi: uint) {
167 // stack range is at TIB: %gs:0x08 (top) and %gs:0x10 (bottom)
168 asm!("mov $0, %gs:0x08" :: "r"(stack_hi) :: "volatile");
169 asm!("mov $0, %gs:0x10" :: "r"(stack_lo) :: "volatile");
173 /// Records the current limit of the stack as specified by `end`.
175 /// This is stored in an OS-dependent location, likely inside of the thread
176 /// local storage. The location that the limit is stored is a pre-ordained
177 /// location because it's where LLVM has emitted code to check.
179 /// Note that this cannot be called under normal circumstances. This function is
180 /// changing the stack limit, so upon returning any further function calls will
181 /// possibly be triggering the morestack logic if you're not careful.
183 /// Also note that this and all of the inside functions are all flagged as
184 /// "inline(always)" because they're messing around with the stack limits. This
185 /// would be unfortunate for the functions themselves to trigger a morestack
186 /// invocation (if they were an actual function call).
188 pub unsafe fn record_sp_limit(limit: uint) {
189 return target_record_sp_limit(limit);
192 #[cfg(target_arch = "x86_64", target_os = "macos")]
193 #[cfg(target_arch = "x86_64", target_os = "ios")] #[inline(always)]
194 unsafe fn target_record_sp_limit(limit: uint) {
195 asm!("movq $$0x60+90*8, %rsi
196 movq $0, %gs:(%rsi)" :: "r"(limit) : "rsi" : "volatile")
198 #[cfg(target_arch = "x86_64", target_os = "linux")] #[inline(always)]
199 unsafe fn target_record_sp_limit(limit: uint) {
200 asm!("movq $0, %fs:112" :: "r"(limit) :: "volatile")
202 #[cfg(target_arch = "x86_64", target_os = "windows")] #[inline(always)]
203 unsafe fn target_record_sp_limit(limit: uint) {
204 // see: http://en.wikipedia.org/wiki/Win32_Thread_Information_Block
205 // store this inside of the "arbitrary data slot", but double the size
206 // because this is 64 bit instead of 32 bit
207 asm!("movq $0, %gs:0x28" :: "r"(limit) :: "volatile")
209 #[cfg(target_arch = "x86_64", target_os = "freebsd")] #[inline(always)]
210 unsafe fn target_record_sp_limit(limit: uint) {
211 asm!("movq $0, %fs:24" :: "r"(limit) :: "volatile")
213 #[cfg(target_arch = "x86_64", target_os = "dragonfly")] #[inline(always)]
214 unsafe fn target_record_sp_limit(limit: uint) {
215 asm!("movq $0, %fs:32" :: "r"(limit) :: "volatile")
219 #[cfg(target_arch = "x86", target_os = "macos")]
220 #[cfg(target_arch = "x86", target_os = "ios")] #[inline(always)]
221 unsafe fn target_record_sp_limit(limit: uint) {
222 asm!("movl $$0x48+90*4, %eax
223 movl $0, %gs:(%eax)" :: "r"(limit) : "eax" : "volatile")
225 #[cfg(target_arch = "x86", target_os = "linux")]
226 #[cfg(target_arch = "x86", target_os = "freebsd")] #[inline(always)]
227 unsafe fn target_record_sp_limit(limit: uint) {
228 asm!("movl $0, %gs:48" :: "r"(limit) :: "volatile")
230 #[cfg(target_arch = "x86", target_os = "windows")] #[inline(always)]
231 unsafe fn target_record_sp_limit(limit: uint) {
232 // see: http://en.wikipedia.org/wiki/Win32_Thread_Information_Block
233 // store this inside of the "arbitrary data slot"
234 asm!("movl $0, %fs:0x14" :: "r"(limit) :: "volatile")
237 // mips, arm - Some brave soul can port these to inline asm, but it's over
238 // my head personally
239 #[cfg(target_arch = "mips")]
240 #[cfg(target_arch = "mipsel")]
241 #[cfg(target_arch = "arm", not(target_os = "ios"))] #[inline(always)]
242 unsafe fn target_record_sp_limit(limit: uint) {
244 return record_sp_limit(limit as *const c_void);
246 fn record_sp_limit(limit: *const c_void);
250 // iOS segmented stack is disabled for now, see related notes
251 #[cfg(target_arch = "arm", target_os = "ios")] #[inline(always)]
252 unsafe fn target_record_sp_limit(_: uint) {
256 /// The counterpart of the function above, this function will fetch the current
257 /// stack limit stored in TLS.
259 /// Note that all of these functions are meant to be exact counterparts of their
260 /// brethren above, except that the operands are reversed.
262 /// As with the setter, this function does not have a __morestack header and can
263 /// therefore be called in a "we're out of stack" situation.
265 pub unsafe fn get_sp_limit() -> uint {
266 return target_get_sp_limit();
269 #[cfg(target_arch = "x86_64", target_os = "macos")]
270 #[cfg(target_arch = "x86_64", target_os = "ios")] #[inline(always)]
271 unsafe fn target_get_sp_limit() -> uint {
273 asm!("movq $$0x60+90*8, %rsi
274 movq %gs:(%rsi), $0" : "=r"(limit) :: "rsi" : "volatile");
277 #[cfg(target_arch = "x86_64", target_os = "linux")] #[inline(always)]
278 unsafe fn target_get_sp_limit() -> uint {
280 asm!("movq %fs:112, $0" : "=r"(limit) ::: "volatile");
283 #[cfg(target_arch = "x86_64", target_os = "windows")] #[inline(always)]
284 unsafe fn target_get_sp_limit() -> uint {
286 asm!("movq %gs:0x28, $0" : "=r"(limit) ::: "volatile");
289 #[cfg(target_arch = "x86_64", target_os = "freebsd")] #[inline(always)]
290 unsafe fn target_get_sp_limit() -> uint {
292 asm!("movq %fs:24, $0" : "=r"(limit) ::: "volatile");
295 #[cfg(target_arch = "x86_64", target_os = "dragonfly")] #[inline(always)]
296 unsafe fn target_get_sp_limit() -> uint {
298 asm!("movq %fs:32, $0" : "=r"(limit) ::: "volatile");
304 #[cfg(target_arch = "x86", target_os = "macos")]
305 #[cfg(target_arch = "x86", target_os = "ios")] #[inline(always)]
306 unsafe fn target_get_sp_limit() -> uint {
308 asm!("movl $$0x48+90*4, %eax
309 movl %gs:(%eax), $0" : "=r"(limit) :: "eax" : "volatile");
312 #[cfg(target_arch = "x86", target_os = "linux")]
313 #[cfg(target_arch = "x86", target_os = "freebsd")] #[inline(always)]
314 unsafe fn target_get_sp_limit() -> uint {
316 asm!("movl %gs:48, $0" : "=r"(limit) ::: "volatile");
319 #[cfg(target_arch = "x86", target_os = "windows")] #[inline(always)]
320 unsafe fn target_get_sp_limit() -> uint {
322 asm!("movl %fs:0x14, $0" : "=r"(limit) ::: "volatile");
326 // mips, arm - Some brave soul can port these to inline asm, but it's over
327 // my head personally
328 #[cfg(target_arch = "mips")]
329 #[cfg(target_arch = "mipsel")]
330 #[cfg(target_arch = "arm", not(target_os = "ios"))] #[inline(always)]
331 unsafe fn target_get_sp_limit() -> uint {
333 return get_sp_limit() as uint;
335 fn get_sp_limit() -> *const c_void;
339 // iOS doesn't support segmented stacks yet. This function might
340 // be called by runtime though so it is unsafe to mark it as
341 // unreachable, let's return a fixed constant.
342 #[cfg(target_arch = "arm", target_os = "ios")] #[inline(always)]
343 unsafe fn target_get_sp_limit() -> uint {