EXECVE - Linux手册页

时间:2019-08-20 17:58:39  来源:igfitidea点击:

Linux程序员手册 第2部分
更新日期: 2020-08-13

名称

execve-执行程序

语法

#包括

int execve(const char * pathname,char * const argv [],
char * const envp []);

说明

execve()执行路径名引用的程序。这将导致调用程序当前正在运行的程序被新程序替换,该程序具有新初始化的堆栈,堆和(初始化和未初始化的)数据段。

路径名必须是二进制可执行文件或以以下形式的行开头的脚本:

#!interpreter [optional-arg]

有关后一种情况的详细信息,请参见下面的"解释器脚本"。

argv是指向字符串的指针数组,该字符串作为命令行参数传递给新程序。按照约定,这些字符串中的第一个(即argv [0])应包含与正在执行的文件关联的文件名。 argv数组必须由NULL指针终止。 (因此,在新程序中,argv [argc]将为NULL。)

envp是指向字符串的指针数组,通常以key = value的形式出现,这些字符串作为新程序的环境传递。 envp数组必须以NULL指针终止。

当新程序的主函数定义为以下参数时,可以通过新程序的主函数对其进行访问:

int main(int argc, char *argv[], char *envp[])

但是,请注意,在POSIX.1中未指定对主函数使用第三个参数。根据POSIX.1,应该通过外部变量environ(7)访问环境。

execve()不会成功返回,并且文本,初始化数据,未初始化数据(bss)和调用进程的堆栈将根据新加载的程序的内容而被覆盖。

如果正在跟踪当前程序,则在成功执行execve()之后会向其发送SIGTRAP信号。

如果在由路径名引用的程序文件上设置了"设置用户ID"位,则调用过程的有效用户ID将更改为程序文件所有者的有效用户ID。同样,如果在程序文件上设置了set-group-ID位,则将调用过程的有效组ID设置为程序文件的组。

如果满足以下任一条件,则不会执行有效ID的上述转换(即,忽略设置用户ID和设置组ID位):

*
为调用线程设置了no_new_privs属性(请参阅prctl(2));
*
基础文件系统已挂载nosuid(mount(2)的MS_NOSUID标志);要么
*
正在跟踪调用过程。

如果以上任何一项为真,则程序文件的功能(请参阅功能(7))也将被忽略。

进程的有效用户ID被复制到保存的set-user-ID中。类似地,有效组ID被复制到保存的set-group-ID。在由于设置用户ID和设置组ID模式位而发生任何有效的ID更改之后,将进行此复制。

通过调用execve(),进程的实际UID和实际GID及其补充组ID保持不变。

如果可执行文件是包含共享库存根的a.out动态链接的二进制可执行文件,则在执行开始时会调用Linux动态链接程序ld.so(8),以将所需的共享库带入内存并将可执行文件与它们链接。

如果可执行文件是动态链接的ELF可执行文件,则使用PT_INTERP段中命名的解释器来加载所需的共享库。对于与glibc链接的二进制文件,此解释器通常为/lib/ld-linux.so.2(请参阅ld-linux.so(8))。

Effect on process attributes

除以下内容外,所有进程属性均在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中指定。在execve()期间,也不会保留以下特定于Linux的进程属性:

*
除非正在执行设置用户ID程序,设置组ID程序或具有功能的程序,否则进程的"可转储"属性设置为值1,在这种情况下,可转储标志可以重置为在prctl(2)中PR_SET_DUMPABLE下描述的情况下,/ proc / sys / fs / suid_dumpable中的值。请注意,对" dumpable"属性的更改可能导致进程的/ proc / [pid]目录中文件的所有权更改为root:root,如proc(5)中所述。
*
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安全位标志被清除。参见功能(7)。
*
将终止信号重置为SIGCHLD(请参见clone(2))。
*
取消共享文件描述符表,从而消除了clone(2)的CLONE_FILES标志的作用。

请注意以下几点:

