我只是在看这个问题并编写了一个 noddy 程序来演示unsetenv()
修改/proc/pid/environ
.令我惊讶的是它没有任何效果!
这就是我所做的:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
printf("pid=%d\n", getpid());
printf("sleeping 10...\n");
sleep(10);
printf("unsetenv result: %d\n", unsetenv("WIBBLE"));
printf("unset; sleeping 10 more...\n");
sleep(10);
return 0;
}
然而,当我跑步时
WIBBLE=hello ./test_program
WIBBLE
然后我在运行之前和之后的环境中看到unsetenv()
:
# before the unsetenv()
$ tr '\0' '\n' < /proc/498/environ | grep WIBBLE
WIBBLE=hello
# after the unsetenv()
$ tr '\0' '\n' < /proc/498/environ | grep WIBBLE
WIBBLE=hello
为什么不unsetenv()
修改/proc/pid/environ?
答案1
当程序启动时,它会以指向某些字符串的指针数组的形式接收其环境,格式为var=value
.在 Linux 上,它们位于堆栈的底部。在最底部,所有的字符串都被一个接一个地塞住(如图所示/proc/pid/environ
)。上面有一个指向这些字符串的指针数组(以 NULL 结尾)(这就是char *envp[]
您的 中的内容int main(int argc, char* argv[], char* envp[])
,libc 通常会初始化environ
)。
putenv()
// setenv()
,unsetenv()
不要修改那些字符串,它们通常甚至不会修改指针。在某些系统上,这些(字符串和指针)是只读的。
虽然 libc 通常会初始化char **environ
为上面第一个指针的地址,但对环境的任何修改(以及那些用于未来执行的修改)通常会导致创建一个新的指针数组并将其分配给environ
.
如果environ
是最初[a,b,c,d,NULL]
,哪里a
是一个指向x=1
,b
to y=2
,c
to z=3
,d
to的指针q=5
,如果你做一个unsetenv("y")
,environ
就必须成为[a,c,d,NULL]
。在初始数组列表是只读的系统上,必须分配新列表并将其environ
存储[a,c,d,NULL]
在其中。接下来unsetenv()
,可以就地修改该列表。只有当您执行unsetenv("x")
上述操作时,列表才可能不会被重新分配(environ
可能只是递增以指向&envp[1]
。我不知道某些 libc 实现是否实际上执行了该优化)。
无论如何,没有理由以任何方式修改存储在堆栈底部的字符串本身。即使unsetenv()
实现实际上是就地修改最初在堆栈上接收到的数据,它也只会修改指针,而不会麻烦地删除它们指向的字符串。 (这似乎是 GNU libc 在 Linux 系统上所做的事情(至少对于 ELF 可执行文件),envp
只要环境变量的数量不增加,它就会就地修改指针列表。
您可以使用以下程序观察行为:
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char **environ;
int main(int argc, char* argv[], char* envp[]) {
char cmd[128];
int i;
printf("envp: %p environ: %p\n", envp, environ);
for (i = 0; envp[i]; i++)
printf(" envp[%d]: %p (%s)\n", i, envp[i], envp[i]);
#define DO(x) x; puts("\nAfter " #x "\n"); \
printf("envp: %p environ: %p\n", envp, environ); \
for (i = 0; environ[i]; i++) \
printf(" environ[%d]: %p (%s)\n", i, environ[i], environ[i])
DO(unsetenv("a"));
DO(setenv("b", "xxx", 1));
DO(setenv("c", "xxx", 1));
puts("\nAddress of heap and stack:");
sprintf(cmd, "grep -e stack -e heap /proc/%u/maps", getpid());
fflush(stdout);
system(cmd);
}
在带有 GNU libc 的 Linux 上(与 klibc、musl libc 或 Dietlibc 相同,除了它们使用 mmapped 匿名内存而不是堆来分配内存),当运行为 时env -i a=1 x=3 ./e
,给出(内联注释):
envp: 0x7ffc2e7b3238 environ: 0x7ffc2e7b3238
envp[0]: 0x7ffc2e7b4fec (a=1)
envp[1]: 0x7ffc2e7b4ff0 (x=3)
# envp[1] is almost at the bottom of the stack. I lied above in that
# there are more things like the path of the executable
# environ initially points to the same pointer list as envp
After unsetenv("a")
envp: 0x7ffc2e7b3238 environ: 0x7ffc2e7b3238
environ[0]: 0x7ffc2e7b4ff0 (x=3)
# here, unsetenv has reused the envp[] list and has not allocated a new
# list. It has shifted the pointers though and not done the optimisation
# I mention above
After setenv("b", "xxx", 1)
envp: 0x7ffc2e7b3238 environ: 0x1bb3420
environ[0]: 0x7ffc2e7b4ff0 (x=3)
environ[1]: 0x1bb3440 (b=xxx)
# a new list has been allocated on the heap. (it could have reused the
# slot freed by unsetenv() above but didn't, Solaris' version does).
# the "b=xxx" string is also allocated on the heap.
After setenv("c", "xxx", 1)
envp: 0x7ffc2e7b3238 environ: 0x1bb3490
environ[0]: 0x7ffc2e7b4ff0 (x=3)
environ[1]: 0x1bb3440 (b=xxx)
environ[2]: 0x1bb3420 (c=xxx)
Address of heap and stack:
01bb3000-01bd4000 rw-p 00000000 00:00 [heap]
7ffc2e794000-7ffc2e7b5000 rw-p 00000000 00:00 0 [stack]
在 FreeBSD(此处为 11-rc1)上,已经在 上分配了一个新列表unsetenv()
。不仅如此,字符串本身也被复制到堆上,因此与第一次修改环境后程序启动时收到的字符串environ
完全断开:envp[]
envp: 0x7fffffffedd8 environ: 0x7fffffffedd8
envp[0]: 0x7fffffffef74 (x=2)
envp[1]: 0x7fffffffef78 (a=1)
After unsetenv("a")
envp: 0x7fffffffedd8 environ: 0x800e24000
environ[0]: 0x800e15008 (x=2)
After setenv("b", "xxx", 1)
envp: 0x7fffffffedd8 environ: 0x800e24000
environ[0]: 0x800e15018 (b=xxx)
environ[1]: 0x800e15008 (x=2)
After setenv("c", "xxx", 1)
envp: 0x7fffffffedd8 environ: 0x800e24000
environ[0]: 0x800e15020 (c=xxx)
environ[1]: 0x800e15018 (b=xxx)
environ[2]: 0x800e15008 (x=2)
在 Solaris(此处为 11)上,我们看到上面提到的优化(最终unsetenv("a")
通过 a 完成),通过重用 for 来environ++
释放插槽,但是当然必须在插入新环境时分配新的指针列表多变的 ():unsetenv()
b
c
envp: 0xfeffef6c environ: 0xfeffef6c
envp[0]: 0xfeffefec (a=1)
envp[1]: 0xfeffeff0 (x=2)
After unsetenv("a")
envp: 0xfeffef6c environ: 0xfeffef70
environ[0]: 0xfeffeff0 (x=2)
After setenv("b", "xxx", 1)
envp: 0xfeffef6c environ: 0xfeffef6c
environ[0]: 0x806145c (b=xxx)
environ[1]: 0xfeffeff0 (x=2)
After setenv("c", "xxx", 1)
envp: 0xfeffef6c environ: 0x8061c48
environ[0]: 0x8061474 (c=xxx)
environ[1]: 0x806145c (b=xxx)
environ[2]: 0xfeffeff0 (x=2)