Flu*_*lup 7 environment-variables glibc
我只是在看这个问题并编写了一个 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;
}
Run Code Online (Sandbox Code Playgroud)
然而,当我跑
WIBBLE=hello ./test_program
Run Code Online (Sandbox Code Playgroud)
然后我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
Run Code Online (Sandbox Code Playgroud)
为什么不unsetenv()修改/proc/pid/environ?
Sté*_*las 14
当程序启动时,它接收它的环境作为指向某些字符串的指针数组,格式为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为上面第一个指针的地址,但对环境的任何修改(以及那些用于将来的 exec),通常都会导致创建一个新的指针数组并将其分配给environ.
如果environ是最初[a,b,c,d,NULL],wherea是一个指向x=1、bto y=2、cto z=3、dto的指针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);
}
Run Code Online (Sandbox Code Playgroud)
在带有 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]
Run Code Online (Sandbox Code Playgroud)
在 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)
Run Code Online (Sandbox Code Playgroud)
在 Solaris(此处为 11)上,我们看到了上面提到的优化(unsetenv("a")最终用 a 完成environ++),通过unsetenv()重用 for 来释放插槽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)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1612 次 |
| 最近记录: |