5.1 Examining core files
In addition to allowing programs to be run under the debugger, an
important benefit of the -g
option is the ability to examine
the cause of a program crash from a "core dump".
When a program exits abnormally (i.e. crashes) 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. This file is
often referred to as a core dump.(14) Combined with information from the
symbol table produced by -g
, the core dump 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 an invalid memory access bug, which
we will use to produce a core file:
int foo (int *p);
int
main (void)
{
int *p = 0; /* null pointer */
return foo (p);
}
int
foo (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. (15)
In order to be able to find the cause of the crash later, we will need
to 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.(16) 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 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:(17)
$ 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 foo (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 debugger 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.