如果目标文件定义了 _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