Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Rust-ABI 的前世今生

Rust-ABI 的前世今生

作者头像
张汉东
发布于 2022-12-08 12:58:53
发布于 2022-12-08 12:58:53
1.7K00
代码可运行
举报
文章被收录于专栏:Rust 编程Rust 编程
运行总次数:0
代码可运行

本文为付费合集 「Rust 生态蜜蜂」8月8号里的选篇,Rust ABI 应该是每个 Rustaceans 都应该知道的。

背景:ABI 概念与 C-ABI

ABI,是 Application Binary Interface 的缩写,应用程序二进制接口。

“维基百科:在计算机软件中,应用二进制接口(ABI)是两个二进制程序模块之间的接口;通常,这些模块之一是库或操作系统工具,而另一个是用户正在运行的程序。 最早的 ABI 是 C-ABI,在阅读这篇文章的大多数人甚至还未出生之前,这份契约就已经缔结完毕了。而看到我这篇文章的大家,也许正在经历另一个高级 ABI 的创建过程: WASI( WebAssembly System Interface)[1]

C-ABI 包含两个关键的内核:

  • 数据的内存布局方式
  • 函数如何调用

而 Cpp ABI 和 Rust ABI 则包含更多内容。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <limits.h>

extern long long do_stuff(long long value);

int main () {
 long long x = do_stuff(-LLONG_MAX);
 /* wow cool stuff with x ! */
 return 0;
}

对于上面 C 代码,在 x86_64 目标的生成汇编大概如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
main:
	movabs  rdi, -9223372036854775807
	sub     rsp, 8
	call    do_stuff
	xor     eax, eax
	add     rsp, 8
	ret

编译器已经在自己和编写函数定义的人之间建立了契约do_stuff。编译器希望,在 x86_64(64 位)计算机上long long只使用 1 个寄存器,并且它必须是 rdi。编译器知道定义了 do_stuff的人将使用完全相同的约定。这不是源码级别的契约,而是编译器代表开发者和其他编译器“签订”的合约。这就是 ABI。通过此 ABI,应用程序之间可以达到相互调用的目标。

C-ABI 虽然是事实标准,这么多年来行业内都通过 C-ABI 来作为多语言交互的标准 ABI。但实际 C 语言是类型不安全的,如果有未定义行为,C-ABI 会被轻松打破。因为链接器并不会关心代码里的类型,它只看符号。而未定义行为并不会破坏符号,比如 do_stuff 函数。

ABI 的核心问题是,它将最终二进制文件中的符号名称与给定的语义集紧密联系在一起。当针对给定接口编译代码时,这些语义,比如调用约定、寄存器使用、栈空间,等等一些其他行为,都提供了一组单一且最终牢不可破的假设。如果要更改符号的语义,则必须更改符号的名称。

“P.S 目前 Swift 5 已经稳定了 ABI,这句话实际上具体来说是指, Apple 平台上 Swift 的 Stabilized ABI 的现实。因为 ABI 稳定是和系统平台和工具链的属性,而非编程语言的属性。Swift 通过引入一种叫做弹性类型(Resilience Type)[2]的东西,可以实现数据结构变化时保证 ABI 兼容,具体来说,对于动态链接库,只有在运行时才能向 dylib 得知类型的具体大小、对齐、偏移量等ABI信息,而非编译时。

Rust ABI

Rust 目前的 ABI 并未稳定,即,Rust 不保证内存中数据结构的调用约定和内存布局不被改变。

稳定的 ABI 可以支持 Rust crates 之间的动态链接,从而允许 Rust 程序支持动态加载的插件(C/C++ 中常见的功能),也允许 Rust 库可由其他语言(比如 Swift)加载。动态链接将导致项目的编译时间更短,磁盘空间使用更少,因为多个项目可以链接到同一个 dylib。但不稳定的 ABI 在性能优化方面也有它的好处。Google Fuschia OS 没有将 Rust 用于微内核的原因之一就是Rust 没有稳定 ABI。

这里有几个示例来说明什么是不稳定的 ABI:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 虽然下面的结构体本质是相同的,但是 Rust 编译器不保证给予它们字段相同的内存偏移量
struct A(u32, u64);
struct B(u32, u64);

// Rust 编译器不保证字段的顺序和定义的一样
struct Rect {
    x: f32,
    y: f32,
    w: f32,
    h: f32,
}

