用Rust编写操作系统时,我们可能遇到一些链接器错误。这篇文章中,我们不将更换编译目标,而传送特定的链接器参数,尝试修复错误。我们将在常用系统Linux、Windows和macOS下,举例编写裸机应用时,可能出现的一些链接器错误;我们将逐个处理它们,还将讨论这种方式开发的必要性。

要注意的是,可执行程序在不同操作系统下格式各异;所以在不同平台下,参数和错误信息可能略有不同。

原文:https://os.phil-opp.com/freestanding-rust-binary/#linker-arguments 原作者:@phil-opp
译文链接:洛佳:使用Rust编写操作系统(附录一):链接器参数
译者:洛佳 华中科技大学
本翻译已被许可。转载请注明出处,商业转载请联系原作者

Linux

我们在Linux下尝试编写裸机程序,可能出现这样的链接器错误:

error: linking with `cc` failed: exit code: 1|= note: "cc" […]= note: /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':(.text+0x12): undefined reference to `__libc_csu_fini'/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':(.text+0x19): undefined reference to `__libc_csu_init'/usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':(.text+0x25): undefined reference to `__libc_start_main'collect2: error: ld returned 1 exit status

这里遇到的问题是,链接器将默认引用C语言运行时的启动流程,或者也被描述为_start函数。它将使用我们在no_std下被排除的C语言标准库实现库libc,因此链接器不能解析相关的引用,得到“undefined reference”问题。为解决这个问题,我们需要告诉链接器,它不应该引用C语言使用的启动流程——我们可以添加-nostartfiles标签来做到这一点。

要通过cargo添加参数到链接器,我们使用cargo rustc命令。这个命令的作用和cargo build相同,但允许开发者向下层的Rust编译器rustc传递参数。另外,rustc提供一个-C link-arg标签,它能够传递所需的参数到链接器。综上所述,我们可以编写下面的命令:

cargo rustc -- -C link-arg=-nostartfiles

这样之后,我们的包应该能成功编译,作为Linux系统下的独立式可执行程序了。这里我们没有显式指定入口点函数的名称,因为链接器将默认使用函数名_start。
Windows

在Windows系统下,可能有不一样的链接器错误:

error: linking with `link.exe` failed: exit code: 1561|= note: "C:\\Program Files (x86)\\…\\link.exe" […]= note: LINK : fatal error LNK1561: entry point must be defined

链接器错误提示“必须定义入口点”,意味着它没有找到入口点。在Windows系统下,默认的入口点函数名由使用的子系统决定[1]。对CONSOLE子系统,链接器将寻找名为mainCRTStartup的函数;而对WINDOWS子系统,它将寻找WinMainCRTStartup。我们的_start函数并非这两个名称——为了使用它,我们可以向链接器传递/ENTRY参数:

cargo rustc -- -C link-arg=/ENTRY:_start

我们也能从这里的参数的格式中看到,Windows系统下的链接器在使用方法上,与Linux下的有较大不同。

运行命令,我们得到了另一个链接器错误:

error: linking with `link.exe` failed: exit code: 1221|= note: "C:\\Program Files (x86)\\…\\link.exe" […]= note: LINK : fatal error LNK1221: a subsystem can't be inferred and must bedefined

产生这个错误,是由于Windows可执行程序可以使用不同的子系统(subsystem)。对一般的Windows程序,使用的子系统将由入口点的函数名推断而来:如果入口点是main函数,将使用CONSOLE子系统;如果是WinMain函数,则使用WINDOWS子系统。由于我们的_start函数名称与上两者不同,我们需要显式指定使用的子系统:

cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"

这里我们使用CONSOLE子系统,但WINDOWS子系统也是可行的。这里,我们使用复数参数link-args代替多个-C link-arg,因为后者要求把所有参数依次列出,比较占用空间。

使用这行命令后,我们的可执行程序应该能在Windows下运行了。
macOS

如果使用macOS系统开发,我们可能遇到这样的链接器错误:

error: linking with `cc` failed: exit code: 1|= note: "cc" […]= note: ld: entry point (_main) undefined. for architecture x86_64clang: error: linker command failed with exit code 1 […]

