Переменные окружения процесса в linux

Как получить переменные окружения процесса

Простой ответ: прочитав файл /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