首页
/ Rust 编译器错误 E0781 详解:为什么 `cmse-nonsecure-call` ABI 只能用于函数指针

Rust 编译器错误 E0781 详解:为什么 `cmse-nonsecure-call` ABI 只能用于函数指针

2026-09-09 15:30:31作者:卓艾滢Kingsley

E0781 是 rustc 针对 ARM TrustZone CMSE(Control-Memory Security Extension)调用约定的一条硬错误:cmse-nonsecure-call ABI 不允许挂在普通函数项上,只能出现在函数指针类型中。本文基于 rustc 的错误码文档 E0781.md 完整还原该错误的触发场景,并深入 rustc_hir_analysis 的校验源码,说明这条限制背后的调用约定约束,以及如何在非安全态(Non-secure state)代码中正确使用这个 ABI。

错误概述与触发示例

rustc 错误码文档 E0781.md 对这条错误给出的原文是:

The cmse-nonsecure-call ABI can only be used with function pointers. (cmse-nonsecure-call ABI 只能用于函数指针。)

文档中给出的错误代码示例(compile_fail,E0781 测试片段)如下:

#![feature(abi_cmse_nonsecure_call)]

pub extern "cmse-nonsecure-call" fn test() {}

这段代码的问题在于:它把 "cmse-nonsecure-call" 直接写在一个 函数项(fn item) 上。rustc 会在此处报错,提示该 ABI 仅允许出现在函数指针类型(如 extern "cmse-nonsecure-call" fn(...) 类型的声明)中。

文档随后给出了正确的使用方向:

The cmse-nonsecure-call ABI should be used by casting function pointers to specific addresses. (cmse-nonsecure-call ABI 应当通过将函数指针转换(cast)到特定地址来使用。)

也就是说,非安全态一侧不定义真正的函数实体,而是持有指向安全侧(Secure state)入口点地址的函数指针,经由该指针发起跨安全域的调用。

背景:CMSE 与两个 TrustZone ABI

cmse-nonsecure-call 属于 ARM Cortex-M TrustZone-CMSE 体系下的受控调用约定:非安全态程序通过特殊指令跳转到安全侧的 Non-Secure Callable 入口,该约定对寄存器、参数传递方式有极严格限制。

extern_abi.rs 中可以看到 rustc 对这两个 ABI 的定位:

/* arm */
/// extremely constrained barely-C ABI for TrustZone
CmseNonSecureCall,
/// extremely constrained barely-C ABI for TrustZone
CmseNonSecureEntry,

源码注释将其描述为"面向 TrustZone 的、约束极严的、勉强算 C 的 ABI"。其中:

  • cmse-nonsecure-call:非安全态(Non-secure)调用安全侧入口时使用的 ABI,只能出现在函数指针类型上;
  • cmse-nonsecure-entry:安全侧入口函数本身声明时使用的 ABI。

两者在字符串化时的映射见 extern_abi.rsCmseNonSecureCall =><= "cmse-nonsecure-call")。

该能力由 unstable feature 门控:unstable.rs 中声明为 (unstable, abi_cmse_nonsecure_call, "1.90.0", Some(81391)),即自 1.90.0 起以 #![feature(abi_cmse_nonsecure_call)] 启用;removed.rs 同时记录了旧名 abi_c_cmse_nonsecure_call 已在 1.90.0 被移除并更名为现名。因此该错误只会在 nightly 通道、显式开启该 feature 的场景下出现。

源码剖析:E0781 是如何被诊断的

诊断逻辑集中在 cmse.rsvalidate_cmse_abi 函数中(入口)。对于 CmseNonSecureCall 分支,关键判断如下(匹配逻辑):