这个错误消息告诉我们,链接器不能找到默认的入口点函数,它被命名为main——出于一些原因,macOS的所有函数名都被加以下划线_前缀。要设置入口点函数到_start,我们传送链接器参数-e:

cargo rustc -- -C link-args="-e __start"

-e参数指定了入口点的名称。由于每个macOS下的函数都有下划线_前缀,我们应该命名入口点函数为__start,而不是_start。

运行这行命令,现在出现了这样的链接器错误:

error: linking with `cc` failed: exit code: 1|= note: "cc" […]= note: ld: dynamic main executables must link with libSystem.dylibfor architecture x86_64clang: error: linker command failed with exit code 1 […]

得到这个错误的原因是,macOS并不官方支持静态链接的二进制库[2],而要求程序默认链接到libSystem库。要链接到静态二进制库,我们把-static标签传送到链接器:

cargo rustc -- -C link-args="-e __start -static"

运行修改后的命令。链接器似乎并不满意,又给我们抛出新的错误:

error: linking with `cc` failed: exit code: 1|= note: "cc" […]= note: ld: library not found for -lcrt0.oclang: error: linker command failed with exit code 1 […]

出现这个错误的原因是,macOS上的程序默认链接到crt0(C runtime zero)库。这和Linux系统上遇到的问题相似,我们可以添加一个-nostartfiles链接器参数:

cargo rustc -- -C link-args="-e __start -static -nostartfiles"

现在,我们的程序应该能够在macOS上成功编译了。
统一所有的编译命令

我们的裸机程序已经可以在多个平台上编译,但对每个平台,我们不得不记忆和使用不同的编译命令。为了避免这么做,我们创建.cargo/config文件,为每个平台填写对应的命令:

# in .cargo/config

[target.‘cfg(target_os = “linux”)’]
rustflags = ["-C", “link-arg=-nostartfiles”]

[target.‘cfg(target_os = “windows”)’]
rustflags = ["-C", “link-args=/ENTRY:_start /SUBSYSTEM:console”]

[target.‘cfg(target_os = “macos”)’]
rustflags = ["-C", “link-args=-e __start -static -nostartfiles”]


这里,rustflags参数包含的内容,将被自动添加到每次rustc调用中。我们可以在官方文档[3]中找到更多关于.cargo/config文件的说明。

做完这一步后,我们使用简单的一行指令——

cargo build

——就能在三个不同的平台上编译裸机程序了。
我们应该这么做吗?

虽然通过上文的方式,的确可以面向多个系统编译独立式可执行程序,但这可能不是一个好的途径。这么描述的原因是,我们的可执行程序仍然需要其它准备,比如在_start函数调用前一个加载完毕的栈。不使用C语言运行环境的前提下,这些准备可能并没有全部完成——这可能导致程序运行失败,比如说会抛出臭名昭著的段错误。

如果我们要为给定的操作系统创建最小的二进制程序,可以试着使用libc库并设定#[start]标记。有一篇官方文档[4]给出了较好的建议。
参考

^https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol^https://developer.apple.com/library/content/qa/qa1118/_index.html^Configuration: Cargo Reference https://doc.rust-lang.org/cargo/reference/config.html^No stdlib: The Rust Book https://doc.rust-lang.org/1.16.0/book/no-stdlib.html

编辑于 2019-06-17

