From 70c3d0a2a1b3aeb97f5835dc481a767ecea909a4 Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Fri, 19 May 2023 10:49:46 +0200 Subject: [PATCH 01/11] Hardfault trampoline is now optional --- cortex-m-rt/Cargo.toml | 2 ++ cortex-m-rt/macros/Cargo.toml | 3 +++ cortex-m-rt/macros/src/lib.rs | 35 ++++++++++++++++++++++++++++++++++- cortex-m-rt/src/lib.rs | 18 +++++++++++++++++- 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/cortex-m-rt/Cargo.toml b/cortex-m-rt/Cargo.toml index 3305d34e..03cd7f63 100644 --- a/cortex-m-rt/Cargo.toml +++ b/cortex-m-rt/Cargo.toml @@ -42,10 +42,12 @@ name = "compiletest" required-features = ["device"] [features] +default = ["hardfault-trampoline"] device = [] set-sp = [] set-vtor = [] zero-init-ram = [] +hardfault-trampoline = ["cortex-m-rt-macros/hardfault-trampoline"] [package.metadata.docs.rs] features = ["device"] diff --git a/cortex-m-rt/macros/Cargo.toml b/cortex-m-rt/macros/Cargo.toml index f548e19f..287ad9c9 100644 --- a/cortex-m-rt/macros/Cargo.toml +++ b/cortex-m-rt/macros/Cargo.toml @@ -21,3 +21,6 @@ proc-macro2 = "1.0" [dependencies.syn] features = ["extra-traits", "full"] version = "2.0" + +[features] +hardfault-trampoline = [] diff --git a/cortex-m-rt/macros/src/lib.rs b/cortex-m-rt/macros/src/lib.rs index 27ee2f16..b529ef26 100644 --- a/cortex-m-rt/macros/src/lib.rs +++ b/cortex-m-rt/macros/src/lib.rs @@ -232,7 +232,7 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { #f ) } - Exception::HardFault => { + Exception::HardFault if cfg!(feature = "hardfault-trampoline") => { let valid_signature = f.sig.constness.is_none() && f.vis == Visibility::Inherited && f.sig.abi.is_none() @@ -283,6 +283,39 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { #f ) } + Exception::HardFault => { + let valid_signature = f.sig.constness.is_none() + && f.vis == Visibility::Inherited + && f.sig.abi.is_none() + && f.sig.inputs.len() == 0 + && f.sig.generics.params.is_empty() + && f.sig.generics.where_clause.is_none() + && f.sig.variadic.is_none() + && match f.sig.output { + ReturnType::Default => false, + ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), + }; + + if !valid_signature { + return parse::Error::new( + fspan, + "`HardFault` handler must have signature `unsafe fn() -> !`", + ) + .to_compile_error() + .into(); + } + + f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site()); + + quote!( + #[export_name = "HardFault"] + // Only emit link_section when building for embedded targets, + // because some hosted platforms (used to check the build) + // cannot handle the long link section names. + #[cfg_attr(target_os = "none", link_section = ".HardFault.user")] + #f + ) + } Exception::NonMaskableInt | Exception::Other => { let valid_signature = f.sig.constness.is_none() && f.vis == Visibility::Inherited diff --git a/cortex-m-rt/src/lib.rs b/cortex-m-rt/src/lib.rs index a6d946c4..a39d32a4 100644 --- a/cortex-m-rt/src/lib.rs +++ b/cortex-m-rt/src/lib.rs @@ -458,7 +458,7 @@ use core::fmt; // HardFault exceptions are bounced through this trampoline which grabs the stack pointer at // the time of the exception and passes it to the user's HardFault handler in r0. // Depending on the stack mode in EXC_RETURN, fetches stack from either MSP or PSP. -#[cfg(cortex_m)] +#[cfg(all(cortex_m, feature = "hardfault-trampoline"))] global_asm!( ".cfi_sections .debug_frame .section .HardFaultTrampoline, \"ax\" @@ -1061,12 +1061,22 @@ pub static __RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset; #[allow(unused_variables)] #[doc(hidden)] #[cfg_attr(cortex_m, link_section = ".HardFault.default")] +#[cfg(feature = "hardfault-trampoline")] #[no_mangle] pub unsafe extern "C" fn HardFault_(ef: &ExceptionFrame) -> ! { #[allow(clippy::empty_loop)] loop {} } +#[doc(hidden)] +#[cfg_attr(cortex_m, link_section = ".HardFault.default")] +#[cfg(not(feature = "hardfault-trampoline"))] +#[no_mangle] +pub unsafe extern "C" fn HardFault_() -> ! { + #[allow(clippy::empty_loop)] + loop {} +} + #[doc(hidden)] #[no_mangle] pub unsafe extern "C" fn DefaultHandler_() -> ! { @@ -1115,7 +1125,10 @@ extern "C" { fn NonMaskableInt(); + #[cfg(feature = "hardfault-trampoline")] fn HardFaultTrampoline(); + #[cfg(not(feature = "hardfault-trampoline"))] + fn HardFault(); #[cfg(not(armv6m))] fn MemoryManagement(); @@ -1154,9 +1167,12 @@ pub static __EXCEPTIONS: [Vector; 14] = [ handler: NonMaskableInt, }, // Exception 3: Hard Fault Interrupt. + #[cfg(feature = "hardfault-trampoline")] Vector { handler: HardFaultTrampoline, }, + #[cfg(not(feature = "hardfault-trampoline"))] + Vector { handler: HardFault }, // Exception 4: Memory Management Interrupt [not on Cortex-M0 variants]. #[cfg(not(armv6m))] Vector { From 9ba8c6a8053b215204eefbd9c4a9de9d0a19d50a Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Fri, 19 May 2023 10:57:49 +0200 Subject: [PATCH 02/11] Fix clippy warnings --- cortex-m-rt/macros/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cortex-m-rt/macros/src/lib.rs b/cortex-m-rt/macros/src/lib.rs index b529ef26..e9be2247 100644 --- a/cortex-m-rt/macros/src/lib.rs +++ b/cortex-m-rt/macros/src/lib.rs @@ -287,7 +287,7 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { let valid_signature = f.sig.constness.is_none() && f.vis == Visibility::Inherited && f.sig.abi.is_none() - && f.sig.inputs.len() == 0 + && f.sig.inputs.is_empty() && f.sig.generics.params.is_empty() && f.sig.generics.where_clause.is_none() && f.sig.variadic.is_none() @@ -667,7 +667,7 @@ fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result< } }; - return Err(parse::Error::new(attr.span(), &err_str) + return Err(parse::Error::new(attr.span(), err_str) .to_compile_error() .into()); } From 9503c15bd94625cfaaffa7b65f22ffbafbbf6919 Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Fri, 19 May 2023 11:01:53 +0200 Subject: [PATCH 03/11] Update changelog --- cortex-m-rt/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cortex-m-rt/CHANGELOG.md b/cortex-m-rt/CHANGELOG.md index fd45a1a0..1637a4c2 100644 --- a/cortex-m-rt/CHANGELOG.md +++ b/cortex-m-rt/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add `zero-init-ram` feature to initialize RAM with zeros on startup. This can be necessary on safety-critical hardware to properly initialize memory integrity measures. +- Add `hardfault-trampoline` feature that is enabled by default which enables + the previously required hardfault trampiline. Now when this feature is not active, there will be + no trampoline and the function you write is the exact function that is used as hardfault handler. ## [v0.7.3] From 36419ff47e31c4d2bfce23569e070f1bd123c678 Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Fri, 19 May 2023 13:02:03 +0200 Subject: [PATCH 04/11] typo --- cortex-m-rt/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cortex-m-rt/CHANGELOG.md b/cortex-m-rt/CHANGELOG.md index 1637a4c2..28ec16cd 100644 --- a/cortex-m-rt/CHANGELOG.md +++ b/cortex-m-rt/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add `zero-init-ram` feature to initialize RAM with zeros on startup. This can be necessary on safety-critical hardware to properly initialize memory integrity measures. - Add `hardfault-trampoline` feature that is enabled by default which enables - the previously required hardfault trampiline. Now when this feature is not active, there will be + the previously required hardfault trampoline. Now when this feature is not active, there will be no trampoline and the function you write is the exact function that is used as hardfault handler. ## [v0.7.3] From 54877baa685c63f085cc50adc251bb3af5bdfb2b Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Fri, 16 Jun 2023 11:46:56 +0200 Subject: [PATCH 05/11] Removed the feature flag and moved the trampoline into the macro --- cortex-m-rt/CHANGELOG.md | 4 +- cortex-m-rt/Cargo.toml | 2 - cortex-m-rt/macros/Cargo.toml | 3 - cortex-m-rt/macros/src/lib.rs | 226 ++++++++++++++++++++++++---------- cortex-m-rt/src/lib.rs | 44 ------- 5 files changed, 159 insertions(+), 120 deletions(-) diff --git a/cortex-m-rt/CHANGELOG.md b/cortex-m-rt/CHANGELOG.md index 28ec16cd..40dad584 100644 --- a/cortex-m-rt/CHANGELOG.md +++ b/cortex-m-rt/CHANGELOG.md @@ -9,9 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add `zero-init-ram` feature to initialize RAM with zeros on startup. This can be necessary on safety-critical hardware to properly initialize memory integrity measures. -- Add `hardfault-trampoline` feature that is enabled by default which enables - the previously required hardfault trampoline. Now when this feature is not active, there will be - no trampoline and the function you write is the exact function that is used as hardfault handler. +- Add optional `exception` argument for `HardFault`. It has one option `trampoline` which is true by default. When set to false, no trampoline will be created and the function will be called as the exception handler directly. ## [v0.7.3] diff --git a/cortex-m-rt/Cargo.toml b/cortex-m-rt/Cargo.toml index 03cd7f63..3305d34e 100644 --- a/cortex-m-rt/Cargo.toml +++ b/cortex-m-rt/Cargo.toml @@ -42,12 +42,10 @@ name = "compiletest" required-features = ["device"] [features] -default = ["hardfault-trampoline"] device = [] set-sp = [] set-vtor = [] zero-init-ram = [] -hardfault-trampoline = ["cortex-m-rt-macros/hardfault-trampoline"] [package.metadata.docs.rs] features = ["device"] diff --git a/cortex-m-rt/macros/Cargo.toml b/cortex-m-rt/macros/Cargo.toml index 287ad9c9..f548e19f 100644 --- a/cortex-m-rt/macros/Cargo.toml +++ b/cortex-m-rt/macros/Cargo.toml @@ -21,6 +21,3 @@ proc-macro2 = "1.0" [dependencies.syn] features = ["extra-traits", "full"] version = "2.0" - -[features] -hardfault-trampoline = [] diff --git a/cortex-m-rt/macros/src/lib.rs b/cortex-m-rt/macros/src/lib.rs index e9be2247..527dcc44 100644 --- a/cortex-m-rt/macros/src/lib.rs +++ b/cortex-m-rt/macros/src/lib.rs @@ -10,8 +10,11 @@ use quote::quote; use std::collections::HashSet; use std::iter; use syn::{ - parse, parse_macro_input, spanned::Spanned, AttrStyle, Attribute, FnArg, Ident, Item, ItemFn, - ItemStatic, ReturnType, Stmt, Type, Visibility, + parse::{self, Parse}, + parse_macro_input, + spanned::Spanned, + AttrStyle, Attribute, FnArg, Ident, Item, ItemFn, ItemStatic, ReturnType, Stmt, Type, + Visibility, }; #[proc_macro_attribute] @@ -113,21 +116,73 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { #[derive(Debug, PartialEq)] enum Exception { DefaultHandler, - HardFault, + HardFault(HardFaultArgs), NonMaskableInt, Other, } +#[derive(Debug, PartialEq)] +struct HardFaultArgs { + trampoline: bool, +} + +impl Default for HardFaultArgs { + fn default() -> Self { + Self { trampoline: true } + } +} + +impl Parse for HardFaultArgs { + fn parse(input: parse::ParseStream) -> syn::Result { + let mut items = Vec::new(); + // Read a list of `ident = value,` + loop { + if input.is_empty() { + break; + } + + let name = input.parse::()?; + input.parse::()?; + let value = input.parse::()?; + + items.push((name, value)); + + if input.is_empty() { + break; + } + + input.parse::()?; + } + + let mut args = Self::default(); + + for (name, value) in items { + match name.to_string().as_str() { + "trampoline" => match value { + syn::Lit::Bool(val) => { + args.trampoline = val.value(); + } + _ => { + return Err(syn::Error::new_spanned( + value, + "Not a valid value. `trampoline` takes a boolean literal", + )) + } + }, + _ => { + return Err(syn::Error::new_spanned(name, "Not a valid argument name")); + } + } + } + + Ok(args) + } +} + #[proc_macro_attribute] pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { let mut f = parse_macro_input!(input as ItemFn); - if !args.is_empty() { - return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") - .to_compile_error() - .into(); - } - if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Exception) { return error; } @@ -137,14 +192,34 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { let ident_s = ident.to_string(); let exn = match &*ident_s { - "DefaultHandler" => Exception::DefaultHandler, - "HardFault" => Exception::HardFault, - "NonMaskableInt" => Exception::NonMaskableInt, + "DefaultHandler" => { + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + Exception::DefaultHandler + } + "HardFault" => Exception::HardFault(parse_macro_input!(args)), + "NonMaskableInt" => { + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + Exception::NonMaskableInt + } // NOTE that at this point we don't check if the exception is available on the target (e.g. // MemoryManagement is not available on Cortex-M0) "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => Exception::Other, _ => { + if !args.is_empty() { + return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") + .to_compile_error() + .into(); + } + return parse::Error::new(ident.span(), "This is not a valid exception name") .to_compile_error() .into(); @@ -153,7 +228,7 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { if f.sig.unsafety.is_none() { match exn { - Exception::DefaultHandler | Exception::HardFault | Exception::NonMaskableInt => { + Exception::DefaultHandler | Exception::HardFault(_) | Exception::NonMaskableInt => { // These are unsafe to define. let name = if exn == Exception::DefaultHandler { "`DefaultHandler`".to_string() @@ -232,17 +307,24 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { #f ) } - Exception::HardFault if cfg!(feature = "hardfault-trampoline") => { + Exception::HardFault(args) => { let valid_signature = f.sig.constness.is_none() && f.vis == Visibility::Inherited && f.sig.abi.is_none() - && f.sig.inputs.len() == 1 - && match &f.sig.inputs[0] { - FnArg::Typed(arg) => match arg.ty.as_ref() { - Type::Reference(r) => r.lifetime.is_none() && r.mutability.is_none(), + && if args.trampoline { + match &f.sig.inputs[0] { + FnArg::Typed(arg) => match arg.ty.as_ref() { + Type::Reference(r) => { + r.lifetime.is_none() + && r.mutability.is_none() + && f.sig.inputs.len() == 1 + } + _ => false, + }, _ => false, - }, - _ => false, + } + } else { + f.sig.inputs.is_empty() } && f.sig.generics.params.is_empty() && f.sig.generics.where_clause.is_none() @@ -255,66 +337,74 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { if !valid_signature { return parse::Error::new( fspan, - "`HardFault` handler must have signature `unsafe fn(&ExceptionFrame) -> !`", + if args.trampoline { + "`HardFault` handler must have signature `unsafe fn(&ExceptionFrame) -> !`" + } else { + "`HardFault` handler must have signature `unsafe fn() -> !`" + }, ) .to_compile_error() .into(); } f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site()); - let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); - let ident = &f.sig.ident; - let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); + if args.trampoline { + let tramp_ident = + Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); + let ident = &f.sig.ident; - quote!( - #(#cfgs)* - #(#attrs)* - #[doc(hidden)] - #[export_name = "HardFault"] - // Only emit link_section when building for embedded targets, - // because some hosted platforms (used to check the build) - // cannot handle the long link section names. - #[cfg_attr(target_os = "none", link_section = ".HardFault.user")] - pub unsafe extern "C" fn #tramp_ident(frame: &::cortex_m_rt::ExceptionFrame) { - #ident(frame) - } + let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); - #f - ) - } - Exception::HardFault => { - let valid_signature = f.sig.constness.is_none() - && f.vis == Visibility::Inherited - && f.sig.abi.is_none() - && f.sig.inputs.is_empty() - && f.sig.generics.params.is_empty() - && f.sig.generics.where_clause.is_none() - && f.sig.variadic.is_none() - && match f.sig.output { - ReturnType::Default => false, - ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), - }; + quote!( + #(#cfgs)* + #(#attrs)* + #[doc(hidden)] + #[export_name = "HardFault"] + // Only emit link_section when building for embedded targets, + // because some hosted platforms (used to check the build) + // cannot handle the long link section names. + #[cfg_attr(target_os = "none", link_section = ".HardFault.user")] + pub unsafe extern "C" fn #tramp_ident(frame: &::cortex_m_rt::ExceptionFrame) { + #ident(frame) + } - if !valid_signature { - return parse::Error::new( - fspan, - "`HardFault` handler must have signature `unsafe fn() -> !`", + #f + + // HardFault exceptions are bounced through this trampoline which grabs the stack pointer at + // the time of the exception and passes it to the user's HardFault handler in r0. + // Depending on the stack mode in EXC_RETURN, fetches stack from either MSP or PSP. + core::arch::global_asm!( + ".cfi_sections .debug_frame + .section .HardFaultTrampoline, \"ax\" + .global HardFaultTrampline + .type HardFaultTrampline,%function + .thumb_func + .cfi_startproc + HardFaultTrampoline:", + "mov r0, lr + movs r1, #4 + tst r0, r1 + bne 0f + mrs r0, MSP + b HardFault + 0: + mrs r0, PSP + b HardFault", + ".cfi_endproc + .size HardFaultTrampoline, . - HardFaultTrampoline", + ); + ) + } else { + quote!( + #[export_name = "HardFault"] + // Only emit link_section when building for embedded targets, + // because some hosted platforms (used to check the build) + // cannot handle the long link section names. + #[cfg_attr(target_os = "none", link_section = ".HardFault.user")] + #f ) - .to_compile_error() - .into(); } - - f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site()); - - quote!( - #[export_name = "HardFault"] - // Only emit link_section when building for embedded targets, - // because some hosted platforms (used to check the build) - // cannot handle the long link section names. - #[cfg_attr(target_os = "none", link_section = ".HardFault.user")] - #f - ) } Exception::NonMaskableInt | Exception::Other => { let valid_signature = f.sig.constness.is_none() diff --git a/cortex-m-rt/src/lib.rs b/cortex-m-rt/src/lib.rs index a39d32a4..6456a5ea 100644 --- a/cortex-m-rt/src/lib.rs +++ b/cortex-m-rt/src/lib.rs @@ -455,31 +455,6 @@ extern crate cortex_m_rt_macros as macros; use core::arch::global_asm; use core::fmt; -// HardFault exceptions are bounced through this trampoline which grabs the stack pointer at -// the time of the exception and passes it to the user's HardFault handler in r0. -// Depending on the stack mode in EXC_RETURN, fetches stack from either MSP or PSP. -#[cfg(all(cortex_m, feature = "hardfault-trampoline"))] -global_asm!( - ".cfi_sections .debug_frame - .section .HardFaultTrampoline, \"ax\" - .global HardFaultTrampline - .type HardFaultTrampline,%function - .thumb_func - .cfi_startproc - HardFaultTrampoline:", - "mov r0, lr - movs r1, #4 - tst r0, r1 - bne 0f - mrs r0, MSP - b HardFault - 0: - mrs r0, PSP - b HardFault", - ".cfi_endproc - .size HardFaultTrampoline, . - HardFaultTrampoline", -); - /// Parse cfg attributes inside a global_asm call. #[cfg(cortex_m)] macro_rules! cfg_global_asm { @@ -1058,19 +1033,8 @@ pub fn heap_start() -> *mut u32 { #[no_mangle] pub static __RESET_VECTOR: unsafe extern "C" fn() -> ! = Reset; -#[allow(unused_variables)] #[doc(hidden)] #[cfg_attr(cortex_m, link_section = ".HardFault.default")] -#[cfg(feature = "hardfault-trampoline")] -#[no_mangle] -pub unsafe extern "C" fn HardFault_(ef: &ExceptionFrame) -> ! { - #[allow(clippy::empty_loop)] - loop {} -} - -#[doc(hidden)] -#[cfg_attr(cortex_m, link_section = ".HardFault.default")] -#[cfg(not(feature = "hardfault-trampoline"))] #[no_mangle] pub unsafe extern "C" fn HardFault_() -> ! { #[allow(clippy::empty_loop)] @@ -1125,9 +1089,6 @@ extern "C" { fn NonMaskableInt(); - #[cfg(feature = "hardfault-trampoline")] - fn HardFaultTrampoline(); - #[cfg(not(feature = "hardfault-trampoline"))] fn HardFault(); #[cfg(not(armv6m))] @@ -1167,11 +1128,6 @@ pub static __EXCEPTIONS: [Vector; 14] = [ handler: NonMaskableInt, }, // Exception 3: Hard Fault Interrupt. - #[cfg(feature = "hardfault-trampoline")] - Vector { - handler: HardFaultTrampoline, - }, - #[cfg(not(feature = "hardfault-trampoline"))] Vector { handler: HardFault }, // Exception 4: Memory Management Interrupt [not on Cortex-M0 variants]. #[cfg(not(armv6m))] From 540d1de34aa69b3ec262b7461c51394ac140418d Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Tue, 20 Jun 2023 21:22:11 +0200 Subject: [PATCH 06/11] Added more tests and fixed a panic --- cortex-m-rt/macros/src/lib.rs | 44 +++++++++++-------- .../hard-fault-bad-signature-2.rs | 18 ++++++++ .../hard-fault-bad-signature-3.rs | 18 ++++++++ 3 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-2.rs create mode 100644 cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-3.rs diff --git a/cortex-m-rt/macros/src/lib.rs b/cortex-m-rt/macros/src/lib.rs index 527dcc44..cd92b85b 100644 --- a/cortex-m-rt/macros/src/lib.rs +++ b/cortex-m-rt/macros/src/lib.rs @@ -7,8 +7,8 @@ extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::Span; use quote::quote; -use std::collections::HashSet; use std::iter; +use std::{collections::HashSet, fmt::Display}; use syn::{ parse::{self, Parse}, parse_macro_input, @@ -121,6 +121,17 @@ enum Exception { Other, } +impl Display for Exception { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Exception::DefaultHandler => write!(f, "`DefaultHandler`"), + Exception::HardFault(_) => write!(f, "`HardFault` handler"), + Exception::NonMaskableInt => write!(f, "`NonMaskableInt` handler"), + Exception::Other => write!(f, "Other exception handler"), + } + } +} + #[derive(Debug, PartialEq)] struct HardFaultArgs { trampoline: bool, @@ -212,14 +223,16 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { // NOTE that at this point we don't check if the exception is available on the target (e.g. // MemoryManagement is not available on Cortex-M0) "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" - | "DebugMonitor" | "PendSV" | "SysTick" => Exception::Other, - _ => { + | "DebugMonitor" | "PendSV" | "SysTick" => { if !args.is_empty() { return parse::Error::new(Span::call_site(), "This attribute accepts no arguments") .to_compile_error() .into(); } + Exception::Other + } + _ => { return parse::Error::new(ident.span(), "This is not a valid exception name") .to_compile_error() .into(); @@ -230,11 +243,7 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { match exn { Exception::DefaultHandler | Exception::HardFault(_) | Exception::NonMaskableInt => { // These are unsafe to define. - let name = if exn == Exception::DefaultHandler { - "`DefaultHandler`".to_string() - } else { - format!("`{:?}` handler", exn) - }; + let name = format!("{}", exn); return parse::Error::new(ident.span(), format_args!("defining a {} is unsafe and requires an `unsafe fn` (see the cortex-m-rt docs)", name)) .to_compile_error() .into(); @@ -312,17 +321,16 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { && f.vis == Visibility::Inherited && f.sig.abi.is_none() && if args.trampoline { - match &f.sig.inputs[0] { - FnArg::Typed(arg) => match arg.ty.as_ref() { - Type::Reference(r) => { - r.lifetime.is_none() - && r.mutability.is_none() - && f.sig.inputs.len() == 1 - } + f.sig.inputs.len() == 1 + && match &f.sig.inputs[0] { + FnArg::Typed(arg) => match arg.ty.as_ref() { + Type::Reference(r) => { + r.lifetime.is_none() && r.mutability.is_none() + } + _ => false, + }, _ => false, - }, - _ => false, - } + } } else { f.sig.inputs.is_empty() } diff --git a/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-2.rs b/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-2.rs new file mode 100644 index 00000000..d20d832a --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-2.rs @@ -0,0 +1,18 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_halt; + +use cortex_m_rt::{entry, exception, ExceptionFrame}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception(trampoline = true)] +unsafe fn HardFault() -> ! { + //~^ ERROR `HardFault` handler must have signature `unsafe fn(&ExceptionFrame) -> !` + loop {} +} diff --git a/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-3.rs b/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-3.rs new file mode 100644 index 00000000..62c84392 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/hard-fault-bad-signature-3.rs @@ -0,0 +1,18 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_halt; + +use cortex_m_rt::{entry, exception, ExceptionFrame}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception(trampoline = false)] +unsafe fn HardFault(_ef: &ExceptionFrame) -> ! { + //~^ ERROR `HardFault` handler must have signature `unsafe fn() -> !` + loop {} +} From 3bd46e56e4e4c138cb51900f47280cc991f51702 Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Fri, 21 Jul 2023 13:23:49 +0200 Subject: [PATCH 07/11] Changed some names around so everything should be called correctly --- cortex-m-rt/macros/src/lib.rs | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/cortex-m-rt/macros/src/lib.rs b/cortex-m-rt/macros/src/lib.rs index cd92b85b..80ac665d 100644 --- a/cortex-m-rt/macros/src/lib.rs +++ b/cortex-m-rt/macros/src/lib.rs @@ -368,11 +368,7 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { #(#cfgs)* #(#attrs)* #[doc(hidden)] - #[export_name = "HardFault"] - // Only emit link_section when building for embedded targets, - // because some hosted platforms (used to check the build) - // cannot handle the long link section names. - #[cfg_attr(target_os = "none", link_section = ".HardFault.user")] + #[export_name = "HardFaultUser"] pub unsafe extern "C" fn #tramp_ident(frame: &::cortex_m_rt::ExceptionFrame) { #ident(frame) } @@ -384,23 +380,23 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { // Depending on the stack mode in EXC_RETURN, fetches stack from either MSP or PSP. core::arch::global_asm!( ".cfi_sections .debug_frame - .section .HardFaultTrampoline, \"ax\" - .global HardFaultTrampline - .type HardFaultTrampline,%function + .section .HardFault.user, \"ax\" + .global HardFault + .type HardFault,%function .thumb_func .cfi_startproc - HardFaultTrampoline:", - "mov r0, lr - movs r1, #4 - tst r0, r1 - bne 0f - mrs r0, MSP - b HardFault + HardFault:", + "mov r0, lr + movs r1, #4 + tst r0, r1 + bne 0f + mrs r0, MSP + b HardFaultUser 0: - mrs r0, PSP - b HardFault", + mrs r0, PSP + b HardFaultUser", ".cfi_endproc - .size HardFaultTrampoline, . - HardFaultTrampoline", + .size HardFault, . - HardFault", ); ) } else { From f07d5b3241cb2e70c37d049caab64d75ef32860b Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Fri, 21 Jul 2023 14:16:46 +0200 Subject: [PATCH 08/11] Changed setup for better compiler diagnostics. We won't get a nasty assembly error anymore --- cortex-m-rt/macros/src/lib.rs | 18 ++++++++----- .../hard-fault-twice-mixed-trampoline.rs | 26 +++++++++++++++++++ .../tests/compile-fail/hard-fault-twice.rs | 2 +- 3 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 cortex-m-rt/tests/compile-fail/hard-fault-twice-mixed-trampoline.rs diff --git a/cortex-m-rt/macros/src/lib.rs b/cortex-m-rt/macros/src/lib.rs index 80ac665d..d89bdeaf 100644 --- a/cortex-m-rt/macros/src/lib.rs +++ b/cortex-m-rt/macros/src/lib.rs @@ -356,10 +356,10 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { } f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site()); + let tramp_ident = + Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); if args.trampoline { - let tramp_ident = - Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); let ident = &f.sig.ident; let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone()); @@ -368,8 +368,8 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { #(#cfgs)* #(#attrs)* #[doc(hidden)] - #[export_name = "HardFaultUser"] - pub unsafe extern "C" fn #tramp_ident(frame: &::cortex_m_rt::ExceptionFrame) { + #[export_name = "_HardFault"] + unsafe extern "C" fn #tramp_ident(frame: &::cortex_m_rt::ExceptionFrame) { #ident(frame) } @@ -391,16 +391,22 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { tst r0, r1 bne 0f mrs r0, MSP - b HardFaultUser + b _HardFault 0: mrs r0, PSP - b HardFaultUser", + b _HardFault", ".cfi_endproc .size HardFault, . - HardFault", ); ) } else { quote!( + #[doc(hidden)] + #[export_name = "_HardFault"] + unsafe extern "C" fn #tramp_ident() { + // This trampoline has no function except making the compiler diagnostics better. + } + #[export_name = "HardFault"] // Only emit link_section when building for embedded targets, // because some hosted platforms (used to check the build) diff --git a/cortex-m-rt/tests/compile-fail/hard-fault-twice-mixed-trampoline.rs b/cortex-m-rt/tests/compile-fail/hard-fault-twice-mixed-trampoline.rs new file mode 100644 index 00000000..36101702 --- /dev/null +++ b/cortex-m-rt/tests/compile-fail/hard-fault-twice-mixed-trampoline.rs @@ -0,0 +1,26 @@ +#![no_main] +#![no_std] + +extern crate cortex_m_rt; +extern crate panic_halt; + +use cortex_m_rt::{entry, exception, ExceptionFrame}; + +#[entry] +fn foo() -> ! { + loop {} +} + +#[exception(trampoline = false)] +unsafe fn HardFault() -> ! { + loop {} +} + +pub mod reachable { + use cortex_m_rt::{exception, ExceptionFrame}; + + #[exception] //~ ERROR symbol `_HardFault` is already defined + unsafe fn HardFault(_ef: &ExceptionFrame) -> ! { + loop {} + } +} diff --git a/cortex-m-rt/tests/compile-fail/hard-fault-twice.rs b/cortex-m-rt/tests/compile-fail/hard-fault-twice.rs index 03b79a57..af80127c 100644 --- a/cortex-m-rt/tests/compile-fail/hard-fault-twice.rs +++ b/cortex-m-rt/tests/compile-fail/hard-fault-twice.rs @@ -19,7 +19,7 @@ unsafe fn HardFault(_ef: &ExceptionFrame) -> ! { pub mod reachable { use cortex_m_rt::{exception, ExceptionFrame}; - #[exception] //~ ERROR symbol `HardFault` is already defined + #[exception] //~ ERROR symbol `_HardFault` is already defined unsafe fn HardFault(_ef: &ExceptionFrame) -> ! { loop {} } From ce12620be0e51f9a8bbe6bb67cfc131201b55f34 Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Fri, 21 Jul 2023 14:17:07 +0200 Subject: [PATCH 09/11] fmt --- cortex-m-rt/macros/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cortex-m-rt/macros/src/lib.rs b/cortex-m-rt/macros/src/lib.rs index d89bdeaf..04774051 100644 --- a/cortex-m-rt/macros/src/lib.rs +++ b/cortex-m-rt/macros/src/lib.rs @@ -356,8 +356,7 @@ pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream { } f.sig.ident = Ident::new(&format!("__cortex_m_rt_{}", f.sig.ident), Span::call_site()); - let tramp_ident = - Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); + let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site()); if args.trampoline { let ident = &f.sig.ident; From 5517d54ed0acc6b3a7a57726230c10bcbfa476ed Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Fri, 21 Jul 2023 14:33:52 +0200 Subject: [PATCH 10/11] Add readme with test running clarification --- cortex-m-rt/tests/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 cortex-m-rt/tests/README.md diff --git a/cortex-m-rt/tests/README.md b/cortex-m-rt/tests/README.md new file mode 100644 index 00000000..1d879fc4 --- /dev/null +++ b/cortex-m-rt/tests/README.md @@ -0,0 +1,7 @@ +# How to run + +To run the compile tests, use the following command on the root of the project: + +``` +cargo test --package cortex-m-rt --test compiletest --features device +``` From faf7bd3cf37343dec23f61c185e0ae22f684e991 Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Wed, 16 Aug 2023 15:20:31 +0200 Subject: [PATCH 11/11] Add to docs --- cortex-m-rt/src/lib.rs | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/cortex-m-rt/src/lib.rs b/cortex-m-rt/src/lib.rs index 6456a5ea..920c9897 100644 --- a/cortex-m-rt/src/lib.rs +++ b/cortex-m-rt/src/lib.rs @@ -225,12 +225,15 @@ //! - `DefaultHandler`. This is the default handler. If not overridden using `#[exception] fn //! DefaultHandler(..` this will be an infinite loop. //! -//! - `HardFaultTrampoline`. This is the real hard fault handler. This function is simply a -//! trampoline that jumps into the user defined hard fault handler named `HardFault`. The -//! trampoline is required to set up the pointer to the stacked exception frame. -//! -//! - `HardFault`. This is the user defined hard fault handler. If not overridden using -//! `#[exception] fn HardFault(..` it will default to an infinite loop. +//! - `HardFault` and `_HardFault`. These function handle the hard fault handling and what they +//! do depends on whether the hard fault is overridden and whether the trampoline is enabled (which it is by default). +//! - No override: Both are the same function. The function is an infinite loop defined in the cortex-m-rt crate. +//! - Trampoline enabled: `HardFault` is the real hard fault handler defined in assembly. This function is simply a +//! trampoline that jumps into the rust defined `_HardFault` function. This second function jumps to the user-defined +//! handler with the exception frame as parameter. This second jump is usually optimised away with inlining. +//! - Trampoline disabled: `HardFault` is the user defined function. This means the user function is called directly +//! from the vector table. `_HardFault` still exists, but is an empty function that is purely there for compiler +//! diagnostics. //! //! - `__STACK_START`. This is the first entry in the `.vector_table` section. This symbol contains //! the initial value of the stack pointer; this is where the stack will be located -- the stack @@ -724,9 +727,18 @@ pub use macros::entry; /// /// # Usage /// -/// `#[exception] unsafe fn HardFault(..` sets the hard fault handler. The handler must have -/// signature `unsafe fn(&ExceptionFrame) -> !`. This handler is not allowed to return as that can -/// cause undefined behavior. +/// ## HardFault handler +/// +/// `#[exception(trampoline = true)] unsafe fn HardFault(..` sets the hard fault handler. +/// If the trampoline parameter is set to true, the handler must have signature `unsafe fn(&ExceptionFrame) -> !`. +/// If set to false, the handler must have signature `unsafe fn() -> !`. +/// +/// This handler is not allowed to return as that can cause undefined behavior. +/// +/// To maintain backwards compatibility the attribute can be used without trampoline parameter (`#[exception]`), +/// which sets the trampoline to true. +/// +/// ## Default handler /// /// `#[exception] unsafe fn DefaultHandler(..` sets the *default* handler. All exceptions which have /// not been assigned a handler will be serviced by this handler. This handler must have signature @@ -734,6 +746,8 @@ pub use macros::entry; /// number when the handler is servicing a core exception; `irqn` will be a positive number when the /// handler is servicing a device specific exception (interrupt). /// +/// ## Other handlers +/// /// `#[exception] fn Name(..` overrides the default handler for the exception with the given `Name`. /// These handlers must have signature `[unsafe] fn() [-> !]`. When overriding these other exception /// it's possible to add state to them by declaring `static mut` variables at the beginning of the