The GNU Project Debugger, or GDB for short, is a very useful debugging tool. It allows you to see what the values in your program look like at any given point in time, and it lets you temporarily modify those values to see how your program runs in response, acting as if you actually made those changes in your code.
In the example below, a student is working on a program but encounters a segmentation fault (also known as a “segfault”). They’re not sure what’s causing it, but thanks to GDB, they can see what’s going on “under the hood” of their program and try to solve the problem.
#include <stdio.h>
void multiply_by_two(int *current, int *arr);
int main(void)
{
int arr[5] = {10, 20, 30, 40, 50};
int *current = arr;
int i = 0;
("Before\n");
printf("------\n");
printf
for (i = 0; i < 5; i++)
{
("arr[%d] = %d\n", i, *current);
printf++;
current}
= NULL;
current (current, arr);
multiply_by_two
("\nAfter\n");
printf("-----\n");
printf
for (i = 0; i < 5; i++)
{
("arr[%d] = %d\n", i, *current);
printf++;
current}
("\nAll done!\n");
printf
return 0;
}
void multiply_by_two(int *current, int *arr)
{
int i = 0;
= arr;
current
for (i = 0; i < 5; i++)
{
*current = *current * 2;
++;
current}
= arr;
current }
Okay, everything looks alright. Time to compile.
my-pc:~intro-to-c/assignments/pointers$ gcc my_file.c
my-pc:~intro-to-c/assignments/pointers$
Yes, no warnings! Okay, now let me run it.
my-pc:~intro-to-c/assignments/pointers$ ./a.out
Before
------
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50
After
-----
Segmentation fault
No, not a segfault! This can’t be possible–my code is perfect! Fine, I guess I’ll just have to debug it. Time to start typing up print statements… or maybe I can try that debugger I’ve been reading about. What is it called again? Oh, yeah, GDB.
Alright, first I have to recompile my code with the -g
flag.
my-pc:~intro-to-c/assignments/pointers$ gcc -g my_file.c
my-pc:~intro-to-c/assignments/pointers$
Using the
-g
flag adds debugging information to your compiled file. Alternatively, you could use the-ggdb
flag, which adds debugging information specifically meant for GDB (e.g.,gcc -ggdb my_file.c
).
Now I need to pass GDB the program’s name, and in I go.
my-pc:~intro-to-c/assignments/pointers$ gdb a.out
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...
(gdb)
This is what it looks like once you enter GDB. It is a command-line program that you interact with using a wide range of commands. This case study will explore some basic commands to get you started.
You may find it useful to pass the
-q
flag to GDB on start-up if you don’t want to see the introductory and copyright information (e.g.,gdb -q a.out
).
Let me run the program and see where it’s stopping.
(gdb) run
Starting program: /home/my-pc/intro-to-c/assignments/pointers/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Before
------
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50
After
-----
Program received signal SIGSEGV, Segmentation fault.
0x000055555555527c in main () at my_file.c:28
28 printf("arr[%d] = %d\n", i, *current);
(gdb)
The
run
command runs your program as if it were running normally in the terminal. Here, we see that the program stopped on the same segmentation fault from earlier.
Hmm, what do the variables look like when I print them?
(gdb) print i
$1 = 0
(gdb) print &arr
$2 = (int (*)[5]) 0x7fffffffdfe0
(gdb) print current
$3 = (int *) 0x0
(gdb)
The
Okay, since i
is 0, that means the segfault happened on
my first time through the loop. I can also see that current
clearly isn’t pointing to arr
’s address.
In fact, the address in current
is NULL
, so
it looks like I dereferenced a NULL
pointer. I’ll continue
and see if anything else pops up.
(gdb) continue
Continuing.
Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb)
The
continue
command lets your program continue running as it normally would. The program will still stop on any breakpoints, a concept we will look at next.
Alright, so the problem is happening before the second for
loop, but where? I’ll set a breakpoint at the top of main()
so I can move through the program and try to figure it out.
(gdb) break main
Breakpoint 1 at 0x555555555195: file my_file.c, line 6.
(gdb) run
Starting program: /home/my-pc/intro-to-c/assignments/pointers/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, main () at my_file.c:6
6 {
(gdb)
The
break
command lets you set breakpoints, specific points where you want your program to stop while it’s running. A breakpoint gives you a location from which you can choose how and where you want to proceed, whether line-by-line or multiples lines at a time.
Here, we pass
break
a function name as an argument, but you can also pass it a line number (e.g.,break 6
). If you wanted to be more specific, you could pass it a location written as a file name, colon, and either a function name or a line number (e.g.,break my_file.c:main
,break my_file.c:6
).
To see all of your current breakpoints, run
info break
.
To delete specific breakpoints, run
delete [breakpoint_number_or_range]
(e.g.,delete 1
,delete 2 3
,delete 4-7
). To delete all breakpoints, typedelete
with no arguments.
Let me list out a couple of lines to see where I should go.
(gdb) list
1 #include <stdio.h>
2
3 void multiply_by_two(int *current, int *arr);
4
5 int main(void)
6 {
7 int arr[5] = {10, 20, 30, 40, 50};
8 int *current = arr;
9 int i = 0;
10
(gdb) list
11 printf("Before\n");
12 printf("------\n");
13
14 for (i = 0; i < 5; i++)
15 {
16 printf("arr[%d] = %d\n", i, *current);
17 current++;
18 }
19
20 current = NULL;
(gdb) list
21 multiply_by_two(current, arr);
22
23 printf("\nAfter\n");
24 printf("-----\n");
25
26 for (i = 0; i < 5; i++)
27 {
28 printf("arr[%d] = %d\n", i, *current);
29 current++;
30 }
(gdb)
The
list
command prints, by default, ten lines of a file to the screen, centered around the line currently you’re on. You can runlist -
to print ten lines before the lines you just printed. (If you didn’t runlist
yet, thenlist -
would start off by printing the same ten lines aslist
.)
Tip: If you ever want to run a command you just used, such as
list
, instead of typing it out again, you can simply press your Enter key.
That looks like enough. Since the first loop works, maybe something
is happening in the multiply_by_two()
function. I’ll skip
ahead to that line and step into the function.
(gdb) until 21
Before
------
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50
main () at my_file.c:21
21 multiply_by_two(current, arr);
(gdb) step
multiply_by_two (current=0x0, arr=0x7fffffffdfe0) at my_file.c:39
39 int i = 0;
(gdb)
The
until
command lets you continue running your program until you reach a certain line. The program will still stop on any breakpoints.
The
step
command lets you enter into a function call. If you wanted to continue past a function call, you could run thenext
command instead.
Let me list out some lines like before to get an overview.
(gdb) list
34 return 0;
35 }
36
37 void multiply_by_two(int *current, int *arr)
38 {
39 int i = 0;
40 current = arr;
41
42 for (i = 0; i < 5; i++)
43 {
(gdb) list
44 *current = *current * 2;
45 current++;
46 }
47
48 current = arr;
49 }
(gdb)
Okay, that’s the whole function. current
starts off
pointing to NULL
, then I set it to point to
arr
just before I leave the function. Shouldn’t the
current
in main()
be pointing to
arr
, too?
Wait a minute… I learned about this! The current
variable here is local to this function’s scope. That
means I actually need to set current
to point to
arr
in the main()
function!
Let me finish this function to see if I’m right.
(gdb) finish
Run till exit from #0 multiply_by_two (current=0x0, arr=0x7fffffffdfe0) at my_file.c:39
main () at my_file.c:23
23 printf("\nAfter\n");
(gdb) print current = arr
$4 = (int *) 0x7fffffffdfe0
(gdb) print current
$5 = (int *) 0x7fffffffdfe0
(gdb) print &arr
$6 = (int (*)[5]) 0x7fffffffdfe0
(gdb)
The
finish
command tells your program to continue running until the function exits. The program will still stop on any breakpoints.
As we saw in the above block with the
print current = arr
line, thecurrent
. If you did something like this while debugging, then once your program started running again, it would run as if that change had actually been made in your code.
Okay, now I set current
to point to arr
,
and I can see that the addresses match. Will it work this time?
(gdb) continue
Continuing.
After
-----
arr[0] = 20
arr[1] = 40
arr[2] = 60
arr[3] = 80
arr[4] = 100
All done!
[Inferior 1 (process 620) exited normally]
(gdb)
Here, we come to end of the program again, but this time, there is no segmentation fault because we changed the value held in
current
. However, any changes you make using
Yes, it worked! Let me quit GDB and edit my code so I can see it if it really, really works.
(gdb) quit
The
quit
command lets you exit GDB. You can also useexit
instead ofquit
.
// In the text editor.
// In the main() function.
...
= NULL;
current (current, arr);
multiply_by_two
// Adding this line!
= arr;
current
("\nAfter\n");
printf("-----\n");
printf...
// In the multiply_by_two() function.
...
for (i = 0; i < 5; i++)
{
*current = *current * 2;
++;
current}
// Deleting this line!
// current = arr;
}
my-pc:~intro-to-c/assignments/pointers$ gcc my_file.c
my-pc:~intro-to-c/assignments/pointers$ ./a.out
Before
------
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50
After
-----
arr[0] = 20
arr[1] = 40
arr[2] = 60
arr[3] = 80
arr[4] = 100
All done!
Yes, it really, really worked! I knew I could do it!