Skip to main content
Redhat Developers  Logo
  • AI

    Get started with AI

    • Red Hat AI
      Accelerate the development and deployment of enterprise AI solutions.
    • AI learning hub
      Explore learning materials and tools, organized by task.
    • AI interactive demos
      Click through scenarios with Red Hat AI, including training LLMs and more.
    • AI/ML learning paths
      Expand your OpenShift AI knowledge using these learning resources.
    • AI quickstarts
      Focused AI use cases designed for fast deployment on Red Hat AI platforms.
    • No-cost AI training
      Foundational Red Hat AI training.

    Featured resources

    • OpenShift AI learning
    • Open source AI for developers
    • AI product application development
    • Open source-powered AI/ML for hybrid cloud
    • AI and Node.js cheat sheet

    Red Hat AI Factory with NVIDIA

    • Red Hat AI Factory with NVIDIA is a co-engineered, enterprise-grade AI solution for building, deploying, and managing AI at scale across hybrid cloud environments.
    • Explore the solution
  • Learn

    Self-guided

    • Documentation
      Find answers, get step-by-step guidance, and learn how to use Red Hat products.
    • Learning paths
      Explore curated walkthroughs for common development tasks.
    • Guided learning
      Receive custom learning paths powered by our AI assistant.
    • See all learning

    Hands-on

    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.
    • Interactive labs
      Learn by doing in these hands-on, browser-based experiences.
    • Interactive demos
      Click through product features in these guided tours.

    Browse by topic

    • AI/ML
    • Automation
    • Java
    • Kubernetes
    • Linux
    • See all topics

    Training & certifications

    • Courses and exams
    • Certifications
    • Skills assessments
    • Red Hat Academy
    • Learning subscription
    • Explore training
  • Build

    Get started

    • Red Hat build of Podman Desktop
      A downloadable, local development hub to experiment with our products and builds.
    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.

    Download products

    • Access product downloads to start building and testing right away.
    • Red Hat Enterprise Linux
    • Red Hat AI
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat Developer Toolset

    References

    • E-books
    • Documentation
    • Cheat sheets
    • Architecture center
  • Community

    Get involved

    • Events
    • Live AI events
    • Red Hat Summit
    • Red Hat Accelerators
    • Community discussions

    Follow along

    • Articles & blogs
    • Developer newsletter
    • Videos
    • Github

    Get help

    • Customer service
    • Customer support
    • Regional contacts
    • Find a partner

    Join the Red Hat Developer program

    • Download Red Hat products and project builds, access support documentation, learning content, and more.
    • Explore the benefits

Printf-style debugging using GDB, Part 1

