Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      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
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java 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

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • 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 1

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

Share:

    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

    • Ollama or vLLM? How to choose the right LLM serving tool for your use case

    • How to build a Model-as-a-Service platform

    • How Quarkus works with OpenTelemetry on OpenShift

    • Our top 10 articles of 2025 (so far)

    • The benefits of auto-merging GitHub and GitLab repositories

    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