Skip to content

Commit a8875a4

Browse files
committed
Fix things
1 parent 0d67572 commit a8875a4

File tree

7 files changed

+101
-57
lines changed

7 files changed

+101
-57
lines changed

build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ fn main() {
1414
cfg.define("APPLE", None);
1515
} else if target.contains("windows") {
1616
cfg.define("WINDOWS", None);
17+
cfg.file("src/arch/windows.c");
1718
} else {
1819
panic!("\n\nusing currently unsupported target triple with \
1920
stacker: {}\n\n", target);

src/arch/i686.S

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ GLOBAL(__stacker_black_box):
1010
ret
1111

1212
GLOBAL(__stacker_switch_stacks):
13+
// CFI instructions tells the unwinder how to unwind this function
14+
// This enables unwinding through our extended stacks and also
15+
// backtrackes
1316
.cfi_startproc
1417
push %ebp
15-
.cfi_def_cfa_offset 8
16-
.cfi_offset ebp, -8
18+
.cfi_def_cfa_offset 8 // restore esp by adding 8
19+
.cfi_offset ebp, -8 // restore ebp from the stack
1720
mov %esp, %ebp
18-
.cfi_def_cfa_register ebp
21+
.cfi_def_cfa_register ebp // restore esp from ebp
1922
mov 16(%ebp), %esp // switch to our new stack
2023
mov 12(%ebp), %eax // load function we're going to call
2124
push 8(%ebp) // push argument to first function

src/arch/i686.asm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ __stacker_stack_pointer PROC
66
MOV EAX, ESP
77
RET
88
__stacker_stack_pointer ENDP
9+
10+
END

src/arch/windows.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include <windows.h>
2+
3+
void __stacker_black_box() {}
4+
5+
PVOID __stacker_get_current_fiber() {
6+
return GetCurrentFiber();
7+
}

src/arch/x86_64.S

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ GLOBAL(__stacker_stack_pointer):
99
ret
1010

1111
GLOBAL(__stacker_switch_stacks):
12+
// CFI instructions tells the unwinder how to unwind this function
13+
// This enables unwinding through our extended stacks and also
14+
// backtrackes
1215
.cfi_startproc
1316
push %rbp
14-
.cfi_def_cfa_offset 16
15-
.cfi_offset rbp, -16
17+
.cfi_def_cfa_offset 16 // restore rsp by adding 16
18+
.cfi_offset rbp, -16 // restore rbp from the stack
1619
mov %rsp, %rbp
17-
.cfi_def_cfa_register rbp
20+
.cfi_def_cfa_register rbp // restore rsp from rbp
1821
mov %rdx, %rsp // switch to our new stack
1922
call *%rsi // call our function pointer, data argument in %rdi
2023
mov %rbp, %rsp // restore the old stack pointer

src/arch/x86_64.asm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ __stacker_stack_pointer PROC
44
MOV RAX, RSP
55
RET
66
__stacker_stack_pointer ENDP
7+
8+
END

src/lib.rs

Lines changed: 77 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ fn get_stack_limit() -> Option<usize> {
9696
STACK_LIMIT.with(|s| s.get())
9797
}
9898

99-
fn set_stack_limit(l: usize) {
100-
STACK_LIMIT.with(|s| s.set(Some(l)))
99+
fn set_stack_limit(l: Option<usize>) {
100+
STACK_LIMIT.with(|s| s.set(l))
101101
}
102102

103103
cfg_if! {
@@ -120,9 +120,7 @@ cfg_if! {
120120
unsafe {
121121
libc::munmap(self.map, self.stack_size);
122122
}
123-
if let Some(limit) = self.old_stack_limit {
124-
set_stack_limit(limit);
125-
}
123+
set_stack_limit(self.old_stack_limit);
126124
}
127125
}
128126

@@ -174,7 +172,7 @@ cfg_if! {
174172
let stack_low = map as usize;
175173

176174
// Prepare stack limits for the stack switch
177-
set_stack_limit(stack_low);
175+
set_stack_limit(Some(stack_low));
178176

179177
// Make sure the stack is 16-byte aligned which should be enough for all
180178
// platforms right now. Allocations on 64-bit are already 16-byte aligned
@@ -204,6 +202,10 @@ cfg_if! {
204202

205203
cfg_if! {
206204
if #[cfg(windows)] {
205+
extern {
206+
fn __stacker_get_current_fiber() -> winapi::PVOID;
207+
}
208+
207209
struct FiberInfo<'a> {
208210
callback: &'a mut FnMut(),
209211
result: Option<std::thread::Result<()>>,
@@ -212,51 +214,72 @@ cfg_if! {
212214

213215
unsafe extern "system" fn fiber_proc(info: winapi::LPVOID) {
214216
let info = &mut *(info as *mut FiberInfo);
217+
218+
// Remember the old stack limit
219+
let old_stack_limit = get_stack_limit();
220+
// Update the limit to that of the fiber stack
221+
set_stack_limit(guess_os_stack_limit());
222+
215223
info.result = Some(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
216224
(info.callback)();
217225
})));
226+
227+
// Restore the stack limit of the previous fiber
228+
set_stack_limit(old_stack_limit);
229+
218230
kernel32::SwitchToFiber(info.parent_fiber);
219231
return;
220232
}
221233

222234
fn _grow(stack_size: usize, callback: &mut FnMut()) {
223235
unsafe {
236+
// Fibers (or stackful coroutines) is the only way to create new stacks on the
237+
// same thread on Windows. So in order to extend the stack we create fiber
238+
// and switch to it so we can use it's stack. After running
239+
// `callback` we switch back to the current stack and destroy
240+
// the fiber and its associated stack.
241+
224242
let was_fiber = kernel32::IsThreadAFiber() == winapi::TRUE;
225243

226-
// Fibers are essentially stackfull coroutines
227244
let mut info = FiberInfo {
228245
callback,
229246
result: None,
230-
parent_fiber: if was_fiber {
231-
extern {
232-
fn GetCurrentFiber() -> *mut winapi::c_void;
247+
248+
// We need a handle to the current stack / fiber so we can switch back to it
249+
parent_fiber: {
250+
// Is the current thread already a fiber? This is the case when we already
251+
// used a fiber to extend the stack
252+
if was_fiber {
253+
// Get a handle to the current fiber. We need to use C for this
254+
// as GetCurrentFiber is an header only function.
255+
__stacker_get_current_fiber()
256+
} else {
257+
// Convert the current thread to a fiber, so we are able to switch back
258+
// to the current stack. Threads coverted to fibers still act like
259+
// regular threads, but they have associated fiber data. We later
260+
// convert it back to a regular thread and free the fiber data.
261+
kernel32::ConvertThreadToFiber(0i32 as _)
233262
}
234-
GetCurrentFiber()
235-
} else {
236-
kernel32::ConvertThreadToFiber(0i32 as _)
237263
},
238264
};
239265
if info.parent_fiber == 0i32 as _ {
266+
// We don't have a handle to the fiber, so we can't switch back
240267
panic!("unable to convert thread to fiber");
241268
}
242-
// remember the old stack limit
243-
let old_stack_limit = get_stack_limit();
244-
// bump the know stack size in the thread local
245-
set_stack_limit(stack_size);
269+
246270
let fiber = kernel32::CreateFiber(stack_size as _, Some(fiber_proc), &mut info as *mut FiberInfo as *mut _);
247271
if fiber == 0i32 as _ {
248272
panic!("unable to allocate fiber");
249273
}
250-
// switch to fiber and immediately execute
274+
275+
// Switch to the fiber we created. This changes stacks and starts executing
276+
// fiber_proc on it. fiber_proc will run `callback` and then switch back
251277
kernel32::SwitchToFiber(fiber);
252-
// fiber execution finished, we can safely delete it now
253-
kernel32::DeleteFiber(fiber);
254278

255-
// restore the old stack limit
256-
if let Some(old) = old_stack_limit {
257-
set_stack_limit(old);
258-
}
279+
// We are back on the old stack and now we have destroy the fiber and its stack
280+
kernel32::DeleteFiber(fiber);
259281

282+
// If the
260283
if !was_fiber {
261284
kernel32::ConvertFiberToThread();
262285
}
@@ -267,35 +290,38 @@ cfg_if! {
267290
}
268291
}
269292

270-
cfg_if! {
271-
if #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] {
272-
#[inline(always)]
273-
// We cannot know the initial stack size on x86
274-
unsafe fn guess_os_stack_limit() -> Option<usize> {
275-
None
276-
}
293+
#[inline(always)]
294+
fn get_thread_stack_guarantee() -> usize {
295+
let min_guarantee = if cfg!(target_pointer_width = "32") {
296+
0x1000
277297
} else {
278-
#[inline(always)]
279-
fn get_thread_stack_guarantee() -> usize {
280-
let min_guarantee = if cfg!(target_pointer_width = "32") {
281-
0x1000
282-
} else {
283-
0x2000
284-
};
285-
let mut stack_guarantee = 0;
286-
unsafe {
287-
kernel32::SetThreadStackGuarantee(&mut stack_guarantee)
288-
};
289-
std::cmp::max(stack_guarantee, min_guarantee) as usize + 0x1000
290-
}
298+
0x2000
299+
};
300+
let mut stack_guarantee = 0;
301+
unsafe {
302+
// Read the current thread stack guarantee
303+
// This is the stack reserved for stack overflow
304+
// exception handling.
305+
// This doesn't return the true value so we need
306+
// some further logic to calculate the real stack
307+
// guarantee. This logic is what is used on x86-32 and
308+
// x86-64 Windows 10. Other versions and platforms may differ
309+
kernel32::SetThreadStackGuarantee(&mut stack_guarantee)
310+
};
311+
std::cmp::max(stack_guarantee, min_guarantee) as usize + 0x1000
312+
}
291313

292-
#[inline(always)]
293-
unsafe fn guess_os_stack_limit() -> Option<usize> {
294-
let mut mi;
295-
kernel32::VirtualQuery(__stacker_stack_pointer(), &mut mi, std::mem::size_of_val(&mi));
296-
Some(mi.AllocationBase + get_thread_stack_guarantee() + 0x1000)
297-
}
298-
}
314+
#[inline(always)]
315+
unsafe fn guess_os_stack_limit() -> Option<usize> {
316+
let mut mi = std::mem::zeroed();
317+
// Query the allocation which contains our stack pointer in order
318+
// to discover the size of the stack
319+
kernel32::VirtualQuery(
320+
__stacker_stack_pointer() as *const _,
321+
&mut mi,
322+
std::mem::size_of_val(&mi) as winapi::SIZE_T,
323+
);
324+
Some(mi.AllocationBase as usize + get_thread_stack_guarantee() + 0x1000)
299325
}
300326
} else if #[cfg(target_os = "linux")] {
301327
use std::mem;

0 commit comments

Comments
 (0)