深入解读 Rust 编译器错误码 E0776:cmse_nonsecure_entry 函数的 ABI 约束与演进
E0776 是 rustc 错误码体系中与 Armv8-M TrustZone-M 安全扩展相关的历史错误码,用于约束 CMSE 非安全入口(non-secure entry)函数的调用约定。本文基于当前仓库中的 E0776 官方文档 展开,并结合编译器中 ABI 校验、LLVM 代码生成等源码实现,完整说明该错误码的产生背景、修复方式及其在现代 rustc 中的演进形态,帮助读者理解 CMSE 入口函数的参数传递限制与正确写法。
错误码现状:E0776 已不再由编译器发出
在深入探讨之前,必须先澄清一个重要事实:E0776 这个错误码在当前版本的 rustc 中已经不再被发出(no longer emitted)。
这一点在 error_codes/lib.rs 中有明确的注释记录:
// E0776, // Removed; `#[cmse_nonsecure_entry]` is now `extern "cmse-nonsecure-entry"`
也就是说,旧的 #[cmse_nonsecure_entry] 属性写法已经被新的 extern "cmse-nonsecure-entry" 函数 ABI 语法所取代。错误码的删除并非因为功能被废弃,而是因为触发该错误的旧语法本身被重构了。rustc 错误码文档体系中保留 E0776 的说明,是为了让开发者(尤其是检索历史错误码的开发者)理解这条错误曾经的含义与修复方式。
错误码原始含义:入口函数需要 C ABI
原始错误信息
E0776 的原始错误信息为:
#[cmse_nonsecure_entry]functions require a C ABI(
#[cmse_nonsecure_entry]函数要求使用 C ABI)
在旧语法时代,#[cmse_nonsecure_entry] 是一个函数属性,需要配合 extern "C" 使用。如果开发者忘记声明 C ABI,就会触发 E0776。
原始错误示例
E0776 文档中给出的错误示例代码如下:
#![feature(cmse_nonsecure_entry)]
#[no_mangle]
#[cmse_nonsecure_entry]
pub fn entry_function(input: Vec<u32>) {}
这段代码包含两个问题:
- 缺少 C ABI 声明:
#[cmse_nonsecure_entry]要求函数使用extern "C"调用约定; - 参数类型不当:
Vec<u32>是一个包含指针、长度、容量的复合类型,体积远超寄存器容量,无法通过寄存器传递。
原始修复方式
文档给出的修复方法是显式声明 C ABI:
#![feature(cmse_nonsecure_entry)]
#[no_mangle]
#[cmse_nonsecure_entry]
pub extern "C" fn entry_function(input: Vec<u32>) {}
需要说明的是,即便补上 extern "C",Vec<u32> 这类无法在寄存器中完整传递的类型仍会触发其他检查(详见下文源码分析),因此这只是针对 E0776 本身的修复示范。
现代写法:直接使用 cmse-nonsecure-entry ABI
E0776 被删除的根本原因,是 rustc 引入了专门的 extern "cmse-nonsecure-entry" 函数 ABI 语法,取代了原先的属性 + C ABI 组合。在 unstable-book 特性文档 中给出了当前的标准写法:
#![no_std]
#![feature(cmse_nonsecure_entry)]
#[no_mangle]
pub extern "cmse-nonsecure-entry" fn entry_function(input: u32) -> u32 {
input + 6
}
关键点:
- 该特性名称为
cmse_nonsecure_entry,在 rustc_feature/src/unstable.rs 中登记为 unstable 特性,自 Rust 1.48.0 起引入,tracking issue 编号为 75835; - 函数仍需搭配
#[no_mangle]或类似导出手段,因为该 ABI 的用途就是生成可被链接器识别的安全网关入口符号; - ABI 语法
extern "cmse-nonsecure-entry"的定义位于 rustc_abi/src/extern_abi.rs,属于ExternAbi::CmseNonSecureEntry变体。
为什么参数必须通过寄存器传递:源码级原理
触发 E0776 的深层原因
E0776 之所以要求 C ABI,根因是 CMSE 非安全入口函数的参数和返回值不允许通过栈传递。这是因为安全世界与非安全世界之间共享栈,如果参数落在栈上,就可能泄露安全数据或成为攻击面。因此编译器强制要求所有参数都必须能装进寄存器。
在 rustc_ast_lowering/src/stability.rs 中,ExternAbi::CmseNonSecureEntry 与 CmseNonSecureCall 一样被标记为需要 unstable feature gate:
ExternAbi::CmseNonSecureEntry => Err(UnstableAbi {
abi,
feature: sym::cmse_nonsecure_entry,
explain: GateReason::Experimental,
}),
这解释了为什么示例代码开头必须写 #![feature(cmse_nonsecure_entry)]。
ABI 校验实现:cmse.rs
rustc 在类型检查阶段的 HIR 类型降级(hir_ty_lowering)模块中实现了专门的 CMSE ABI 校验逻辑,文件位于 compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs。其中 validate_cmse_abi 函数负责检查两个核心约束:
1. 参数总量不能超过 4 个 32 位寄存器(16 字节)
is_valid_cmse_inputs 函数(cmse.rs)逐个累加参数类型的内存布局大小,并做对齐处理:
accum += size;
accum = accum.next_multiple_of(Ord::max(4, align));
// i.e. exceeds 4 32-bit registers
if accum > 16 {
excess_argument_spans.push(hir_ty.span);
}
当累计大小超过 16 字节(即 4 个 32 位寄存器)时,编译器会发出 CmseInputsStackSpill 诊断,提示参数会溢出到栈上。这正解释了为何 Vec<u32> 无法作为入口函数参数——它的大小远超过寄存器容量。
2. 返回值必须能完整放入寄存器
is_valid_cmse_output_layout 函数(cmse.rs)规定返回值要么不超过 4 字节,要么是恰好 8 字节且剥离开透明包装后为 i64/u64/f64 之一:
if size <= 4 {
return true;
} else if size != 8 {
return false;
}
// Accept (transparently wrapped) scalar 64-bit primitives.
matches!(
layout.peel_transparent_wrappers(&cx).ty.kind(),
ty::Int(ty::IntTy::I64) | ty::Uint(ty::UintTy::U64) | ty::Float(ty::FloatTy::F64)
)
此外,validate_cmse_abi 还显式禁止两类情况:
- C 可变参数(c-variadic):
extern "cmse-nonsecure-entry"函数不能是可变参数函数(cmse.rs); - 返回值中的
impl Trait(opaque type):在返回类型中使用impl Trait会引发布局计算查询环,被显式禁止(cmse.rs)。
这些校验由 rustc_codegen_ssa/src/mir/block.rs 中的相关逻辑共同配合,确保从类型检查到 MIR 代码生成全程贯彻"参数不得使用非安全栈"这一安全约束。
LLVM 端的属性生成
在后端代码生成阶段,rustc_codegen_llvm/src/abi.rs 会为 CMSE 非安全入口函数附加 LLVM 函数属性:
CanonAbi::Arm(ArmCall::CCmseNonSecureEntry) => {
func_attrs.push(llvm::CreateAttrString(cx.llcx, "cmse_nonsecure_entry"))
}
LLVM 收到该属性后会执行一系列平台相关的转换(具体在 LLVM 的 ARM 后端实现):为函数生成带 __acle_se_ 前缀的导出符号、插入清除可能含安全信息的寄存器的代码、并以 BXNS 指令返回。这些行为在 unstable-book 特性文档 中有明确记载:
- 在函数上添加
__acle_se_前缀加标准函数名的特殊符号; - 约束参数数量以避免使用非安全栈;
- 函数返回前清除可能包含安全信息的寄存器;
- 使用
BXNS指令返回。
__acle_se_ 符号会被链接器用来生成安全网关 veneer(secure gateway veneer),这是 TrustZone-M 安全状态切换到非安全状态的必需机制。
相关错误码:E0775 与目标平台限制
CMSE 入口函数还有一条密切相关的错误码 E0775,两者同属 CMSE 特性家族,可以对照阅读。E0775 的文档位于 compiler/rustc_error_codes/src/error_codes/E0775.md,其含义为:
#[cmse_nonsecure_entry]is only valid for targets with the TrustZone-M extension.
即 CMSE 入口函数只能在具备 TrustZone-M 扩展的目标平台上使用。当前支持的目标平台包括:
thumbv8m.main-none-eabithumbv8m.main-none-eabihfthumbv8m.base-none-eabi
(对应 Armv8-M 架构轮廓,目标名中包含 thumbv8m。)
E0776 与 E0775 的关系可以理解为:E0776 关注"调用约定是否符合要求"(是否使用 C ABI / 能否用寄存器传参),E0775 关注"目标平台是否支持"(是否具备 TrustZone-M 扩展)。
实战建议与总结
结合当前仓库源码,使用 CMSE 非安全入口函数时应遵循以下要点:
- 使用现代 ABI 语法:优先使用
extern "cmse-nonsecure-entry"而非旧的#[cmse_nonsecure_entry]属性 +extern "C"组合,这是 E0776 被移除后 rustc 推荐的写法; - 保持 unstable 特性启用:需要
#; - 控制参数与返回值体积:全部参数累计大小不得超过 16 字节(4 个 32 位寄存器),返回值只能是 ≤4 字节的类型或透明包装的 64 位标量(
i64/u64/f64),否则会触发参数/返回值栈溢出的编译错误——Vec<u32>这类堆分配类型绝不能用作出入口函数参数; - 选择正确的目标平台:目标必须是
thumbv8m系列(如thumbv8m.main-none-eabi),否则会触发 E0775; - 避免可变参数与
impl Trait:入口函数不允许 c-variadic 和 opaque 返回类型; - 配合导出符号使用:函数应使用
#[no_mangle],使编译器生成的__acle_se_前缀符号能正确导出,供链接器生成安全网关 veneer。
E0776 的消亡是 rustc 错误码体系随语言演进迭代的典型案例:一条错误码所约束的约束条件并未消失,而是随着更精确的 ABI 语法引入而转化为类型系统层面的直接检查。理解这条错误码的历史,也就理解了 CMSE 入口函数"参数必须走寄存器"这一核心安全设计原则。
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 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python270
cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端TypeScript2 K146
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python46066
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go20143
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java34051