如果目标文件定义了 _start 并且不使用任何库,为什么我仍然需要 link 它才能执行它?
If an object file defines _start and doesn't use any libraries, why do I still need to link it before I can execute it?
我有一个 hello world 程序:
.global _start
.text
_start:
# write (1, msj, 13)
mov , %rax # system call 1 is write
mov , %rdi # file handler 1 is stdout
mov $message, %rsi # address of string to output
mov , %rdx # number of bytes
syscall
# exit(0)
mov , %rax # system call 60 is exit
xor %rdi, %rdi # we want to return code 0
syscall
message:
.ascii "Hello, world\n"
我可以assemble将其放入目标文件中:
as hello.s -o hello.o
此目标文件不可执行。当我尝试执行它时,我得到:
bash: ./hello.o: cannot execute binary file: Exec format error
我需要调用链接器才能使其可行:
ld hello.o -o hello
此时,hello
程序运行。但是,这里链接器的使用让我感到困惑……我没有链接任何外部库!我似乎只是将目标文件链接到任何东西。
链接器为这样一个“自包含”程序做了什么?
ELF 文件有不同的类型,例如 ELFTYPE_EXEC(传统的非 PIE 可执行文件)或 ELFTYPE_REL(可重定位 object 文件,通常带有 .o
文件名).
as
没有输出可执行文件而不是目标文件的特例模式。还有其他 assemblers,或至少一个:FASM,它们有一种特殊模式可以直接输出 ELF 可执行文件。
鉴于 as
生成的 ELF 目标文件,您可以:
- link 将它变成一个简单的静态可执行文件,就像您正在做的那样
- link 将其转换为 PIE 可执行文件
- link 将其转换为动态可执行文件,甚至可能 link 一些
.so
共享库;那些可以在 _start
之前具有 运行 的静态构造函数(初始化函数)。 (例如,glibc 的 libc.so
就是这样做的,这就是为什么它恰好可以在 Linux 上从 _start
调用 libc 函数而无需手动调用 glibc init 函数,if 你动态 link.)
.o
需要 linked,因为没有选择绝对地址来加载它,以在 [=19= 中填写诸如 64 位绝对立即数之类的内容].
(如果你使用 lea message(%rip), %rsi
代码将与位置无关,但 .text
和 .rodata
部分之间的距离尚不清楚。虽然你把你的字符串就在 .text
中,如果你没有选择 方式将地址存入寄存器,那么它会在 assemble 时得到解决,这样你就可以站在 -单独的代码块+数据。但最有效的方法 mov $message, %esi
还需要一个绝对(32 位)地址。)
as
不知道你想做什么,GNU Binutils 主要是为编译器后端使用而编写的,所以让 as
变得更复杂是没有意义的直接写一个 ELF 类型的 EXEC
文件,因为那是 ld
的用途。这是 Unix 哲学,即制作独立的小工具,做好一件事。
如果你想 assemble + link 一个命令,制作一个 shell 脚本,或者使用编译器前端:
gcc -nostdlib -static -no-pie start.s -o static_executable
我有一个 hello world 程序:
.global _start
.text
_start:
# write (1, msj, 13)
mov , %rax # system call 1 is write
mov , %rdi # file handler 1 is stdout
mov $message, %rsi # address of string to output
mov , %rdx # number of bytes
syscall
# exit(0)
mov , %rax # system call 60 is exit
xor %rdi, %rdi # we want to return code 0
syscall
message:
.ascii "Hello, world\n"
我可以assemble将其放入目标文件中:
as hello.s -o hello.o
此目标文件不可执行。当我尝试执行它时,我得到:
bash: ./hello.o: cannot execute binary file: Exec format error
我需要调用链接器才能使其可行:
ld hello.o -o hello
此时,hello
程序运行。但是,这里链接器的使用让我感到困惑……我没有链接任何外部库!我似乎只是将目标文件链接到任何东西。
链接器为这样一个“自包含”程序做了什么?
ELF 文件有不同的类型,例如 ELFTYPE_EXEC(传统的非 PIE 可执行文件)或 ELFTYPE_REL(可重定位 object 文件,通常带有 .o
文件名).
as
没有输出可执行文件而不是目标文件的特例模式。还有其他 assemblers,或至少一个:FASM,它们有一种特殊模式可以直接输出 ELF 可执行文件。
鉴于 as
生成的 ELF 目标文件,您可以:
- link 将它变成一个简单的静态可执行文件,就像您正在做的那样
- link 将其转换为 PIE 可执行文件
- link 将其转换为动态可执行文件,甚至可能 link 一些
.so
共享库;那些可以在_start
之前具有 运行 的静态构造函数(初始化函数)。 (例如,glibc 的libc.so
就是这样做的,这就是为什么它恰好可以在 Linux 上从_start
调用 libc 函数而无需手动调用 glibc init 函数,if 你动态 link.)
.o
需要 linked,因为没有选择绝对地址来加载它,以在 [=19= 中填写诸如 64 位绝对立即数之类的内容].
(如果你使用 lea message(%rip), %rsi
代码将与位置无关,但 .text
和 .rodata
部分之间的距离尚不清楚。虽然你把你的字符串就在 .text
中,如果你没有选择 mov $message, %esi
还需要一个绝对(32 位)地址。)
as
不知道你想做什么,GNU Binutils 主要是为编译器后端使用而编写的,所以让 as
变得更复杂是没有意义的直接写一个 ELF 类型的 EXEC
文件,因为那是 ld
的用途。这是 Unix 哲学,即制作独立的小工具,做好一件事。
如果你想 assemble + link 一个命令,制作一个 shell 脚本,或者使用编译器前端:
gcc -nostdlib -static -no-pie start.s -o static_executable