news 2026/9/10 12:45:03

深入解析 Rust 编译器错误 E0787:Naked 函数必须由单个 `naked_asm!` 内联汇编块构成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入解析 Rust 编译器错误 E0787:Naked 函数必须由单个 `naked_asm!` 内联汇编块构成

深入解析 Rust 编译器错误 E0787:Naked 函数必须由单个naked_asm!内联汇编块构成

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

本篇文章基于 rustc 编译器源码仓库(compiler/rustc_error_codes)中的错误说明文档,系统讲解编译错误E0787(不支持的裸函数定义,An unsupported naked function definition)的触发条件、修复方法,以及其背后的类型检查与代码生成实现原理。读完本文,你将掌握#[unsafe(naked)]函数的全部约束规则(单个naked_asm!块、不允许asm!、只允许const/sym操作数、允许att_syntaxraw选项),并能够从源码层面理解编译器是如何检查并诊断这些错误的。

什么是 Naked 函数(裸函数)

Naked 函数是 Rust 中一类特殊的函数:函数体不产生编译器插入的任何序言(prologue)与尾声(epilogue)代码,函数入口处完全由开发者通过内联汇编自行控制。这类函数常用于实现操作系统内核的启动入口、中断处理程序、上下文切换等对指令序列有绝对控制需求的底层场景。

在当前的 Rust 版本中,声明裸函数使用#[unsafe(naked)]属性(早期版本为#[naked]),并且函数体必须包含且仅能包含一个naked_asm!内联汇编块。编译器对裸函数施加了极其严格的限制,因为它无法为函数生成任何辅助代码,函数必须靠汇编块自给自足。

E0787 的触发场景与错误示例

当裸函数的定义不符合约束时,编译器会抛出 E0787。官方错误文档(E0787.md)给出了最典型的错误示例:

#[unsafe(naked)] pub extern "C" fn f() -> u32 { 42 }

这段代码中,函数体只是一条普通的返回值表达式42,没有任何naked_asm!汇编块,因此触发 E0787。

错误的完整规则清单

根据错误文档,裸函数定义必须满足以下全部条件,违反任意一条都会报 E0787:

  1. 函数体必须由且仅由一个naked_asm!汇编块构成。不能有多个naked_asm!块,也不能混入其他普通 Rust 语句或表达式。
  2. 执行流程不能越过汇编块末尾。即汇编代码必须以ret等返回指令结束,或通过跳转等方式发散(diverge),绝不允许"落空"(fall through)到函数末尾。
  3. naked_asm!块中只允许使用att_syntaxraw这两个选项,其余所有asm!选项(如volatilealignstackintel等)都不被允许。
  4. 操作数限制naked_asm!块中不允许出现除constsym以外的任何操作数。inoutinoutlabel等操作数在裸函数上下文中都是非法的。

从源码看 E0787 是如何被检查出来的

E0787 的诊断逻辑位于类型检查阶段的专用检查模块 compiler/rustc_hir_typeck/src/naked_functions.rs。该模块通过typeck_naked_fn对每个#[unsafe(naked)]函数执行三步校验:

pub(crate) fn typeck_naked_fn<'tcx>( tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &'tcx hir::Body<'tcx>, ) { debug_assert!(find_attr!(tcx, def_id, Naked(..))); check_no_patterns(tcx, body.params); check_no_parameters_use(tcx, body); check_asm(tcx, def_id, body); }
  • check_no_patterns:函数参数只能使用平凡的绑定模式(通配符_或简单标识符),不允许解构模式;
  • check_no_parameters_use:函数体内不允许使用任何函数参数(因为裸函数没有编译器生成的参数加载代码,参数实际并不存在);
  • check_asm:函数体只能包含单个内联汇编块。

其中check_asm是 E0787 的直接来源。它遍历函数体的 HIR,将每个语句/表达式归类为四种ItemKind

