我有一个进程,其环境如下:
root@a-vm:/proc/1363# hexdump -C environ
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
0000016c
我从来没有见过这样的事情;我希望environ
包含 nul 终止key=value
对,因此此输出违反了各种断言。我是否正在查看已知的内核错误,或者 Unix/Linux 中是否有某种合法的方法来实现此目的? (……如果是这样,为什么?为什么内核允许这种废话?)
(在 Linux、3.13.0/Ubuntu Trusty 上)
(我在试图弄清楚为什么这个过程没有将一些临时输出写入正确的位置时遇到了这个问题;它应该使用某个目录进行临时存储,并且通过设置环境变量来通知该目录TMP
;但是我'我设置TMP
为看起来非常正常的路径,而不是一堆空值,而且我从来没有见过完全空的环境。)
答案1
这不是废话,Linux 中有一种合法的方法可以实现这一点,而您的期望是错误的。
内核传递给程序启动代码的参数和环境字符串存储在普通应用程序空间虚拟内存中,就像任何其他程序数据一样;而且,就像任何其他程序数据变量一样,它们是可以修改的。程序修改它们是完全合法的。
(请注意,这是从内核提供和强制执行的角度来看的。特定编程语言的标准可能所说的不一定相同。但就内核而言,它只是应用程序空间的一个区域可读可写的程序数据的虚拟内存,内核不关心您编译机器代码的编程语言。)
该/proc/${PID}/environ
文件只是该应用程序空间虚拟内存的一个窗口。 Linux 并不记住进程的实际环境数据,而是只记住启动进程的环境区域的起始地址和结束地址,并且文件/proc/${PID}/environ
只是读出当前内存中的所有内容。您不应期望此文件包含以 ␀ 结尾的字符串列表。这是一个错误的期望。
没有 GNU C 库函数可以修改包含这些字符串的内存。但各种程序都有自己的功能来做到这一点。
例如,考虑一下 OpenSSH。 OpenSSH 服务器修改ps
其参数向量的显示内容,以读取诸如sshd: JdeBP [priv]
.
OpenSSH 服务器包含尝试在 Linux 上模仿 OpenBSD 上的 BSD C 库的功能的代码。在 OpenBSD 上,有一个名为的 BSD C 库函数setproctitle()
可以重写ps
命令报告的进程参数向量。它调用sysctl()
将一个新的参数向量传递给内核,该向量ps
可以使用 读取sysctl()
。 FreeBSD也有类似的功能。
正如所解释的,在 Linux 上,内核不记住实际的参数和环境,只记住启动进程时最初放置它们的内存区域的起始和结束地址。所以OpenSSH的Linux端口有一个兼容setproctitle()
功能,而是覆盖上述内存区域。
该兼容性函数计算环境区域的总大小和参数区域,并覆盖所有的使用新的参数字符串。这样做是因为在通常情况下,调用的程序setproctitle()
想要写入比进程最初拥有的参数数据组更长的参数数据。 sshd
经常这样做。因此,它允许新参数覆盖参数区域后面的环境区域,从而为程序提供更多空间来容纳更长的参数字符串集。
重要的是,它还填充区域中未使用的部分它不需要用 ␀s 覆盖总参数和环境数据的原始长度。
你所看到的就是这个的确切结果。如果您在系统上找到 OpenSSH 服务器进程,您会发现它的/proc/${PID}/environ
.
进一步阅读
setproctitle
。 FreeBSD 11.0 手册。setproctitle
。 OpenBSD 手册。setproctitle()
。 OpenSSH 便携式版本。environ_read()
。 fs/proc/base.fs/proc/base.fs/proc/base.fs/proc/base.fs/proc/base.fs/proc/base.fs Linux 内核。自由电子。
答案2
这完全可以通过将NUL
s 写入环境变量所在的内存位置来实现:
#include <stdio.h>
#include <unistd.h>
extern char **environ;
int main(void)
{
int i;
char *p = *environ;
/* hopefully your ENV is longer than this */
for (i = 0; i < 10; i++) *(p + i) = 0;
printf("hexdump -C /proc/%d/environ\n", getpid());
sleep(99999);
}
如果您使用空环境启动程序,则该environ
文件将完全为空:
execle("/bin/sleep", "sleep", "999", (char *)NULL, (char *const) NULL)
因此,这种情况是进程运行后所做的事情(并且几乎没有什么可以阻止这种情况发生,除非您以某种方式锁定了该内存,然后setenv(3)
调用可能会出现问题......)。