Skip to main content
Redhat Developers  Logo
  • Products

    Platforms

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat AI
      Red Hat AI
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • View All Red Hat Products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat Developer Hub
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat OpenShift Local
    • Red Hat Developer Sandbox

      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Secure Development & Architectures

      • Security
      • Secure coding
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • Product Documentation
    • API Catalog
    • Legacy Documentation
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

Printf-style debugging using GDB, Part 3

December 9, 2021
Kevin Buettner
Related topics:
C, C#, C++LinuxOpen source
Related products:
Red Hat Enterprise Linux

Share:

    Welcome back to this series about using the GNU debugger (GDB) to print information in a way that is similar to using print statements in your code. The first article introduced you to using GDB for printf-style debugging, and the second article showed how to save commands and output. This final article demonstrates the power of GDB to interact with C and C++ functions and automate GDB behavior.

    Calling program-defined output routines

    Our example program contains a function named print_tree that outputs a constructed tree. Suppose that you wish to use that function to examine the tree on which the insert function will operate each time it is called. This can be done by setting a breakpoint on insert and then specifying GDB commands to execute each time the breakpoint is hit. But before looking at how that's done, let's first look at how ordinary GDB breakpoints work.

    Setting a breakpoint

    A breakpoint can be set using the break command. We can use it to set a breakpoint at the start of the insert function, as follows:

    (gdb) break insert
    Breakpoint 1 at 0x40127a: file tree.c, line 40.
    

    If you refer back to the source code from Part 1, you'll see that line 40 is the first executable line of the function. If you run the program, GDB stops at the breakpoint, showing the values of the arguments to the function in addition to the line at which GDB has stopped:

    (gdb) run
    Starting program: /home/kev/ctests/tree 
    
    Breakpoint 1, insert (tree=0x0, data=0x40203f "dog") at tree.c:40
    40      if (tree == NULL)
    

    There are many interesting things you could do now, such as examining a stack trace via the backtrace command, or perhaps printing other values using GDB's print command. However, I wish to demonstrate the call and continue commands that we'll use in the next section.

    Call and continue

    In the following example, I issue the continue command to ask GDB to continue past that breakpoint seven times, stopping again the eighth time it is hit. By default, the continue command causes the program to execute to the next breakpoint. Providing a numeric argument to this command tells GDB to continue that number of times without stopping at the intermediate breakpoints. After the continue command stops, the call command calls print_tree():

    (gdb) continue 8
    Will ignore next 7 crossings of breakpoint 1.  Continuing.
    
    Breakpoint 1, insert (tree=0x4052a0, data=0x402055 "gecko") at tree.c:40
    40      if (tree == NULL)
    (gdb) call print_tree(tree)
      cat
    dog
        javelina
      wolf
    (gdb) 

    GDB's printf command

    GDB also has a printf command, which I've used here:

    (gdb) printf "tree is %lx and data is %s\n", tree, data
    tree is 4052a0 and data is gecko
    

    GDB's printf command prints to GDB's console, not to the program output. For this example, we will probably find it more useful to call the program's printf() function from the standard C library. It will print to the program's output, which is also where the program's print_tree function prints its output:

    (gdb) call printf("tree is %lx and data is %s\n", tree, data)
    tree is 4052a0 and data is gecko
    $1 = 33
    (gdb) 
    

    This output differs from GDB's built-in printf command by printing an additional line ($1 = 33). What's happening here is that GDB is calling printf() to output the expected result. The printf() function returns an integer representing the number of characters printed. This return value is printed to the GDB console and saved to the value history. If you want to suppress the printing of the return value (as well as its appearance in the value history), cast the return value of printf() to void, like this:

    (gdb) call (void) printf("tree is %lx and data is %s\n", tree, data)
    tree is 4052a0 and data is gecko
    

    Attaching commands to a breakpoint

    We are now ready to use GDB's commands command to attach a list of commands to a previously set breakpoint or list of breakpoints. When no breakpoint number or list is provided, commands adds commands to the most recently defined breakpoint. Assuming that to be the case for the breakpoint set in the previous section, we can use the commands command as follows:

    (gdb) commands
    Type commands for breakpoint(s) 1, one per line.
    End with a line saying just "end".
    >silent
    >call (void) printf("Entering insert(tree=%lx, data=%s)\n", tree, data)
    >if (tree != 0)
     >call (void) printf("Tree is...\n")
     >call (void) print_tree(tree)
     >end
    >continue
    >end
    (gdb) 

    The first command associated with the breakpoint is silent. It tells GDB not to print the usual messages that are printed when stopping at a breakpoint.

    Next is a call command. It invokes printf(), which prints a message showing that the insert() function has been entered along with the values of tree and data.

    Next comes an if command. It checks whether the value of tree is non-zero (that is, non-NULL). If this condition is true, then the two call commands are executed, because there is data in tree. If not, those call commands are skipped. The end command terminates the block of commands for the if command.

    A continue command comes next. It causes GDB to resume execution until either another breakpoint is hit or the program terminates.

    Finally, an end command terminates the list of commands to attach to the breakpoint.

    With the breakpoint and its associated commands in place, running the program produces the following output:

    (gdb) run
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /home/kev/ctests/tree 
    Entering insert(tree=0, data=dog)
    Entering insert(tree=4056e0, data=cat)
    Tree is...
    dog
    Entering insert(tree=0, data=cat)
    Entering insert(tree=4056e0, data=wolf)
    Tree is...
      cat
    dog
    ...
    Entering insert(tree=405970, data=scorpion)
    Tree is...
      gecko
    javelina
    Entering insert(tree=0, data=scorpion)
    cat coyote dog gecko javelina scorpion wolf 
    
      cat
        coyote
    dog
          gecko
        javelina
          scorpion
      wolf
    [Inferior 1 (process 326307) exited normally]
    

    Saving the insert function breakpoint

    Let's use the info breakpoints command to look at the breakpoint for insert:

    (gdb) info breakpoints 
    Num     Type           Disp Enb Address            What
    1       breakpoint     keep y   0x000000000040127a in insert at tree.c:40
        breakpoint already hit 19 times
            silent
            call (void) printf("Entering insert(tree=%lx, data=%s)\n", tree, data)
            if (tree != 0)
              call (void) printf("Tree is...\n")
              call (void) print_tree(tree)
            end
            continue
    (gdb) 

    Observe that the info breakpoints output shows the number of times that the breakpoint has been hit. In this program, we see that insert was called 19 times. Although it's not especially relevant for the current discussion, knowing how many times a particular function was called might be useful for optimization or performance analysis.

    Let's save this breakpoint to a file named my-insert-breakpoint:

    (gdb) save breakpoints my-insert-breakpoint
    Saved to file 'my-insert-breakpoint'.
    

    The my-insert-breakpoint file now contains GDB commands that, when run, will recreate the insert() breakpoint plus its associated commands for use in a future GDB session:

    break tree.c:insert
      commands
        silent
        call (void) printf("Entering insert(tree=%lx, data=%s)\n", tree, data)
        if (tree != 0)
          call (void) printf("Tree is...\n")
          call (void) print_tree(tree)
        end
        continue
      end
    

    Running a program with insert and dprintf breakpoints

    I now have two files with saved breakpoints, one named my-dprintf-breakpoints and the other named my-insert-breakpoint. Let's start GDB, load the dprintf and breakpoint commands listed in the files, and then run the program with output redirected to my-program-output:

    $ gdb -q ./tree
    Reading symbols from ./tree...
    (gdb) source my-dprintf-breakpoints
    Dprintf 1 at 0x401281: file tree.c, line 41.
    Dprintf 2 at 0x4012b9: file tree.c, line 47.
    Dprintf 3 at 0x4012de: file tree.c, line 49.
    (gdb) source my-insert-breakpoint
    Breakpoint 4 at 0x40127a: file tree.c, line 40.
    (gdb) set dprintf-style call
    (gdb) run >my-program-output
    Starting program: /home/kev/ctests/tree >my-program-output
    [Inferior 1 (process 327130) exited normally]
    (gdb) quit
    

    Note that the set dprintf-style call command had not been automatically added to either of the files loaded via the source command. It might make sense to manually add it to the my-dprintf-breakpoints file. Alternately, it could be placed into another file—let's call it tree-debugging-commands:

    file tree
    source my-dprintf-breakpoints
    source my-insert-breakpoint
    set dprintf-style call

    This file, tree-debugging-commands, first specifies the program to debug via the file command. In earlier examples, we caused tree to be loaded by mentioning it on the gdb command line; here, however, we don't list it on the command line, but instead cause it to be loaded via the file command.

    The remaining commands in tree-debugging-commands should be familiar by now. Commands contained in the my-dprintf-breakpoints and my-insert-breakpoints files are executed, followed by the set dprintf-style call command. Recall that this command causes dprintf breakpoints to call printf() in the program being debugged (instead of using GDB's internal printf command).

    With that file in place, we can run GDB as follows:

    $ gdb -q -x tree-debugging-commands
    Dprintf 1 at 0x401281: file tree.c, line 41.
    Dprintf 2 at 0x4012b9: file tree.c, line 47.
    Dprintf 3 at 0x4012de: file tree.c, line 49.
    Breakpoint 4 at 0x40127a: file tree.c, line 40.
    (gdb) run >my-program-output
    Starting program: /home/kev/ctests/tree >my-program-output
    [Inferior 1 (process 351102) exited normally]
    (gdb) quit

    Additional commands

    It should also be possible to achieve the same effect, but without needing to interact with GDB, by using the following command:

    $ gdb -q -x tree-debugging-commands -ex 'run >my-program-output' -ex quit

    As noted in Part 1 of this series, the -q option suppresses the GDB banner, copyright, and help information when GDB starts up. The -x option, in this case, causes GDB to load and execute the commands from the file tree-debugging-commands. The -ex options cause the command following the option to be run. So, in this case, after loading and running the commands in tree-debugging-commands, a run command is issued from the first -ex option; moreover, the output from the run is redirected to the file my-program-output. The command following the second -ex option is quit; this causes GDB to quit without ever showing a prompt.

    The run and quit commands could also be placed in the command file, tree-debugging-commands. If this were done, the command line would be shortened to look like this:

    $ gdb -q -x tree-debugging-commands

    A bugfix for dprintf breakpoints

    While writing this article, I discovered a bug in GDB that caused dprintf breakpoints to be (essentially) disabled when running a program from the command line or from within a GDB script. This bug has been fixed in the upstream GDB sources. On Fedora Linux, gdb-11.1-5 (and later) contain this fix. If you are using a version of GDB without this fix, you will need to issue the run command from the GDB prompt.

    Go further with GDB

    I hope this series has been useful to developers familiar with debugging their code using print statements, but who previously had little or no familiarity with GDB. I also hope that it whets your appetite for doing more with GDB.

    This article demonstrated how to set a breakpoint and run until it is hit, a very common use of GDB. Once GDB is stopped at a breakpoint, you can enter a variety of GDB commands to reveal more about the state of the program at that point. If you want to go further with GDB commands, I recommend the following:

    • The GDB developer's GNU Debugger tutorial, Part 1 is a general guide to debugging with the GNU Debugger.
    • Debugging with GDB, the GDB manual, is the authoritative reference for using GDB.
    Last updated: June 14, 2023

    Related Posts

    • Printf-style debugging using GDB, Part 1

    • Printf-style debugging using GDB, Part 2

    • The GDB developer's GNU Debugger tutorial, Part 1: Getting started with the debugger

    • Debugging Python C extensions with GDB

    • Debugging GraalVM-native images using gdb

    Recent Posts

    • Migrating Ansible Automation Platform 2.4 to 2.5

    • Multicluster resiliency with global load balancing and mesh federation

    • Simplify local prototyping with Camel JBang infrastructure

    • Smart deployments at scale: Leveraging ApplicationSets and Helm with cluster labels in Red Hat Advanced Cluster Management for Kubernetes

    • How to verify container signatures in disconnected OpenShift

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    Build

    • Developer Sandbox
    • Developer Tools
    • Interactive Tutorials
    • API Catalog

    Quicklinks

    • Learning Resources
    • E-books
    • Cheat Sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site Status Dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2025 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Report a website issue