为什么 execve 和 brk(NULL) 总是前两个系统调用?

为什么 execve 和 brk(NULL) 总是前两个系统调用?

当我尝试时

strace ping google.com

或者

strace ls 

或者

even strace curl <domain>

前两个系统调用始终是,

execve("/usr/bin/curl", ["curl", "google.com"], 0x7ffecf1bc378 /* 61 vars */) = 0
brk(NULL)                               = 0x55f553c49000

有人可以告诉我当我执行任何操作时 execve 是否总是第一个系统调用吗?

我阅读了本手册页,https://linux.die.net/man/2/execve 但不明白到底execve是系统调用还是可执行程序?

答案1

在Linux中,一个新进程是通过以下方式创建的fork(),这使得子进程几乎与父进程相同。创建一个新进程,其程序是不同的与原始进程的程序相比,新的子进程立即调用execve(),这基本上是“用另一个程序替换我当前的程序”的过程。

brk(NULL)是询问它在哪里的过程堆内存结束。许多程序将此称为它们的第一个系统调用(将在 后立即显示execve()),因为它们malloc()立即使用(或它们malloc()在内部使用的库调用)。如果程序及其库调用malloc()暂时不需要调用,那么除此之外的其他内容brk(NULL)将是第二个系统调用。

答案2

我认为所有这些系统调用只是动态链接器在执行之前设置的东西。我做了一个小测试程序来检查这一点。

me@bar:~/foo$ cat main.c
#include <unistd.h>
void main(){
        syscall(60,0);
}

现在我们先静态编译,然后动态编译。该-Wl,-emain参数告诉 gcc 传递-emain给链接器,这意味着设置main为入口点。果然程序立即退出。


me@bar:~/foo$ gcc -static main.c -Wl,-emain -o main
me@bar:~/foo$ strace ./main 2>&1 | head
execve("./main", ["./main"], 0x7fff6d7c4b40 /* 50 vars */) = 0
exit(0)                                 = ?
+++ exited with 0 +++

现在没有-static标志,我们得到你通常看到的东西。

me@bar:~/foo$ gcc main.c -Wl,-emain -o main
me@bar:~/foo$ strace ./main 2>&1 | head
execve("./main", ["./main"], 0x7fff06b48d20 /* 50 vars */) = 0
brk(NULL)                               = 0x558c61a90000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd68e8e950) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=77239, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 77239, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe55ed7e000
...

即使代码是直接跳转到main!

现在我认为这可能是 libc 特有的,也许它需要自行设置或类似的东西。但实际上不是,我尝试制作一个小动态库来链接,然后在 strace 上显示以下内容:

$ ldd main
    linux-vdso.so.1 (0x00007fff9c8cd000)
    libfoo.so => /home/me/path/to/libfoo.so (0x00007f80ed20a000)


$ strace ./main
execve("./main", ["./main"], 0x7ffcdf5b5a70 /* 51 vars */) = 0
brk(NULL)
...
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/home/me/path/to/libfoo.so", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832
newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=13240, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f028541c000
mmap(NULL, 16384, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0285418000
mmap(0x7f0285419000, 4096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0x7f0285419000
mmap(0x7f028541a000, 4096, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f028541a000
mmap(0x7f028541b000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f028541b000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f028541d040) = 0
set_tid_address(0x7f028541d310)         = 6310
set_robust_list(0x7f028541d320, 24)     = 0
rseq(0x7f028541d960, 0x20, 0, 0x53053053) = 0
mprotect(0x7f028541b000, 4096, PROT_READ) = 0
mprotect(0x402000, 4096, PROT_READ)     = 0
exit(0)                                 = ?
+++ exited with 0 +++

我不确定大多数调用在做什么,但很明显它们只是将我的小函数libfoo(实际上是一个仅执行退出系统调用的函数)映射到内存中。因此,这些跟踪显然来自动态链接器,将动态库映射到内存(在文件描述符 3 上打开它之后),然后将执行移交给程序本身(在本例中只是立即退出)。
现在,我猜想brk调用是因为动态链接器需要计算出程序在运行时将具有的内存布局,因此它需要对该值进行一些数学计算。 Linux 是自由软件,这可能在动态链接器的源代码中有所解释。希望这可以帮助。

相关内容