execve(2) - Linux 手册页
名称
execve - 执行程序
概要
#include <unistd.h>
int execve(const char *filename, char *const argv[],
char *const envp[]);
描述
execve() 执行由 filename 指向的程序。filename 必须是一个二进制可执行文件,或者是一个以如下形式开头的脚本:
-
#! interpreter [optional-arg]
- 关于后一种情况的详细信息,请参阅下文的“解释器脚本”。
argv 是传递给新程序的参数字符串数组。按照惯例,这些字符串中的第一个应包含与被执行文件关联的文件名。envp 是字符串数组,通常形式为 key=value,作为环境变量传递给新程序。argv 和 envp 都必须以 NULL 指针结束。当调用程序的 main 函数被定义为如下形式时,可以通过它访问参数向量和环境变量:
-
int main(int argc, char *argv[], char *envp[])
- execve() 在成功时不会返回,调用进程的文本段、数据段、bss 段和堆栈会被加载的程序覆盖。
如果当前程序正处于被 ptrace 的状态,则在 execve() 成功后会向其发送一个 SIGTRAP 信号。
如果 filename 指向的程序文件设置了 set-user-ID 位,且底层文件系统没有以 nosuid 方式挂载(即 mount(2) 的 MS_NOSUID 标志),并且调用进程没有被 ptrace,那么调用进程的有效用户 ID 将被更改为程序文件的所有者。同样,当设置了程序文件的 set-group-ID 位时,调用进程的有效组 ID 将被设置为程序文件的组。
进程的有效用户 ID 被复制到保存的 set-user-ID 中;同样,有效组 ID 被复制到保存的 set-group-ID 中。此复制操作发生在使用 set-user-ID 和 set-group-ID 权限位导致的任何有效 ID 更改之后。
如果可执行文件是一个包含共享库存根的 a.out 动态链接二进制可执行文件,则会在执行开始时调用 Linux 动态链接器 ld.so(8),以将所需的共享库加载到内存中并将可执行文件与它们链接。
如果可执行文件是一个动态链接的 ELF 可执行文件,则使用 PT_INTERP 段中命名的解释器来加载所需的共享库。对于与 glibc 2 链接的二进制文件,此解释器通常是 /lib/ld-linux.so.2。(对于与旧版 Linux libc5 链接的二进制文件,解释器通常是 /lib/ld-linux.so.1。)
在 execve() 期间,除以下各项外,所有进程属性均会保留:
- *
任何被捕获的信号处理方式都会重置为默认值(signal(7))。
*
任何备用信号堆栈都不会被保留(sigaltstack(2))。
*
内存映射不会被保留(mmap(2))。
*
附加的 System V 共享内存段会被分离(shmat(2))。
*
POSIX 共享内存区域会被取消映射(shm_open(3))。
- *
打开的 POSIX 消息队列描述符会被关闭(mq_overview(7))。
*
任何打开的 POSIX 命名信号量都会被关闭(sem_overview(7))。
*
POSIX 定时器不会被保留(timer_create(2))。
*
任何打开的目录流都会被关闭(opendir(3))。
*
内存锁不会被保留(mlock(2), mlockall(2))。
*
退出处理程序不会被保留(atexit(3), on_exit(3))。
*
浮点环境重置为默认值(请参阅 fenv(3))。
- 上述列表中的进程属性均在 POSIX.1-2001 中指定。以下 Linux 特有的进程属性在 execve() 期间也不会被保留:
- *
prctl(2) 的 PR_SET_DUMPABLE 标志会被设置,除非正在执行 set-user-ID 或 set-group-ID 程序,在这种情况下该标志会被清除。
*
prctl(2) 的 PR_SET_KEEPCAPS 标志会被清除。
*
(自 Linux 2.4.36 / 2.6.23 起)如果正在执行 set-user-ID 或 set-group-ID 程序,则由 prctl(2) 的 PR_SET_PDEATHSIG 标志设置的父进程死亡信号会被清除。
*
由 prctl(2) 的 PR_SET_NAME 设置的进程名称(并由 ps -o comm 显示)会重置为新可执行文件的名称。
*
SECBIT_KEEP_CAPS securebits 标志会被清除。请参阅 capabilities(7)。
*
终止信号重置为 SIGCHLD(请参阅 clone(2))。
- 请注意以下进一步几点:
- *
调用线程之外的所有线程在 execve() 期间都会被销毁。互斥锁、条件变量和其他 pthreads 对象不会被保留。
*
程序启动时会执行相当于 setlocale(LC_ALL, "C") 的操作。
*
POSIX.1-2001 规定,任何被忽略或设置为默认值的信号处理方式保持不变。POSIX.1-2001 规定了一个例外:如果 SIGCHLD 被忽略,则实现可以保留处理方式不变或将其重置为默认值;Linux 采用前者。
*
任何挂起的异步 I/O 操作都会被取消(aio_read(3), aio_write(3))。
*
有关 execve() 期间 capabilities 的处理,请参阅 capabilities(7)。
*
默认情况下,文件描述符在 execve() 之后保持打开状态。标记为 close-on-exec 的文件描述符会被关闭;请参阅 fcntl(2) 中关于 FD_CLOEXEC 的描述。(如果文件描述符被关闭,这将导致该进程在底层文件上获得的所有记录锁被释放。详见 fcntl(2)。)POSIX.1-2001 指出,如果文件描述符 0、1 和 2 在 execve() 成功后本应关闭,且进程由于执行文件设置了 set-user-ID 或 set-group-ID 权限位而获得特权,则系统可能会为这些文件描述符中的每一个打开一个未指定的文件。作为一项基本原则,任何可移植程序,无论是否有特权,都不能假设这三个文件描述符在 execve() 之后仍保持关闭状态。
解释器脚本
- 解释器脚本是一个已启用执行权限的文本文件,其第一行形式为:
-
#! interpreter [optional-arg]
- interpreter 必须是一个可执行文件的有效路径名,该文件本身不能是脚本。如果 execve() 的 filename 参数指定了一个解释器脚本,则 interpreter 将被调用并带有以下参数:
-
interpreter [optional-arg] filename arg...
- 其中 arg... 是 execve() 的 argv 参数所指向的一系列单词。
为了实现可移植性,optional-arg 应要么缺席,要么指定为单个单词(即不应包含空格);请参阅下方的“注意”。
参数和环境变量的大小限制
- 大多数 UNIX 实现对传递给新程序的命令行参数 (argv) 和环境变量 (envp) 字符串的总大小施加了限制。POSIX.1 允许实现使用 ARG_MAX 常量(定义在 <limits.h> 中,或在运行时通过调用 sysconf(_SC_ARG_MAX) 获取)来声明此限制。
在内核 2.6.23 之前的 Linux 上,用于存储环境变量和参数字符串的内存限制为 32 页(由内核常量 MAX_ARG_PAGES 定义)。在页面大小为 4 kB 的架构上,这产生的最大大小为 128 kB。
在内核 2.6.23 及更高版本上,大多数架构支持一种大小限制,该限制源自 execve() 调用时生效的软 RLIMIT_STACK 资源限制(请参阅 getrlimit(2))。(无内存管理单元的架构除外:它们维持内核 2.6.23 之前生效的限制。)此更改允许程序拥有大得多的参数和/或环境变量列表。对于这些架构,总大小限制为允许堆栈大小的 1/4。(施加 1/4 的限制可确保新程序始终拥有一定的堆栈空间。)自 Linux 2.6.25 起,内核为此大小限制设置了 32 页的下限,因此即使在 RLIMIT_STACK 设置得非常低时,应用程序也能保证拥有至少与 Linux 2.6.23 及更早版本相当的参数和环境空间。(此保证在 Linux 2.6.23 和 2.6.24 中未提供。)此外,每个字符串的限制为 32 页(内核常量 MAX_ARG_STRLEN),字符串的最大数量为 0x7FFFFFFF。
返回值
成功时,execve() 不会返回;出错时,返回 -1,并设置 errno 以指示错误。
错误
- E2BIG
环境变量 (envp) 和参数列表 (argv) 中的总字节数过大。
EACCES
对 filename 或脚本解释器名称的路径前缀的某个组件没有搜索权限。(另请参阅 path_resolution(7)。)
EACCES
文件或脚本解释器不是普通文件。
EACCES
对文件、脚本或 ELF 解释器没有执行权限。
EACCES
文件系统以 noexec 方式挂载。
EFAULT
filename 指向可访问地址空间之外。
EINVAL
ELF 可执行文件包含多个 PT_INTERP 段(即试图指定多个解释器)。
EIO
发生了 I/O 错误。
EISDIR
ELF 解释器是一个目录。
- ELIBBAD
- ELF 解释器格式无法识别。
- ELOOP
在解析 filename 或脚本或 ELF 解释器名称时遇到过多的符号链接。
EMFILE
进程已达到打开文件的最大数量。
- ENAMETOOLONG
- filename 太长。
- ENFILE
已达到系统打开文件总数的限制。
ENOENT
文件 filename 或脚本或 ELF 解释器不存在,或者文件或解释器所需的共享库无法找到。
- ENOEXEC
- 可执行文件格式无法识别、属于错误的架构,或者存在其他格式错误,导致无法执行。
- ENOMEM
内核内存不足。
- ENOTDIR
- filename 或脚本或 ELF 解释器的路径前缀的某个组件不是目录。
- EPERM
文件系统以 nosuid 方式挂载,用户不是超级用户,且文件设置了 set-user-ID 或 set-group-ID 位。
EPERM
进程正在被跟踪,用户不是超级用户,且文件设置了 set-user-ID 或 set-group-ID 位。
- ETXTBSY
- 可执行文件被一个或多个进程以写入方式打开。
符合
SVr4, 4.3BSD, POSIX.1-2001。POSIX.1-2001 未记录 #! 行为,但在其他方面兼容。
说明
Set-user-ID 和 set-group-ID 进程不能被 ptrace(2) 跟踪。
Linux 忽略脚本上的 set-user-ID 和 set-group-ID 位。
以 nosuid 方式挂载文件系统的结果在不同 Linux 内核版本中有所不同:有些会在执行 set-user-ID 和 set-group-ID 可执行文件会使用户获得其本无权拥有的权限时拒绝执行(并返回 EPERM),有些则只会忽略 set-user-ID 和 set-group-ID 位并成功执行 exec()。
#! 可执行 shell 脚本的第一行最大允许长度为 127 个字符。
解释器脚本的 optional-arg 参数的语义在不同实现中有所不同。在 Linux 上,紧跟在 interpreter 名称后的整个字符串作为单个参数传递给解释器,并且该字符串可以包含空格。但是,某些其他系统上的行为有所不同。某些系统使用第一个空格来终止 optional-arg。在某些系统上,解释器脚本可以有多个参数,且 optional-arg 中的空格用于分隔这些参数。
在 Linux 上,argv 可以指定为 NULL,其效果与将此参数指定为指向包含单个 NULL 指针的列表的指针相同。请勿利用此不当特性! 它是非标准的且不可移植:在大多数其他 UNIX 系统上,这样做会导致错误 (EFAULT)。
POSIX.1-2001 指出 sysconf(3) 返回的值在进程的生命周期内应该是恒定的。然而,自 Linux 2.6.23 起,如果 RLIMIT_STACK 资源限制发生变化,则 _SC_ARG_MAX 报告的值也会发生变化,以反映用于容纳命令行参数和环境变量的空间限制已改变的事实。
历史
- 在 UNIX V6 中,exec() 调用的参数列表以 0 结尾,而 main 的参数列表以 -1 结尾。因此,此参数列表无法直接用于进一步的 exec() 调用。自 UNIX V7 起,两者均以 NULL 结尾。
示例
以下程序设计为由下面的第二个程序执行。它只是将其命令行参数逐行回显。
-
/* myecho.c */ #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int j; for (j = 0; j < argc; j++) printf("argv[%d]: %s\n", j, argv[j]); exit(EXIT_SUCCESS); }
- 此程序可用于执行其命令行参数中命名的程序。
-
/* execve.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { char *newargv[] = { NULL, "hello", "world", NULL }; char *newenviron[] = { NULL }; if (argc != 2) {
- fprintf(stderr, "用法: %s <要执行的文件>\n", argv[0]);
exit(EXIT_FAILURE);
- }
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
perror("execve"); /* execve() 仅在出错时返回 */
exit(EXIT_FAILURE);
} - 我们可以使用第二个程序来执行第一个程序,如下所示:
-
$ cc myecho.c -o myecho $ cc execve.c -o execve $ ./execve ./myecho argv[0]: ./myecho argv[1]: hello argv[2]: world
- 我们还可以使用这些程序来演示脚本解释器的使用。为此,我们创建一个“解释器”为我们 myecho 程序的脚本:
-
$ cat > script.sh #! ./myecho script-arg ^D $ chmod +x script.sh
- 然后我们可以使用我们的程序来执行该脚本:
-
$ ./execve ./script.sh argv[0]: ./myecho argv[1]: script-arg argv[2]: ./script.sh argv[3]: hello argv[4]: world
参见
chmod(2), fork(2), ptrace(2), execl(3), fexecve(3), getopt(3), credentials(7), environ(7), path_resolution(7), ld.so(8)