`execve()` 如何调用特殊的启动例程和 main() 函数?

Tim*_*Tim 2 linux system-calls exec

当 C 程序被 kernel\xe2\x80\x94by 执行时execve()

\n\n
    \n
  • 在哪里调用在调用 main 函数之前调用的execve()特殊启动例程?crt0

  • \n
  • execve()main函数在哪里调用?

  • \n
\n\n

我在https://elixir.bootlin.com/linux/latest/source/fs/exec.c中找不到它们。

\n\n

从理解Linux内核开始,execve()内部寻找一个可以加载可执行文件的linux_binfmt对象并调用其方法来加载,同时还加载动态链接器来加载和链接可执行文件所使用的共享库。但书中没有说明如何从可执行文件中调用启动例程和程序的 then 。load_binary()load_binary()execve()crt0main()

\n\n

谢谢。

\n

小智 5

execve内核代码都不会调用该函数_start(可执行文件的入口点,无论它被称为什么)。

那是因为它们在不同的上下文中运行;就好像它们在不同的机器上运行一样。

发生的情况是,内核execve在返回用户模式时安排系统调用,将IP(指令指针)寄存器设置为指向函数的开头_start,并将SP(堆栈指针)寄存器设置为指向函数的开头。 argv + env 字符串列表,因此从用户模式的角度来看,效果就像有人调用该_start函数一样:

_start(argc, argv0, argv1, ... , NULL, env0, env1, ... NULL)
Run Code Online (Sandbox Code Playgroud)

在所有参数都在堆栈上传递的调用约定中。

当然,在此之前,内核已经负责将这些 argv + env 复制到正确的位置,映射包含该_start函数的段等。


请注意,argv + env 字符串全部打包在一个块中,例如。

"prog\0arg1\0arg2\0VAR1=foo\0VAR2=bar\0"
Run Code Online (Sandbox Code Playgroud)

该块开始和结束的虚拟地址可以通过/proc/PID/stat文件访问;引用procfs(5)手册页:

(48) arg_start  %lu  (since Linux 3.5)  [PT]
        Address  above  which  program  command-line arguments
        (argv) are placed.

(49) arg_end  %lu  (since Linux 3.5)  [PT]
        Address below program  command-line  arguments  (argv)
        are placed.
Run Code Online (Sandbox Code Playgroud)

写入该地址将修改ps输出中出现的任何内容:

$ sleep 3600 3600 3600 3600 3600 3600 3600 &
[2] 4927
$ awk '{print $48,$49,$49-$48-1}' /proc/4927/stat
140735402952841 140735402952882 40
$ printf 'Somebody set up us the bomb Main screen turn on\0' | dd bs=1 count=40 of
=/proc/4927/mem seek=140735402952841 conv=notrunc
40+0 records in
40+0 records out
40 bytes copied, 0.000229779 s, 174 kB/s
$ ps 4927
  PID TTY      STAT   TIME COMMAND
 4927 pts/4    S      0:00 Somebody set up us the bomb Main screen
Run Code Online (Sandbox Code Playgroud)