linker - 如果位置计数器在链接描述文件中初始化为太小或太大,则静态可执行段错误
问题描述
我正在尝试为该程序生成一个静态可执行文件(使用 musl):
电源:
.section .text
.global main
main:
mov $msg, %rdi
mov $0, %rax
call printf
mov %rax, %rdi
mov $60, %rax
syscall
msg:
.ascii "hello world from printf\n\0"
编译命令:
clang -g -c main.S -o main.o
链接命令(musl libc 放在musl
目录下(1.2.1 版)):
ld main.o musl/crt1.o -o sm -Tstatic.ld -static -lc -lm -Lmusl
链接描述文件 ( static.ld
):
ENTRY(_start)
SECTIONS
{
. = 0x100e8;
}
此配置会生成一个工作可执行文件,但如果我将位置计数器偏移量更改为0x10000
or 0x20000
,则生成的可执行文件在启动期间会因段错误而崩溃。在调试时,我发现 musl 初始化代码试图读取程序头(在 aux 向量中接收到的位置),并且由于某种原因,由 aux 向量给出的程序头的内存地址未映射到我们的地址空间中。
这种行为的原因是什么?链接描述文件中的计数器偏移到底是什么?除了更改加载地址之外,它如何影响链接器输出?
注意:当 musl 初始化代码尝试访问程序头时会发生段错误
解决方案
这里有几个问题。
您
main.S
有一个堆栈对齐错误:在调用任何其他函数之前x86_64
,您必须将堆栈重新对齐到 16 字节边界(您可以假设入口对齐 8 字节)。
没有这个,我会printf
因为movaps %xmm0,0x40(%rsp)
with misaligned导致内部崩溃$rsp
。您的链接顺序错误:
crt1.o
应该先链接main.o
SIZEOF_HEADERS == 0xe8
如果您在开始部分之前不留空间.text
,则将其留给链接器以将程序头放在其他地方,并且确实如此。问题是:musl(和许多其他代码)假设文件头和程序头被映射(但 ELF 格式不需要这个)。所以他们崩溃了。
指定起始地址的正确方法:
ENTRY(_start)
SECTIONS
{
. = 0x10000 + SIZEOF_HEADERS;
}
更新:
为什么顺序很重要?
链接器(通常)将从左到右组装初始化器(构造器)。当您从 调用标准 C 库例程时main()
,您希望标准库在被调用之前已经初始化。 main()
代码中crt1.o
负责执行这样的初始化。
如果您以错误的顺序链接:crt1.o
after main.o
,则构造可能无法正确进行。您是否能够观察到这一点取决于标准库的实现细节,以及您正在使用它的哪些部分。所以你的二进制文件可能看起来工作正常。但最好以正确的顺序链接对象。
我要留下 0x10000 空间,对于标题来说还不够吗?
您正在干扰内置的默认链接器脚本,而是为其提供了关于如何在内存中布局程序的不完整规范。当你这样做时,你需要知道链接器将如何反应。不同的接头会有不同的反应。
binutils 的ld
反应是不发出LOAD
覆盖程序头的段。ld.lld
反应不同——它实际上移动了.text
程序头。
但是,生成的二进制文件仍然崩溃,因为二进制布局不是内核所期望的,并且AT_PHDR
辅助向量中内核提供的地址是错误的。
看起来内核希望第一个LOAD
段是包含程序头的段。可以说这是内核中的一个错误——ELF 规范中没有任何内容需要这个。但是所有普通的二进制文件在第一段都有程序头LOAD
,所以你只需要做同样的事情(或者说服内核开发人员添加代码来处理你奇怪的二进制布局)。
推荐阅读
- apache-spark - 使用 spark-submi 无法从 hive 中找到表
- php - MySQL 根据日记帐、收据和付款表查询所有帐户的期初余额、借方、贷方和期末余额
- node.js - 在 Axios 中发布包含 cookie 时出现网络错误
- python - 如何从子类中获取抽象类名
- html - 如何在 html 和 css 样式中拉伸背景图像?
- sql - 如何连接oracle11g数据库?
- postgresql - Postgresql 没有连接
- react-native - React Native:双击退出应用程序
- html - 很难解释,模仿这个链接栏悬停效果
- adal.js - ADAL、Angular 6 和保护 MVC API