enum ItemKind { NakedAsm, // 合法的 naked_asm! 块 InlineAsm, // 非法的普通 asm! 块 NonAsm, // 非汇编语句 Err, // 解析错误节点 }

归类的具体逻辑非常严格(见 naked_functions.rs 中CheckInlineAssembly::check_expr):任何普通表达式——字面量、二元运算、函数调用、if/match、循环、闭包、路径引用等——都会被标记为NonAsmDropTemps和块表达式(Block)会被递归遍历其内部语句;而asm!宏展开产生的InlineAsm节点与naked_asm!产生的NakedAsm节点会被分别记录。global_asm!出现在函数体内则直接触发span_bug!(它本就不允许出现在该位置)。

当检查结果不是"恰好一个NakedAsm"时,编译器就会发射 E0787。诊断结构体定义在 compiler/rustc_hir_typeck/src/diagnostics.rs 中:

impl<G: EmissionGuarantee> Diagnostic<'_, G> for NakedFunctionsAsmBlock { fn into_diag(self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> { let mut diag = Diag::new( dcx, level, msg!("naked functions must contain a single `naked_asm!` invocation"), ); diag.span(self.span); diag.code(E0787); for span in self.multiple_asms.iter() { diag.span_label( *span, msg!("multiple `naked_asm!` invocations are not allowed in naked functions"), ); } for span in self.non_asms.iter() { diag.span_label(*span, msg!("not allowed in naked functions")); } diag } }

可以看到,E0787 的主错误消息是 "naked functions must contain a singlenaked_asm!invocation",并且会根据具体情况给出两类补充标注:

  • 多个naked_asm!块时,在每个多余块处标注 "multiplenaked_asm!invocations are not allowed in naked functions";
  • 存在非汇编语句时,在每个违规语句处标注 "not allowed in naked functions"。

此外,若函数体内出现的是普通asm!而非naked_asm!,编译器会单独发射NakedFunctionsMustNakedAsm诊断(同样使用 E0787 错误码),并给出 "consider using thenaked_asm!macro instead" 的修复建议:

#[derive(Diagnostic)] #[diag("the `asm!` macro is not allowed in naked functions", code = E0787)] pub(crate) struct NakedFunctionsMustNakedAsm { #[primary_span] #[label("consider using the `naked_asm!` macro instead")] pub span: Span, }

值得注意的一个细节是:check_asm中有一个"降噪"逻辑——如果裸函数里只有一个汇编块且同时存在错误节点(例如开发者临时添加了compile_error!("...")),则不再叠加发射 E0787,避免错误信息刷屏。

正确写法:合法的裸函数定义

把错误示例修正为合法形式如下:

#![feature(unsafe_attributes)] use std::arch::naked_asm; #[unsafe(naked)] pub extern "C" fn f() -> u32 { // 汇编块必须以 ret 结束,不得越过末尾继续执行 naked_asm!("mov eax, 42", "ret") }

几点注意事项:

  • 返回指令必须显式书写:编译器不会帮你生成ret,汇编块要么以返回指令结束,要么以跳转/死循环等方式发散,绝不能"执行到汇编块结束就没了"。这一点也体现在代码生成阶段:rustc 期望裸函数的 MIR 起始块(START_BLOCK)以InlineAsm终止符结束,否则会触发内部断言(见下文)。
  • 允许的选项naked_asm!内可以使用att_syntax(AT&T 语法风格)与raw(不对模板字符串做格式检查)两个选项,例如:
#[unsafe(naked)] pub extern "C" fn f() -> u32 { naked_asm!( "movl $42, %eax", "ret", options(att_syntax), ) }
  • 操作数仅限constsym:你可以在汇编块中引用常量与符号,但不能使用in/out/inout/label等寄存器或内存操作数。这与代码生成阶段只处理ConstSymFnSymStatic三种操作数的实现是严格对应的。

为什么操作数会被限制为constsym

答案藏在代码生成层。裸函数的汇编并不是作为普通函数体生成的,而是被转换成类似global_asm!的全局汇编来发射。见 compiler/rustc_codegen_ssa/src/mir/naked_asm.rs 中的codegen_naked_asm

let operands: Vec<_> = operands.iter().map(|op| inline_to_global_operand::<Cx>(cx, instance, op)).collect();

其中inline_to_global_operand的匹配分支很能说明问题:它只实现了InlineAsmOperand::Const(常量)、InlineAsmOperand::SymFn(函数符号)、InlineAsmOperand::SymStatic(静态变量符号)三种情况的转换,而对InOutInOutLabel操作数一律调用bug!("invalid operand type for naked_asm!")。也就是说,寄存器输入输出类操作数在裸函数场景下从代码生成层面就是不被支持的,类型检查阶段的限制与之一一对应。

同时,这段代码也展示了裸函数代码生成的另外两个关键行为:

