A typical process runs within its own virtual address space, which is distinct from the virtual address spaces of other processes. The virtual address space typically contains four distinct types of content:
Executable code. This part of the virtual address space contains the machine code instructions to be executed by the processor. It is often write protected and shared among processes that use the same main program or the same shared libraries.
Static data. This part of the virtual address space contains the statically allocated variables to be used by the process.
Heap. This part of the virtual address space contains the dynamically allocated variables to be used by the process.
Stack. This part of the virtual address space contains the stack to be used by the process for storing items such as return addresses, procedure arguments, temporarily saved registers or locally allocated variables.
Each distinct type of content typically occupies one or several continuous blocks of memory within the virtual address space. The initial placement of these blocks is managed by the loader of the operating system, the content of these blocks is managed by the process owning them.
The blocks that contain executable code and static data are of little interest from the process memory management point of view as their layout is determined by the compiler and does not change during process execution. The blocks that contain stack and heap, however, change during process execution and merit further attention.
While the blocks containing the executable code and static data are fixed in size, the blocks containing the heap and the stack may need to grow as the process owning them executes. The need for growth is difficult to predict during the initial placement of the blocks. To avoid restricting the growth by placing either heap or stack too close to other blocks, they are typically placed near the opposite ends of the process virtual address space with an empty space between them. The heap block is then grown upwards and the stack block downwards as necessary.
When multiple blocks of memory within the virtual address space need to grow as the process owning them executes, the initial placement of the blocks becomes a problem. This can be partially alleviated by using hardware that supports large virtual addresses, where enough empty space can be set aside between the blocks without exhausting the virtual address space, or by using hardware that supports segmentation, where blocks can be moved in the virtual address space as necessary.
In Linux, the location of blocks of memory within the virtual address
space of a process is exported by the virtual memory manager of the operating
system in the maps
file of the proc
filesystem.
> cat /proc/self/maps 00111000-00234000 r-xp 00000000 03:01 3653725 /lib/libc-2.3.5.so 00234000-00236000 r-xp 00123000 03:01 3653725 /lib/libc-2.3.5.so 00236000-00238000 rwxp 00125000 03:01 3653725 /lib/libc-2.3.5.so 00238000-0023a000 rwxp 00238000 00:00 0 007b5000-007cf000 r-xp 00000000 03:01 3653658 /lib/ld-2.3.5.so 007cf000-007d0000 r-xp 00019000 03:01 3653658 /lib/ld-2.3.5.so 007d0000-007d1000 rwxp 0001a000 03:01 3653658 /lib/ld-2.3.5.so 008ed000-008ee000 r-xp 008ed000 00:00 0 [vdso] 08048000-0804d000 r-xp 00000000 03:01 3473470 /bin/cat 0804d000-0804e000 rw-p 00004000 03:01 3473470 /bin/cat 09ab8000-09ad9000 rw-p 09ab8000 00:00 0 [heap] b7d88000-b7f88000 r--p 00000000 03:01 6750409 /usr/lib/locale/locale-archive b7f88000-b7f89000 rw-p b7f88000 00:00 0 b7f96000-b7f97000 rw-p b7f96000 00:00 0 bfd81000-bfd97000 rw-p bfd81000 00:00 0 [stack]
The example shows the location of blocks of memory within the virtual
address space of the cat
command. The first column of the
example shows the address of the blocks, the second column shows the flags, the
third, fourth, fifth and sixth columns show the offset, device, inode and name
of the file that is mapped into the block, if any. The blocks that contain
executable code are easily distinguished by the executable flag. Similarly, the
blocks that contain read-only and read-write static data are easily
distinguished by the readable and writeable flags and the file that is mapped
into the block. Finally, the blocks with the readable and writeable flags but no
file contain the heap and the stack.
The address of the blocks is often randomized to prevent buffer overflow attacks on the process. The attacks are carried out by supplying the process with an input that will cause the process to write past the end of the buffer allocated for the input. When the buffer is a locally allocated variable, it resides on the stack and being able to write past the end of the buffer means being able to modify return addresses that also reside on the stack. The attack can therefore overwrite some of the input buffers with malicious machine code instructions to be executed and overwrite some of the return addresses to point to the malicious machine code instructions. The process will then unwittingly execute the malicious machine code instructions by returning to the modified return address. Randomizing the addresses of the blocks makes this attack more difficult.