Rust 编译器会对上面的结构体进行优化,如果内存布局是确定的,就不利于优化了。比如没有办法对结构体字段进行重排以便达到最小化内存占用的优化目标。内存布局不确定性也有利于模糊测试(Fuzzer),因为模糊测试需要将字段随机排序以便更容易地暴露潜在的问题。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#[repr(C)]
struct MyStruct {
    x: u32,
    y: Vec<u8>,
    z: u32,
}

对于该示例来说,虽然使用了#[repr(C)] 让结构体字段的顺序确定了,但是字段的偏移量依然无法确定,因为 Vec<8>没有任何确定性的排序,从而z的偏移量是无法确定的。所以这种类型不适合使用 C 的 FFi。而且,Rust 的 C-ABI 也不是标准 C-ABI,存在一些差异。而且 Rust 的 C-ABI 也不支持 trait 对象,之前有 Pre-RFC 提议让 `#[repr(C)]`支持 trait[3],但是也不了了之了。不过目前有第三方库支持在 C-ABI 之间传递 trait 对象:thin_trait_object[4]abi_stable[5]

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
pub struct Foo<T> {
    a: T
}

// 单态化 Foo<T> 之后
pub struct Foo_u64{
    a: u64
}

对于泛型代码Foo<T>来说,在编译期会静态分发,即单态化为具体的类型实例Foo_u64,将其编译为动态库(比如 .so)并不会包含Foo<T>的泛型定义,如果对于使用Foo_u32的库来说,就无法动态链接了。注意,生命周期参数不受影响。需要把库和应用代码共同编译才可以完整链接需要的函数,相对比较麻烦。这一点 Swift 5 稳定 ABI 有不错的应对方法,即,Swift 可以将一个泛型函数编译为一个可以动态处理的替换实现。

更多关于 Rust ABI 和类型布局相关信息可以参考这个博客文章:Notes on Type Layouts and ABIs in Rust[6]

2020年5月,在 Rust 内部论坛(IRLO)讨论[7]过关于稳定 Rust 的模块化 ABI 的提案。模块化 ABI 就是想把 ABI 实现作为 crate 发布,然后对 crate 进行版本化。目前第三方库 abi_stable 的实现和此想法非常接近。但是想稳定化 ABI 这个过程是非常困难的,目前官方还没有对此提上日程。

Rust 插件系统

正是因为目前 Rust ABI 不稳定,所以在用 Rust 编写插件系统时有很多不便。博客系列 A Plugin System in Rust[8] 对如何用 Rust 实现插件系统做了非常好的总结。这里简单概括一下,感兴趣的可以直接深入学习此系列博客内容。

一个良好的 Rust 插件系统要考虑的几个点:

  • 必须:能够在启动时和运行时加载/卸载插件
  • 必须:支持跨平台
  • 必须:低开销
  • 必须:能用 Rust 开发插件
  • 可选:安全性
  • 可选:向后兼容性
  • 可选:从现有实现移植的工作量不多

实现插件系统的几种方式:

  • 使用脚本语言编写插件在运行时扩展其功能,比如 Python 和 Lua 等。
  • 进程间通信。
    • 基于套接字。(在Rust 不稳定 ABI 的情况下,需要 Rust 插 Rust 来说是一个比较稳妥的方案)
    • 基于管道。(在Rust 不稳定 ABI 的情况下,需要 Rust 插 Rust 来说是一个比较稳妥的方案)
    • 基于内存共享。(会过多使用 unsafe,不推荐)
  • 动态加载。这是最常见的方式。但是 Rust ABI 不稳定,目前需要使用 thin_trait_object[9]abi_stable[10]cglue[11] 这样的第三方库支持。
  • 基于 WebAssembly 。不考虑性能的情况下,这也是一种方案。

在这篇博客内容中,作者对这几种方式的优劣都做了对比。他最终得出一个不可能三角:安全性(Safety)、可用性(Unsability )和 性能(Performance) ,即,要选择合适你的插件系统必须在这三个特性之间做权衡,你不可能同时满足这三个特性,只能选择满足其中两个而放弃另外一个。

Rust ABI 不稳定带来的问题比想象严重:

