訪問進程環境變量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\n", 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信號。
訪問進程環境變量environ時的一個坑