From 7d5075edc2df0fe405a59ab38b143585eff2f2fd Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 11 Aug 2016 22:49:30 +0000 Subject: [PATCH] Add more examples to README. This also renames some functions for a mild increase in backtrace clarity. --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++ src/arch/x86.rs | 12 ++++---- src/arch/x86_64.rs | 12 ++++---- 3 files changed, 85 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9befb73..d1d2843 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,26 @@ It also provides the necessary low-level building blocks: libfringe emphasizes safety and correctness, and goes to great lengths to never violate the platform ABI. +## Usage example + +```rust +extern crate fringe; + +use fringe::{OsStack, Generator}; + +fn main() { + let stack = OsStack::new(1 << 16).unwrap(); + let mut gen = Generator::new(stack, move |yielder, ()| { + for i in 1..4 { yielder.generate(i) } + }); + + println!("{:?}", gen.resume(())); // Some(1) + println!("{:?}", gen.resume(())); // Some(2) + println!("{:?}", gen.resume(())); // Some(3) + println!("{:?}", gen.resume(())); // None +} +``` + ## Performance libfringe does context switches in 3ns flat on x86 and x86_64! @@ -33,6 +53,59 @@ libfringe does context switches in 3ns flat on x86 and x86_64! test swap ... bench: 6 ns/iter (+/- 0) ``` +## Debuggability + +Uniquely among libraries implementing context switching, libfringe ensures that the call stack +does not abruptly end at the boundary of a generator. Let's consider this buggy code: + +```rust +extern crate fringe; + +use fringe::{OsStack, Generator}; + +fn main() { + let stack = OsStack::new(1 << 16).unwrap(); + let mut gen = Generator::new(stack, move |yielder, mut index| { + let values = [1, 2, 3]; + loop { index = yielder.generate(values[index]) } + }); + + println!("{:?}", gen.resume(5)); +} +``` + +It crashes with the following backtrace (redacted for clarity): + +``` +thread 'main' panicked at 'assertion failed: index < self.len()', ../src/libcore/slice.rs:531 +stack backtrace: + [... core::panicking internals ...] + 9: 0x559ee50f677b - core::panicking::panic::hbfac80217e56ecbe + 10: 0x559ee50b6b4c - core::slice::_ for [T]>::index::hcb117ddcc7cf2f33 + at .../src/libcore/slice.rs:21 + 11: 0x559ee50b7288 - crash_test::main::_{{closure}}::hc7da249d76d51364 + at .../crash_test.rs:9 + 12: 0x559ee50b6f23 - _>::unsafe_new::generator_wrapper::ha2da172d4f041d38 + at .../libfringe/src/generator.rs:94 + 13: 0x559ee50b76d3 - fringe::arch::imp::init::trampoline_2::hdb11eb4bdafcdeb9 + at .../libfringe/src/arch/x86_64.rs:71 + 14: 0x559ee50b76c4 - fringe::arch::imp::init::trampoline_1::h6b071b2a8ea6aab3 + at .../libfringe/src/arch/x86_64.rs:43 + 15: 0x559ee50b7098 - _>::resume::h8d2b90d386543e29 + at .../libfringe/src/arch/x86_64.rs:131 + at .../libfringe/src/context.rs:52 + at .../libfringe/src/generator.rs:129 + 16: 0x559ee50b71c8 - crash_test::main::hfc5e04bc99de7a6a + at .../crash_test.rs:12 + [... standard library startup internals ...] +``` + +Similarly, debuggers, profilers, and all other tools using the DWARF debug information have +full insight into the call stacks. + +Note that the stack should be deep enough for the panic machinery to store its stateā€”at any point +there should be at least 8 KiB of free stack space, or panicking will result in a segfault. + ## Limitations The only architectures currently supported are x86 and x86_64. diff --git a/src/arch/x86.rs b/src/arch/x86.rs index 1aa6f9f..6272232 100644 --- a/src/arch/x86.rs +++ b/src/arch/x86.rs @@ -35,7 +35,7 @@ pub struct StackPointer(*mut usize); pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackPointer { #[naked] - unsafe extern "C" fn init_trampoline_1() { + unsafe extern "C" fn trampoline_1() { asm!( r#" # gdb has a hardcoded check that rejects backtraces where frame addresses @@ -59,11 +59,11 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackP .Lend: .size __morestack, .Lend-__morestack "# - : : "s" (init_trampoline_2 as usize) : "memory" : "volatile") + : : "s" (trampoline_2 as usize) : "memory" : "volatile") } #[naked] - unsafe extern "C" fn init_trampoline_2() { + unsafe extern "C" fn trampoline_2() { asm!( r#" # Set up the second part of our DWARF CFI. @@ -87,7 +87,7 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackP let mut sp = StackPointer(stack.top() as *mut usize); push(&mut sp, 0xdead0cfa); // CFA slot push(&mut sp, f as usize); // function - push(&mut sp, init_trampoline_1 as usize); + push(&mut sp, trampoline_1 as usize); push(&mut sp, 0xdeadbbbb); // saved %ebp sp } @@ -99,7 +99,7 @@ pub unsafe fn swap(arg: usize, old_sp: &mut StackPointer, new_sp: &StackPointer, let new_cfa = (new_stack.top() as *mut usize).offset(-1); #[naked] - unsafe extern "C" fn swap_trampoline() { + unsafe extern "C" fn trampoline() { asm!( r#" # Save frame pointer explicitly; the unwinder uses it to find CFA of @@ -135,7 +135,7 @@ pub unsafe fn swap(arg: usize, old_sp: &mut StackPointer, new_sp: &StackPointer, call ${1:c} "# : "={eax}" (ret) - : "s" (swap_trampoline as usize) + : "s" (trampoline as usize) "{eax}" (arg) "{esi}" (old_sp) "{edx}" (new_sp) diff --git a/src/arch/x86_64.rs b/src/arch/x86_64.rs index 109f8e6..25c808f 100644 --- a/src/arch/x86_64.rs +++ b/src/arch/x86_64.rs @@ -39,7 +39,7 @@ pub struct StackPointer(*mut usize); pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackPointer { #[naked] - unsafe extern "C" fn init_trampoline_1() { + unsafe extern "C" fn trampoline_1() { asm!( r#" # gdb has a hardcoded check that rejects backtraces where frame addresses @@ -63,11 +63,11 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackP .Lend: .size __morestack, .Lend-__morestack "# - : : "s" (init_trampoline_2 as usize) : "memory" : "volatile") + : : "s" (trampoline_2 as usize) : "memory" : "volatile") } #[naked] - unsafe extern "C" fn init_trampoline_2() { + unsafe extern "C" fn trampoline_2() { asm!( r#" # Set up the second part of our DWARF CFI. @@ -89,7 +89,7 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackP push(&mut sp, 0xdeaddeaddead0cfa); // CFA slot push(&mut sp, 0 as usize); // alignment push(&mut sp, f as usize); // function - push(&mut sp, init_trampoline_1 as usize); + push(&mut sp, trampoline_1 as usize); push(&mut sp, 0xdeaddeaddeadbbbb); // saved %rbp sp } @@ -101,7 +101,7 @@ pub unsafe fn swap(arg: usize, old_sp: &mut StackPointer, new_sp: &StackPointer, let new_cfa = (new_stack.top() as *mut usize).offset(-1); #[naked] - unsafe extern "C" fn swap_trampoline() { + unsafe extern "C" fn trampoline() { asm!( r#" # Save frame pointer explicitly; the unwinder uses it to find CFA of @@ -137,7 +137,7 @@ pub unsafe fn swap(arg: usize, old_sp: &mut StackPointer, new_sp: &StackPointer, call ${1:c} "# : "={rdi}" (ret) - : "s" (swap_trampoline as usize) + : "s" (trampoline as usize) "{rdi}" (arg) "{rsi}" (old_sp) "{rdx}" (new_sp)