访问进程环境变量environ时的一个坑

在unistd.h中定义了变量char **environ;来表示当前所有环境变量,一般来说访问特定环境变量可以用getenv,但是想遍历所有环境变量就得使用environ。

即在程序内全局声明extern char **environ;当然设定main函数第3个参数也可以,不过不推荐,因为ISO C的main函数没有第三个参数。

environ维护了一个char*数组,每个元素都是一个指针指向函数栈帧顶部的环境变量,数组结尾是NULL。

于是正确的遍历姿势是下面这样

    for (int i = 0; environ[i] != NULL; i++)
        puts(environ[i]);

然后我试了下错误的姿势

	for (char *ptr = environ[0]; ptr; ptr++)
		puts(ptr);

结果是程序dump了,审查了下发现错误出在ptr++上,因为ptr类型是char*,执行++后指针只向前移动1 byte。

于是就变成这样

	for (char *ptr = environ[0]; ptr; ptr += (strlen(ptr) + 1)
		puts(environ[i]);

代码已经比较丑陋了,而且还多出了不必要的计算,即strlen函数,但是程序依然dump了。

我的调试方式是这样的

    for (char *ptr = environ[0]; ptr; ptr += (strlen(ptr) + 1))
    {
        static int i = 0;
        if (strcmp(ptr, environ[i]) != 0)
        {
            printf("error: %d
", i);
            break;
        }
        puts(ptr);
        i++;
    }

 错误如下

Program received signal SIGSEGV, Segmentation fault.
__strcmp_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcmp-sse2-unaligned.S:204
204    ../sysdeps/x86_64/multiarch/strcmp-sse2-unaligned.S: No such file or directory.

对啊,environ数组最后一个元素是NULL,但是strcmp必须接收非NULL指针作为参数(因为strcmp的参数s1、s2必须可以用*s1、*s2来访问,NULL是地址0,是用户无法访问的地址,用户访问无法访问的地址时就会产生SIGSEGV信号)。

于是我定位到了strcmp这句

(gdb) b 15 if environ[i]==0
(gdb) p ptr
$1 = 0x7fffffffefe3 "/home/xyz/TLPI/a.out"
(gdb) p environ[i]
$2 = 0x0

 原因也清楚了。在C程序的存储空间高地址是命令行参数和环境变量依次排列,如下图

n1是环境变量的数量,n2是命令行参数的数量。因此在ptr指向最后一个环境变量时,ptr+=(strlen[ptr]+1)后指向的是argv[0]。

字符指针数组environ保存了n1+1个元素,多出一个元素是NULL。而ptr+=(strlen[ptr]+1)则是直接访问程序的存储空间,并没有一个终止符。

当ptr到达内存中不可访问的区域(即argv[n2-1]的下面,函数栈帧的地址),就会引发SIGSEGV信号。

原文地址:https://www.cnblogs.com/Harley-Quinn/p/6817958.html