2010年(37)
分类: LINUX
2010-06-10 15:35:49
This article is an excerpt from the book "" (ISBN: 0-9541617-9-3), published by Network Theory Ltd.
For more information, visit the .
Normally an executable file does not contain any references to the original program source code, such as variable names or line-numbers--the executable file is simply the sequence of machine code instructions produced by the compiler. This is insufficient for debugging, since there is no easy way to find the cause of an error if the program crashes.
GCC provides the -g
debug option to store additional debugging information in object files and executables. This debugging information allows errors to be traced back from a specific machine instruction to the corresponding line in the original source file, and permits the execution of a program to be traced in a debugger, such as the GNU Debugger gdb
. Using a debugger also allows the values of variables to be examined while the program is running.
The debug option works by storing the names of functions and variables (and all the references to them) with the corresponding source code line-numbers in a symbol table in object files and executables.
One helpful application of the -g
debugging option is to find the circumstances of a program crash.
When a program exits abnormally the operating system can write out a core file, usually named 'core', which contains the in-memory state of the program at the time it crashed. Combined with information from the symbol table produced by -g
, the core file can be used to find the line where the program stopped, and the values of its variables at that point.
This is useful both during the development of software, and after deployment--it allows problems to be investigated when a program has crashed "in the field".
Here is a simple program containing a bug which we will use to cause a crash and produce a core file:
int a (int *p); int main (void) { int *p = 0; /* null pointer */ return a (p); } int a (int *p) { int y = *p; return y; }
The program attempts to dereference a null pointer p
, which is an invalid operation. On most systems this will cause a crash--historically, a null pointer corresponds to memory location 0, which is usually restricted to the operating system kernel and not accessible to user programs.
In order to be able to find the cause of the crash later, we can compile the program with the -g
option:
$ gcc -Wall -g null.c
Note that a null pointer will only cause a problem at run-time, so the option -Wall
does not produce any warnings.
Running the executable file on an x86 GNU/Linux system will cause the operating system to terminate the program abnormally:
$ ./a.out Segmentation fault (core dumped)
Whenever the error message 'core dumped' is displayed, the operating system should produce a file called 'core' in the current directory. This core file contains a complete copy of the pages of memory used by the program at the time it was terminated. Incidentally, the term segmentation fault refers to the fact that the program tried to access a restricted memory "segment" outside the area of memory which had been allocated to it.
Some systems are configured not to write core files by default, since the files can be large and rapidly fill up the available disk space on a system. In the GNU Bash shell the command ulimit -c
controls the maximum size of core files. If the size limit is set to zero, no core files are produced. The current size limit can be shown by typing the following command:
$ ulimit -c 0
If the result is zero, as shown above, then it can be increased with the following command to allow core files of any size to be written:
$ ulimit -c unlimited
Note that this setting only applies to the current shell. To set the limit for future sessions the command should be placed in an appropriate login file, such as '.bash_profile' for the GNU Bash shell.
Core files can be loaded into the GNU Debugger gdb
with the following command:
$ gdb EXECUTABLE-FILE CORE-FILE
Note that both the original executable file and the core file are required for debugging--it is not possible to debug a core file without the corresponding executable. In this example we can load the executable and core file with the command:
$ gdb a.out core
The debugger immediately begins printing diagnostic information, and shows a listing of the line where the program crashed (line 13):
$ gdb a.out core Core was generated by './a.out'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 #0 0x080483ed in a (p=0x0) at null.c:13 13 int y = *p; (gdb)
The final line (gdb)
is the GNU Debugger prompt--it indicates that further commands can be entered at this point.
To investigate the cause of the crash we display the value of the pointer p
using the print
command:
(gdb) print p $1 = (int *) 0x0
This shows that p
is a null pointer (0x0
) of type 'int *', so we know that dereferencing it with the expression *p
in this line has caused the crash.
The debugger can also show the function calls and arguments up to the current point of execution--this is called a stack backtrace and is displayed with the command backtrace
:
(gdb) backtrace #0 0x080483ed in a (p=0x0) at null.c:13 #1 0x080483d9 in main () at null.c:7
In this case the backtrace shows that the crash at line 13 occurred when the function a()
was called with an argument of p=0x0
from line 7 in main()
. It is possible to move to different levels in the stack trace, and examine their variables, using the debugger commands up
and down
.
A complete description of all the commands available in gdb
can be found in the manual "Debugging with GDB: The GNU Source-Level Debugger".