Environment Variables in Linux
Last week, one of my friend who's new to Linux asked me some quesitons about environment variables in Linux. It was not too difficult for me to answer her questions, but it trigged me to think how does Linux handles environment variables. Where does the system store the environment variables?
According to the Wikipedia page, "in all Unix and Unix-like systems, as well as on Windows, each process has its own separate set of environment variables. By default, when a process is created, it inherits a duplicate run-time environment of its parent process, except for explicit changes made by the parent when it creates the child. At the API level, these changes must be done between running fork and exec."
Hey, this gave us some hit that the handling of environment variables is related with function call fork and exec. Thanks to the people who created the manual page for the exec function call, I noticed it mentioned some external variable named environ as part of the SYNOPSIS section.
EXEC(3) Linux Programmer's Manual EXEC(3)
NAME
execl, execlp, execle, execv, execvp, execvpe - execute a file
SYNOPSIS
#include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg, ...
/* (char *) NULL */);
The SEE ALSO section also mentioned there is a manual for environ in chapter 7 of the manuals.
SEE ALSO
sh(1), execve(2), execveat(2), fork(2), ptrace(2), fexecve(3), system(3), environ(7)
The environ(7) man page provides more detailed information about the variable.
ENVIRON(7) Linux Programmer's Manual ENVIRON(7)
NAME
environ - user environment
SYNOPSIS
extern char **environ;
DESCRIPTION
The variable environ points to an array of pointers to strings called the "environment". The last pointer in this array has the value NULL. (This variable must be declared in the user program, but is declared in the header file <unistd.h> if the _GNU_SOURCE feature test macro is defined.) This array of strings is made available to the process by the exec(3) call that started the process. When a child process is created via fork(2), it inherits a copy of its parent's environment.
By convention the strings in environ have the form "name=value".
As the manual page mentioned unistd.h, I was able to find the .h file and found the location where it's declared.
/* NULL-terminated array of "NAME=VALUE" environment variables. */
extern char **__environ;
#ifdef __USE_GNU
extern char **environ;
#endif
The actual definition of the variable is in glibc's environ.c file.
/* This must be initialized; we cannot have a weak alias into bss. */
char **__environ = NULL;
weak_alias (__environ, environ)
But how does it look like when a program is running? To answer the question, I created a simple C program.
$ cat test.c
#include <stdio.h>
int main(void)
{
int test_val=0;
printf("Hello World!\n");
return test_val;
}
Then compiled the program with gcc and its -ggdb option.
$ gcc -ggdb -o test test.c
$
It created the target binary file named test with debug information for gdb. After that, we can debug the programe with gdb command.
$ gdb -q test
Reading symbols from test...
(gdb) list
1 #include <stdio.h>
2
3 int main(void)
4 {
5 int test_val=0;
6 printf("Hello World!\n");
7 return test_val;
8 }
In order to check the variables and their values, I set a breakpoint on line 6 and let the program run.
(gdb) break 6
Breakpoint 1 at 0x115c: file test.c, line 6.
(gdb) run
Starting program: /home/mrbob/test/test
Breakpoint 1, main () at test.c:6
6 printf("Hello World!\n");
With gdb, it confirmed that the variable named environ is avaiable in the current context.
(gdb) info address environ
Symbol "environ" is static storage at address 0x7ffff7fa9600.
(gdb) info symbol 0x7ffff7fa9600
environ in section .bss of /lib/x86_64-linux-gnu/libc.so.6
0x7ffff7fa9600 is the address where the variable exists. According to the memory mapping of the process, it is in the memory space for this process.
(gdb) info proc mappings
process 18924
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x555555554000 0x555555555000 0x1000 0x0 /home/mrbob/test/test
0x555555555000 0x555555556000 0x1000 0x1000 /home/mrbob/test/test
0x555555556000 0x555555557000 0x1000 0x2000 /home/mrbob/test/test
0x555555557000 0x555555558000 0x1000 0x2000 /home/mrbob/test/test
0x555555558000 0x555555559000 0x1000 0x3000 /home/mrbob/test/test
0x7ffff7dba000 0x7ffff7ddc000 0x22000 0x0 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7ddc000 0x7ffff7f54000 0x178000 0x22000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7f54000 0x7ffff7fa2000 0x4e000 0x19a000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa2000 0x7ffff7fa6000 0x4000 0x1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa6000 0x7ffff7fa8000 0x2000 0x1eb000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa8000 0x7ffff7fae000 0x6000 0x0
0x7ffff7fc9000 0x7ffff7fcd000 0x4000 0x0 [vvar]
0x7ffff7fcd000 0x7ffff7fcf000 0x2000 0x0 [vdso]
0x7ffff7fcf000 0x7ffff7fd0000 0x1000 0x0 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7fd0000 0x7ffff7ff3000 0x23000 0x1000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ff3000 0x7ffff7ffb000 0x8000 0x24000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffc000 0x7ffff7ffd000 0x1000 0x2c000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffd000 0x7ffff7ffe000 0x1000 0x2d000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffe000 0x7ffff7fff000 0x1000 0x0
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
More over, we can even check the strings defined in the array.
(gdb) print (char*)environ[0]
$14 = 0x7fffffffe347 "SHELL=/bin/bash"
(gdb) print (char*)environ[1]
$15 = 0x7fffffffe357 "SESSION_MANAGER=local/Bob-Desktop:@/tmp/.ICE-unix/2702,unix/Bob-Desktop:/tmp/.ICE-unix/2702"
(gdb) print (char*)environ[2]
$16 = 0x7fffffffe3b3 "QT_ACCESSIBILITY=1"
Comments
Post a Comment