Mastering GDB. Power up your debugging skills | by Andrew Xu | May, 2022

Energy up your debugging abilities

Picture by Sigmund on Unsplash

The GNU Undertaking debugger (GDB) is a really helpful debugger underneath Linux.
Good programmers often use gdb to debug bugs, when a program core dumps, or a program happens sudden behaviors. On this article, I’ll present you how you can use gdb effectively.

Compilers usually make many optimizations with a view to generate optimum code, which makes debugging troublesome. If you happen to encounter errors like symbols not discovered, you’ll want to open applicable compile flags for debugging, in order that the debugger can get sufficient info.

  • use -g to let the compiler construct debug info into the executable.
  • use -ggdb3 could make gdb debug macros.
  • use -fno-omit-frame-pointer to keep away from the compiler optimizing out small capabilities, with the intention to see the total name stack.
  • use -0g to let the compiler activate optimizations that don’t have an effect on debugging.

After compiling a program with applicable flags, we are able to use GDB to debug the generated executable. There are various totally different eventualities, and the startup methodology in every state of affairs is barely totally different.

  • Within the first state of affairs, now we have an executable that accepts some parameters. We will begin gdb with gdb executable, after which run this system with run arg1 arg2 .
  • Within the second state of affairs, a core dump file is generated when an executable is working, in all probability a section fault. We will begin gdb with gdb executable coredump_file and gdb will cease at the place the error occurs.
  • Within the third state of affairs, now we have an executable that’s working. We will use gdb connect pid to connect to the method.
screenshots of gdb starting
Screenshots of beginning gdb

After beginning gdb, we often set a breakpoint. This system will pause when it hits a checkpoint. And we are able to observe the standing of this system.

  • break func will break at a operate.
  • break will break at line 10 of the, if there is just one file, we are able to omit the filename, simply use break 10 . And we are able to kind listing to indicate the codes across the present line.
  • data breaks can present you all breakpoints you will have set.
  • delete 2 will delete the second breakpoint.
  • You too can set conditional breakpoints, simply append if [condition] to interrupt command.
  • watch [variable] can set a watch on a variable, watches are one other type of breakpoint, that can pause this system if the watched variable has modified.
Illustration of setting breakpoints

When paused on a breakpoint, we are able to observe the standing of this system by printing variables and expressions.

  • data args can present us the arguments of a operate.
  • data locals can present us the native variables of the present operate.
  • print can be utilized to indicate the results of a variable or an expression. See the bellow illustrations. print a prints a variable. print a=2 will first consider a=2 after which print the end result. Notice that should you straight kind a=2 , you’re going to get an error. print sum(1, a) calls the operate sum .

(gdb) print a
$1 = 1
(gdb) print a=2
$2 = 2
(gdb) print sum(1, a)

You too can format the end result of print ,

  • print /x <exp> will present the lead to hexadecimal.
  • print /t <exp> will present the lead to binary.
  • print /d <exp> will present the lead to unsigned int format.
  • print /c <exp> will present the lead to signed int format.
  • Notice the outcomes of print , reminiscent of $1 , $2 , are additionally variables that can be utilized additional.
  • dprintf locaion, format-string, expr1, expr2 . drpintf is a handy command that may dynamically print on the location, simply as you will have inserted an printf expression on the location.
  • set $foo = 4 can set a variable. That is handy if you wish to avoid wasting intermediate outcomes.
  • command 2 is one other gdb command, that you may set instructions to be executed when hit the particular breakpoints. The illustration under will execute data locals routinely when the primary breakpoints are hit. You too can use this methodology to attain the identical results of dprintf .

(gdb) command 1
Kind instructions for breakpoint(s) 1, one per line.
Finish with a line saying simply “finish”.
>data locals

  • print *&arr[96]@5 can print the 96–100 components of an array.

Notice at present we cannot simply print std::vector or different c++ stdlib containers. We’ll focus on this matter later.

There are a number of instructions permitting us to manage the execution of this system within the gdb session.

  • run will begin working this system and cease on the first breakpoint, if there aren’t any breakpoints, this system will cease on the exit.
  • begin will begin this system and cease the fundamental operate.
  • proceed will proceed this system and cease on the subsequent breakpoint or exit.
  • end will cease when the present operate finishes.
  • subsequent will cease on the subsequent line.
  • step will enter a operate and cease there.

Gdb accommodates a textual content consumer interface (TUI), identical to most IDEs, which might show traces of code whereas debugging. tui allow to enter the tui mode, and tui disable to exit.

Illustration of TUI
Illustration of TUI

Backtrace is helpful for debugging, particularly once we debug a core dump.

  • bt will present the present backtrace.
  • up will transfer you to the higher body.
  • down will transfer you to the decrease body.
  • body 2 will straight take you to the second body.

When debugging a multithread program,

  • data threads reveals all threads.
  • thread 5 will go to the fifth thread.
  • thread apply all [command] will execute a command on all threads. It’s very handy to make use of thread apply all bt full to print all backtrace of all threads.

Gdb has a configuration file, situated at ~/.gitinit or within the present folder, identical to .vimrc for vim . The configuration file within the present path has a better precedence to be loaded than ~/.gitinit . Let’s focus on some configurations.

  • set logging on will output all output to gdb.txt . That is helpful when instructions output a lot info, just like the above thread apply all bt full .
  • set historical past save on saves the command historical past.
  • set pagination off disable the interactive show of lengthy display output.
  • set print fairly on can show a c++ class with a extra fairly format.
  • set affirm off disable affirmation.

Since gdb cannot print c++ containers reminiscent of std::vector conveniently, we are able to write some scripts to make it simple. Follows

Typically we encounter errors like xxx.c: No such file or listing . Because of this gdb cannot discover the supply file. The options are:

  • use listing [folder] so as to add the particular listing to gdb’s search path.
  • use set substitute-path [src] [dst] to substitute path.

You too can develop macro to see the small print after preprocessing.

  • use macro develop some_macro(macro_arg) to develop macro.
  • use data macro some_macro to see its definition.

Notice gdb will analyze the context of the present operate, solely macros which are seen within the present context will be expanded.

One highly effective command of gdb is document , which data the state of the working program, and means that you can back-run this system. Gdb additionally gives a bunch of instructions beginning with reverse prefix, like reverse-continue , reverse-finish , reverse-next , reverse-nexti , reverse-step , reverse-stepi .

One utilization of document is to seek out reminiscence corruption. You may run this system in gdb, document the execution of this system, and cease on the location the place reminiscence corruption occurred. Then you may watch the reminiscence handle that was corrupted. Then reverse run this system to see the place the content material of the handle has modified.

The disadvantage of document is that it’ll decelerate your program. It nonetheless takes some work to make use of it nicely.

Typically, you’ll want to regulate the code repeatedly and run this system a number of occasions to progressively perceive the small print of the bug. However should you restart gdb each time, it will likely be inconvenient to set checkpoints repeatedly. We will save the debug session and reload it later.

If you happen to use a makefile to construct your program, you may merely kind make inside the gdb session, it should rebuild your program and reset the checkpoints.

You too can use save breakpoints bp.txt to avoid wasting the breakpoints to bp.txt, and use supply bp.txt to reload these breakpoints later, or use gdb -x bp.txt --args [exe] to load it when gdb begins.

Along with gdb, there are lots of different debug strategies: reminiscent of analyzing logs. Binary search git commits. They’ve their very own benefits and drawbacks. When debugging a program, you may mix these strategies to resolve the issue effectively. The benefit is that you’ve got full entry to the working program, that is extraordinarily helpful when a bug is difficult to breed.

More Posts