Skip to content

Tracking Issue for the extern "C-cmse-nonsecure-call" ABI #81391

Open
@hug-dev

Description

@hug-dev
Contributor

This is a tracking issue for the PR #81346.
The feature gate for the issue is #![feature(abi_c_cmse_nonsecure_call)].

Description

The TrustZone-M feature is available for targets with the Armv8-M architecture profile (thumbv8m in their target name).
LLVM, the Rust compiler and the linker are providing support for the TrustZone-M feature.

One of the things provided, with this unstable feature, is the C-cmse-nonsecure-call function ABI. This ABI is used on function pointers to non-secure code to mark a non-secure function call (see section 5.5 for details).

With this ABI, the compiler will do the following to perform the call:

  • save registers needed after the call to Secure memory
  • clear all registers that might contain confidential information
  • clear the Least Significant Bit of the function address
  • branches using the BLXNS instruction

To avoid using the non-secure stack, the compiler will constrain the number and type of parameters/return value.

The extern "C-cmse-nonsecure-call" ABI is otherwise equivalent to the extern "C" ABI.

Example

#![no_std]
#![feature(abi_c_cmse_nonsecure_call)]

#[no_mangle]
pub fn call_nonsecure_function(addr: usize) -> u32 {
    let non_secure_function =
        unsafe { core::mem::transmute::<usize, extern "C-cmse-nonsecure-call" fn() -> u32>(addr) };
    non_secure_function()
}
$ rustc --emit asm --crate-type lib --target thumbv8m.main-none-eabi function.rs

