首页
/ 深入解读 Rust 编译器错误码 E0776:cmse_nonsecure_entry 函数的 ABI 约束与演进

深入解读 Rust 编译器错误码 E0776:cmse_nonsecure_entry 函数的 ABI 约束与演进

2026-09-09 20:57:19作者:秋泉律Samson

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>) {}

这段代码包含两个问题:

  1. 缺少 C ABI 声明#[cmse_nonsecure_entry] 要求函数使用 extern "C" 调用约定;
  2. 参数类型不当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::CmseNonSecureEntryCmseNonSecureCall 一样被标记为需要 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-eabi
  • thumbv8m.main-none-eabihf
  • thumbv8m.base-none-eabi

(对应 Armv8-M 架构轮廓,目标名中包含 thumbv8m。)

E0776 与 E0775 的关系可以理解为:E0776 关注"调用约定是否符合要求"(是否使用 C ABI / 能否用寄存器传参),E0775 关注"目标平台是否支持"(是否具备 TrustZone-M 扩展)。

实战建议与总结

结合当前仓库源码,使用 CMSE 非安全入口函数时应遵循以下要点:

  1. 使用现代 ABI 语法:优先使用 extern "cmse-nonsecure-entry" 而非旧的 #[cmse_nonsecure_entry] 属性 + extern "C" 组合,这是 E0776 被移除后 rustc 推荐的写法;
  2. 保持 unstable 特性启用:需要 #![feature(cmse_nonsecure_entry)](rustc 1.48.0 起,tracking issue 75835);
  3. 控制参数与返回值体积:全部参数累计大小不得超过 16 字节(4 个 32 位寄存器),返回值只能是 ≤4 字节的类型或透明包装的 64 位标量(i64/u64/f64),否则会触发参数/返回值栈溢出的编译错误——Vec<u32> 这类堆分配类型绝不能用作出入口函数参数;
  4. 选择正确的目标平台:目标必须是 thumbv8m 系列(如 thumbv8m.main-none-eabi),否则会触发 E0775;
  5. 避免可变参数与 impl Trait:入口函数不允许 c-variadic 和 opaque 返回类型;
  6. 配合导出符号使用:函数应使用 #[no_mangle],使编译器生成的 __acle_se_ 前缀符号能正确导出,供链接器生成安全网关 veneer。

E0776 的消亡是 rustc 错误码体系随语言演进迭代的典型案例:一条错误码所约束的约束条件并未消失,而是随着更精确的 ABI 语法引入而转化为类型系统层面的直接检查。理解这条错误码的历史,也就理解了 CMSE 入口函数"参数必须走寄存器"这一核心安全设计原则。

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

项目优选

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