Rust: (作者 洛佳) 使用Rust编写操作系统(附录一):链接器参数相关推荐

  1. 系统无法执行指定的程序。_使用Rust编写操作系统(一):独立式可执行程序

    创建一个不连接标准库的Rust可执行文件,将是我们迈出的第一步.无需底层操作系统的支撑,这将能让在裸机(bare metal)上运行Rust代码成为现实. 简介 要编写一个操作系统内核,我们需要不基于 ...

  2. 【Rust日报】2020-11-03 《Rust日报》总第1000期

    今日头版 <Rust日报>第1000期,感谢有你 两年半的时间,我们一期期走来,到了今天发行的第1000期.回想我第一次看<Rust日报>,还是在Rust 2018刚推出的时候 ...

  3. Google 高薪争夺 Rust 人才,将用 Rust 重构关键组件!

    作者 | Carol 出品 | CSDN(ID:CSDNnews) 头图 | CSDN 下载自东方 IC 近日,Google 宣布其目前正在致力于用 Rust 来缓解开源软件中的内存安全问题.Goog ...

  4. 【Rust日报】 2019-05-31:rust.cc社区提供了国内crates镜像

    Enum的值如何作为类型 這位寫C++的老兄想寫以前的Enum fn pet(_: Animal::Whale) {}fn pet(_: Animal::Dog) {}// or somehow de ...

  5. 【Rust日报】 2019-05-15:Rust 4周岁生日快乐!

    Rust四周年啦 不知不觉,Rust1.0发布已经四周年了. Rust 1.0是2015年5月15号发布.所以,四年了,大家学会Rust了吗? Rust四周年 Rust完全态 所以我说,Rust还有两 ...

  6. 【Rust 日报】2021-08-29 Embedded Rust 第一步:选择一块板子

    Embedded Rust 第一步:选择一块板子 内容整理自 robyoung (Rob Young) 的文章:First steps with Embedded Rust: Selecting a ...

  7. 与 Rust 社区亲密接触!Rust 大会火热报名!

    首先抢票:www.huodongxing.com/event/64794- 大会官网:rustcon.asia 2019 年 4 月 20 日在北京望京凯悦,在秘猿科技与 PingCAP 携手下,我们 ...

  8. 使用Rust开发编译系统(C以及Rust编译的过程)

    C以及Rust编译的过程 主流的编译器 GCC LLVM C语言编译过程 LLVM编译过程 将C源码转为LLVM IR 将IR转化为BitCode 将BitCode转为目标平台汇编码 执行BitCod ...

  9. 【Rust投稿】捋捋 Rust 中的 impl Trait 和 dyn Trait

    本文来自 PrivateRookie 的知乎投稿:https://zhuanlan.zhihu.com/p/109990547 缘起 一切都要从年末换工作碰上特殊时期, 在家闲着无聊又读了几首诗, 突 ...

  10. 【Rust 日报】2022-04-28 Spacedrive 一个开源的跨平台文件资源管理器

    jaq是JSON数据处理工具jq的一个克隆版本. jaq专注于三个目标: 正确性:jaq的目标是为jq提供一个更正确和可预测的实现,同时在大多数情况下保持与jq的兼容性. 性能.我创建jaq的初衷是为 ...

最新文章

  1. [转]内核和用户空间数据交换
  2. jieba分词错误_如何掌握分词技术,你需要学会这些
  3. stylus在vue中的使用
  4. [转]JAVA AES 加密算法
  5. html动图放大太模糊怎么调,如何解决图片放大画质失真问题
  6. 计算两日期之间差多少天----日期格式为:yyyy-mm-dd
  7. Android编译默认英语,Android 编译系统 (一)
  8. 蓝桥杯 基础练习 2n皇后问题(从n皇后问题入手)
  9. 边开发就能边测试?一招教你在linux服务器配置Jenkins持续集成神器
  10. 用MeGUI压制720x480 MP4视频,详细教程[面向有一定基础者]
  11. uniapp得到用户当前定位以及用户选择位置
  12. 网线水晶头接法图解 网线水晶头接法详细步骤
  13. 中国人民大学信息学院夏令营经验贴
  14. linux 7.5安装教程,如何在CentOS Linux 7.5上安装 Pip
  15. ECMAScript 6基础总结
  16. Java——第二次上机(第二部分)
  17. 旋转编码器旋钮程序_让我们使用SwiftUI构建具有旋转手势的复古音频旋钮
  18. 免费网络硬盘、FTP、大容量邮箱、电子相册合集
  19. 腾讯自研云原生数据库CynosDB发布 兼容MySQL和PostgreSQL
  20. 2020最新Java面试合集,跳槽必看

热门文章

  1. c语言:简单排序:冒泡排序法、选择排序法、插入排序法(待写)
  2. Windows安装office出现1046错误
  3. [C++基础]031_如何正确获取用户的输入
  4. 浅析h3c交换机端口模式access,trunk与hybrid之联系与区别
  5. 面试中遇见的一些考试题目
  6. 计算几何基本知识整理
  7. IDEA阅读spring源码并调试
  8. python3之urllib代理池
  9. Shiro学习(23)多项目集中权限管理
  10. localhost不能访问127.0.0.1可以访问的原因及解决方法 被打磨的不像人样