星期六, 1月 20, 2024

Linux 下環境變數的儲存

今天心血來潮,忽然好奇環境變數在 Linux 的 Process 中是如何儲存的。因為在 Linux 下可以透過 /proc/$pid/environ 看到程式一開始執行時的環境變數

在 C 語言中,要存取環境變數主要可以透過

  • C functions: getenv, setenv, putenv, ...
  • 全域變數extern char **environ

透過全域變數 extern char **environ,可以知道環境變數的結構如下

在 Linux 下,如果想知道一個記憶體位置是位在哪個區塊,可以從 /proc/self/maps 觀看

在比對 environ, environ array, environ variables 後可以得知

程式開始執行時
  • environ 左值位於執行檔中(因此固定不動)
  • environ array 位於 [stack] 中
  • 所有的 environ varibales 位於 [stack] 中,且所有的 environ variables 緊密排列
當加入一個新的環境變數後或設置環境變數後
  • environ array 位於 [heap] 中
  • 所有的 environ variables 位於 [stack] 中,除了新加入的環境變數會在 [heap] 中

觀察過後會發現利用 C functions 對環境變數的修改都不會直接修改到 [stack] 中的 environ variables。於是猜測說 /proc/$pid/environ 的內容是直接對應到 [stack] 中的那些 environ varibales。於是進行實驗

environ[0][0] = 's';
sh-5.1$ cat /proc/297884/environ | tr '\0' '\n' | head -n 1
sHELL=/bin/bash
sh-5.1$

直接修改 [stack] 中的環境變數內容果然反應到了 /proc/$pid/environ 上面

在 Linux 下面想要改變 /proc/$pid/environ 對應到的 process 記憶體區塊,可以透過 prctl 搭配 PR_SET_MM_ENV_START, PR_SET_MM_ENV_END 來進行修改

以下的程式會使用 prctl 改變環境變數對應的區域,並且輸出環境變數 "SHELL" 與 /proc/$pid/environ 的內容。注意此程式需要使用 root 來跑,否則 prctl 執行會失敗

#include <sys/prctl.h>
#include <stdio.h>
#include <stdlib.h>

int
main()
{
        char buffer[4096];
        size_t nLen = 0;
        FILE *fp = NULL;
        char envs[] = "LINUX=123";

        prctl(PR_SET_MM, PR_SET_MM_ENV_START, &envs[0], 0, 0); 
        prctl(PR_SET_MM, PR_SET_MM_ENV_END, &envs[sizeof(envs)], 0, 0); 

        printf("%s\n", getenv("SHELL"));

        fp = fopen("/proc/self/environ", "r");
        nLen = fread(buffer, 1, sizeof(buffer), fp);
        fclose(fp);

        fwrite(buffer, nLen, 1, stdout);
        fputc('\n', stdout);

        return 0;
}
此程式的輸出為
/bin/bash
LINUX=123

可以看到對程式內部而言,環境變數沒有什麼變化,但從 /proc/$pid/environ 已經變成程式另外指定的區域了

沒有留言: