我使用
Apple clang version 14.0.0 (clang-1400.0.29.202)
在运行 macOS 13.2.1 的 M1(即 ARM)Mac 上编译了 curl,并使用 反汇编了生成的可执行文件
objdump -D --macho ./src/curl
我看到的第一行是
/Users/space/code/third-party/curl/src/curl:
(__TEXT,__text) section
_slist_wc_append:
100003924: ff c3 00 d1 sub sp, sp, #48
100003928: fd 7b 02 a9 stp x29, x30, [sp, #32]
10000392c: fd 83 00 91 add x29, sp, #32
100003930: e0 0b 00 f9 str x0, [sp, #16]
100003934: e1 07 00 f9 str x1, [sp, #8]
100003938: e1 07 40 f9 ldr x1, [sp, #8]
10000393c: 00 00 80 d2 mov x0, #0
100003940: 8e 66 02 94 bl _curl_slist_append
[...]
_main
直到字节 1000147b4
才出现(反汇编中的第 17388 行)。 slist_wc_append()
与 curl 的 --libcurl
参数有关,这导致它也生成 C 代码,发出与它发出的相同的网络请求并将其保存到文件中,为什么它是可执行文件中的第一个过程?
我期望可执行文件总是从第一条指令开始执行,并且它将是
_main
的第一条指令。我反汇编了 CPython 和 Bash,他们这样做了。操作系统是否在符号表中查找_main
的位置并在执行之前将堆栈指针设置为该位置?我已经看到可以告诉汇编程序使用不同的名称而不是 _main
和 .global _different_name
。 curl 在调用 _main
之前做了什么特别的事情吗?
在 macOS 上,Mach-O 文件的
eip
字段用于设置指令指针。
https://github.com/corkami/pics/blob/master/binary/macho101/macho101-64.pdf
ELF on Linux 也有类似的
e_entry
字段
https://github.com/corkami/pics/blob/master/binary/elf101/elf101-64.pdf
启动程序要复杂得多。操作系统将文件加载到内存中,做很多其他事情(比如重新定位地址,确保动态加载的库不会有任何地址冲突等)并最终将控制权传递给你的程序。但它不是
main
功能;它通常是别的东西,称为启动代码。它准备环境、初始化变量并执行调用 main
函数所需的所有必要操作,该函数是程序的启动点。
在将控制转移到程序入口点时,操作系统不一定遵循标准调用约定。此外,入口点不是一个函数——它没有被“调用”,也不能“返回”给它的调用者,因为没有函数——所以程序入口点有点特殊,它的主要目的是开始正常的函数调用,即与
main
.
所以,
_start
是入口点的通用名称,它的工作是调用 main
作为函数,并在必要时终止可执行文件(通过一些系统调用),如果函数 main
返回。
为什么 _main 不总是第一个过程?
它可以是第一个过程/功能,但在调用
main
之前可能不是由于其他要求。在任何情况下_start
不是真正的程序,因此不能作为第一个程序/功能。