*
execve()期间,除调用线程之外的所有其他线程均被销毁。互斥体,条件变量和其他pthreads对象不会保留。
*
setlocale(LC_ALL," C")的等效项在程序启动时执行。
*
POSIX.1指定任何被忽略或设置为默认值的信号的处理均保持不变。 POSIX.1指定一个例外:如果SIGCHLD被忽略,则实现可以保留该处置不变或将其重置为默认处置;否则,请执行POSIX.1。 Linux是前者。
*
任何未完成的异步I / O操作都将被取消(aio_read(3),aio_write(3))。
*
有关execve()期间功能的处理,请参见capabilities(7)。
*
默认情况下,文件描述符在execve()中保持打开状态。标记为close-on-exec的文件描述符已关闭;请参阅fcntl(2)中FD_CLOEXEC的描述。 (如果关闭了文件描述符,则将释放此过程在底层文件上获得的所有记录锁。有关详细信息,请参见fcntl(2)。)POSIX.1表示,如果文件描述符0、1和2会否则,在成功执行execve()之后将关闭,并且该进程将获得特权,因为已在已执行的文件上设置了设置用户ID或设置组ID模式位,然后系统可能会为每个文件打开一个未指定的文件文件描述符。作为一般原则,任何可移植程序(无论是否具有特权)都不能假定这三个文件描述符将在execve()中保持关闭状态。

Interpreter scripts

解释器脚本是已启用执行权限的文本文件,其第一行的格式为:

#!interpreter [optional-arg]

解释器必须是可执行文件的有效路径名。

如果execve()的pathname参数指定了解释器脚本,则将使用以下参数来调用解释器:

interpreter [optional-arg] pathname arg...

其中,路径名是指定为execve()的第一个参数的文件的绝对路径名,而arg ...是execve()的argv参数所指向的一系列单词,从argv [1]开始。注意,没有办法获取传递给execve()调用的argv [0]。

对于可移植的用途,应该不存在optional-arg,或者将其指定为单个单词(即,不应包含空格);请参阅下面的注释。

从Linux 2.6.28开始,内核允许脚本的解释程序本身就是脚本。此权限是递归的,最多四个递归,因此解释器可以是由脚本解释的脚本,依此类推。

Limits on size of arguments and environment

大多数UNIX实现对可能传递给新程序的命令行参数(argv)和环境(envp)字符串的总大小施加了一定的限制。 POSIX.1允许实现使用ARG_MAX常数(在调用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限制可确保新程序始终具有一定的堆栈空间。)此外,总大小限制为内核常量_STK_LIM(8 MB)的值的3/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
在路径名的路径前缀或脚本解释器的名称的组件上拒绝搜索许可。 (另请参见path_resolution(7)。)
EACCES
该文件或脚本解释器不是常规文件。
EACCES
文件,脚本或ELF解释器的执行权限被拒绝。
EACCES
文件系统已挂载noexec。
EAGAIN(since Linux 3.1)
使用set * uid()调用之一更改了它的实际UID之后,调用者(现在仍然是)超出了RLIMIT_NPROC资源限制(请参阅setrlimit(2))。有关此错误的更详细说明,请参见"注意"。
EFAULT
向量argv或envp中的路径名或指针之一指向您可访问的地址空间之外。
EINVAL
一个ELF可执行文件有多个PT_INTERP段(即,试图命名多个解释器)。
EIO
发生I / O错误。
EISDIR
ELF解释器是一个目录。
ELIBBAD
ELF解释器的格式不正确。
ELOOP
解析路径名或脚本或ELF解释器的名称时遇到太多符号链接。
ELOOP
在递归脚本解释过程中达到了最大递归限制(请参见上面的"解释器脚本")。在Linux 3.8之前,此情况下产生的错误是ENOEXEC。
EMFILE
已达到打开文件描述符数量的每个进程限制。
ENAMETOOLONG
路径名太长。
ENFILE
已达到系统范围内打开文件总数的限制。
ENOENT
文件路径名或脚本或ELF解释器不存在。
ENOEXEC
可执行文件格式不正确,结构错误或存在其他格式错误,意味着无法执行。
ENOMEM
内核内存不足。
ENOTDIR
路径名或脚本或ELF解释器的路径前缀的组成部分不是目录。
EPERM
文件系统安装为nosuid,用户不是超级用户,并且文件设置了set-user-ID或set-group-ID位。
EPERM
正在跟踪该进程,该用户不是超级用户,并且该文件设置了set-user-ID或set-group-ID位。
EPERM
"能力哑巴"应用程序将无法获得可执行文件授予的全部允许能力。参见功能(7)。
ETXTBSY
指定的可执行文件已打开,可以由一个或多个进程进行写入。

遵循规范

POSIX.1-2001,POSIX.1-2008,SVr4、4.3BSD。 POSIX没有记录#!行为,但它在其他UNIX系统上存在(有所变化)。

备注

有时会看到execve()(以及exec(3)中描述的相关功能)被描述为"执行新进程"(或类似过程)。这是一个极具误导性的描述:没有新的流程;调用过程的许多属性保持不变(尤其是其PID)。 execve()所做的一切只是安排一个现有进程(调用进程)来执行一个新程序。

设置用户ID和设置组ID进程不能为ptrace(2)d。