  1. 符号与节(section)管理:编译器会为裸函数手工发射.globl/.weak.type.balign等汇编指令,并依据目标平台二进制格式(ELF/Mach-O/COFF/Wasm/XCOFF)选择不同的汇编模板。例如 ELF 下默认 4 字节对齐、COFF 下默认 16 字节对齐,ARM 目标还会根据instruction_set属性发射.arm.thumb_func指令。
  2. 链接属性约束Linkage::Internal(内部链接)、CommonAvailableExternallyExternalWeak等链接类型在裸函数中会导致编译失败(emit_fatal),因为内联汇编无法可靠表达这些链接语义——例如 LLVM 在外部弱符号上会触发 SIGILL。

相关的其他裸函数错误

E0787 之外,裸函数还有一系列配套的错误码,理解它们有助于完整掌握裸函数的限制体系:

  • E0736(见 E0736.md):#[naked]函数不允许同时使用某些属性,包括#[inline]#[track_caller]#[test]/#[ignore]/#[should_panic]等。原因在于这些属性要求编译器插入额外代码(内联决策、调用者位置跟踪、测试脚手架),而裸函数刻意禁止编译器生成任何额外代码。其错误示例为:
#[inline] #[unsafe(naked)] fn foo() {}
  • 还有一组未使用 E0787 错误码、但由同一个naked_functions.rs模块发出的诊断,包括:参数解构模式不被允许(NoPatterns)、函数体内使用参数不被允许(ParamsNotAllowed)等。

在仓库中进一步验证

如果你想在真实编译器中验证这些行为,可以关注以下几处仓库内容:

  • 错误文档本体:E0787.md、E0736.md
  • 类型检查实现:compiler/rustc_hir_typeck/src/naked_functions.rs
  • 诊断结构定义:compiler/rustc_hir_typeck/src/diagnostics.rs
  • 代码生成实现:compiler/rustc_codegen_ssa/src/mir/naked_asm.rs
  • naked_asm!宏的展开入口位于 compiler/rustc_builtin_macros/src/asm.rs(expand_naked_asm函数),其中的选项合法性检查(is_supported_option)与模板解析逻辑印证了"仅允许att_syntax/raw"的约束;
  • 代码生成层的回归测试位于 tests/codegen-llvm/naked-fn/,例如 naked-functions.rs 验证了不同目标平台下裸函数生成的汇编是否符合预期。

总结

E0787 是 Rust 编译器用来守护裸函数(#[unsafe(naked)])定义合法性的核心错误码,其背后是一整套"严格限制"的设计哲学:裸函数没有编译器生成的序言/尾声,函数体必须是一个自包含、以返回或跳转终结的naked_asm!块;操作数仅限constsym;选项仅限att_syntaxraw。这些限制在类型检查阶段由 naked_functions.rs 强制实施,在代码生成阶段由 naked_asm.rs 兜底验证,形成了一条完整的"检查—诊断—生成"链路。理解 E0787,就等于理解了 Rust 中所有底层汇编编程的边界。

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

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

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

云客服系统适配哪些行业服务场景?2026年全行业适配深度解析

摘要据IDC《2025年中国云客服市场跟踪报告》显示&#xff0c;2025年中国云客服市场规模达108.3亿元&#xff0c;同比增长23.6%&#xff0c;行业渗透率分化持续加剧&#xff1a;电商零售渗透率超70%&#xff0c;金融保险约32%&#xff0c;制造业首次突破20%。不同行业的客服诉求…

作者头像 李华
网站建设 2026/9/10 12:37:40

智慧水利安全工程平台的架构、功能与建设要求

中国水利工程体系建设经过数十年发展&#xff0c;已建成江河堤防28.69万公里、各类水库8.6万多座&#xff0c;防洪减灾能力显著提升。与此同时&#xff0c;水资源日益紧张、水环境日趋恶化的形势&#xff0c;对水利治理的精细化水平提出了更高要求。随着物联网、大数据、人工智…

作者头像 李华
网站建设 2026/9/10 12:35:15

Python多进程编程实战:启动方式与性能优化指南

1. Python多进程启动方式深度解析 最近在优化一个数据处理项目时&#xff0c;我发现当数据量达到百万级别后&#xff0c;单进程处理效率明显不足。于是我开始系统研究Python中的多进程启动方式&#xff0c;经过两周的实测对比&#xff0c;总结出这份全面的技术指南。 Python的…

作者头像 李华
网站建设 2026/9/10 12:34:59

品牌备案不是维权通行证:美国商标注册与代理推荐

品牌备案不是维权通行证&#xff1a;美国商标注册与代理推荐在Amazon经营中&#xff0c;不少卖家完成品牌备案&#xff08;Brand Registry&#xff09;后就认为拿到了维权通行证——链接被跟卖、Listing被抄袭时&#xff0c;直接在后台提交投诉即可。但平台规则并非如此运行。品…

作者头像 李华