dive into the environment

drumatoと飯食ってる時に「環境変数ってどうなってんだ?」となったので、調べた。

とりあえずプロセスに付随する情報なので、task_structを読んでみるもそれっぽい情報はなし。

environment variablesでgrepしてみると、mm_structの方に

unsigned long arg_start, arg_end, env_start, env_end;

とあるのを見つけた。確かにGDBで見るとスタックにあるし、mainの引数もargc, argv, envpだな。

それを裏付けるように、fs/proc/base.cenviron_read

static ssize_t environ_read(struct file *file, char __user *buf,
            size_t count, loff_t *ppos)
{
    char *page;
    unsigned long src = *ppos;
    int ret = 0;
    struct mm_struct *mm = file->private_data;
    unsigned long env_start, env_end;

    /* Ensure the process spawned far enough to have an environment. */
    if (!mm || !mm->env_end)
        return 0;

    page = (char *)__get_free_page(GFP_KERNEL);
    if (!page)
        return -ENOMEM;

    ret = 0;
    if (!mmget_not_zero(mm))
        goto free;

    spin_lock(&mm->arg_lock);
    env_start = mm->env_start;
    env_end = mm->env_end;
    spin_unlock(&mm->arg_lock);

    while (count > 0) {
        size_t this_len, max_len;
        int retval;

        if (src >= (env_end - env_start))
            break;

        this_len = env_end - (env_start + src);

        max_len = min_t(size_t, PAGE_SIZE, count);
        this_len = min(max_len, this_len);

        retval = access_remote_vm(mm, (env_start + src), page, this_len, FOLL_ANON);

        if (retval <= 0) {
            ret = retval;
            break;
        }

        if (copy_to_user(buf, page, retval)) {
            ret = -EFAULT;
            break;
        }

        ret += retval;
        src += retval;
        buf += retval;
        count -= retval;
    }
    *ppos = src;
    mmput(mm);

free:
    free_page((unsigned long) page);
    return ret;
}

って感じでmm_structenv_startenv_endを使っている。

という訳で環境変数はユーザ空間にあることがわかった。

getenvの実装も気になったので読んでみると

char *
getenv (const char *name)
{
  size_t len = strlen (name);
  char **ep;
  uint16_t name_start;

  if (__environ == NULL || name[0] == '\0')
    return NULL;

...

みたいな感じで__environを参照している。その__environ

/* This file just defines the `__environ' variable (and alias `environ').  */

#include <unistd.h>
#include <stddef.h>

/* This must be initialized; we cannot have a weak alias into bss.  */
char **__environ = NULL;

てな感じで定義されており、じゃあどこで初期化されるのか?というと__libc_start_mainの中にある

STATIC int
LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
         int argc, char **argv,
#ifdef LIBC_START_MAIN_AUXVEC_ARG
         ElfW(auxv_t) *auxvec,
#endif
         __typeof (main) init,
         void (*fini) (void),
         void (*rtld_fini) (void), void *stack_end)
{
  /* Result of the 'main' function.  */
  int result;

  __libc_multiple_libcs = &_dl_starting_up && !_dl_starting_up;

#ifndef SHARED
  _dl_relocate_static_pie ();

  char **ev = &argv[argc + 1];

  __environ = ev;

よく考えたらexecveの引数もconst char *filename, char *const argv[], char *const envp[]だもんな

なるほどね〜