See the previous article in the series, How to use the ObjDump tool with x86.

What are segmentation faults in x86 assembly?

A segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (for example, attempting to write to a read-only location). Let us consider the following x86 assembly example. As we can notice, the preceding program calls the subroutine _printMessage when it is executed. When we  read this program without executing, it looks innocent without any evident problems. Let us assemble and link it using the following commands. section .text global _start _printMessage: mov eax, 4 mov ebx, 1 mov ecx, message mov edx, 32 int 0x80 ret _start: call _printMessage Now, let us run the program and observe the output. As we can notice in the preceding excerpt, there is a segmentation fault when the program is executed.

How to detect segmentation faults in x86 assembly

Segmentation faults always occur during runtime but they can be detected at the code level. The previous sample program that was causing the segmentation faults, is due to lack of an exit routine within the program. So when the program completes executing the code responsible for printing the string, it doesn’t know how to exit and thus lands on some invalid memory address. Another way to detect segmentation faults is to look for core dumps. Core dumps are usually generated when there is a segmentation fault. Core dumps provide the situation of the program at the time of the crash and thus we will be able to analyze the crash. Core dumps must be enabled on most systems as shown below. When a segmentation fault occurs, a new core file will be generated as shown below. As shown in the proceeding excerpt, there is a new file named core in the current directory. $ ls core seg seg.nasm  seg.o $

How to fix segmentation faults x86 assembly

Segmentation faults can occur due to a variety of problems. Fixing a segmentation fault always depends on the root cause of the segmentation fault. Let us go through the same example we used earlier and attempt to fix the segmentation fault. Following is the original x86 assembly program causing a segmentation fault. As mentioned earlier, there isn’t an exit routine to gracefully exit this program. So, let us add a call to the exit routine immediately after the control is returned from  _printMessage. This looks as follows section .text global _start _printMessage: mov eax, 4 mov ebx, 1 mov ecx, message mov edx, 32 int 0x80 ret _start: call _printMessage Notice the additional piece of code added in the preceding excerpt. When _printMessage completes execution, the control will be transferred to the caller and call _exit instruction will be executed, which is responsible for gracefully exiting the program without any segmentation faults. To verify, let us assemble and link the program using the following commands. section .text global _start _printMessage: mov eax, 4 mov ebx, 1 mov ecx, message mov edx, 32 int 0x80 ret _exit: mov eax, 1 mov ebx, 0 int 0x80 _start: call _printMessage call _exit Run the binary and we should see the following message without any segmentation fault. As mentioned earlier, the solution to fix a segmentation fault always depends on the root cause. $

How to fix segmentation fault in c

Segmentation faults in C programs are often seen due to the fact that C programming offers access to low-level memory. Let us consider the following example written in C language. The preceding program causes a segmentation fault when it is run. The string variable str in this example stores in read-only part of the data segment and we are attempting to modify read-only memory using the line *(str+1) = ‘x’; char *str; str = “test string”; *(str+1) = ‘x’; return 0; } Similarly, segmentation faults can occur when an array out of bound is accessed as shown in the following example. This example also leads to a segmentation fault. In addition to it, if the data being passed to the test variable is user-controlled, it can lead to stack-based buffer overflow attacks. Running this program shows the following error due to a security feature called stack cookies. char test[3]; test[4] = ‘A’; } The preceding excerpt shows that the out-of-bound access on an array can also lead to segfaults. Fixing these issues in C programs again falls back to the reason for the Segfault. We should avoid accessing protected memory regions to minimize segfaults. Aborted (core dumped) $

How to debug segmentation fault

Let us go through our first x86 example that was causing a segfault to get an overview of debugging segmentation faults using gdb. Let us begin by running the program, so we can get the core dump when the segmentation fault occurs. Now, a core dump should have been generated. Let us load the core dump along with the target executable as shown in the following command. Loading the executable along with the core dump makes the debugging process much easier. $ As we can notice in the preceding output, the core dump is loaded using GDB and the segmentation fault occurred at the address 0x0804901c. To confirm this, we can check the output of info registers. 78 commands loaded for GDB 9.1 using Python engine 3.8 [*] 2 commands could not be loaded, run gef missing to know why. [New LWP 6172] Core was generated by `./print’. Program terminated with signal SIGSEGV, Segmentation fault. #0  0x0804901c in ?? () gef➤ As highlighted, the eip register contains the same address. This means, the program attempted to execute the instruction at this address and it has resulted in a segmentation fault. Let us go through the disassembly and understand where this instruction is. ecx            0x804a000           0x804a000 edx            0x20                0x20 ebx            0x1                 0x1 esp            0xffbe8aa0          0xffbe8aa0 ebp            0x0                 0x0 esi            0x0                 0x0 edi            0x0                 0x0 eip            0x804901c           0x804901c eflags         0x10202             [ IF RF ] cs             0x23                0x23 ss             0x2b                0x2b ds             0x2b                0x2b es             0x2b                0x2b fs             0x0                 0x0 gs             0x0                 0x0 gef➤ First, let us get the list of functions available and identify which function possibly caused the segfault. As highlighted in the preceding excerpt, the _printMessage and _start functions’ address ranges are close to the address that caused the segmentation fault. So, let us begin with the disassembly of the function _printMessage. Non-debugging symbols: 0x08049000  _printMessage 0x08049017  _start 0xf7fae560  __kernel_vsyscall 0xf7fae580  __kernel_sigreturn 0xf7fae590  __kernel_rt_sigreturn 0xf7fae9a0  __vdso_gettimeofday 0xf7faecd0  __vdso_time 0xf7faed10  __vdso_clock_gettime 0xf7faf0c0  __vdso_clock_gettime64 0xf7faf470  __vdso_clock_getres gef➤ Let us set a breakpoint at ret instruction and run the program. The following command shows how to setup the breakpoint. 0x08049000 <+0>: mov    eax,0x4 0x08049005 <+5>: mov    ebx,0x1 0x0804900a <+10>: mov    ecx,0x804a000 0x0804900f <+15>: mov    edx,0x20 0x08049014 <+20>: int    0x80 0x08049016 <+22>: ret End of assembler dump. gef➤ Type run to start the program execution. gef➤ As we can notice in the preceding excerpt, when the ret instruction gets executed, the control gets passed to the region not controlled by the program code leading to unauthorized memory access and thus a segmentation fault. 0x8049014 <_printMessage+20> int    0x80 →  0x8049016 <_printMessage+22> ret ↳   0x804901c                  add    BYTE PTR [eax], al 0x804901e                  add    BYTE PTR [eax], al 0x8049020                  add    BYTE PTR [eax], al 0x8049022                  add    BYTE PTR [eax], al 0x8049024                  add    BYTE PTR [eax], al 0x8049026                  add    BYTE PTR [eax], al

Conclusion

This article has outlined some basic concepts around segmentation faults in x86 assembly and how one can use them for debugging programs. We have seen various simple examples to better understand the concepts. We briefly discussed core dumps, which can help us to detect and analyze program crashes. See the next article in this series, How to control the flow of a program in x86 assembly.  

Sources

https://www.geeksforgeeks.org/core-dump-segmentation-fault-c-cpp/ http://www.brendangregg.com/blog/2016-08-09/gdb-example-ncurses.html https://embeddedbits.org/linux-core-dump-analysis/