|
| 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. |
| 4 | +// |
| 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. |
| 10 | + |
| 11 | +//! Rust stack-limit management |
| 12 | +//! |
| 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 |
| 16 | +//! exceeded. |
| 17 | +//! |
| 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. |
| 22 | +//! |
| 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. |
| 26 | +
|
| 27 | +use rt::task::Task; |
| 28 | +use option::None; |
| 29 | +use rt::local::Local; |
| 30 | +use unstable::intrinsics; |
| 31 | + |
| 32 | +static RED_ZONE: uint = 20 * 1024; |
| 33 | + |
| 34 | +/// This function is invoked from rust's current __morestack function. Segmented |
| 35 | +/// stacks are currently not enabled as segmented stacks, but rather one giant |
| 36 | +/// stack segment. This means that whenever we run out of stack, we want to |
| 37 | +/// truly consider it to be stack overflow rather than allocating a new stack. |
| 38 | +#[no_mangle] // - this is called from C code |
| 39 | +#[no_split_stack] // - it would be sad for this function to trigger __morestack |
| 40 | +#[doc(hidden)] // - Function must be `pub` to get exported, but it's |
| 41 | + // irrelevant for documentation purposes. |
| 42 | +#[cfg(not(test))] // in testing, use the original libstd's version |
| 43 | +pub extern "C" fn rust_stack_exhausted() { |
| 44 | + |
| 45 | + unsafe { |
| 46 | + // We're calling this function because the stack just ran out. We need |
| 47 | + // to call some other rust functions, but if we invoke the functions |
| 48 | + // right now it'll just trigger this handler being called again. In |
| 49 | + // order to alleviate this, we move the stack limit to be inside of the |
| 50 | + // red zone that was allocated for exactly this reason. |
| 51 | + let limit = get_sp_limit(); |
| 52 | + record_sp_limit(limit - RED_ZONE / 2); |
| 53 | + |
| 54 | + // This probably isn't the best course of action. Ideally one would want |
| 55 | + // to unwind the stack here instead of just aborting the entire process. |
| 56 | + // This is a tricky problem, however. There's a few things which need to |
| 57 | + // be considered: |
| 58 | + // |
| 59 | + // 1. We're here because of a stack overflow, yet unwinding will run |
| 60 | + // destructors and hence arbitrary code. What if that code overflows |
| 61 | + // the stack? One possibility is to use the above allocation of an |
| 62 | + // extra 10k to hope that we don't hit the limit, and if we do then |
| 63 | + // abort the whole program. Not the best, but kind of hard to deal |
| 64 | + // with unless we want to switch stacks. |
| 65 | + // |
| 66 | + // 2. LLVM will optimize functions based on whether they can unwind or |
| 67 | + // not. It will flag functions with 'nounwind' if it believes that |
| 68 | + // the function cannot trigger unwinding, but if we do unwind on |
| 69 | + // stack overflow then it means that we could unwind in any function |
| 70 | + // anywhere. We would have to make sure that LLVM only places the |
| 71 | + // nounwind flag on functions which don't call any other functions. |
| 72 | + // |
| 73 | + // 3. The function that overflowed may have owned arguments. These |
| 74 | + // arguments need to have their destructors run, but we haven't even |
| 75 | + // begun executing the function yet, so unwinding will not run the |
| 76 | + // any landing pads for these functions. If this is ignored, then |
| 77 | + // the arguments will just be leaked. |
| 78 | + // |
| 79 | + // Exactly what to do here is a very delicate topic, and is possibly |
| 80 | + // still up in the air for what exactly to do. Some relevant issues: |
| 81 | + // |
| 82 | + // #3555 - out-of-stack failure leaks arguments |
| 83 | + // #3695 - should there be a stack limit? |
| 84 | + // #9855 - possible strategies which could be taken |
| 85 | + // #9854 - unwinding on windows through __morestack has never worked |
| 86 | + // #2361 - possible implementation of not using landing pads |
| 87 | + |
| 88 | + let mut task = Local::borrow(None::<Task>); |
| 89 | + let n = task.get().name.as_ref() |
| 90 | + .map(|n| n.as_slice()).unwrap_or("<unnamed>"); |
| 91 | + |
| 92 | + // See the message below for why this is not emitted to the |
| 93 | + // task's logger. This has the additional conundrum of the |
| 94 | + // logger may not be initialized just yet, meaning that an FFI |
| 95 | + // call would happen to initialized it (calling out to libuv), |
| 96 | + // and the FFI call needs 2MB of stack when we just ran out. |
| 97 | + println!("task '{}' has overflowed its stack", n); |
| 98 | + |
| 99 | + intrinsics::abort(); |
| 100 | + } |
| 101 | +} |
| 102 | + |
| 103 | +#[inline(always)] |
| 104 | +pub unsafe fn record_stack_bounds(stack_lo: uint, stack_hi: uint) { |
| 105 | + // When the old runtime had segmented stacks, it used a calculation that was |
| 106 | + // "limit + RED_ZONE + FUDGE". The red zone was for things like dynamic |
| 107 | + // symbol resolution, llvm function calls, etc. In theory this red zone |
| 108 | + // value is 0, but it matters far less when we have gigantic stacks because |
| 109 | + // we don't need to be so exact about our stack budget. The "fudge factor" |
| 110 | + // was because LLVM doesn't emit a stack check for functions < 256 bytes in |
| 111 | + // size. Again though, we have giant stacks, so we round all these |
| 112 | + // calculations up to the nice round number of 20k. |
| 113 | + record_sp_limit(stack_lo + RED_ZONE); |
| 114 | + |
| 115 | + return target_record_stack_bounds(stack_lo, stack_hi); |
| 116 | + |
| 117 | + #[cfg(not(windows))] #[cfg(not(target_arch = "x86_64"))] #[inline(always)] |
| 118 | + unsafe fn target_record_stack_bounds(_stack_lo: uint, _stack_hi: uint) {} |
| 119 | + #[cfg(windows, target_arch = "x86_64")] #[inline(always)] |
| 120 | + unsafe fn target_record_stack_bounds(stack_lo: uint, stack_hi: uint) { |
| 121 | + // Windows compiles C functions which may check the stack bounds. This |
| 122 | + // means that if we want to perform valid FFI on windows, then we need |
| 123 | + // to ensure that the stack bounds are what they truly are for this |
| 124 | + // task. More info can be found at: |
| 125 | + // https://github.com/mozilla/rust/issues/3445#issuecomment-26114839 |
| 126 | + // |
| 127 | + // stack range is at TIB: %gs:0x08 (top) and %gs:0x10 (bottom) |
| 128 | + asm!("mov $0, %gs:0x08" :: "r"(stack_hi) :: "volatile"); |
| 129 | + asm!("mov $0, %gs:0x10" :: "r"(stack_lo) :: "volatile"); |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +/// Records the current limit of the stack as specified by `end`. |
| 134 | +/// |
| 135 | +/// This is stored in an OS-dependent location, likely inside of the thread |
| 136 | +/// local storage. The location that the limit is stored is a pre-ordained |
| 137 | +/// location because it's where LLVM has emitted code to check. |
| 138 | +/// |
| 139 | +/// Note that this cannot be called under normal circumstances. This function is |
| 140 | +/// changing the stack limit, so upon returning any further function calls will |
| 141 | +/// possibly be triggering the morestack logic if you're not careful. |
| 142 | +/// |
| 143 | +/// Also note that this and all of the inside functions are all flagged as |
| 144 | +/// "inline(always)" because they're messing around with the stack limits. This |
| 145 | +/// would be unfortunate for the functions themselves to trigger a morestack |
| 146 | +/// invocation (if they were an actual function call). |
| 147 | +#[inline(always)] |
| 148 | +pub unsafe fn record_sp_limit(limit: uint) { |
| 149 | + return target_record_sp_limit(limit); |
| 150 | + |
| 151 | + // x86-64 |
| 152 | + #[cfg(target_arch = "x86_64", target_os = "macos")] #[inline(always)] |
| 153 | + unsafe fn target_record_sp_limit(limit: uint) { |
| 154 | + asm!("movq $$0x60+90*8, %rsi |
| 155 | + movq $0, %gs:(%rsi)" :: "r"(limit) : "rsi" : "volatile") |
| 156 | + } |
| 157 | + #[cfg(target_arch = "x86_64", target_os = "linux")] #[inline(always)] |
| 158 | + unsafe fn target_record_sp_limit(limit: uint) { |
| 159 | + asm!("movq $0, %fs:112" :: "r"(limit) :: "volatile") |
| 160 | + } |
| 161 | + #[cfg(target_arch = "x86_64", target_os = "win32")] #[inline(always)] |
| 162 | + unsafe fn target_record_sp_limit(limit: uint) { |
| 163 | + // see: http://en.wikipedia.org/wiki/Win32_Thread_Information_Block |
| 164 | + // store this inside of the "arbitrary data slot", but double the size |
| 165 | + // because this is 64 bit instead of 32 bit |
| 166 | + asm!("movq $0, %gs:0x28" :: "r"(limit) :: "volatile") |
| 167 | + } |
| 168 | + #[cfg(target_arch = "x86_64", target_os = "freebsd")] #[inline(always)] |
| 169 | + unsafe fn target_record_sp_limit(limit: uint) { |
| 170 | + asm!("movq $0, %fs:24" :: "r"(limit) :: "volatile") |
| 171 | + } |
| 172 | + |
| 173 | + // x86 |
| 174 | + #[cfg(target_arch = "x86", target_os = "macos")] #[inline(always)] |
| 175 | + unsafe fn target_record_sp_limit(limit: uint) { |
| 176 | + asm!("movl $$0x48+90*4, %eax |
| 177 | + movl $0, %gs:(%eax)" :: "r"(limit) : "eax" : "volatile") |
| 178 | + } |
| 179 | + #[cfg(target_arch = "x86", target_os = "linux")] |
| 180 | + #[cfg(target_arch = "x86", target_os = "freebsd")] #[inline(always)] |
| 181 | + unsafe fn target_record_sp_limit(limit: uint) { |
| 182 | + asm!("movl $0, %gs:48" :: "r"(limit) :: "volatile") |
| 183 | + } |
| 184 | + #[cfg(target_arch = "x86", target_os = "win32")] #[inline(always)] |
| 185 | + unsafe fn target_record_sp_limit(limit: uint) { |
| 186 | + // see: http://en.wikipedia.org/wiki/Win32_Thread_Information_Block |
| 187 | + // store this inside of the "arbitrary data slot" |
| 188 | + asm!("movl $0, %fs:0x14" :: "r"(limit) :: "volatile") |
| 189 | + } |
| 190 | + |
| 191 | + // mips, arm - Some brave soul can port these to inline asm, but it's over |
| 192 | + // my head personally |
| 193 | + #[cfg(target_arch = "mips")] |
| 194 | + #[cfg(target_arch = "arm")] #[inline(always)] |
| 195 | + unsafe fn target_record_sp_limit(limit: uint) { |
| 196 | + return record_sp_limit(limit as *c_void); |
| 197 | + extern { |
| 198 | + fn record_sp_limit(limit: *c_void); |
| 199 | + } |
| 200 | + } |
| 201 | +} |
| 202 | + |
| 203 | +/// The counterpart of the function above, this function will fetch the current |
| 204 | +/// stack limit stored in TLS. |
| 205 | +/// |
| 206 | +/// Note that all of these functions are meant to be exact counterparts of their |
| 207 | +/// brethren above, except that the operands are reversed. |
| 208 | +/// |
| 209 | +/// As with the setter, this function does not have a __morestack header and can |
| 210 | +/// therefore be called in a "we're out of stack" situation. |
| 211 | +#[inline(always)] |
| 212 | +pub unsafe fn get_sp_limit() -> uint { |
| 213 | + return target_get_sp_limit(); |
| 214 | + |
| 215 | + // x86-64 |
| 216 | + #[cfg(target_arch = "x86_64", target_os = "macos")] #[inline(always)] |
| 217 | + unsafe fn target_get_sp_limit() -> uint { |
| 218 | + let limit; |
| 219 | + asm!("movq $$0x60+90*8, %rsi |
| 220 | + movq %gs:(%rsi), $0" : "=r"(limit) :: "rsi" : "volatile"); |
| 221 | + return limit; |
| 222 | + } |
| 223 | + #[cfg(target_arch = "x86_64", target_os = "linux")] #[inline(always)] |
| 224 | + unsafe fn target_get_sp_limit() -> uint { |
| 225 | + let limit; |
| 226 | + asm!("movq %fs:112, $0" : "=r"(limit) ::: "volatile"); |
| 227 | + return limit; |
| 228 | + } |
| 229 | + #[cfg(target_arch = "x86_64", target_os = "win32")] #[inline(always)] |
| 230 | + unsafe fn target_get_sp_limit() -> uint { |
| 231 | + let limit; |
| 232 | + asm!("movq %gs:0x28, $0" : "=r"(limit) ::: "volatile"); |
| 233 | + return limit; |
| 234 | + } |
| 235 | + #[cfg(target_arch = "x86_64", target_os = "freebsd")] #[inline(always)] |
| 236 | + unsafe fn target_get_sp_limit() -> uint { |
| 237 | + let limit; |
| 238 | + asm!("movq %fs:24, $0" : "=r"(limit) ::: "volatile"); |
| 239 | + return limit; |
| 240 | + } |
| 241 | + |
| 242 | + // x86 |
| 243 | + #[cfg(target_arch = "x86", target_os = "macos")] #[inline(always)] |
| 244 | + unsafe fn target_get_sp_limit() -> uint { |
| 245 | + let limit; |
| 246 | + asm!("movl $$0x48+90*4, %eax |
| 247 | + movl %gs:(%eax), $0" : "=r"(limit) :: "eax" : "volatile"); |
| 248 | + return limit; |
| 249 | + } |
| 250 | + #[cfg(target_arch = "x86", target_os = "linux")] |
| 251 | + #[cfg(target_arch = "x86", target_os = "freebsd")] #[inline(always)] |
| 252 | + unsafe fn target_get_sp_limit() -> uint { |
| 253 | + let limit; |
| 254 | + asm!("movl %gs:48, $0" : "=r"(limit) ::: "volatile"); |
| 255 | + return limit; |
| 256 | + } |
| 257 | + #[cfg(target_arch = "x86", target_os = "win32")] #[inline(always)] |
| 258 | + unsafe fn target_get_sp_limit() -> uint { |
| 259 | + let limit; |
| 260 | + asm!("movl %fs:0x14, $0" : "=r"(limit) ::: "volatile"); |
| 261 | + return limit; |
| 262 | + } |
| 263 | + |
| 264 | + // mips, arm - Some brave soul can port these to inline asm, but it's over |
| 265 | + // my head personally |
| 266 | + #[cfg(target_arch = "mips")] |
| 267 | + #[cfg(target_arch = "arm")] #[inline(always)] |
| 268 | + unsafe fn target_get_sp_limit() -> uint { |
| 269 | + return get_sp_limit() as uint; |
| 270 | + extern { |
| 271 | + fn get_sp_limit() -> *c_void; |
| 272 | + } |
| 273 | + } |
| 274 | +} |
0 commit comments