Be sure to read the tutorials page to set up your environment and pick a text editor before starting this lab.
s
, n
and b
and do in gdb?Assume you have the following program, segfault.c
that is segfaulting (you can download it here :
#include <stdio.h>
int main () {
int foo[5], n;
memset((char *)0x0, 1, 100);
printf (" Initial value of n is %d \n", n);
return 0;
}
To use GDB, first compile your program using the -g option in cc or gcc, for example: By default, all CMPT-295 builds include the debugger flag.
$ gcc -g -o segfault segfault.c
# If logged into CSIL. You need to do this everytime you log in to CSIL
$ module load cmpt295/cgdb
# If on VM you do not need to do anything.
$ cgdb segfault
To investigate why the program is crashing, run the program first using the run
command. Then, you could try the where
command. It will show you a stack trace, with the source line number where each function in the stack was.
Note: The run command will cause your source code to be loaded in a second window, one of the many advantages of using cgdb.
In this simple case, it is obvious that the program crashed on the memset()
function call.
In real world programs with more complicated code, if the where
command is not enough to isolate the problem, you’ll need to do debugging at a more detailed level.
For that, you may need to set break points at spots where you think the problem might be. In the example shown below, break points are set at line numbers 3, 4, and 6. Then, the program is run once again, while stepping through each break point using the run and next commands respectively:
(gdb) break 3
Breakpoint 1 at 0x80483a8: file segfault.c, line 3.
(gdb) break 4
Breakpoint 2 at 0x80483b8: file segfault.c, line 4.
(gdb) break 6
Breakpoint 3 at 0x80483b8: file segfault.c, line 6.
(gdb)
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: segfault
Breakpoint 1, main () at segfault.c:3
(gdb) next
Breakpoint 2, main () at segfault.c:6
(gdb) next
Program received signal SIGSEGV, Segmentation fault.
0x009d4b47 in memset () from /lib/tls/libc.so.6
(gdb)
Given that the example was too simple in the first place, it should still be obvious that the call to memset()
is what was causing the segfault.
Once you find the bug, you might want to kill the program (that crashed halfway through) using the kill command:
(gdb) help
List of classes of commands:
aliases -- Aliases of other commands
. . .
Type "help" followed by a class name for a list of commands in that class.
Type "help" followed by command name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb)
(gdb) kill
Kill the program being debugged? (y or n) y
(gdb)
After acquiring the source file, you will need to open lab1.c
in your emacs editor of choice. See the tutorials if you are unsure how to make edits.
** If you are familiar
The lab1.c
file contains a number of comments explaining some basics of C. There are five different parts to this lab and you will need to modify or write some lines of code for each one. We recommend keeping a fresh copy of lab1.c
around for reference (as you may lose track of all the changes you end up making).
In particular, it will be helpful for this lab (and for using C moving forward) if you take a little time to familiarize yourself with the printf() function, which is used to output formatted messages to the console.
The source file lab1.c
won’t do anything by itself; you need a compiler (specifically the GNU C compiler) to generate to an executable from it. The GNU C compiler is available on the instructional Linux machines in the lab.
Using any one of these machines, open a terminal and execute gcc -v
. We see:
$ gcc -v
Using built-in specs.
COLLECT\_GCC=gcc
COLLECT\_LTO\_WRAPPER=/opt/rh/devtoolset-7/root/usr/libexec/gcc/x86\_64-redhat-linux/7/lto-wrapper
Target: x86\_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,lto --prefix=/opt/rh/devtoolset-7/root/usr --mandir=/opt/rh/devtoolset-7/root/usr/share/man --infodir=/opt/rh/devtoolset-7/root/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-\_\_cxa\_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --enable-plugin --with-linker-hash-style=gnu --enable-initfini-array --with-default-libstdcxx-abi=gcc4-compatible --with-isl=/builddir/build/BUILD/gcc-7.3.1-20180303/obj-x86\_64-redhat-linux/isl-install --enable-libmpx --enable-gnu-indirect-function --with-tune=generic --with-arch\_32=i686 --build=x86\_64-redhat-linux
Thread model: posix
gcc version 7.3.1 20180303 (Red Hat 7.3.1-5) (GCC)
The output tells me a bunch of the configuration options for the my installation of GCC as well as the version number, which is 7.3.1. Assuming that you have saved lab1.c
somewhere on your machine, navigate to that directory and then use GCC to compile it with the following command:
$ gcc -g -Wall -std=c99 -o lab1.bin lab1.c
It’s not that important right now for you to know what all of these options do, but -g
tells the compiler to include debugging symbols, -Wall
says to print warnings for all types of potential problems, -std=c99
says to use the C99 standard (now only 19 years old!), -o lab1
instructs the compiler to output the executable code to a file called lab1
, and lab1.c
is the source file being compiled.
During execution of that command, you can safely ignore warning about unused variables if you haven’t made any changes yet. This warning would not be shown if you removed -Wall
from the gcc
command, but you will want -Wall
to catch potential errors when you write code yourself.
Having executed the gcc
command, you should be able to see a file named lab1
in the same directory:
$ ls
lab1 lab1.c
The lab1
file is an executable file, which you can run using the command ./lab1.bin
. You should see:
$ ./lab1.bin
Usage: ./lab1.bin <num>
In this case, the executable lab1
is expecting a command-line argument, which is text that is provided to the executable from the command-line when the program is run. In particular, lab1
wants a number from 1 to 5, corresponding to which part of the lab code you want to run. See main()
in lab1.c
for more details. For example (your values of p
and q
may differ):
$ ./lab1.bin 1
\*\*\* LAB 1 PART 1 \*\*\*
x = 295
y = 410
p = 0x7fffaec6a2ec
q = 0x7fffaec6a2e8
x & x = 295
With that, you should have everything you need to complete the assignment. Show your code to the TA; you will want to work on the different parts of the lab in order (from 1 to 5). Each question can be answered and/or verified by appropriate edits to the source code. Note that every time you want to test a code modification, you will need to use the gcc -g -Wall -std=c99 -o lab1.bin lab1.c
command to produce an updated lab1.bin
executable file (Tip: Use the up and down keys to scroll through previous terminal commands you’ve executed).
Most of the code behaviors will seem inexplicable at this point, but our goal is that you will be able to explain to someone else what is going on by the end of this course! =)
You will now debug a C program that implements a simple linked list.
Note that there are two driver files included in the tar file for this lab: driver1.c and driver2.c. Each uses the file linkedlist.c, a dynamically-allocated linked list with two bugs. driver1.c will find one bug, and driver2.c will find the other. Select either driver1.c or driver2.c and use that driver file to complete this exercise. Compile linkedlist.c and driver1.c (or driver2.c):
$ cd part2
$ gcc -g -Wall driver1.c linkedlist.c -o listtest
Run listtest, and notice that this program ends with a segmentation fault. Using GDB, find the bug in this linked list, and fix the code so that the test passes:
$ ./listtest
Test OK.
(If you get stuck, you can ask the TA for a hint or watch the instructor video on the lab page).
You are encouraged to discuss GDB with other students. You should try to find the bug on your own, and let others using the same driver to find it on their own as well.
We will reveal the bugs in the lab/class
The way a running program accesses these additional parameters is that these are passed as parameters to the function main: Here argc means argument count and argument vector. The first argument is the number of parameters passed plus one to include the name of the program that was executed to get those process running. Thus, argc is always greater than zero and argv[0] is the name of the executable (including the path) that was run to begin this process. For example, if we run
int main( int argc, char *argv[] ) {
#include <stdio.h>
int main( int argc, char *argv[] ) {
printf( "argv[0]: %s\n", argv[0] );
return 0;
}
First compile and run it so that the executable name is a.out and then we compile it again and run it so the executable name is arg:
$ gcc argument0.c
$ ./a.out
argv[0]: ./a.out
$ gcc -o arg argument0.c
$ ./arg
argv[0]: ./arg
If more additional command-line arguments are passed, the string of all characters is parsed and separated into substrings based on a few rules; however, if all the characters are either characters, numbers or spaces, the shell will separate the based on spaces and assign args[1] the address of the first, args[2] the address of the second, and so on.
The following program prints all the arguments:
#include <stdio.h>
int main( int argc, char *argv[] ) {
int i;
printf( "argc: %d\n", argc );
printf( "argv[0]: %s\n", argv[0] );
if ( argc == 1 ) {
printf( "No arguments were passed.\n" );
} else {
printf( "Arguments:\n" );
for ( i = 1; i < argc; ++i ) {
printf( " %d. %s\n", i, argv[i] );
}
}
return 0;
}
Here we execute this program with one and then twelve command-line arguments:
$ gcc argument.c
$ ./a.out first
argc: 2
argv[0]: ./a.out
Arguments:
1. first
$ ./a.out first second third fourth
argc: 5
argv[0]: ./a.out
Arguments:
1. first
2. second
3. third
4. fourth