作者在尝试动态加载实现插件,发现 Rust ABI 的不稳定带来的问题比他想象的更加严重。在这之前,他一直认为即使Rust的ABI不稳定,只要库和主二进制文件是用相同的编译器以及std等等版本编译的,就可以安全地动态加载一个库。然而事实证明,ABI 不仅仅是可能在不同的编译器版本之间发生“断裂”,在编译器执行过程中也会发生“断裂”,即,Rust 编译器并不保证同一个类型的布局在每次执行的时候都一致,类型布局可以随着每次编译而改变。所以他的方案是使用 #[repr(C)] 的 C-ABI,以及使用 abi_stable 来获得稳定的 std 库。

他也尝试了 WebAssembly 插件的方式,但是因为性能和 Wasm 没有提供在主机和插件之间有效传递数据的解决方案的原因而放弃。

作者后面尝试了使用 abi_stable 来开发插件系统。abi_stable 插件是按模块来构建的,并且提供了很多 FFI 安全(FFI安全,指FFI 边界提供了稳定的内存布局)的类型,包括 trait 对象的支持,以及提供了处理 FFI 边界恐慌(Panic)的方法。

如果想了解 abi_stable、 cglue、async_ffi 应用以及相关性能测试的更多细节,可以进一步阅读该系列博客。这里就不做过多摘录。

小结

虽然当前 Rust ABI 不稳定带来诸多不便,但是社区目前还是有一些靠谱的解决办法。Rust ABI 到底什么时候稳定,这是个问题,毕竟 C 语言花了几十年才得到一个事实性的稳定标准,而 Swift 在5.0 就拥有了稳定的 ABI 是因为苹果巨大的软件生态必须要求它这么做。而 Rust 目前则没有太大的稳定 ABI 的压力。

