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,作为环境变量传递给新程序。argvenvp 都必须以 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)

引用自

access(2), ash(1), cap_get_file(3), capset(2), catopen(3), chdir(2), chroot(2), console_ioctl(4), core(5), csh(1), dash(1), elf(1), elf(5), environ(5), eventfd(2), exec(3), exit(2), exit(3), expect(1), explain(1), explain(3), explain_execve(3), explain_execve_or_die(3), flock(2), getexeccon(3), getitimer(2), getrusage(2), ioctl(2), ioperm(2), iopl(2), iv_signal(3), ksh(1), libexpect(3), lynx(1), mksh(1), mq_close(3), open(2), pmcd(1), proc(5), pthread_kill_other_threads_np(3), pthreads(7), sbrk(2), sched_getaffinity(2), sched_setscheduler(2), sem_close(3), semop(2), set_mempolicy(2), setgroups(2), setpgrp(2), setpriority(2), setsid(2), sigaction(2), signalfd(2), sigpending(2), sigprocmask(2), sigvec(3), stat(2), sudo_plugin(8), system(3), timerfd_create(2), umask(2), vfork(2), xs(1)