Linux系统调用execve第二个参数传参规则及运行异常问题咨询
execve传参异常问题解析
核心规则说明
你需要先区分两类规则:一类是内核强制校验的execve接口合法性规则,违反后execve会直接调用失败,返回原程序继续执行后续代码;另一类是Unix生态下所有程序共同遵守的参数约定,违反后即使execve调用成功,被启动的程序也会运行异常。
内核强制校验规则
execve函数原型为:
int execve(const char *pathname, char *const argv[], char *const envp[]);
要让调用成功(成功后新程序会覆盖原进程地址空间,永远不会返回原代码),必须满足:
pathname指向的文件存在,是当前用户有权限执行的合法二进制或脚本文件argv是一个以NULL指针结尾的字符串指针数组,不存在非法内存地址envp是一个以NULL指针结尾的字符串指针数组,不存在非法内存地址
内核不会校验
argv数组内的字符串内容,也不要求argv[0]必须和pathname一致,只要满足上述三个条件,execve就会成功加载新程序。只要你观察到execve后续的代码(比如打印"hi")被执行,就说明execve调用触发了错误,可以通过perror("execve fail")打印具体错误原因。
通用程序约定
所有标准Unix程序启动后默认遵守以下参数约定:
- 程序收到的
argv[0]是自身的调用名称(比如路径、软链接名),很多程序会根据这个值切换运行模式,比如bash发现argv[0]为sh时会进入POSIX兼容模式 argv[1]及之后的元素才是程序接收的业务参数- 额外注意:如果要让bash执行传入的命令文本,必须显式传
-c参数,否则bash会把传入的字符串当成脚本文件路径,尝试打开不存在的文件后直接报错退出。
测试用例拆解
你执行测试的命令为:
./test /bin/bash "echo 'this is a test' | wc -c"
此时test程序自身的argv数组结构如下:
| 数组下标 | 存储内容 |
|---|---|
| argv[0] | "./test" |
| argv[1] | "/bin/bash" |
| argv[2] | "echo 'this is a test' |
| argv[3] | NULL(C标准规定main的argv数组必须以NULL结尾) |
第一个版本:execve(argv[1], &argv[1], NULL)
传给execve的argv数组起始地址为&argv[1],内核看到的传给bash的参数列表为:
- bash收到的
argv[0]= "/bin/bash",符合argv[0]为程序名的约定 - bash收到的
argv[1]= "echo 'this is a test' | wc -c" - bash收到的
argv[2]= NULL,满足内核要求的NULL终止条件
因此这个调用满足内核合法性校验,execve会成功加载bash,不会返回原程序。
注意:这个版本虽然execve调用成功,但bash并不会执行你传入的管道命令——因为你没传
-c参数,bash会把传入的字符串当成脚本文件路径,尝试打开失败后会直接退出,你观察到的"正常执行"实际是bash启动后找不到有效输入直接退出的表现,并非命令执行成功。
第二个版本:execve(argv[1], &argv[2], NULL)
传给execve的argv数组起始地址为&argv[2],内核看到的传给bash的参数列表为:
- bash收到的
argv[0]= "echo 'this is a test' | wc -c" - bash收到的
argv[1]= NULL,同样满足argv数组NULL终止的要求
按接口规则这个调用本可以成功加载bash,你观察到它打印"hi"说明execve直接调用失败,核心原因是你给第三个参数envp直接传了NULL:
POSIX标准明确规定,给execve传空指针作为envp时行为是未定义的。在Linux的多数实现中,NULL对应0地址,属于进程禁止访问的内存区间,内核尝试读取环境变量时会触发内存访问错误,直接返回EFAULT导致execve失败。两个版本表现不一致是栈布局差异导致的未定义行为,和argv参数本身无关。
正确实现参考
如果要通过execve启动bash执行指定命令字符串,正确写法如下:
#include <unistd.h> #include <stdio.h> extern char **environ; // 系统全局环境变量数组,直接传给execve即可 int main(int argc, char *argv[]) { // 构造传给bash的参数数组,必须以NULL结尾 char *bash_argv[] = { "/bin/bash", // argv[0] 程序名 "-c", // 告诉bash后续参数是要执行的命令文本 argv[2], // 待执行的命令字符串 NULL // 强制要求的数组终止符 }; execve("/bin/bash", bash_argv, environ); // 走到这里说明execve调用失败 perror("execve failed"); return 1; }
内容的提问来源于stack exchange,提问作者Olivia22
相关产品推荐
相关产品推荐

