Debugging a simple program after SIGSEGV

Hi!


Long time no see! Sorry but I was very busy during these months. From today, I'll try to post some interesting things.


There are some interesting things I learnt during these years about debugging programs. For example, you try to access an invalid pointer and your program crashes. Depending on how big is your program, it's very difficult to get why the crash happened.


I'll explain a simple way to get SEGMENTATION FAULT and fix it.


For simple definition:
It means that your program is trying to access a memory address that not belongs to it.


Simple example:
Let's suppose that your program wants to access memory address of Linux Kernel (of course, you cannot do it!)


Let's start ( Get this source code ):
1:  #include <stdio.h>  
2:  #include <stdlib.h>  
3:  #include <execinfo.h>  // Register Signals
4:  #include <signal.h>    // Get Stack Trace
5:  #include <unistd.h>  
6:    
7:  void handler ( int sig ) {  
8:      void *array [ 10 ];  
9:      size_t size;  
10:    
11:      // get void*'s for all entries on the stack  
12:      size = backtrace ( array, 10 );  
13:    
14:      // print out all the frames to stderr  
15:      fprintf ( stderr, "Error: signal %d\n", sig );  
16:    
17:      char **buffer = backtrace_symbols ( array, size );  
18:    
19:      if ( buffer == 0 ) {  
20:          fprintf ( stderr, "backtrace_symbols" );  
21:          exit ( 1 );  
22:      }  
23:    
24:      fprintf ( stderr, "Obtained %zd stack frames.\n", size );  
25:      for ( size_t i = 0; i < size; i++ ) {  
26:          fprintf ( stderr, "%d. %s - %p\n", i, buffer [ i ], array [ i ] );  
27:      }  
28:      fprintf ( stderr, "\n\n" );  
29:    
30:      free ( buffer );  
31:      exit ( 1 );  
32:  }  
33:    
34:  void baz ( ) {  
35:      int *foo = ( int * ) -1;   // Make a bad pointer  
36:      printf ( "%d\n", *foo ); // causes segfault  
37:  }  
38:    
39:  void bar ( ) {  
40:      baz ( );  
41:  }  
42:    
43:  void foo ( ) {  
44:      bar ( );  
45:  }  
46:    
47:  int main ( int argc, char ** argv ) {  
48:      signal ( SIGSEGV, handler );  
49:      foo ( );  
50:      return 0;  
51:  }  

First of all, we need to include the following header files.


3:  #include <execinfo.h>    // Register signals
4:  #include <signal.h>      // Get Stack Trace

Also, we need to register a handler for SIGSEGV.

48:      signal ( SIGSEGV, handler );

Let's implement the handler callback.


7:  void handler ( int sig ) {
        ...
32: }

"backtrace" function needs an array of pointer void and the maximum size of it.


8:      void *array [ 10 ];

The variable "size" will keep how many items "backtrace" function will return.


9:      size_t size;

So, invoking "backtrace" function, and keep the results:


12:      size = backtrace ( array, 10 );

The "handler" callback has one parameter. When a signal is raised, the handler is invoked and the signal type is passed as argument.


14:      // print out all the frames to stderr
15:      fprintf ( stderr, "Error: signal %d\n", sig );

Since the "array" variable is just an array of memory address, the function list is not human readable. So, to get function names (if it's available), just invoke "backtrace_symbols". You need to declare an array of chars.


19:      if ( buffer == 0 ) {
20:          fprintf ( stderr, "backtrace_symbols" );
21:          exit ( 1 );
22:      }

Now, print to console, the array of chars.


24:      fprintf ( stderr, "Obtained %zd stack frames.\n", size );
25:      for ( size_t i = 0; i < size; i++ ) {
26:          fprintf ( stderr, "%d. %s - %p\n", i, buffer [ i ], array [ i ] );
27:      }

Free the array of strings. It's not necessary to free each string (check documentation).

30:      free ( buffer );


To avoid a strange behaviour, let's exit the application.


31:      exit ( 1 );

So far, so good!

Let's make some function invocations to force SIGSEGV.

Since "backtrace" function will print a stack of function calls, we'll invoke some functions until it reaches the problem line. To raise SIGSEGV, we need an invalid pointer.


The "baz" function has the problem line. A negative value is assigned to a pointer. Pointers cannot be negative (invalid memory address). If you try to use it, a signal of type SIGSEGV is raised.


34:  void baz ( ) {  
35:      int *foo = ( int * ) -1;   // Make a bad pointer  
36:      printf ( "%d\n", *foo );   // it causes SEGFAULT
37:  }  
38:    
39:  void bar ( ) {  
40:      baz ( );  
41:  }  
42:    
43:  void foo ( ) {  
44:      bar ( );  
45:  }  
46:    
47:  int main ( int argc, char ** argv ) {  
48:      signal ( SIGSEGV, handler );  
49:      foo ( );  
50:      return 0;  
51:  }  

Finally, compile using the following parameters:

$ gcc example.c -o example -std=c99 -g  


The compiler needs to produce symbols, so "-g" and "-rdynamic" are used. If you do not specify them, just function address is printed (of course, there's some ways to retrieve these information - it could be a subject to future post).


Now, run the program. You should get something like:



Let's check the output:
1. Each line has an index (from 0 to 7). The last function invocation is the index 0. As you could see, the "handler" just print the stack trace and finish this application.
2. The right part of each line is the function address in hexadecimal values.
3, If you could not see any function names (only function address), it means that there's no libraries available with symbols (compiled with "-g").


So, I could consider the line with index 2 whose function is "baz" caused the segmentation fault.

Analysing this function, we could see that it tries to print an invalid address (line 36 of source code, a negative address).

34:  void baz ( ) {  
35:      int *foo = ( int * ) -1;   // Make a bad pointer  
36:      printf ( "%d\n", *foo );   // it causes SEGFAULT
37:  }  


It's very simple explanation about Segmentation Fault and how we could handle it.


Thanks for reading!


See you!

References:



Comments