了解Linux内核说依次execve()
调用do_execve( )
which
将文件路径名、命令行参数和环境字符串复制到一个或多个新分配的页框中。(最终,它们被分配给用户模式地址空间。)
我是否正确,在execve()
成功终止后,该进程会调用_start
的例程rt0.o
?
根据APUE:
当内核(通过 exec 函数之一)执行 C 程序时,会在调用 main 函数之前调用一个特殊的启动例程。可执行程序文件指定该例程作为程序的起始地址;这是由 C 编译器调用链接编辑器时设置的。该启动例程从内核获取值(命令行参数和环境)并进行设置这样主函数就会被调用,如前面所示。
该__start
例程是否还会再次复制命令行参数和环境?
do_execve()
复制_start
命令行参数和环境之间有什么区别?复制两次不是很浪费吗?
谢谢。
答案1
我是否正确,在 execve() 成功终止后,进程会调用 crt0.o 的 _start 例程?
不必要。当execve
系统调用返回时,进程将从二进制文件入口点的任何文本/代码地址继续执行(在 ELF 中,这是e_entry
标头中的字段)。例子:
echo 'void run(void){ printf("in run\n"); exit(0); }' |
gcc -Wl,-e,run -nostartfiles -include stdio.h -include stdlib.h -Wall -x c - -o /tmp/run
/tmp/run
in run
_start
只是许多(大多数?)Unix 系统上入口点例程的常用名称。
该
_start
例程是否还会再次复制命令行参数和环境?
它可以做到这一点,但通常它不会做这样的事情。它唯一应该做的就是重新排列它们,以便它们可以传递给 C 函数,例如main
.
问题是你不能简单地将入口点声明为 C 函数
_start(argc, ...)
并用 获取参数va_args
,因为例如。 on x86_64
,C 调用约定期望(前几个)参数在寄存器中传递,这就是不是它们是如何传递到_start
.
_start
在打电话之前通常还有其他事情要做main
;非常重要的事情是运行静态构造函数,这是用 编写的程序所必需的C++
,但如果它在 ELF 二进制文件中定义了正确的节属性,则任何程序都可以使用静态构造函数(使用,您可以通过定义函数gcc
在程序中执行此操作)C
和__attribute__((constructor))
)。
libc.so
基于 glibc 的系统中的标准启动代码还将通过(动态链接) --中定义的函数__libc_start_main()
,这非常好,因为您可以从预加载的动态库覆盖它并添加您自己的初始化内容,而无需修改二进制文件。看这里举个例子。