Rust 编译器错误 E0781 详解:为什么 `cmse-nonsecure-call` ABI 只能用于函数指针
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-callABI can only be used with function pointers. (cmse-nonsecure-callABI 只能用于函数指针。)
文档中给出的错误代码示例(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-callABI should be used by casting function pointers to specific addresses. (cmse-nonsecure-callABI 应当通过将函数指针转换(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.rs(CmseNonSecureCall =><= "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.rs 的 validate_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;
}
},
从源码结构看,可以归纳出以下要点:
- 触发时机:
validate_cmse_abi由类型降低阶段调用(调用点),每当 rustc 遇到携带 CMSE ABI 的函数签名时都会走一次校验; - 判定标准:编译器检查携带该 ABI 的 HIR 节点是否为
TyKind::FnPtr(函数指针类型节点)。如果是普通fn项、extern块中的函数声明等,即触发 E0781; - 错误定位的精细处理:若 ABI 写在外联模块(
extern "cmse-nonsecure-call" { ... })内的声明上,报错 span 会被提升到整个ForeignMod(extern 块),使诊断指向更合理的代码位置; - 早退设计:一旦发出 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。
相关资源
- 错误码定义文档:E0781.md
- CMSE ABI 校验实现:rustc_hir_analysis/src/hir_ty_lowering/cmse.rs
- ABI 枚举定义:rustc_abi/src/extern_abi.rs
- feature 门控(unstable/removed):rustc_feature/src/unstable.rs、rustc_feature/src/removed.rs
- 尾调用不兼容测试:tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.rs
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K637- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python230
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java281
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java200
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript180
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python300