< 返回我的博客

爱国的张浩予 发表于 2025-06-15 15:37

Tags:unwind,panic,abi,ffi,cargo.toml

崩溃发生时的栈展开(Stack Unwinding

Rust代码编译过程中,前端编译器rustc会向编译后的MIR中间码,注入微量的“运行时上下文”程序,以零开发成本地完成对运行时程序的

  • 变量借入规则检查(Borrow Check)
  • 程序崩溃“收尾”

等“公共服务类”功能。文章标题中的Stack Unwinding就属于后一项工作范畴(它有时也被简写为Unwind)。即,在程序崩溃发生时,‘程序运行时’会

  1. 沿着‘函数调用栈’主动地依次释放全部变量的内存占用,
  2. 执行由元属性#[panic_handler]装饰的 (崩溃)善后处理函数 — 以释放外设资源(比如,打印机,数据库连接、监控摄像头等)
  3. 向操作系统发送应用程序崩溃信号 — 从系统层面收集应用程序崩溃日志等

但对功能单一微型IoT设备来讲,软件自身的“栈展开”处理就有些“鸡肋”了。因为‘硬件简陋’与‘功能简单’到任何来自软件的‘自救善后’处理都显得多余,还不如程序包少占点儿硬盘与少费点儿内存来得实惠。于是,一旦程序崩溃,它就直接死给操作系统看。操作系统的轮询扫描服务会

  • 发现既被标注又未活跃使用的内存段,并回收之。
  • 强制断开外设连接
  • 根据注册的脚本程序,自动重启应用程序

Cargo Package工程的包管理器配置文件中,@Rustacean 可强制要求编译输出的二进制程序分发包包含Stack Unwinding程序模块和执行Unwind“收尾”处理,以收获更轻巧的可执行(或链接库)文件。

# 在 Cargo.toml 配置文件中
[profile.release]
panic = 'abort'
# 于是,由“cargo build --release”命令输出的二进制文件就禁用【栈展开】能力了

另外,若来不急精读冗长的Cargo Package工程配置文件,那么直接执行命令rustc --print cfg | grep panic也能立即获悉Cargo Package的栈展开模式。例如,

rustc --print cfg | grep panic
panic="unwind"

但故事可完全没有止步于此,当应用程序涉及FFI时,Stack Unwinding有”大坑“。假设主工程Cargo Package依赖某个C ABI的外部链接库,那么

  • 不论对该链接库的链接方式是‘动态’的、还是‘静态’的
  • 也不论该链接库是用什么编程语言制作的(比如,Go, C, C++, 甚至Rust自身)

Cargo Package都必须与此C ABI链接库依赖采用一致的《栈展开》设置。这类跨ABI的《栈展开》也被记作Unwind ABI

于是,若主工程Rust端对panic!采用了abort处理策略(见前文的Cargo.toml配置)和禁止栈展开,但链接库的C++端却对throw执行栈展开处理,那么当

  • 被部署的二进制程序真发生程序崩溃,且
  • 函数调用栈包含了来自C ABI链接库的导出函数

时,真实的程序崩溃原因就会被“ABI不匹配”的次生崩溃所淹没和覆盖掉。然后,“找不到程序崩溃直接原因”的苦恼就会成就 @Rustacean 心态崩溃的瞬间。

综前所述,在给Cargo Package引入跨ABI的外部链接库依赖时,还请 @Rustacean 大哥们一定仔细阅读链接库的README文档,和同时对”来路不明“链接库多拒绝。

评论区

写评论
ajiao401 2025-06-16 09:38

虽然简短,但是技术细节清晰,而且从只言片语中,比如:直接死给操作系统看,还能看到咱们程序员有趣生动的灵魂!

1 共 1 条评论, 1 页