延伸阅读

  • To Save C, We Must Save ABI[12]
  • Binary Banshees and Digital Demons[13]
  • Rust ABI Wiki[14]
  • abi_stable[15] and cglue: Rust ABI 安全代码生成器[16]
  • Rust Reference: ABI[17]
  • Series: Plugins in Rust [18]
  • How Swift Achieved Dynamic Linking Where Rust Couldn't[19]
  • Rust 稳定模块化 ABI 提案[20]
  • The Lost Art of Structure Packing[21]
  • Notes on Type Layouts and ABIs in Rust[22]
  • RFC 讨论:定义 Rust ABI[23]
  • So you want to live-reload Rust[24]
  • [async_ffi](

参考资料

[1]

WASI( WebAssembly System Interface): https://wasi.dev/

[2]

弹性类型(Resilience Type): https://github.com/apple/swift/blob/main/docs/LibraryEvolution.rst

[3]

repr(C)]让结构体字段的顺序确定了,但是字段的偏移量依然无法确定,因为Vec<8>没有任何确定性的排序,从而z的偏移量是无法确定的。所以这种类型不适合使用 C 的 FFi。而且,Rust 的 C-ABI 也不是标准 C-ABI,存在一些差异。而且 Rust 的 C-ABI 也不支持 trait 对象,之前有 [Pre-RFC 提议让 #[repr(C)]`支持 trait: https://internals.rust-lang.org/t/pre-rfc-repr-c-for-traits/12598/13

[4]

thin_trait_object: https://github.com/kotauskas/thin_trait_object

[5]

abi_stable: https://docs.rs/abi_stable/latest/abi_stable/

[6]

Notes on Type Layouts and ABIs in Rust: https://gankra.github.io/blah/rust-layouts-and-abis/

[7]

讨论: https://internals.rust-lang.org/t/a-stable-modular-abi-for-rust/12347

[8]

A Plugin System in Rust: https://nullderef.com/series/rust-plugins/

[9]

thin_trait_object: https://github.com/kotauskas/thin_trait_object

[10]

abi_stable: https://docs.rs/abi_stable/latest/abi_stable/

[11]

cglue: https://github.com/h33p/cglue

[12]

To Save C, We Must Save ABI: https://thephd.dev/to-save-c-we-must-save-abi-fixing-c-function-abi

[13]

Binary Banshees and Digital Demons: https://thephd.dev/binary-banshees-digital-demons-abi-c-c++-help-me-god-please

[14]

Rust ABI Wiki: https://slightknack.github.io/rust-abi-wiki/intro/intro.html

[15]

abi_stable: https://docs.rs/abi_stable/latest/abi_stable/

[16]

cglue: Rust ABI 安全代码生成器: https://github.com/h33p/cglue

[17]

Rust Reference: ABI: https://doc.rust-lang.org/reference/abi.html

[18]

Series: Plugins in Rust : https://nullderef.com/blog/plugin-start/

[19]

How Swift Achieved Dynamic Linking Where Rust Couldn't: https://gankra.github.io/blah/swift-abi/

[20]

Rust 稳定模块化 ABI 提案: https://internals.rust-lang.org/t/a-stable-modular-abi-for-rust/12347

[21]

The Lost Art of Structure Packing: http://www.catb.org/esr/structure-packing/

[22]

Notes on Type Layouts and ABIs in Rust: https://gankra.github.io/blah/rust-layouts-and-abis/

[23]

RFC 讨论:定义 Rust ABI: https://github.com/rust-lang/rfcs/issues/600

[24]

So you want to live-reload Rust: https://fasterthanli.me/articles/so-you-want-to-live-reload-rust

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 觉学社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Rust 1.41.0 发布更新要点
Rust 1.41.0 于美国时间 2020年1月30日 发布。来看看主要有哪些改进。
MikeLoveRust
2020/02/20
5880
【Rust笔记】浅聊 Rust 程序内存布局
内存布局看似是底层和距离应用程序开发比较遥远的概念集合,但其对前端应用的功能实现颇具现实意义。从WASM业务模块至Nodejs N-API插件,无处不涉及到FFI跨语言互操作。甚至,做个文本数据的字符集转换也得FFI调用操作系统链接库libiconv,因为这意味着更小的.exe/.node发布文件。而C ABI与内存布局正是跨(计算机)语言数据结构的基础。
MikeLoveRust
2023/10/18
6680
【Rust笔记】浅聊 Rust 程序内存布局
【Rust 研学】Rust Nation UK 2024 | Rust ABI 稳定之路
本系列为我学习 Rust Nation UK 2024 大会的笔记,不会是所有演讲,只拣一些我感兴趣的内容。本文原视频回放在 The path to a stable ABI for Rust[1] ,演讲者为 Rust 官方团队成员也是当前库团队共同 Leader Amanieu D'Antras,也是 parking_lot 库的作者。
张汉东
2024/04/22
5460
【Rust 研学】Rust Nation UK 2024 | Rust ABI 稳定之路
【Rust日报】2019-11-11 - 关于Rust中的类型布局和ABI的注意事项
3.4.1 Problem and Motivation for Calling Conventions
MikeLoveRust
2019/11/13
6400
【Rust日报】2019-11-11 - 关于Rust中的类型布局和ABI的注意事项
Rust FFI 编程 - Rust 语言层面对 FFI 的支持
Rust 语言对 FFI 有比较完善的支持。本节主要讲在基础设施层面,Rust 语言对 FFI 的支持。
MikeLoveRust
2020/04/20
3.3K0
【连载】两百行Rust代码解析绿色线程原理(二)一个能跑通的例子
首先,让我们在名为 green_threads 的文件夹中启动一个新项目。命令行执行:
MikeLoveRust
2020/02/12
8430
Rust FFI 编程 - Rust导出共享库03
我们已经了解了,Rust语言是多泛式(混合泛式)的语言,它可以做命令式(过程式)编程,也可以做面向对象编程,也可以做函数式编程。把Rust简单地归类为某种泛式的编程语言,都不太合适。Rust就是Rust。
MikeLoveRust
2020/08/04
8590
透过 Rust 探索系统的本原:泛型
在 Fundamentals of Generic Programming[1] 里,Alexander Stepanov(泛型概念的创立者)用一段优雅的文字描绘了计算机技术不断泛化(generalized)的历史:
tyrchen
2021/05/11
1.2K0
透过 Rust 探索系统的本原:泛型
【Rust 基础篇】Rust FFI:连接Rust与其他编程语言的桥梁
Rust是一种以安全性和高效性著称的系统级编程语言,具有出色的性能和内存安全特性。然而,在现实世界中,我们很少有项目是完全用一种编程语言编写的。通常,我们需要在项目中使用多种编程语言,特别是在与现有代码库或底层系统交互时。为了实现跨语言的互操作性,Rust提供了"FFI(Foreign Function Interface)",允许Rust代码与其他编程语言进行交互。本篇博客将深入探讨Rust FFI,包括FFI的定义、使用场景、使用方法以及注意事项,以便读者了解如何在Rust中使用FFI与其他编程语言进行无缝集成。
繁依Fanyi
2023/10/12
1.2K0
听GPT 讲Rust源代码--compiler(15)
在Rust源代码中,rustc_arena/src/lib.rs文件定义了TypedArena,ArenaChunk,DroplessArena和Arena结构体,以及一些与内存分配和容器操作相关的函数。
fliter
2024/03/18
1870
听GPT 讲Rust源代码--compiler(15)
Rust FFI 编程 - 手动绑定 C 库入门 02
本篇是《手动绑定 C 库入门》的第二篇。了解第一篇后,我们知道在调用 C 库时,需要重新在 Rust 中对该 C 库中的数据类型和函数签名进行封装。这篇我们将实践涉及到诸如数组,结构体等类型时,如何进行手动绑定。
MikeLoveRust
2020/05/24
1.3K0
听GPT 讲Rust源代码--src/tools(21)
在Rust的源代码中,rust/src/tools/miri/src/shims/x86/mod.rs文件的作用是为对x86平台的处理提供支持。它包含一些用于模拟硬件操作的shim函数和相关的类型定义。
fliter
2024/01/09
1810
“C不再是一种编程语言”
本文标题里的观点很“刺激”,它来自国外一位 Swift 和 Rust 专家 Aria Beingessner,他近日撰写了一篇文章《C 不再是一种编程语言》,在技术社区引起了热议。
开发者技术前线
2022/04/08
6900
“C不再是一种编程语言”
【Rust 日报】2021-11-11 保持冷静,学习Rust,我们很快就会在Linux中更多的看到这种语言
事实证明,这两个字符串并没有太大区别。 str只是一个由[u8]字节片支持的字符串。同样地,String只是一个由Vec<u8>支持的字符串。
MikeLoveRust
2021/11/15
6100
【Rust 日报】2021-11-11 保持冷静,学习Rust,我们很快就会在Linux中更多的看到这种语言
【Rust日报】2024-05-06 WebAssembly 基础
在讨论如何在Rust中加载共享库时,作者展示了如何使用libloading库中的Library和Symbol数据结构来实现。也讨论了在跨FFI边界时确保类型安全以及Rust的ABI不稳定性的问题,并介绍了使用abi_stable库来解决它。
MikeLoveRust
2024/05/10
1740
【Rust日报】2024-05-06 WebAssembly 基础
Rust项目中的Labels
按照issue数量从多到少排序: https://github.com/rust-lang/rust/labels?page=2&sort=count-desc,仅列出前几页
fliter
2024/02/26
1680
Rust项目中的Labels
Rust 欧洲之声|Rust 和 Cpp 互操作
Slint[1] 曾经的名字叫 SixtyFPS ,是 QtQml 引擎核心开发者和维护者出来创业的项目。Slint 可以有效地为任何显示器开发流畅的图形用户界面:嵌入式设备和桌面应用程序。我们支持多种编程语言,例如 Rust、C++ 和 JavaScript。Slint 也许是 Qt 的替代品。
张汉东
2022/12/08
3.7K0
【Rust日报】2020-07-17 无船同志新博客:Shipping Const Generics in 2020
1) 修复了浮点数往小整数转换的时候会导致Undefined behavior的问题(这是在未使用unsafe的时候导致的UB,官方团队称这种为unsound bug)
MikeLoveRust
2020/07/21
3270
Rust漫画 #3 | 二次元 Rust Meetup 讨论会:Rewrite it in Rust 是否有害?
你好啊,作为一名程序员,参加线下的 Meetup 技术交流会也许是你唯一的社交活动。无论是线上还是线下,请都不要错过。今天,也许是你参加的第一次二次元 Rust Meetup 。
张汉东
2023/10/25
7910
Rust漫画 #3 |  二次元 Rust Meetup 讨论会:Rewrite it in Rust 是否有害?
【2023 Week-1】Rust视界周刊 | 用 Rust 和 C++ 代码编译时间同样糟糕吗?
interoperable_abi[1] 意味着提供一个新的 Rust ABI extern "interop" 和 repr(interop) ,用于实现有安全数据 类型的高级编程语言之间的互操作性。
张汉东
2023/03/06
1.1K0
【2023 Week-1】Rust视界周刊   | 用 Rust 和 C++ 代码编译时间同样糟糕吗?
推荐阅读
相关推荐
Rust 1.41.0 发布更新要点
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验