October 5, 2021
Kevin Buettner
Related topics:
C, C#, C++Linux
Related products:
Red Hat Enterprise Linux

    Programmers often debug software by adding print statements to source code. Knowing that a certain point in the program has been reached can be immensely helpful. It's also useful to print values of variables at various points during program execution. An obvious drawback of this technique is the need to change source code, both to add the print statements and later to remove or disable them after the bug has been fixed. Adding new code can potentially introduce new bugs, and if you've added many print statements, you might forget to remove some of them when cleaning up after debugging.

    You can use the popular GNU Project Debugger (GDB) to perform the same style of debugging for various programming languages, especially C and C++, without changing source files. This article is the first of a series describing how to use GDB to add print statements to your C and C++ code. We'll start with some basics and move through more advanced ways to call program-defined functions that display data.

    Prerequisites

    To use the techniques described in this article, you need to satisfy the following prerequisites:

    • You must have a C/C++ compiler such as GCC or Clang installed on your development machine.
    • Likewise, you will need GDB installed on your development machine.
    • The program that you wish to debug needs to be compiled with debugging information. When using either the gcc or clang command, add the -g option to enable debugging information.
    • The program—or at least the source files on which you wish to use GDB's dprintf command—should be compiled without optimization.

    Regarding the last point, you can disable optimization either by removing any existing -O, -O2, etc., options from the set of compiler flags (for example, CFLAGS or CXXFLAGS) or by adding either -O0 or -Og as the very last optimization option. When you run gcc without specifying any optimization options, -O0 is used as the default. When the program is being compiled with a lot of options, it may be easier to simply append either -O0 or -Og to the list of compiler options, because the final optimization option overrides any previous optimization options. The -O0 option is slightly different from the -Og option, because the latter enables some optimizations that won't affect the debugging experience whereas -O0 disables all optimizations.

    Note, too, that the latter two points might require you to recompile and relink your program with a debugging option enabled and with optimization disabled.

    The techniques presented here might also work for some optimized code. In recent years, when gcc is used, the quality of the debugging information has improved greatly for optimized code; however, there are still cases where values of variables are unavailable or possibly even incorrect. Programmers can avoid these problems by disabling optimization.

    Example code

    This article demonstrates the use of GDB to add printf-style output for a little function named insert. This function is from a small program that I wrote for pedagogical purposes. The program, which is a little over 100 lines long, is contained in a single source file named tree.c that is available from my GitHub repository.

    The following insert function inserts a node into a binary search tree. Note the line numbers, which we'll use later in the article:

    		
    37      struct node *
    38      insert (struct node *tree, char *data)
    39      {
    40        if (tree == NULL)
    41          return alloc_node (NULL, NULL, data);
    42        else
    43          {
    44            int cmp = strcmp (tree->data, data);
    45     
    46            if (cmp > 0)
    47              tree->left = insert (tree->left, data);
    48            else if (cmp < 0)
    49              tree->right = insert (tree->right, data);
    50            return tree;
    51          }
    52      }
    

    The main function contains calls to insert plus additional calls to functions for printing the tree. Note the line numbers here:

    	 
    96    struct node *tree = NULL;
    97     
    98    tree = insert (tree, "dog");
    99    tree = insert (tree, "cat");
    100   tree = insert (tree, "wolf");
    101   tree = insert (tree, "javelina");
    102   tree = insert (tree, "gecko");
    103   tree = insert (tree, "coyote");
    104   tree = insert (tree, "scorpion");
    105   
    106   print_tree_flat (tree);
    107   printf ("\n");
    108   print_tree (tree);
    

    I compiled tree.c for use with GDB using the following command:

    $ gcc -o tree -g tree.c

    The -g option places debugging information in the binary. Also, the program is compiled without optimization.

    Using GDB for printf-style output

    With the properly compiled binary on your system, you can simulate print statements in GDB.

    Debugging with GDB

    We can use the gdb command to debug the example program:

    $ gdb ./tree

    This command starts by printing a copyright message along with legal and help information. If you wish to silence that output, add the -q option to the gdb command line. This is what output should look like when using the -q option:

    $ gdb -q ./tree
    Reading symbols from ./tree...
    (gdb) 

    If you also see the message (No debugging symbols found in ./tree), it means that you did not enable the generation of debugging information during the compilation and linking of the program. If this is the case, use GDB's quit command to exit GDB and fix the problem by recompiling with the -g option.

    Virtual print statements

    We'll now use GDB's dprintf command to place a special kind of breakpoint that simulates the addition of a comparable printf() statement to the source code. We'll place virtual print statements on lines 41, 47, and 49:

    (gdb) dprintf 41,"Allocating node for data=%s\n", data
    Dprintf 1 at 0x401281: file tree.c, line 41.
    (gdb) dprintf 47,"Recursing left for %s at node %s\n", data, tree->data
    Dprintf 2 at 0x4012b9: file tree.c, line 47.
    (gdb) dprintf 49,"Recursing right for %s at node %s\n", data, tree->data
    Dprintf 3 at 0x4012de: file tree.c, line 49.
    (gdb) 
    

    The first dprintf command shown for line 41 is roughly equivalent to adding three lines of code near lines 40 and 41:

      if (tree == NULL)
      { /* DEBUG - delete later.  */
        printf ("Allocating node for data=%s\n", data); /* DEBUG - delete later. */
        return alloc_node (NULL, NULL, data);
      } /* DEBUG - delete later.  */
    

    Note that, when adding a call to printf() in the traditional fashion, three lines of code would need to be added in this particular place. (If you added the printf() without the curly braces, the if statement would execute only the printf(), and the return alloc_node statement would no longer be conditionally executed—instead, it would always be executed.)

    As indicated by the comments, you would need to delete these added lines later when debugging is done (although the added braces are actually fine to leave in place). If you add lots of debugging statements to your code, you might forget to delete some of them when debugging is complete. As noted earlier, this is a distinct advantage of using GDB's dprintf command: No source code is modified, so subtle bugs won't be introduced when adding the print statement; there's also no need to remember all the places where a print statement was added when cleaning up after debugging.

    Run the program

    Use GDB's run command to run your program. Once the command is issued, GDB output and program output appear mixed together in the terminal used for the GDB session. Here's an example running our tree program:

    
    (gdb) run
    Starting program: /home/kev/ctests/tree 
    Allocating node for data=dog
    Recursing left for cat at node dog
    Allocating node for data=cat
    Recursing right for wolf at node dog
    Allocating node for data=wolf
    Recursing right for javelina at node dog
    Recursing left for javelina at node wolf
    Allocating node for data=javelina
    Recursing right for gecko at node dog
    Recursing left for gecko at node wolf
    Recursing left for gecko at node javelina
    Allocating node for data=gecko
    Recursing left for coyote at node dog
    Recursing right for coyote at node cat
    Allocating node for data=coyote
    Recursing right for scorpion at node dog
    Recursing left for scorpion at node wolf
    Recursing right for scorpion at node javelina
    Allocating node for data=scorpion
    cat coyote dog gecko javelina scorpion wolf 
    
      cat
        coyote
    dog
          gecko
        javelina
          scorpion
      wolf
    [Inferior 1 (process 306927) exited normally]
    (gdb) 

    In this display, the user typed the run command at the (gdb) prompt. The rest of the lines are output either from GDB or from the program. The only program output occurs towards the end, starting with the line "cat coyote dog..." and finishing with the line "wolf." Lines starting with either "Recursing" or "Allocating" were output by the dprintf commands established earlier. It's important to understand that, by default, these lines were output by GDB. This is different from traditional printf-style debugging, and we'll look at this difference in the next article in this series. Finally, there are two lines of GDB output, the second line and the penultimate one, which show that the program is starting and exiting.

    Comparing dprintf and printf()

    There are differences and similarities between GDB's dprintf command and the C-language printf() function:

    • The dprintf command does not use parentheses to group the command's arguments.
    • The first argument of the dprintf command specifies a source location at which a dynamic printf statement should be placed. Output from the dynamic printf is printed prior to the execution of that source location. The source location may be a line number, such as 41, but the location will often include a filename plus a line number, such as tree.c:41. The location could also be the name of a function or an instruction address in the program. For a function location, the output from the dynamic printf occurs prior to the first executable line of the function. When the location is an instruction address, output occurs before the instruction at that address is executed.
    • The dprintf command creates a special kind of breakpoint. It is only when one of these special breakpoints is hit during the program run that output is printed.
    • The format string used by dprintf is the same as that used by printf(). In fact, as we shall see later, the format string specified in the dprintf command might be passed to a dynamically constructed call to printf() in the program being debugged.
    • In both dprintf and printf(), comma-separated expressions follow the format string. These are evaluated and output according to the specification provided by the format string.

    Conclusion

    This article has offered the basics of printf-style debugging in GDB. The next article in this series takes you to a higher level of control over debugging, by showing you how to save your dprintf commands and the GDB output for later use.

    Last updated: June 7, 2023

    Related Posts

    • 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

    • Remote debugging with GDB

    • How to debug C and C++ programs with rr

    Recent Posts

    • Every layer counts: Defense in depth for AI agents with Red Hat AI

    • Fun in the RUN instruction: Why container builds with distroless images can surprise you

    • Trusted software factory: Building trust in the agentic AI era

    • Build a zero trust AI pipeline with OpenShift and RHEL CVMs

    • Red Hat Hardened Images: Top 5 benefits for software developers

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    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
    © 2026 Red Hat

    Red Hat legal and privacy links

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

    Chat Support

    Please log in with your Red Hat account to access chat support.