如何解决如果位置计数器在链接描述文件中被初始化为太小或太大,则静态可执行段错误
我正在尝试为这个程序生成一个静态可执行文件(使用 musl):
main.S:
.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
或 0x20000
,生成的可执行文件会在启动期间因段错误而崩溃。在调试时,我发现 musl 初始化代码尝试读取程序头(在 aux 向量中接收的位置),并且由于某种原因,由 aux 向量给出的程序头的内存地址在我们的地址空间中未映射。
这种行为的原因是什么?链接描述文件中的计数器偏移到底是什么?除了改变加载地址之外,它如何影响链接器输出?
解决方法
这里有几个问题。
-
您的
main.S
有一个堆栈对齐错误:在x86_64
上,您必须在调用任何其他函数之前将堆栈重新对齐到 16 字节边界(您可以假设入口为 8 字节对齐。
如果没有这个,我会在printf
内崩溃,因为movaps %xmm0,0x40(%rsp)
与$rsp
未对齐。 -
您的链接顺序错误:
之前链接crt1.o
应该在 beforemain.o
-
当您在开始
SIZEOF_HEADERS == 0xe8
部分之前没有留下.text
空间时,您将把它留给链接器将程序头放在其他地方,它确实如此。问题是:musl(和许多其他代码)假设文件头和程序头被映射(但 ELF 格式不需要这个)。所以他们崩溃了。
指定起始地址的正确方法:
ENTRY(_start)
SECTIONS
{
. = 0x10000 + SIZEOF_HEADERS;
}
更新:
为什么顺序很重要?
链接器(通常)将从左到右组装初始值设定项(构造函数)。当您从 main()
调用标准 C 库例程时,您希望标准库在调用 main()
之前 自行初始化。 crt1.o
中的代码负责执行此类初始化。
如果您以错误的顺序链接:crt1.o
after main.o
,构建可能无法正确进行。您是否能够观察到这一点取决于标准库的实现细节,以及您正在使用它的哪些部分。所以你的二进制文件可能看起来可以正常工作。但是还是以正确的顺序链接对象更好。
我要留 0x10000 空间,标题不够用吗?
您正在干扰内置的默认链接器脚本,而是给它不完整的关于如何在内存中布置程序的规范。当你这样做时,你需要知道链接器将如何反应。不同的链接器会有不同的反应。
binutils ld
的反应是不发出覆盖程序头的 LOAD
段。 ld.lld
的反应不同——它实际上将 .text
移过程序头。
结果二进制文件仍然崩溃,因为二进制布局不是内核期望的,内核提供的辅助向量中的 AT_PHDR
地址是错误的。
看起来内核希望第一个 LOAD
段是包含程序头的段。可以说这是内核中的一个错误——ELF 规范中没有任何要求。但是所有普通二进制文件在第一个 LOAD
段都有程序头,所以你只需要这样做(或者说服内核开发人员添加代码来处理你奇怪的二进制布局) .
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。