Как получить переменные окружения процесса
Простой ответ: прочитав файл /proc/<pid>/environ
Однако таким образом можно получить только те переменные окружения, которые были заданы на момент создания процесса. Если же во время работы программа меняла/устанавливала новые/удаляла переменные окружения через, например, setenv / unsetenv, то эти изменения в файле отражены не будут. Эти значения можно уже будет достать только из оперативной памяти, либо если текущая программа вызовет другую через fork или exec - в таком случае для нового процесса будет заново проинициализирован /proc/<pid>/environ
с переменными окружениям родителя/замещённого процесса
Почему так происходит?
Файл /proc/<pid>/environ
находится в директории /proc
, которая, по сути, предоставляет доступ к структурам данных ядра в оперативной памяти через специальный драйвер файловой системы procfs
Чтение переменных окружения из памяти процесса
Будем разбирать на примере кода procfs ядра 5.4
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
(тип mm_struct) процесса возвращаются данные с помощью функции access_remote_vm между адресами памяти, заданными в mm->env_start
и mm->env_end
Значит, в этом непрерывном блоке памяти хранятся данные о переменных окружения. Осталось понять, как в этот блок памяти попадают эти данные, и как определяются адреса env_start
, env_end
начала и конца этого блока
Как переменные окружения попадают в память при запуске процесса
В этой статье подробно рассматривается этот процесс
При запуске процесса через системный вызов execve ядром выполняется функция do_execve. Далее в результате цепочки вызовов в памяти создаётся структура linux_binprm
, в которую через copy_strings
копируются переменные окружения, заданные в envp
при старте процесса.
Далее, после подготовки необходимых структур, в дело вступает непосредственная загрузка исполняемого файла, в которой в функции create_elf_tables в структуре mm
создаётся env_end
и env_start
, содержащие текущий адрес памяти из указателя стека p
. Затем начиная с этого адреса итеративно в память записываются данные указателей на переменные окружения, каждый раз смещая указатель p
. В результате env_end
будет содержать адрес памяти после всех данных
current->mm->env_end = current->mm->env_start = p;
while (envc-- > 0) {
size_t len;
if (__put_user((elf_addr_t)p, sp++))
return -EFAULT;
len = strnlen_user((void __user *)p, MAX_ARG_STRLEN);
if (!len || len > MAX_ARG_STRLEN)
return -EINVAL;
p += len;
}
if (__put_user(0, sp++))
return -EFAULT;
current->mm->env_end = p;
Таким образом получилась непрерывная область памяти в стэке, в которую в runtime уже ничего нельзя дописать, и из которой можно считать данные переменных окружения, благодаря известным env_start
и env_end
, что и делает procfs
в environ_read