Illegal Memory Access and Segmentation Fault
Introduction
In computer programming, when we use pointers for accessing memory without proper bounds checking, it can lead to illegal memory access. With the presence of illegal memory access, sometimes the computer program may encounter a segmentation fault, but sometimes it may not.
In this blog post, I would like to quickly discuss why illegal memory access sometimes may not cause a segmentation fault.
Illegal Memory Access and Segmentation Fault
Memory Access Boundary Checking
Frequent memory access boundary checking in software implementations can be expensive, especially in performance-critical applications. Without proper boundary checking, however, might result in undefined behavior if the program accesses memory outside the allocated bounds.
Sometimes, such undefined behavior results in a segmentation fault. This is because the operating system detects that the computer program is trying to access memory that it does not own, and will terminate program execution to prevent the program from corrupting the other software running. That’s why we could usually write buggy programs without damaging the operating system. Unlike the memory access boundary checking implemented in the software, the memory access boundary checking implemented in the operating system is more efficient and is accelerated by the hardware, such as the memory management unit (MMU) in the CPU.
How the operating system detects such illegal memory access is implementation-dependent. But the simplest way is to check the address of the memory access against the address range of the allocated memory for the computer program. If the address is outside the allocated memory range, then the operating system will terminate the program, resulting in a segmentation fault. However, if the address is within the allocated memory range, even if the address is intended to be out of bounds, the operating system will not terminate the program.
Example
The following C++ program demonstrates how illegal memory access can lead to a segmentation fault or not, depending on the offset used for accessing the out-of-bounds element.
1 |
|
On my computer, the output of the program is as follows.
1 | $ g++ oob.cpp -o oob |
We could see when we tried to access the index of 32
, 41
, 131
, 1031
, and 10031
in the vector of size 32
, the program did not crash with a segmentation fault. However, when we tried to access the index of 100031
, the program crashed with a segmentation fault.
Using Valgrind, we could know the size of memory allocated for the computer program.
1 | $ valgrind --tool=memcheck ./oob |
In our case, we have total heap usage: 3 allocs, 3 frees, 73,856 bytes allocated
. So the memory allocated for the computer program is 73,856
bytes, which can be translated to a vector of 18,464
integers (assuming each integer is 4
bytes). Therefore, the out-of-bounds access at index 100031
is definitely outside the allocated memory range, which results in a segmentation fault, whereas the out-of-bounds access at index 32
, 41
, 131
, 1031
, and 10031
are still within the allocated memory range by chance, which does not result in a segmentation fault.
Interestingly, Valgrind detects the out-of-bounds access not only at index 100031
but also all the other out-of-bounds accesses at index 32
, 41
, 131
, 1031
, and 10031
. This is because Valgrind runs a virtual memory manager that keeps track of all memory allocations and deallocations, allowing it to detect illegal memory accesses more easily. In our case, Valgrind knows that the vector v
corresponds to a block of size 128 allocated bytes. When Valgrind sees an instruction corresponding to v[32]
, it knows this is an out-of-bounds access by checking the allocated block size of v
. We might try some trick to fool Valgrind, such as float const* ptr{&v[31]}; ptr[1];
which is equivalent as v[32]
. But it will not work, because Valgrind tracks the provenance of pointers, not just their final memory address. That is also to say, Valgrind will translate float const* ptr{&v[31]}; ptr[1];
to v[32]
and know this is an out-of-bounds access.
In short, the operating system books keeping the memory addresses at the program level, while Valgrind books keeping the memory addresses at the more fine-grained object level. Therefore, Valgrind can detect the out-of-bounds access more accurately.
Conclusions
The illegal memory access may not always result in a segmentation fault from the operating system, depending on whether the illegal access is still within the allocated memory for the computer program. If the illegal memory access is outside the allocated memory range, it will result in a segmentation fault and the developer will be alarmed. However, if the illegal memory access is still within the allocated memory range, it will not result in a segmentation fault but the computer program behavior becomes undefined, which can be hard to debug.
Illegal Memory Access and Segmentation Fault
https://leimao.github.io/blog/Illegal-Memory-Access-Segmentation-Fault/