news 2026/9/10 8:55:10

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入解读 Rust 编译器错误码 E0776:cmse_nonsecure_entry 函数的 ABI 约束与演进

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

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

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 入口函数"参数必须走寄存器"这一核心安全设计原则。

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 8:51:35

AI应用开发必备Python基础:从环境到实战的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 8:50:38

STM32H503单通道ADC电压采集实战:时钟、校准与滤波

简介&#xff1a;面向STM32H系列单片机开发者&#xff0c;提供基于HAL库的单通道ADC电压采集驱动&#xff0c;解决从寄存器配置到数据读取的快速落地问题。资源包含完整可编译的Keil工程&#xff0c;共268个文件&#xff0c;其中141个头文件与121个C源文件构成核心代码&#xf…

作者头像 李华
网站建设 2026/9/10 8:50:01

Context-Mode:基于SQLite+FTS5+BM25的轻量级上下文裁剪机制

1. 项目概述&#xff1a;Context-Mode 不是玄学&#xff0c;而是现代智能体系统里最务实的上下文调度机制 “Context-mode”这个词最近在开发者社区里频繁出现&#xff0c;尤其和 MCP、SQLite、FTS5、BM25 这几个词绑在一起刷屏。很多人第一反应是——这又是个新造的概念&…

作者头像 李华
网站建设 2026/9/10 8:49:48

东华OJ第64题N的倍数C++解法:循环取模与格式避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 8:49:29

Codex不是模型而是协议:Agent时代的任务执行标准

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华