From 94f5ecb4a9f9cbf22b4a23bb9dcbf71159f4db18 Mon Sep 17 00:00:00 2001 From: Myung Gyungmin Date: Thu, 8 Feb 2024 16:22:06 +0900 Subject: [PATCH 01/10] Add critical-section dependency and signal module --- Cargo.toml | 4 ++ src/lib.rs | 13 ++--- src/malloc.rs | 8 +-- src/signal.rs | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 src/signal.rs diff --git a/Cargo.toml b/Cargo.toml index 87122e3..efc0294 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,11 @@ readme = "README.md" repository = "https://github.com/thejpster/tinyrlibc" [dependencies] +critical-section = "1.1.2" [dev-dependencies] static-alloc = "0.2.4" +critical-section = { version = "1.1.2", features = ["std"]} [build-dependencies] cc = "1.0" @@ -43,6 +45,7 @@ all = [ "isalpha", "isupper", "alloc", + "signal", ] abs = [] strcmp = [] @@ -68,3 +71,4 @@ isdigit = [] isalpha = [] isupper = [] alloc = [] +signal = [] diff --git a/src/lib.rs b/src/lib.rs index 12f5030..06f1601 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,15 +16,6 @@ mod malloc; #[cfg(feature = "alloc")] pub use self::malloc::{calloc, free, malloc, realloc}; -// A new global allocator is required for the tests, but not for the library itself. -// This is because the default alloc crate uses the system allocator, collides with -// the one in this crate, and causes a link error. -#[cfg(all(feature = "alloc", test))] -use static_alloc::Bump; -#[cfg(all(feature = "alloc", test))] -#[global_allocator] -static ALLOCATOR: Bump<[u8; 1024 * 1024]> = Bump::uninit(); - mod itoa; #[cfg(feature = "itoa")] pub use self::itoa::itoa; @@ -91,6 +82,10 @@ mod strchr; #[cfg(feature = "strchr")] pub use self::strchr::strchr; +mod signal; +#[cfg(feature = "signal")] +pub use self::signal::{abort, raise, signal}; + mod snprintf; mod ctype; diff --git a/src/malloc.rs b/src/malloc.rs index 3b533cd..e37dd24 100644 --- a/src/malloc.rs +++ b/src/malloc.rs @@ -12,7 +12,7 @@ const MAX_ALIGN: usize = 16; /// Rust implementation of C library function `malloc` /// /// See [malloc](https://linux.die.net/man/3/malloc) for alignment details. -#[no_mangle] +#[cfg_attr(not(test), no_mangle)] pub unsafe extern "C" fn malloc(size: CSizeT) -> *mut u8 { // size + MAX_ALIGN for to store the size of the allocated memory. let layout = alloc::alloc::Layout::from_size_align(size + MAX_ALIGN, MAX_ALIGN).unwrap(); @@ -29,7 +29,7 @@ pub unsafe extern "C" fn malloc(size: CSizeT) -> *mut u8 { /// Rust implementation of C library function `calloc` /// /// See [calloc](https://linux.die.net/man/3/calloc) for alignment details. -#[no_mangle] +#[cfg_attr(not(test), no_mangle)] pub unsafe extern "C" fn calloc(nmemb: CSizeT, size: CSizeT) -> *mut u8 { let total_size = nmemb * size; let layout = alloc::alloc::Layout::from_size_align(total_size + MAX_ALIGN, MAX_ALIGN).unwrap(); @@ -46,7 +46,7 @@ pub unsafe extern "C" fn calloc(nmemb: CSizeT, size: CSizeT) -> *mut u8 { /// Rust implementation of C library function `realloc` /// /// See [realloc](https://linux.die.net/man/3/realloc) for alignment details. -#[no_mangle] +#[cfg_attr(not(test), no_mangle)] pub unsafe extern "C" fn realloc(ptr: *mut u8, size: CSizeT) -> *mut u8 { if ptr.is_null() { return malloc(size); @@ -64,7 +64,7 @@ pub unsafe extern "C" fn realloc(ptr: *mut u8, size: CSizeT) -> *mut u8 { } /// Rust implementation of C library function `free` -#[no_mangle] +#[cfg_attr(not(test), no_mangle)] pub unsafe extern "C" fn free(ptr: *mut u8) { if ptr.is_null() { return; diff --git a/src/signal.rs b/src/signal.rs new file mode 100644 index 0000000..593c9c8 --- /dev/null +++ b/src/signal.rs @@ -0,0 +1,134 @@ +//! Rust implementation of the C standard library's `signal` related functions. +//! +//! Licensed under the Blue Oak Model Licence 1.0.0 + +use core::cell::RefCell; +use critical_section::Mutex; + +// Signal hanling is emulated by the `critical-section` crate. +static SIGNAL_HANDLERS: Mutex> = + Mutex::new(RefCell::new([default_handler; 16])); + +const SIG_DFL: usize = 0; +const SIG_IGN: usize = 1; +const SIG_ERR: isize = -1; + +// Only ANSI C signals are now supported. +// SIGSEGV, SIGILL, SIGFPE are not supported on bare metal, but handlers are invoked when raise() is called. +// TODO: Support SIGSEGV, SIGILL, SIGFPE by using the `cortex-m-rt` or `riscv-rt` crate. +const SIGTERM: i32 = 15; +const SIGSEGV: i32 = 11; +const SIGINT: i32 = 2; +const SIGILL: i32 = 4; +const SIGABRT: i32 = 6; +const SIGFPE: i32 = 8; + +const SIGNALS: [i32; 6] = [SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE]; + +extern "C" fn ignore_handler(_sig: i32) {} + +extern "C" fn default_handler(_sig: i32) { + // TODO: This should call core::intrinsics::abort() but that's unstable. + panic!("Aborted"); +} + +/// Rust implementation of the C standard library's `signal` function. +#[cfg_attr(all(not(test), feature = "signal"), no_mangle)] +pub unsafe extern "C" fn signal(sig: i32, handler: extern "C" fn(i32)) -> extern "C" fn(i32) { + if SIGNALS.iter().all(|&s| s != sig) { + return core::mem::transmute(SIG_ERR); + } + critical_section::with(|cs| { + let mut handlers = SIGNAL_HANDLERS.borrow(cs).borrow_mut(); + let old_handler = handlers[sig as usize]; + handlers[sig as usize] = handler; + old_handler + }) +} + +/// Rust implementation of the C standard library's `raise` function. +#[cfg_attr(all(not(test), feature = "signal"), no_mangle)] +pub unsafe extern "C" fn raise(sig: i32) -> i32 { + if SIGNALS.iter().all(|&s| s != sig) { + return -1; + } + critical_section::with(|cs| { + let handlers = SIGNAL_HANDLERS.borrow(cs).borrow(); + let handler = handlers[sig as usize]; + match handler as usize { + SIG_DFL => default_handler(sig), + SIG_IGN => ignore_handler(sig), + _ => handler(sig), + } + 0 + }) +} + +#[cfg_attr(all(not(test), feature = "signal"), no_mangle)] +pub extern "C" fn abort() { + unsafe { + raise(SIGABRT); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_signal() { + unsafe { + static COUNT: Mutex> = Mutex::new(RefCell::new(0)); + extern "C" fn count_handler(_sig: i32) { + critical_section::with(|cs| { + let mut count = COUNT.borrow(cs).borrow_mut(); + *count += 1; + }); + } + println!("default_handler: {}", default_handler as usize); + println!("count_handler: {}", count_handler as usize); + let old_handler = signal(SIGTERM, count_handler); + println!("old_handler: {}", old_handler as usize); + println!("count_handler: {}", count_handler as usize); + println!("default_handler: {}", default_handler as usize); + assert_eq!(old_handler as usize, default_handler as usize); + (0..10).for_each(|_| { + raise(SIGTERM); + }); + let old_handler = signal(SIGTERM, default_handler); + critical_section::with(|cs| { + let count = COUNT.borrow(cs).borrow(); + assert_eq!(*count, 10); + }); + assert_eq!(old_handler as usize, count_handler as usize); + } + } + + #[test] + fn test_abort() { + let result = std::panic::catch_unwind(|| { + abort(); + }); + assert!(result.is_err()); + } + + #[test] + fn test_abort_signal() { + static TRIGGER: Mutex> = Mutex::new(RefCell::new(false)); + extern "C" fn trigger_handler(_sig: i32) { + critical_section::with(|cs| { + let mut trigger = TRIGGER.borrow(cs).borrow_mut(); + *trigger = true; + }); + } + unsafe { signal(SIGABRT, trigger_handler) }; + let result = std::panic::catch_unwind(|| { + abort(); + }); + assert!(result.is_ok()); + critical_section::with(|cs| { + let trigger = TRIGGER.borrow(cs).borrow(); + assert!(*trigger); + }); + } +} From 8a936f4cd5dec0b7ab20f276c8819f39919452b1 Mon Sep 17 00:00:00 2001 From: Myung Gyungmin Date: Thu, 8 Feb 2024 16:39:13 +0900 Subject: [PATCH 02/10] Add signal-related functions to README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index ac155e0..fdb4703 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,10 @@ This crate basically came about so that the [nrfxlib](https://github.com/NordicP * calloc * realloc * free +* signal + * signal + * raise + * abort ## Non-standard helper functions From 4ac217083b8df0b6f877dd8ded5817e945d7c6d5 Mon Sep 17 00:00:00 2001 From: Myung Gyungmin Date: Thu, 8 Feb 2024 18:19:35 +0900 Subject: [PATCH 03/10] Refactor signal handling in signal.rs --- src/signal.rs | 109 +++++++++++++++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 45 deletions(-) diff --git a/src/signal.rs b/src/signal.rs index 593c9c8..6bebbea 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -1,18 +1,26 @@ //! Rust implementation of the C standard library's `signal` related functions. //! +//! Copyright (c) Gyungmin Myung //! Licensed under the Blue Oak Model Licence 1.0.0 -use core::cell::RefCell; +use core::{cell::RefCell, default}; use critical_section::Mutex; -// Signal hanling is emulated by the `critical-section` crate. -static SIGNAL_HANDLERS: Mutex> = - Mutex::new(RefCell::new([default_handler; 16])); +// Signal handling is emulated by the `critical-section` crate. +static SIGNAL_HANDLERS: Mutex; 16]>> = + Mutex::new(RefCell::new([None; 16])); + +type SignalHandler = Option; const SIG_DFL: usize = 0; const SIG_IGN: usize = 1; const SIG_ERR: isize = -1; +// This is required because rust doesn't support 0, 1, -1 as a function pointer. +fn signal_handler(ptr: usize) -> SignalHandler { + unsafe { core::mem::transmute(ptr) } +} + // Only ANSI C signals are now supported. // SIGSEGV, SIGILL, SIGFPE are not supported on bare metal, but handlers are invoked when raise() is called. // TODO: Support SIGSEGV, SIGILL, SIGFPE by using the `cortex-m-rt` or `riscv-rt` crate. @@ -25,18 +33,18 @@ const SIGFPE: i32 = 8; const SIGNALS: [i32; 6] = [SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE]; -extern "C" fn ignore_handler(_sig: i32) {} +fn ignore_handler(_sig: i32) {} -extern "C" fn default_handler(_sig: i32) { +fn default_handler(_sig: i32) { // TODO: This should call core::intrinsics::abort() but that's unstable. panic!("Aborted"); } /// Rust implementation of the C standard library's `signal` function. #[cfg_attr(all(not(test), feature = "signal"), no_mangle)] -pub unsafe extern "C" fn signal(sig: i32, handler: extern "C" fn(i32)) -> extern "C" fn(i32) { +pub unsafe extern "C" fn signal(sig: i32, handler: SignalHandler) -> SignalHandler { if SIGNALS.iter().all(|&s| s != sig) { - return core::mem::transmute(SIG_ERR); + return signal_handler(SIG_ERR as usize); } critical_section::with(|cs| { let mut handlers = SIGNAL_HANDLERS.borrow(cs).borrow_mut(); @@ -55,10 +63,12 @@ pub unsafe extern "C" fn raise(sig: i32) -> i32 { critical_section::with(|cs| { let handlers = SIGNAL_HANDLERS.borrow(cs).borrow(); let handler = handlers[sig as usize]; - match handler as usize { - SIG_DFL => default_handler(sig), - SIG_IGN => ignore_handler(sig), - _ => handler(sig), + if handler == signal_handler(SIG_DFL) { + default_handler(sig); + } else if handler == signal_handler(SIG_IGN) { + ignore_handler(sig); + } else { + handler.unwrap()(sig); } 0 }) @@ -77,31 +87,24 @@ mod tests { #[test] fn test_signal() { - unsafe { - static COUNT: Mutex> = Mutex::new(RefCell::new(0)); + critical_section::with(|cs| unsafe { + static mut COUNT: usize = 0; extern "C" fn count_handler(_sig: i32) { - critical_section::with(|cs| { - let mut count = COUNT.borrow(cs).borrow_mut(); - *count += 1; - }); + unsafe { COUNT += 1 }; } - println!("default_handler: {}", default_handler as usize); - println!("count_handler: {}", count_handler as usize); - let old_handler = signal(SIGTERM, count_handler); - println!("old_handler: {}", old_handler as usize); - println!("count_handler: {}", count_handler as usize); - println!("default_handler: {}", default_handler as usize); - assert_eq!(old_handler as usize, default_handler as usize); + dbg!(SIGNAL_HANDLERS.borrow(cs).borrow()); + let old_handler = signal(SIGTERM, Some(count_handler)); + assert_eq!(old_handler, signal_handler(SIG_DFL)); (0..10).for_each(|_| { raise(SIGTERM); }); - let old_handler = signal(SIGTERM, default_handler); - critical_section::with(|cs| { - let count = COUNT.borrow(cs).borrow(); - assert_eq!(*count, 10); - }); - assert_eq!(old_handler as usize, count_handler as usize); - } + let old_handler = signal(SIGTERM, core::mem::transmute(SIG_DFL)); + assert_eq!(COUNT, 10); + assert_eq!( + old_handler.unwrap() as usize, + Some(count_handler).unwrap() as usize + ); + }); } #[test] @@ -113,22 +116,38 @@ mod tests { } #[test] - fn test_abort_signal() { - static TRIGGER: Mutex> = Mutex::new(RefCell::new(false)); - extern "C" fn trigger_handler(_sig: i32) { - critical_section::with(|cs| { - let mut trigger = TRIGGER.borrow(cs).borrow_mut(); - *trigger = true; + fn test_signal_error() { + let err = unsafe { signal(1000, signal_handler(SIG_DFL)) }; + assert_eq!(err, signal_handler(SIG_ERR as usize)); + } + + #[test] + fn test_raise() { + let result = std::panic::catch_unwind(|| { + unsafe { raise(SIGTERM) }; + }); + assert!(result.is_err()); + } + + #[test] + fn test_ignore() { + critical_section::with(|_cs| unsafe { + let old_handler = signal(SIGTERM, core::mem::transmute(SIG_IGN)); + assert_eq!(old_handler, signal_handler(SIG_DFL)); + let result = std::panic::catch_unwind(|| { + raise(SIGTERM); }); - } - unsafe { signal(SIGABRT, trigger_handler) }; + assert!(result.is_ok()); + let old_handler = signal(SIGTERM, core::mem::transmute(SIG_DFL)); + assert_eq!(old_handler, signal_handler(SIG_IGN)); + }); + } + + #[test] + fn test_raise_error() { let result = std::panic::catch_unwind(|| { - abort(); + assert!(unsafe { raise(1000) == -1 }); }); assert!(result.is_ok()); - critical_section::with(|cs| { - let trigger = TRIGGER.borrow(cs).borrow(); - assert!(*trigger); - }); } } From acbaf2b4989fcc7ac8792c827beccd2ecdf702e4 Mon Sep 17 00:00:00 2001 From: Myung Gyungmin Date: Thu, 8 Feb 2024 18:24:24 +0900 Subject: [PATCH 04/10] make alloc/signal optional and update README.md --- Cargo.toml | 2 -- README.md | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index efc0294..f9d4756 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,8 +44,6 @@ all = [ "isdigit", "isalpha", "isupper", - "alloc", - "signal", ] abs = [] strcmp = [] diff --git a/README.md b/README.md index fdb4703..6e09adc 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ This crate basically came about so that the [nrfxlib](https://github.com/NordicP * calloc * realloc * free -* signal +* signal (optional) * signal * raise * abort From 2bbe32c7d66fd5cc70e3595b2fce12d18bde8c96 Mon Sep 17 00:00:00 2001 From: Myung Gyungmin Date: Thu, 8 Feb 2024 18:36:41 +0900 Subject: [PATCH 05/10] Add --all-features flag to fix CI --- .github/workflows/build.yml | 2 +- src/lib.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bd71834..788f566 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: with: use-cross: true command: build - args: --target=${{ matrix.TARGET }} + args: --target=${{ matrix.TARGET }} --all-features test: name: Tests diff --git a/src/lib.rs b/src/lib.rs index 06f1601..40747af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,7 @@ mod strchr; #[cfg(feature = "strchr")] pub use self::strchr::strchr; +#[cfg(feature = "signal")] mod signal; #[cfg(feature = "signal")] pub use self::signal::{abort, raise, signal}; From 78d45431cbf656703f6b8274307bf100637d305f Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 9 Feb 2024 10:22:22 +0000 Subject: [PATCH 06/10] Change signal code to use atomics. --- Cargo.toml | 5 +- src/signal.rs | 161 +++++++++++++++++++++++++++++--------------------- 2 files changed, 96 insertions(+), 70 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f9d4756..1d23830 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,10 @@ readme = "README.md" repository = "https://github.com/thejpster/tinyrlibc" [dependencies] -critical-section = "1.1.2" +portable-atomic = { version = "1.6.0", optional = true } [dev-dependencies] static-alloc = "0.2.4" -critical-section = { version = "1.1.2", features = ["std"]} [build-dependencies] cc = "1.0" @@ -69,4 +68,4 @@ isdigit = [] isalpha = [] isupper = [] alloc = [] -signal = [] +signal = ["dep:portable-atomic"] diff --git a/src/signal.rs b/src/signal.rs index 6bebbea..4489e9d 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -4,81 +4,116 @@ //! Licensed under the Blue Oak Model Licence 1.0.0 use core::{cell::RefCell, default}; -use critical_section::Mutex; +use portable_atomic::{AtomicUsize, Ordering}; -// Signal handling is emulated by the `critical-section` crate. -static SIGNAL_HANDLERS: Mutex; 16]>> = - Mutex::new(RefCell::new([None; 16])); +/// An initialiser for our array. +/// +/// We turn off the clippy warning because it's wrong - and there's no other +/// way to initialise an array of atomics. +#[allow(clippy::declare_interior_mutable_const)] +const SIG_DFL_ATOMIC: AtomicUsize = AtomicUsize::new(SIG_DFL); -type SignalHandler = Option; +/// Our array of registered signal handlers. +/// +/// Signals in C are either 0, 1, -1, or a function pointer. We cast function +/// pointers into `usize` so they can be stored in this array. +static SIGNAL_HANDLERS: [AtomicUsize; 16] = [SIG_DFL_ATOMIC; 16]; +/// A signal handler - either a function pointer or a magic integer. +pub type SignalHandler = usize; + +/// Indicates we should use the default signal handler const SIG_DFL: usize = 0; + +/// Indicates we should use the default signal handler const SIG_IGN: usize = 1; -const SIG_ERR: isize = -1; -// This is required because rust doesn't support 0, 1, -1 as a function pointer. -fn signal_handler(ptr: usize) -> SignalHandler { - unsafe { core::mem::transmute(ptr) } -} +/// Indicates we should use the default signal handler +const SIG_ERR: usize = usize::MAX; -// Only ANSI C signals are now supported. -// SIGSEGV, SIGILL, SIGFPE are not supported on bare metal, but handlers are invoked when raise() is called. -// TODO: Support SIGSEGV, SIGILL, SIGFPE by using the `cortex-m-rt` or `riscv-rt` crate. +/// The TERM signal const SIGTERM: i32 = 15; + +/// The SEGV signal const SIGSEGV: i32 = 11; + +/// The INT signal const SIGINT: i32 = 2; + +/// The ILL signal const SIGILL: i32 = 4; + +/// The ABRT signal const SIGABRT: i32 = 6; + +/// The FPE signal const SIGFPE: i32 = 8; +/// The list of support signals. +/// +/// Only ANSI C signals are now supported. +/// +/// SIGSEGV, SIGILL, SIGFPE are not supported on bare metal, but handlers are +/// invoked when raise() is called. +/// +/// We will index `SIGNAL_HANDLERS` by any integer in this list, so ensure that +/// the array is made larger if required. +/// +/// TODO: Support SIGSEGV, SIGILL, SIGFPE by using the `cortex-m-rt` or +/// `riscv-rt` crate. const SIGNALS: [i32; 6] = [SIGTERM, SIGSEGV, SIGINT, SIGILL, SIGABRT, SIGFPE]; +/// An empty handler function that does nothing fn ignore_handler(_sig: i32) {} +/// The default handler functions +/// +/// Performs a panic. fn default_handler(_sig: i32) { // TODO: This should call core::intrinsics::abort() but that's unstable. panic!("Aborted"); } /// Rust implementation of the C standard library's `signal` function. +/// +/// Using `not(test)` ensures we don't replace the actual OS `signal` function +/// when running tests! #[cfg_attr(all(not(test), feature = "signal"), no_mangle)] pub unsafe extern "C" fn signal(sig: i32, handler: SignalHandler) -> SignalHandler { - if SIGNALS.iter().all(|&s| s != sig) { - return signal_handler(SIG_ERR as usize); + if !SIGNALS.contains(&sig) { + return SIG_ERR; } - critical_section::with(|cs| { - let mut handlers = SIGNAL_HANDLERS.borrow(cs).borrow_mut(); - let old_handler = handlers[sig as usize]; - handlers[sig as usize] = handler; - old_handler - }) + SIGNAL_HANDLERS[sig as usize].swap(handler, Ordering::Relaxed) } /// Rust implementation of the C standard library's `raise` function. +/// +/// Using `not(test)` ensures we don't replace the actual OS `raise` function +/// when running tests! #[cfg_attr(all(not(test), feature = "signal"), no_mangle)] -pub unsafe extern "C" fn raise(sig: i32) -> i32 { - if SIGNALS.iter().all(|&s| s != sig) { +pub extern "C" fn raise(sig: i32) -> i32 { + if !SIGNALS.contains(&sig) { return -1; } - critical_section::with(|cs| { - let handlers = SIGNAL_HANDLERS.borrow(cs).borrow(); - let handler = handlers[sig as usize]; - if handler == signal_handler(SIG_DFL) { + let handler = SIGNAL_HANDLERS[sig as usize].load(Ordering::Relaxed); + match handler { + SIG_DFL => { default_handler(sig); - } else if handler == signal_handler(SIG_IGN) { + } + SIG_IGN => { ignore_handler(sig); - } else { - handler.unwrap()(sig); } - 0 - }) + _ => unsafe { + let handler_fn: unsafe extern "C" fn(core::ffi::c_int) = core::mem::transmute(handler); + handler_fn(sig); + }, + } + 0 } #[cfg_attr(all(not(test), feature = "signal"), no_mangle)] pub extern "C" fn abort() { - unsafe { - raise(SIGABRT); - } + raise(SIGABRT); } #[cfg(test)] @@ -87,24 +122,20 @@ mod tests { #[test] fn test_signal() { - critical_section::with(|cs| unsafe { - static mut COUNT: usize = 0; - extern "C" fn count_handler(_sig: i32) { - unsafe { COUNT += 1 }; - } - dbg!(SIGNAL_HANDLERS.borrow(cs).borrow()); - let old_handler = signal(SIGTERM, Some(count_handler)); - assert_eq!(old_handler, signal_handler(SIG_DFL)); - (0..10).for_each(|_| { - raise(SIGTERM); - }); - let old_handler = signal(SIGTERM, core::mem::transmute(SIG_DFL)); - assert_eq!(COUNT, 10); - assert_eq!( - old_handler.unwrap() as usize, - Some(count_handler).unwrap() as usize - ); + static COUNT: AtomicUsize = AtomicUsize::new(0); + extern "C" fn count_handler(_sig: i32) { + COUNT.fetch_add(1, Ordering::Relaxed); + } + dbg!(&SIGNAL_HANDLERS); + let count_handler_ptr = count_handler as *const fn(i32) as usize; + let old_handler = unsafe { signal(SIGTERM, count_handler_ptr) }; + assert_eq!(old_handler, SIG_DFL); + (0..10).for_each(|_| { + raise(SIGTERM); }); + let old_handler = unsafe { signal(SIGTERM, SIG_DFL) }; + assert_eq!(COUNT.load(Ordering::Relaxed), 10); + assert_eq!(old_handler, count_handler_ptr); } #[test] @@ -117,36 +148,32 @@ mod tests { #[test] fn test_signal_error() { - let err = unsafe { signal(1000, signal_handler(SIG_DFL)) }; - assert_eq!(err, signal_handler(SIG_ERR as usize)); + let err = unsafe { signal(1000, SIG_DFL) }; + assert_eq!(err, SIG_ERR); } #[test] fn test_raise() { - let result = std::panic::catch_unwind(|| { - unsafe { raise(SIGTERM) }; - }); + let result = std::panic::catch_unwind(|| raise(SIGTERM)); assert!(result.is_err()); } #[test] fn test_ignore() { - critical_section::with(|_cs| unsafe { - let old_handler = signal(SIGTERM, core::mem::transmute(SIG_IGN)); - assert_eq!(old_handler, signal_handler(SIG_DFL)); - let result = std::panic::catch_unwind(|| { - raise(SIGTERM); - }); - assert!(result.is_ok()); - let old_handler = signal(SIGTERM, core::mem::transmute(SIG_DFL)); - assert_eq!(old_handler, signal_handler(SIG_IGN)); + let old_handler = unsafe { signal(SIGTERM, core::mem::transmute(SIG_IGN)) }; + assert_eq!(old_handler, SIG_DFL); + let result = std::panic::catch_unwind(|| { + raise(SIGTERM); }); + assert!(result.is_ok()); + let old_handler = unsafe { signal(SIGTERM, SIG_DFL) }; + assert_eq!(old_handler, SIG_IGN); } #[test] fn test_raise_error() { let result = std::panic::catch_unwind(|| { - assert!(unsafe { raise(1000) == -1 }); + assert!(raise(1000) == -1); }); assert!(result.is_ok()); } From ebc73cbcd24c67d89ba9e97d098d5ed956d5ac6f Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 9 Feb 2024 10:36:21 +0000 Subject: [PATCH 07/10] Turn on CAS support if required. --- Cargo.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 1d23830..080d1c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,14 @@ repository = "https://github.com/thejpster/tinyrlibc" [dependencies] portable-atomic = { version = "1.6.0", optional = true } +# Doesn't have CAS atomics, so add critical-section +[target.thumbv6m-none-eabi.dependencies] +portable-atomic = { version = "1.6.0", optional = true, features = ["critical-section"] } + +# Doesn't have CAS atomics, so add critical-section +[target.riscv32i-unknown-none-elf.dependencies] +portable-atomic = { version = "1.6.0", optional = true, features = ["critical-section"] } + [dev-dependencies] static-alloc = "0.2.4" From b37049e744e227101465ab551ca266c93b61c4eb Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 9 Feb 2024 18:54:13 +0000 Subject: [PATCH 08/10] Add a feature to control portable-atomic/critical-section. Better than doing it by target. --- Cargo.toml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 080d1c3..c80f068 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,14 +11,6 @@ repository = "https://github.com/thejpster/tinyrlibc" [dependencies] portable-atomic = { version = "1.6.0", optional = true } -# Doesn't have CAS atomics, so add critical-section -[target.thumbv6m-none-eabi.dependencies] -portable-atomic = { version = "1.6.0", optional = true, features = ["critical-section"] } - -# Doesn't have CAS atomics, so add critical-section -[target.riscv32i-unknown-none-elf.dependencies] -portable-atomic = { version = "1.6.0", optional = true, features = ["critical-section"] } - [dev-dependencies] static-alloc = "0.2.4" @@ -77,3 +69,4 @@ isalpha = [] isupper = [] alloc = [] signal = ["dep:portable-atomic"] +signal-cs = ["portable-atomic/critical-section"] From b0d55221d42cb648862900c69fc8bf60fed72528 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 9 Feb 2024 18:57:23 +0000 Subject: [PATCH 09/10] Serialise the running of the signal tests. --- src/signal.rs | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/signal.rs b/src/signal.rs index 4489e9d..a7e4561 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -120,13 +120,38 @@ pub extern "C" fn abort() { mod tests { use super::*; + struct State { + inner: std::sync::Mutex<()>, + } + + impl State { + fn lock(&self) -> std::sync::MutexGuard<()> { + // Ensure we have exclusive access + let guard = self.inner.lock().unwrap(); + // Reset the global signal handler list to defaults + for sig in SIGNAL_HANDLERS.iter() { + sig.store(SIG_DFL, Ordering::SeqCst); + } + guard + } + } + + /// Used to ensure we don't run multiple signal test concurrently, because + /// they share some global state. + /// + /// If a test fails, the lock will be poisoned and all subsequent tests will + /// fail. + static TEST_LOCK: State = State { + inner: std::sync::Mutex::new(()), + }; + #[test] fn test_signal() { + let _guard = TEST_LOCK.lock(); static COUNT: AtomicUsize = AtomicUsize::new(0); extern "C" fn count_handler(_sig: i32) { COUNT.fetch_add(1, Ordering::Relaxed); } - dbg!(&SIGNAL_HANDLERS); let count_handler_ptr = count_handler as *const fn(i32) as usize; let old_handler = unsafe { signal(SIGTERM, count_handler_ptr) }; assert_eq!(old_handler, SIG_DFL); @@ -140,6 +165,7 @@ mod tests { #[test] fn test_abort() { + let _guard = TEST_LOCK.lock(); let result = std::panic::catch_unwind(|| { abort(); }); @@ -148,6 +174,7 @@ mod tests { #[test] fn test_signal_error() { + let _guard = TEST_LOCK.lock(); let err = unsafe { signal(1000, SIG_DFL) }; assert_eq!(err, SIG_ERR); } @@ -160,21 +187,17 @@ mod tests { #[test] fn test_ignore() { - let old_handler = unsafe { signal(SIGTERM, core::mem::transmute(SIG_IGN)) }; + let _guard = TEST_LOCK.lock(); + let old_handler = unsafe { signal(SIGTERM, SIG_IGN) }; assert_eq!(old_handler, SIG_DFL); - let result = std::panic::catch_unwind(|| { - raise(SIGTERM); - }); - assert!(result.is_ok()); + // Shouldn't cause a panic + raise(SIGTERM); let old_handler = unsafe { signal(SIGTERM, SIG_DFL) }; assert_eq!(old_handler, SIG_IGN); } #[test] fn test_raise_error() { - let result = std::panic::catch_unwind(|| { - assert!(raise(1000) == -1); - }); - assert!(result.is_ok()); + assert!(raise(1000) == -1); } } From d889219f5b0026fed8f864bd65f99d021d34082a Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 17 Mar 2024 10:47:19 +0000 Subject: [PATCH 10/10] Add CHANGELOG entry for signal API --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f4c59c..8d8fb1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ ## Unreleased -* [#23] - Add `strncasecmp` +* [#23] - Add `strncasecmp` +* [#21] - Add signal API + +[#23]: https://github.com/rust-embedded-community/tinyrlibc/pull/21 +[#21]: https://github.com/rust-embedded-community/tinyrlibc/pull/21 ## v0.3.0 (2022-10-18)