call_nonsecure_function:
        .fnstart
        .save   {r7, lr}
        push    {r7, lr}
        .setfp  r7, sp
        mov     r7, sp
        .pad    #16
        sub     sp, #16
        str     r0, [sp, #12]
        ldr     r0, [sp, #12]
        str     r0, [sp, #8]
        b       .LBB0_1
.LBB0_1:
        ldr     r0, [sp, #8]
        push.w  {r4, r5, r6, r7, r8, r9, r10, r11}
        bic     r0, r0, #1
        mov     r1, r0
        mov     r2, r0
        mov     r3, r0
        mov     r4, r0
        mov     r5, r0
        mov     r6, r0
        mov     r7, r0
        mov     r8, r0
        mov     r9, r0
        mov     r10, r0
        mov     r11, r0
        mov     r12, r0
        msr     apsr_nzcvq, r0
        blxns   r0
        pop.w   {r4, r5, r6, r7, r8, r9, r10, r11}
        str     r0, [sp, #4]
        b       .LBB0_2
.LBB0_2:
        ldr     r0, [sp, #4]
        add     sp, #16
        pop     {r7, pc}

Steps

Activity

added
C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
on Jan 25, 2021
added
O-ArmTarget: 32-bit Arm processors (armv6, armv7, thumb...), including 64-bit Arm in AArch32 state
WG-embeddedWorking group: Embedded systems
on Jan 25, 2021
nagisa

nagisa commented on Jan 26, 2021

@nagisa
Member

What's the name of the actual underlying calling convention? Is it AAPCS? I think the extern name should contain it too somehow.

hug-dev

hug-dev commented on Jan 26, 2021

@hug-dev
ContributorAuthor

What's the name of the actual underlying calling convention?

It will use the C convention ultimately, the current implementation maps it to llvm::CCallConv (I guess similar than AAPCS as this feature is only available on Arm processors?).
Do you mean that because it might be possible to have the cmse_nonsecure_call feature available for other ABIs as well?

I think restricting it to only ever use the C ABI is not a bad thing: this is used to switch to functions that are defined in other executable files that could have been written in any programming language.

nagisa

nagisa commented on Jan 26, 2021

@nagisa
Member

Well, what I really want is for the underlying ABI to be explicit in the ABI string, whatever it is, so that it is more obvious that a transmute as given in the example above is… valid. It would also, as you mention, enable us to add non-secure options for other calling conventions if necessary.

So a couple of proposals: C_cmse_nonsecure or cmse_nonsecure_C.

hug-dev

hug-dev commented on Jan 26, 2021

@hug-dev
ContributorAuthor

Ok makes sense! Will modify the implementation PR and this with the C in front: extern "C-cmse-nonsecure-call".

changed the title [-]Tracking Issue for the cmse-nonsecure-call ABI[/-] [+]Tracking Issue for the C-cmse-nonsecure-call ABI[/+] on Jan 27, 2021
added a commit that references this issue on Jul 19, 2024

Rollup merge of rust-lang#127814 - folkertdev:c-cmse-nonsecure-call-e…

3b20150
added a commit that references this issue on Jul 19, 2024
folkertdev

folkertdev commented on Jul 30, 2024

@folkertdev
Contributor

Request for Stabilization

Summary

We propose to stabilize the C-cmse-nonsecure-call ABI. It can only be used in function pointer types, never in an actual *extern "C-cmse-nonsecure-call" {} block. Such function pointers are typically received via FFI.

Usage example

https://godbolt.org/z/KT6hc5Y7W

// `cargo build --target thumbv8m.main-none-eabi`
#![feature(abi_c_cmse_nonsecure_call)]
#![no_std]

#[no_mangle]
pub fn call_nonsecure(f: unsafe extern "C-cmse-nonsecure-call" fn(u8, u16, u32) -> f32) -> f32 {
    unsafe { f(1, 2, 3) }
}

Which produces this LLVM IR:

; Function Attrs: nounwind
define dso_local float @call_nonsecure(ptr %f) unnamed_addr #0 !dbg !5 {
start:
  %_0 = call float %f(i8 zeroext 1, i16 zeroext 2, i32 3) #1, !dbg !10
  ret float %_0, !dbg !11
}

attributes #0 = { nounwind "frame-pointer"="all" "target-cpu"="generic" }
attributes #1 = { nounwind "cmse_nonsecure_call" }

Which produces this assembly:

call_nonsecure:
        push    {r7, lr}
        mov     r7, sp
        mov     r3, r0
        movs    r0, #1
        movs    r1, #2
        movs    r2, #3
        push.w  {r4, r5, r6, r7, r8, r9, r10, r11}
        bic     r3, r3, #1
        sub     sp, #136
        vlstm   sp
        mov     r4, r3 ; <- All unused registers get cleared, to not leak information
        mov     r5, r3
        mov     r6, r3
        mov     r7, r3
        mov     r8, r3
        mov     r9, r3
        mov     r10, r3
        mov     r11, r3
        mov     r12, r3
        msr     apsr_nzcvq, r3
        blxns   r3 ; <- the expected non-secure call instruction
        vlldm   sp
        add     sp, #136
        pop.w   {r4, r5, r6, r7, r8, r9, r10, r11}
        pop     {r7, pc}

Error messages

The calling requirements for this ABI are checked within rustc to produce good error messsages.

#![feature(abi_c_cmse_nonsecure_call)]
#![no_std]

#[no_mangle]
pub fn call_nonsecure(f: unsafe extern "C-cmse-nonsecure-call" fn(u64, u64, u64) -> (u64, u64)) {
    unsafe { f(1, 2, 3) };
}

Produces the following errors:

error[E0798]: arguments for `"C-cmse-nonsecure-call"` function too large to pass via registers
 --> <source>:5:77
  |
5 | pub fn call_nonsecure(f: unsafe extern "C-cmse-nonsecure-call" fn(u64, u64, u64) -> (u64, u64)) {
  |                                                                             ^^^ this argument doesn't fit in the available registers
  |
  = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass all their arguments via the 4 32-bit available argument registers

error[E0798]: return value of `"C-cmse-nonsecure-call"` function too large to pass via registers
 --> <source>:5:85
  |
5 | pub fn call_nonsecure(f: unsafe extern "C-cmse-nonsecure-call" fn(u64, u64, u64) -> (u64, u64)) {
  |                                                                                     ^^^^^^^^^^ this type doesn't fit in the available registers
  |
  = note: functions with the `"C-cmse-nonsecure-call"` ABI must pass their result via the available return registers
  = note: the result must either be a (transparently wrapped) i64, u64 or f64, or be at most 4 bytes in size

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0798`.

Documentation

Tests

Test cases are in

https://github.com/rust-lang/rust/tree/master/tests/ui/cmse-nonsecure/cmse-nonsecure-call

  • the abi is not allowed in extern blocks or extern fn definitions (and therefore is only allowed in function pointers)
  • part of this ABI is that arguments and return values must be passed via registers. This is validated pre-monomorphization so that good error messages can be generated.
  • to make the above restriction work, generics are not allowed in signatures that use this abi. That is fine because these are (effectively) external C function, so generics are not meaningful.

The tests in the (as yet unmerged) PR for cmse-nonsecure-entry validate the assembly output of this ABI.

C examples

Clang: https://godbolt.org/z/7ch3xcz96
GCC: https://godbolt.org/z/16arxab5x

tdittr

tdittr commented on Jul 30, 2024

@tdittr
Contributor

This tracking issues seems to be missing some tags compared to #75835

@rustbot label +T-compiler +T-lang +A-codegen

26 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ABIArea: Concerning the application binary interface (ABI)A-codegenArea: Code generationC-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCF-abi_c_cmse_nonsecure_call`#![feature(abi_c_cmse_nonsecure_call)]`O-ArmTarget: 32-bit Arm processors (armv6, armv7, thumb...), including 64-bit Arm in AArch32 stateT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamWG-embeddedWorking group: Embedded systems

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @nikomatsakis@nagisa@traviscross@jonas-schievink@tdittr

        Issue actions

          Tracking Issue for the `extern "C-cmse-nonsecure-call"` ABI · Issue #81391 · rust-lang/rust