Consider this example:
int global_x = 5;
int global_y;
int main()
{
int local_a;
char* buf = malloc(100);
}
While the following descriptions are not always the case (particularly in tiny embedded implementations), a typical program on a modern platform will be layed out as follows:
global_x
is an initialized global variable. It is typically stored in a .data
section in the executable file, and when the program is loaded, it lives in read-write, non-executable section of memory along side your program text.
global_y
is an uninitialized global variable. Space is reserved in the .bss
section of the program, and takes up no space in the executable file. It lives in a simliar section as above when loaded.
local_a
is a local (or "automatic") variable that lives in the call stack of main. Its lifetime is constrained to the duration of execution in that function. (i.e. Once the function returns, the variable does not exist anymore, and is trashed.)
buf
points to a dynamically-allocated buffer of 100 bytes that is pulled from the heap. The heap is an area of memory that usually will expand as needed to accomodate the memory requirements of the application. When heap space is exhausted (as determined by malloc
and supporting library functions), additional memory will be requested from the operating system (e.g. brk
or mmap
on Linux).
On Linux, you can look at /prod/[pid]/maps
to see the memory mapping for the process [pid]
. For example:
$ cat /proc/30009/maps
00400000-0040c000 r-xp 00000000 fd:01 268784 /usr/bin/cat
0060b000-0060c000 r--p 0000b000 fd:01 268784 /usr/bin/cat
0060c000-0060d000 rw-p 0000c000 fd:01 268784 /usr/bin/cat
01a7a000-01a9b000 rw-p 00000000 00:00 0 [heap]
365ec00000-365ec20000 r-xp 00000000 fd:01 263066 /usr/lib64/ld-2.18.so
365ee1f000-365ee20000 r--p 0001f000 fd:01 263066 /usr/lib64/ld-2.18.so
365ee20000-365ee21000 rw-p 00020000 fd:01 263066 /usr/lib64/ld-2.18.so
365ee21000-365ee22000 rw-p 00000000 00:00 0
365f000000-365f1b4000 r-xp 00000000 fd:01 263128 /usr/lib64/libc-2.18.so
365f1b4000-365f3b4000 ---p 001b4000 fd:01 263128 /usr/lib64/libc-2.18.so
365f3b4000-365f3b8000 r--p 001b4000 fd:01 263128 /usr/lib64/libc-2.18.so
365f3b8000-365f3ba000 rw-p 001b8000 fd:01 263128 /usr/lib64/libc-2.18.so
365f3ba000-365f3bf000 rw-p 00000000 00:00 0
7f685162b000-7f6857b54000 r--p 00000000 fd:01 302750 /usr/lib/locale/locale-archive
7f6857b54000-7f6857b57000 rw-p 00000000 00:00 0
7f6857b6c000-7f6857b6d000 rw-p 00000000 00:00 0
7fffa6f09000-7fffa6f2a000 rw-p 00000000 00:00 0 [stack]
7fffa6ffe000-7fffa7000000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
We see that there are three parts of /usr/bin/cat
mapped in at 0x400000
, 0x60b000
, and 0x60c000
.
By looking at the output of readelf -a /usr/bin/cat
you can identify which sections correspond to the segments that are loaded at those addresses. This is left as an excercise to the reader :-)