挂载文件系统nosuid的结果在Linux内核版本之间有所不同:有些会拒绝执行set-user-ID和set-group-ID可执行文件,而这会赋予用户尚未拥有的权限(并返回EPERM),有些则会拒绝。只需成功忽略set-user-ID和set-group-ID位以及exec()。

在Linux上,可以将argv和envp指定为NULL。在这两种情况下,这与将参数指定为指向包含单个空指针的列表的指针具有相同的效果。不要利用这种非标准且不可移植的功能!在许多其他UNIX系统上,将argv指定为NULL将导致错误(EFAULT)。其他一些UNIX系统将envp == NULL的情况与Linux相同。

POSIX.1说sysconf(3)返回的值在进程的生存期内应该是不变的。但是,从Linux 2.6.23开始,如果RLIMIT_STACK资源限制更改,那么_SC_ARG_MAX报告的值也将更改,以反映用于保存命令行参数和环境变量的空间限制已更改的事实。

execve()失败的大多数情况下,控制权返回到原始可执行映像,然后execve()的调用者可以处理该错误。但是,在(很少)情况下(通常是由资源耗尽造成的),失败可能会发生在无法返回的点上:原始可执行映像已被拆除,但是新映像无法完全构建。在这种情况下,内核会使用SIGSEGV(直到Linux 3.17之前为SIGKILL)信号终止进程。

Interpreter scripts

内核在"#!"后面的文本上加上最大长度。脚本开头的字符;超出限制的字符将被忽略。在Linux 5.1之前,限制为127个字符。从Linux 5.1开始,限制为255个字符。

解释器脚本的optional-arg参数的语义在实现中会有所不同。在Linux上,解释器名称后面的整个字符串作为单个参数传递给解释器,并且此字符串可以包含空格。但是,某些其他系统上的行为也有所不同。某些系统使用第一个空格来终止optional-arg。在某些系统上,解释器脚本可以具有多个参数,而optional-arg中的空格用于分隔参数。

Linux(像大多数其他现代UNIX系统一样)忽略脚本上的set-user-ID和set-group-ID位。

execve() and EAGAIN

调用execve()时可能发生的EAGAIN错误(从Linux 3.1开始)的详细说明如下。

当先前对setuid(2),setreuid(2)或setresuid(2)的调用导致该进程的真实用户ID发生更改,并且该更改导致该进程超出了其RLIMIT_NPROC资源限制时,即可能发生EAGAIN错误。 ,则属于新的实际UID的进程数超过了资源限制)。从Linux 2.6.0到3.0,这导致set * uid()调用失败。 (在2.6之前,未对更改其用户ID的进程施加资源限制。)

从Linux 3.1开始,刚刚描述的场景不再导致set * uid()调用失败,因为它常常导致安全漏洞,有漏洞的应用程序不检查返回状态,并假设-如果调用者具有root用户特权-通话将始终成功。相反,set * uid()调用现在成功更改了真实的UID,但是内核设置了一个内部标志PF_NPROC_EXCEEDED,以指出已超过RLIMIT_NPROC资源限制。如果设置了PF_NPROC_EXCEEDED标志,并且在随后的execve()调用时仍超出了资源限制,则该调用将失败,并显示错误EAGAIN。此内核逻辑确保仍对公共特权守护程序工作流(即fork(2)+ set * uid()+ execve())实施RLIMIT_NPROC资源限制。

如果在execve()调用时仍未超出资源限制(因为属于该实际UID的其他进程在set * uid()调用与execve()调用之间终止),则execve()调用成功内核清除PF_NPROC_EXCEEDED进程标志。如果此过程随后对fork(2)的调用成功,则也清除该标志。

Historical

在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, "Usage: %s <file-to-exec>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    newargv[0] = argv[1];

    execve(argv[1], newargv, newenviron);
    perror("execve");   /* execve() returns only on error */
    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
#!./myecho script-arg
haD
$ chmod +x script

然后,我们可以使用我们的程序执行脚本:

$ ./execve ./script
argv[0]: ./myecho
argv[1]: script-arg
argv[2]: ./script
argv[3]: hello
argv[4]: world

另外参见

chmod(2),execveat(2),fork(2),get_robust_list(2),ptrace(2),exec(3),fexecve(3),getopt(3),system(3),capabilities(7),凭据(7),环境(7),路径分辨率(7),ld.so(8)

出版信息

这个页面是Linux手册页项目5.08版的一部分。有关项目的说明、有关报告错误的信息以及此页面的最新版本,请访问https://www.kernel.org/doc/man-pages/