最近看到了一篇关于人们可能对argv内容有误解的文章,这里进行一个C语言的实践。
首先我们的callee.c
内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <stdio.h>
int main(int argc, char *argv[], char *envp[]) { printf("argc: %d\n", argc); for (int i = 0; i < argc; i++) printf("argv[%d]: %s\n", i, argv[i]); printf("envp[0]: %s\n", envp[0]); printf("====================================================================\n"); printf("envp = %p\targc + argv + 1 = %p\n", envp, argc + argv + 1); printf("argv: %p value: %p\n", argv, *argv); printf("envp: %p value: %p\n", envp, *envp); for (int i = 0; i < argc; i++) printf("argv[%d]: %p\n", i, argv[i]); for (int i = 0; envp[i] != NULL; i++) printf("envp[%d]: %p\n", i, envp[i]); return 0; }
|
直接执行的输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| $ ./callee argc: 1 argv[0]: ./callee envp[0]: SHELL=/bin/zsh ==================================================================== envp = 0x7ffd190083f8 argc + argv + 1 = 0x7ffd190083f8 argv: 0x7ffd190083e8 value: 0x7ffd19009909 envp: 0x7ffd190083f8 value: 0x7ffd19009912 argv[0]: 0x7ffd19009909 envp[0]: 0x7ffd19009912 envp[1]: 0x7ffd19009921 envp[2]: 0x7ffd19009999 ...
|
这里我们的argv[0]为程序的filename,而envp内容为当前的环境变量。同时以下等式成立:
即envp的地址为argc(参数个数)+argv首地址+1(argv结束的NULL),说明argv结束后紧接着就是envp。
但是我们知道Linux中我们可以直接调用execve
接口将当前执行的程序覆盖为新的程序,同时指定argv和envp。
1 2 3
| #include <unistd.h>
int execve(const char *pathname, char *const argv[], char *const envp[]);
|
于是就有了我们下面的caller.c
:
1 2 3 4 5 6 7 8 9 10 11
| #include <stdio.h> #include <unistd.h>
int main() { char *argv[] = { "WHO AM I" }; char *envp[] = { "BAD VALUE", NULL }; execve("./callee", argv, envp); perror("execve"); return 1; }
|
执行的输出如下:
1 2 3 4 5 6 7 8 9 10 11 12
| $ ./caller argc: 2 argv[0]: WHO AM I argv[1]: BAD VALUE envp[0]: BAD VALUE ==================================================================== envp = 0x7ffd6747db90 argc + argv + 1 = 0x7ffd6747db90 argv: 0x7ffd6747db78 value: 0x7ffd6747dfd2 envp: 0x7ffd6747db90 value: 0x7ffd6747dfe5 argv[0]: 0x7ffd6747dfd2 argv[1]: 0x7ffd6747dfdb envp[0]: 0x7ffd6747dfe5
|
这里我们可以发现argv[0]不再是程序的filename,而是一个自定义的值。
因为下面原因:
- argv和envp都以NULL结尾,但是argv在传参时没有强制要求NULL,而envp如果末尾不为NULL就会出错。
- envp本来要求格式为
key=value
,但实际上不会校验。
所以我们的argv数组找到的NULL其实是envp的NULL,这里我们argv包含了argv[0]和envp[0]。这说明:
- 靠argv[0]判断filename可能是不准确的。
- envp数组可以成为argv的一部分,需要小心。