ExternAbi::CmseNonSecureCall => match tcx.hir_node(hir_id) {
    // 合法:当前节点是 FnPtr 类型的类型节点
    hir::Node::Ty(hir::Ty { kind: hir::TyKind::FnPtr(fn_ptr_ty), .. }) => fn_ptr_ty.decl,
    _ => {
        // 不合法:报 E0781
        let span = match tcx.parent_hir_node(hir_id) {
            hir::Node::Item(hir::Item {
                kind: hir::ItemKind::ForeignMod { .. },
                span, ..
            }) => *span,
            _ => tcx.hir_span(hir_id),
        };
        struct_span_code_err!(
            dcx,
            span,
            E0781,
            "the `\"cmse-nonsecure-call\"` ABI is only allowed on function pointers"
        )
        .emit();
        return;
    }
},

从源码结构看,可以归纳出以下要点:

  1. 触发时机validate_cmse_abi 由类型降低阶段调用(调用点),每当 rustc 遇到携带 CMSE ABI 的函数签名时都会走一次校验;
  2. 判定标准:编译器检查携带该 ABI 的 HIR 节点是否为 TyKind::FnPtr(函数指针类型节点)。如果是普通 fn 项、extern 块中的函数声明等,即触发 E0781;
  3. 错误定位的精细处理:若 ABI 写在外联模块(extern "cmse-nonsecure-call" { ... })内的声明上,报错 span 会被提升到整个 ForeignMod(extern 块),使诊断指向更合理的代码位置;
  4. 早退设计:一旦发出 E0781,函数立即 return,不再继续做后续的寄存器布局检查——因为对象类型都不对,布局校验已无意义。

为什么必须是函数指针:寄存器约束是根本原因

同一文件中紧随其后的两个校验函数解释了这条 ABI 的"极其受限"具体体现在哪:

  • 参数约束is_valid_cmse_inputs):所有参数必须经寄存器传递,不得溢出到栈。实现上按"每个参数大小向上对齐到 max(4, 对齐值)"累加,总量超过 16 字节(即 4 个 32 位寄存器)就报 CmseInputsStackSpill 错误;
  • 返回值约束is_valid_cmse_output):返回值必须放进寄存器,允许 ≤4 字节,或恰好 8 字节的 64 位标量(i64/u64/f64,含透明包装)。

文件头部注释(L11-L13)说明了设计意图:"参数和结果必须通过寄存器返回,LLVM 也会校验这些条件,但 rustc 在这里提前检查可以给出更好的错误信息。"

可以推断,cmse-nonsecure-call 的调用路径由汇编层的特殊跳转指令实现(安全侧入口地址在链接期确定),非安全态一侧没有常规的函数实体可供调用约定展开,因此 rustc 从类型层面强制"只许声明函数指针,再转换到具体入口地址"这一使用形态。

正确写法

按错误码文档的指引,正确形态是声明函数指针类型 + 将指针转换到特定地址。结合上述寄存器约束,一个典型用法示意如下:

#![feature(abi_cmse_nonsecure_call)]

// 1. 用 cmse-nonsecure-call ABI 声明函数指针类型
//    注意参数总量 ≤ 16 字节、返回值 ≤ 4 字节(或 8 字节 64 位标量)
type SecureEntry = unsafe extern "cmse-nonsecure-call" fn(u32, u32) -> u32;

// 2. 将安全侧入口地址(如 0x0800_8000)转换为该函数指针类型
let entry: SecureEntry = unsafe { core::mem::transmute(0x0800_8000usize) };

// 3. 通过函数指针发起调用
let result = entry(1, 2);

要点回顾:

  • 不要写 extern "cmse-nonsecure-call" fn foo() {} 这样的函数项——这是 E0781 的直接触发条件;
  • 参数与返回值必须满足寄存器约束,否则即使通过了 E0781 检查,也会在后续布局校验中报 CmseInputsStackSpill/CmseOutputStackSpill 类错误;
  • 该 feature 目前为 unstable(1.90.0 起名为 abi_cmse_nonsecure_call),只能在 nightly 上使用;
  • 此外该 ABI 也不支持 #[tail_call] 显式尾调用,参见测试 cmse-nonsecure-call.rs

相关资源

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.15 K
2.77 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
901
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
929
1.85 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
534
604
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23