cortex-m icon indicating copy to clipboard operation
cortex-m copied to clipboard

Any reason why the `#[exception]` macro disallows `extern "C" fn`s?

Open SCingolani opened this issue 10 months ago • 2 comments

I have the following signature for my HardFault handler:

#[exception(trampoline = false)]
#[naked]
unsafe fn HardFault() -> ! {
...

This triggers the compiler warning

warning: Rust ABI is unsupported in naked functions note: #[warn(undefined_naked_function_abi)] on by default

Which could be avoided by modifying the signature to be:

#[exception(trampoline = false)]
#[naked]
unsafe extern "C" fn HardFault() -> ! {
...

But this is not accepted by the macro:

HardFault handler must have signature unsafe fn() -> !

Is there any reason for the macro to not allow specifying the ABI?

Also, could someone point me as to why does the following not produce the same warning?

#[pre_init]
#[naked]
unsafe fn pre_init() {
...

SCingolani avatar Jan 17 '25 09:01 SCingolani

Do you still get this error on the latest nightly? I can compile the following OK:

#![no_std]
#![no_main]
#![feature(naked_functions)]

#[naked]
#[cortex_m_rt::pre_init]
unsafe fn pre_init() {
    core::arch::naked_asm!("nop");
}

#[naked]
#[cortex_m_rt::exception(trampoline=false)]
unsafe fn HardFault() -> ! {
    core::arch::naked_asm!("nop");
}

I don't think there's any reason to prohibit extern "C" functions here, but because the macro already generates the actual ISR and makes it extern "C" as appropriate, I don't think we should need to change the user interface. We added naked to the list of allowed attributes a while ago and what it means has changed as it moves towards stabilisation, so we should probably just make sure that the attribute does what's expected in this context.

You might find using cargo expand illuminating, for example the above expands to:

#[export_name = "__pre_init"]
#[allow(missing_docs)]
#[naked]
pub unsafe fn pre_init() {
    asm!("nop");
}
#[doc(hidden)]
#[export_name = "_HardFault"]
unsafe extern "C" fn __cortex_m_rt_HardFault_trampoline() {}
#[export_name = "HardFault"]
#[link_section = ".HardFault.user"]
#[naked]
unsafe fn __cortex_m_rt_HardFault() -> ! {
    asm!("nop");
}

in cortex-m-rt, there's a bl __pre_init which is how the pre-init function gets called, while HardFault is referred to from as extern "C" fn HardFault(); and placed into the vector table so will be called directly by hardware. The _HardFault is empty and just for diagnostics. It looks like this should all work as expected with naked functions.

adamgreig avatar Jan 22 '25 02:01 adamgreig

Strange, with an empty project containing just the code from your test and rustc 1.86.0-nightly (a9730c3b5 2025-02-05) I still get:

error: #[panic_handler] function required, but not found

warning: Rust ABI is unsupported in naked functions --> src/main.rs:13:1 | 13 | unsafe fn HardFault() -> ! { | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: #[warn(undefined_naked_function_abi)] on by default

SCingolani avatar Feb 06 '25 10:02 SCingolani

Arguably it's an error that we accept non-extern "C" functions. Rust functions have no defined ABI, and yet the hardware assumes it is entering a function that preserves R4-R11 and returns to LR.

jonathanpallant avatar Oct 09 '25 16:10 jonathanpallant

Generally the cortex-m-rt macros add extern "C" to the generated function/trampoline, which is why we don't require the user write it, but it looks like the pre_init macro doesn't add extern "C" (but should), probably a leftover from when it was called from Rust code instead of the startup assembly.

Additionally it looks like in latest stable Rust we do get the latest version of this error, because #[unsafe(naked)] ends up applied to the non-C functions:

#[export_name = "__pre_init"]
#[allow(missing_docs)]
#[unsafe(naked)]
pub unsafe fn pre_init() {
    asm!("nop");
}
#[doc(hidden)]
#[export_name = "_HardFault"]
unsafe extern "C" fn __cortex_m_rt_HardFault_trampoline() {}
#[export_name = "HardFault"]
#[link_section = ".HardFault.user"]
#[unsafe(naked)]
unsafe fn __cortex_m_rt_HardFault() -> ! {
    asm!("nop");
}

I think the fix is probably to have the macros generate pre_init and the HardFault exception as extern "C" too.

adamgreig avatar Oct 09 '25 18:10 adamgreig