We may earn an affiliate commission when you visit our partners.
Mike Shah

Overview

Newly revamped in 2024 with twice the content, and higher quality videos.

Read more

Overview

Newly revamped in 2024 with twice the content, and higher quality videos.

In this course you will learn how to use the popular debugger GDB to find errors in your C and C++ code.  Learning how to use a debugger will allow you to save time when finding errors and spend more time building better software. Being able to debug code is a necessary skill for all software developers to have, and you need nothing more than a terminal window to do so. The lessons learned from this course however will go behind the GDB debugger, and even show you a few other great tools like valgrind for finding bugs in your code.

Topics you'll learn

Students should take this course if they want to learn:

  • How to use the popular GDB debugger

  • General debugging techniques, and why certain bugs occur

  • Some more advanced topics like reverse-debugging writing scripts for debugging not covered in other basic courses.

Why you should take this course?

Learning how to use a debugger will at first challenge conventional 'printf' debugging strategies that you may be able to get away with. But as you build larger software and work on software with larger teams, it will become essential to learn how to find and fix bugs. With this course and some practice, you will be able to work more quickly and save time fixing bugs, and then can spend your other efforts building great software. I can recall several instances when I first started working as a software engineer, and it took me weeks to find and fix a single bug. Had I better debugging skills at the time, I could have saved myself (and the company) a lot more time (and myself pain. ). So unlock your full debugging potential by taking this course.

Who am I?

I have been teaching for over 10 years in universities and as a professor. I have worked in industry in big companies, startups, and as a consultant. I am looking forward to being your instructor for this course, and I hope you will get great value out of the lessons learned.

Enroll now

What's inside

Learning objectives

  • How to debug using a debugger like gdb
  • How to detect memory leaks using valgrind
  • How to log errors and get input from a running program.
  • Learn additional debugging tools (sanitizers, tracing tools, and static analysis tools)

Syllabus

Introduction

Welcome to Hands on Debugging in C and C++. This is the course I believe that everyone should have almost as soon as they learn the C or C++ programming languages. 

In this course we are going to learn how to find, debug, and fix errors in our code. We will look at C and C++ examples for the code we debug.

Note: This is not a C or C++ course, but you should be familiar with one language or the other to get the most out of this course.

Please check out my other Udemy courses on the associated languages if you'd like to learn more!

Read more

In this lesson I show you a working example of what you will learn in this course.

Just sit back and relax for now and you'll see a couple of the fundamental skills that you'll be learning in this course!

When you complete this course, you can revisit this video to refresh on all of the skills that you have learned.

In this lesson I want to just make clear a few of the key course objectives that you will be learning in this course. This is a hands on course, so we'll learn how to use GDB, learn some debugging techniques, and ultimately you will practice and follow along with me as you learn new tools.

An enlightening story about Dr. Admiral Grace Hopper!

Note: The origin of the word 'bug' is actually a bit older, but I believe Grace Hopper (who should be a household name!) popularized the term for computer programmers.

One of the first steps to avoiding creating bugs is to write your code neatly. Indentation errors can make your own code harder to read, or someone else. Do make use of your editors refactoring tools, or otherwise get in the habit of writing nice code!

  • A tool like 'indent' on Unix can be used to quickly style code.

While this course is going to focus on examples specific to GDB's usage with C and C++ code, keep in mind that GDB supports many languages. So the skills you learn in this course are applicable to many languages, and you can follow along or revisit these lessons in other languages that you use.

Some resources and code examples used in this course. You are otherwise encouraged to use your own examples, and write small files to play around with.

Students will learn how to get started in this course

In this course we are going to focus on examples in GDB [https://www.sourceware.org/gdb/]. The GNU Debugger (i.e. GDB) is a free debugger available on almost every platform and it is incredibly powerful!

Note that the techniques you learn and will be shown with GDB can be used in other debuggers like lldb [https://lldb.llvm.org/] and many of the commands are also the same as GDB (lldb to gdb command map - https://lldb.llvm.org/use/map.html).

Other folks may also want to stick to VSCode, or perhaps Visual Studio (if you're on windows), or even other IDEs. That will be okay but, I think you'll get the most out of this course if you follow along with GDB.

Note: Some other tools that we use may also be Linux specific (e.g. strace or valgrind), but equivalents usually can be found for windows and mac. Feel free to ask for advice in the Q&A.

  • Install GDB

    • Ubuntu

      • sudo apt-get install gdb

    • CentOS

      • sudo yum install gdb

  • install LLDB (Note: I recommend GDB for this course)

    • Ubuntu

      • sudo apt-get install lldb


A link to my free YouTube lesson on building GDB from source: https://www.youtube.com/watch?v=QFFU1Y8tRV4

In this lesson I am going to show you a few ways to get started on windows. I primarily recommend that you use GDB from within the Windows Subsystem for Linux (WSL) -- it truly provides a great debugging experience on the terminal.

  • Instructions on install WSL on windows

    • https://docs.microsoft.com/en-us/windows/wsl/install (Or a google search 'install WSL on windows')

    • Then once Ubuntu is setup, do 'sudo apt-get install gdb'

Some alternatives for debugging would be to follow along in this course through an IDE like Visual Studio. That said, you will need to translate some of the steps a bit, and do some learning on your own.

MinGW is a third alternative, though it does not provide the text user-interface (TUI) mode.

LLDB is one of the most prevalent debuggers on MAC. Many folks might prefer it to GDB for example, though this course primarily uses GDB.


If you'd like, I have the following resources for using LLDB that may be helpful for getting started on this course.

  • Free lesson for getting started in 11 minutes: https://www.youtube.com/watch?v=v_C1cvo1biI

  • A useful command map of GDB to LLDB commands: https://lldb.llvm.org/use/map.html

Use the compiler to try to find bugs before they occur.

The debugging that we are going to talk about in this course is concerned with using a debugger, and actively debugging our program while it is running. This is 'run-time' debugging. Catching errors before the program however, happens at 'compile-time' before our program starts. The distinction is important, and can help you when searching the web for help, and otherwise give you the proper vocabulary to discuss bugs in your program.

Compiler errors happen during the 'parsing' stage of compiling your code. These errors happen at compile-time and must be fixed before we can run our code.

In this lesson I want to show you that while compilers are good at identifying syntactically correct programs, they're not always good at understanding our intent. This can lead to mischievous bugs that are difficult to detect.

Sometimes our compilers can provide us warnings (e.g. using the -Wall flag), and sometimes we can use defensive programming mechanisms ( if(7==x) versus if(x==7)) to write safer software. But then again, it is easy to introduce bugs, so we must learn more techniques!

Compiler warnings are not errors, but they are warnings of code that could cause an error when the program starts running. 

  • Tips

    • Compile with -Wall

      • This will display all warnings the compiler knows about.

    • Compile with -Werror

      • This treats warnings as errors, and it is likely the right thing to do to be extra careful.

    • Compile with -Wconversion

      • Treats implicit conversions (casting) as a warning.

  • See more warning options here: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html

It may not always work, but it can be useful to leverage multiple compilers, for example using clang and gcc. They may be able to give you a different set of warnings to help you track down pesky bugs!

Note: Sometimes you may need to use a specific compiler for your project, and it's probably not worth risking doing a build on a wrong tool. So take this as just another trick that may be useful in some sitautions.

Check your knowledge and understand what information your C and C++ compiler is providing you.

One of the first steps to avoiding creating bugs is to write your code neatly. Indentation errors can make your own code harder to read, or someone else. Do make use of your editors refactoring tools, or otherwise get in the habit of writing nice code!

  • A tool like 'indent' on Unix can be used to quickly style code.

  • Other tools like clang-tidy are also great for refactoring code, and provide better styling options to match your teams or projects coding standards.

Learn some foundational techniques for debugging

In this lesson I've given you some buggy code in c. The goal is to be able to fix this program, and swap the contents of the two arrays. However, there are multiple problems--how are you going to go about fixing the actual program?

printf debugging is a common technique used in many languages (console.log, or std::cout, writeln, etc.).

It's an 'okay' technique that allows you to get useful information out about what your program is doing and the state of a specific object. However, there is a cost of having to recompile your programs, which can be very expensive!

For object-oriented languages like C++ it can often be useful to have for example 'print()' member functions to display the 'state' of your object.

In this lesson I take one more look at debugging using printf, but this time using the C and C++ preprocessor to turn on our logging on and off. You'll often see this in codebases as a way to log information for debug builds as opposed to release builds. This can again be an 'okay' technique, though it is requiring us to make modifications in the source code. As we use our debugger, we'll want to move away from making modifications to our source code.

Setting Breakpoints

The Basics of starting and executing GDB.

  1. Help page: man gdb

  2. Starting gdb: gdb ./prog

    1. Checking your version: gdb --version

    2. Note: You can start gdb with 'gdb --silent' to avoid the information.

    3. Within GDB

      1. print $_gdb_major

      2. print $_gdb_setting_setting(“pagination”)

        1. Gives us the settings that we have

  3. Quit GDB

    1. 'quit' or 'q' will terminate GDB

  4. Starting gdb with arguments

    1. gdb ./prog my_program_arguments_as_normal

      1. Note that it is important that the program you compiled uses -g to retrieve debugging symbols.

    2. Can use show args and set args to chang the arguments.

  5. Start our program

    1. run

  6. Start our program but pause at the entry point (i.e. main function)

    1. start

  7. list

    1. Shows a listing of where we are in our code (with about 10 lines of code)

  8. next or n

    1. Execute the next line of source code

  9. step or s

    1. Execute the next instruction, and jump into the function.

  10. continue or c 

    1. Run program until a breakpoint is hit, an interrupt (e.g. Ctrl+C), or the program completes execution.

Tip: Pressing 'enter' will repeat the last command that you have typed in.


Note:

  • stepi and nexti also allow you to move through assembly instructions, but we will be focusing on C in this course.

When we compile our programs in C, C++, DLang, etc. we will compile with the -g flag. This adds 'symbol' information to our binaries that we execute, as well as information like the line number of file that the symbol originates. Having this information makes it much easier to work with a tool like GDB.

  • Another tip is to turn off optimizations with -O0 (capital letter o followed by zero)

  • Note: 

    • Can you debug a 'release build' without debug symbols? 

      • The answer is yes, but it will become more difficult--often you will have to loop at the assembly code to do this.

    • Can you debug an 'optimized' debug build?

      • Yes, but again optimizations may change the code around enough such that line numbers do not match up--so be careful, and compile with -O0 (upper case 'oh' and a zero) to be safe.

In this lesson we learn how to 'inspect' values by printing them and the addresses. Now you can finally remove all of the 'printf' and 'cout' statements in your code.

  • Lists source code

    • l or list

      • You can type out list linenumber to show a specific line number as well.

    • (set listsize number to change how much source code is displayed)

  • Print a value

    • p or print and the value

      • Note: We can even evaluate expressions in our program which is pretty neat (e.g. p i+j where i and j are variables in our program)

      • Note: We can even print out values of pointers using the '*' operator.

      • Note: We can even print out addresses using the '&' operator.

  • continue or c

    • This will continue executing your program until a breakpoint is hit.

Pro Tip: Sometimes hitting 'ctrl+l' (i.e. ctrl +  lowercase 'L') will help you clear the screen if the interface gets messy.

Pro Tip: When launching gdb --silent will suppress the additional messages for GDB at the prompt.

When debugging another programmers source code, it can sometimes be helpful to figure out the variables type.

  • Return the type of a variable

    • whatis symbol

      • e.g. whatis A (Where 'A' is some variable)

      • e.g. whatis main (Where 'main' is a function)

      • e.g. whatis l->root (Explore a filed of a struct)

    • ptype symbol

      • ptype will do almost the same thing as whatis, but it will unroll any typedefs to give you more information.

    • info types

      • Shows all of the types that are found in your binary

    • info scope location

      • More general command that will find variables within a certain scope, and then you can further query.

        • info scope main

          • Prints all the variables found within main.

We can pause our programs execution just before we execute a line of code using a breakpoint.

  • Set a breakpoint

    • br linenumber

    • br functionname

    • br sourcefile:linenumber

  • Execute until we hit the next breakpoint

    • 'continue' or 'c'

  • View your breakpoints

    • info breakpoints

  • Deleting a breakpoint when you do not want it to be available.

    • delete breakpoint_number


Note: There are other types of breakpoints as well.

  • temporary breakpoint (tbreak) which will only break once, and then remove itself.

  • hbreak a hardware breakpoint, which may be needed if you are using embedded breakpoints. 

GDB Text User Interface (TUI)

The Text User-Interface (TUI) is an excellent way to use GDB from the terminal to read your code as you execute commands in GDB.

  • You can launch gdb in TUI mode using gdb --tui ./prog 

    • Alternatively: layout src

      • help layout - will retrieve all the other layouts possible

  • Navigation

    • Ctrl-x  1

      • View source and GDB commands

      • (Note you can just type: layout next to rotate between different layouts. Most often we will just use the source and command windows.)

        • focus cmd - focus on command window

        • focus src - focus on the source window

    • Ctrl-x a

      • Toggle between TUI and Command Line mode

    • Ctrl-x o

      • Cycle between windows

  • Commands

    • Ctrl+p and Ctrl+n

      • Cycle between commands

    • Up/Down arrows

      • Cycle between commands if in the command-line window


Other navigation notes:

  • winheight src -2

    • Shrink the window height


Often times your program may be logging lots of information to standard output which can cluttter the debugging experience. In this lesson, we attempt to fix that by redirecting our output.

  • Redirect with -tty

    • Open a second terminal and type 'tty'

    • This tells you where the terminal is connected to for standard input.

  • Within gdb type 'tty location_of_terminal_from_tty_command`

  • Redirect to another file by simply running with the output redirection

    • (Within GDB)

    • run > outputfilename.txt



And that's it! Now you can use GDB more cleanly.

This is exactly like a regular breakpoint, but we have a condition associated with our breakpoint. This can be a useful technique so that we avoid having to hit 'next' or 'step' many times, especially if we have huge loops to go through.

  • Here's an example where we break at line 17 if the value x is greater than 100

    • break 17 if x >100

    • Note -- you can also use 'C style syntax' to create more complicated expresssions

      • e.g. break 9 if i < 100 && i > 50

Note: Conditional breakpoints may impact your performance, so if you are executing a graphical application, understand that conditional breaks are constantly being checked and may lower performance. This is something to keep in mind anytime you are using a debugging tool.

We can run our code until a given location more expicity.

  • advance

    • We then need to specify a location, either a line, an explicit location (e.g. a function name), or and address

      • advance 10

      • advance foo

      • More help on locations: https://sourceware.org/gdb/onlinedocs/gdb/Specify-Location.html#Specify-Location

  • until

    • Is another useful command t hat can run until a location is reached, or until the current stack frame returns.

  • skip

    • Occasionally you will jump into some file that you do not care about, or otherwise is a library

    • You can skip these files by specifying which file to skip

      • skip filename

      • info skip will provide details on what has been skipped (skip delete will remove the previously set skips).

  • jump or 'j'

    • The jump command allows you to jump to a specific memory in location to start executing.

Instead of setting many breakpoints around our code, we can instead 'watch' a specific variable so that our program will 'pause' every time that variable is modified.

  • watch variablename

  • rwatch variablename (Stops if the address of the variable is read)

One trick that can be useful is to save your breakpoints

  • Save your breakpoints

    • save breakpoints filename

    • source filename - Reload the breakpoints

    • Note: That if you edit your code, some of the breakpoints may move (i.e. the line numbers have changed).

      • IDEs typically manage this for you, and this is one advantage of IDEs which move breakpoints as you add or remove lines of code.

  • Breakpoints can also be enabled or disabled

    • enable breakpoint number

    • disable breakpoint number

On occasion you may want to display a variety of information to your screen. Printing repeatedly can get tedious, so the 'display' command is very useful for helping you display variables or information each step of the way.

  • display [format] variable_name

    • e.g. display p

    • e.g. display /b p (byte format)

  • info display

  • undisplay number

    • e.g. undisplay 1 (this removes the first item from 'info display')

Now that you have learned a few things, I want to show you how to get help within GDB. You can always refer to my other videos, but here are a few tricks.

  • Type man help on the terminal to get a listing of general commands.

  • Type help within GDB to get a listing of the help commands.

  • Type 'info' when you start gdb.

    • Try some commands like:

      • info source (once the program starts running)

  • The apropos command is useful for 'querying' based off of a keyword to find help if you cannot remember the exact help command.

Understand the foundations of computer systems in order to assist in debugging.

In this lesson I explain to you some of the basic components of an executable file (Or sometimes an executable object file, or a 'binary'). It's important to understand some of the makeup of a process when debugging, as you may often find yourself having to understand different types of memory and memory errors.

We often think about our programs execution in terms of 'functions', and functions are a key abstraction for us to actually get work done. In this lesson, I describe what is going on in our call stack, and the information that is stored in a 'stack frame'. It's important to understand the call stack, because it becomes a critical tool for understanding how to debug -- it answers the question how did we get here.

  • Continue until we return from the current function in the stack frame.

    • 'finish'

  • Navigates us up the call stack after we have a crash

    • 'up' or 'u'

  • Navigate us down the call stack

    • 'down' or 'd'

  • Display the whole stack trace

    • 'backtrace' or 'bt'

  • Retrieve current stack frames argument values

    • info args

      • Useful to see what input to function is.

    • info frame

      • Retrieve information about the frame.

    • info locals

      • Information on the local variables.

Segmentation faults are...good. They are designed to keep our operating system safe from other processes. Finding segmentation faults however, is the difficult part. Segmentation faults happen when you access memory that does not belong to your process.

Try to find the bug in this program using gdb!

Let's go through the solution!

Memory leaks occur when we do not free a resource that we have allocated. They can be very subtle with commands like 'strdup' that allocate.

We can use a memory checker like 'valgrind' to find these types of errors.

Note: How bad are memory leaks? You will notice the recording freeze for about a minute as our system becomes unresponsive! (I thought this was quite funny! :) )

In this lesson we are going to learn more about how stack buffer overflows occur. This is in part a review to the program stack, and will hopefully help you build an intuition about memory safety in a process.

  • Optional Activities:

    • Stack buffer overflow

      • https://en.wikipedia.org/wiki/Stack_buffer_overflow

    • Review the wikipedia article on the call stack at your convenience:

      • https://en.wikipedia.org/wiki/Call_stack

    • Review stack-based memory allocation

      • https://en.wikipedia.org/wiki/Stack-based_memory_allocation

Try to spot the memory leak in the leak_exercise.c file provided.

Here I'll show you how to run both valgrind and the address sanitizer to fix our memory lek.

Understand different ways to utilize various debugging tools.

It's something that you naturally do--narrow down and find your bug. You can think of it like a binary search for the root cause within your source files (OR your data files as well)

In this lesson we learn about the assert statement and static_assert (available in c++ 11)

  • assert

    • 'asserts' something absolutely must be true at run-time.

  • static_assert (C++11)

    • 'asserts' something absolutely must be true at compile-time

    • Note: Many programming languages otherwise have their versions of these asserts

One way to investigate how a program is working, by explicitly playing with the functions from within GDB by calling them. You can call functions from within GDB with the call command.

  • e.g. call add(2,2);

    • This may be a neat way to test your assumptions about how some code works.

  • A quick tip:

    • Type: list add to quickly find that function.

    • Type: info functions to see all of the functions available

It can be very helpful to attach to a running process, especially if a process is long running (i.e. in an infinite loop).

  • gdb -p <process id>

  • You can run detach when you have completed debugging

If an application has frozen, it can also be helpful to attach to it and see if you can even fix the program.

What is a core dump? A core dump (or core file) is a snapshot of the memory of a program when it crashed (i.e. a fault occurred). You can use core dumps to try to investigate why your program crashed. 

Core files may be useful to share to colleagues to help debug and see if they can spot the issue at the time of the crash (e.g. a bug crash database, or more generally for monitoring the state of a program).  (You can run ulimit -c to see how many core dumps will be created on your unix operating system).

Core Dumps (updating ulimit)

  1. ( install: sudo apt install systemd-coredump)

  2. Run ulimit -c 

    1. If your value is 0, then change to ulimit -c unlimited

      1. Now your core dumps will be recorded -- though you may want to change this to 0 later on, so as not to fill your machine with core dumps!

  3. Compile the sample.c file provided (gcc -g sample.c -o prog)

  4. Run ./prog and observe the core file created.

  5. Then run coredumpctl gdb

    1. This will load the last core dump file. You can use coredumpctrl dump to further inspect where the core file actually lives if you like.

    2. (Alternatively run gdb -c corefilename)

  6. Now you can investigate what went wrong using regular GDB command!

Note: Ctrl+'\' can force a program to terminate and generate a core dump if you want a core of the program.


More information on core dumps:

  • https://en.wikipedia.org/wiki/Core_dump

In this lesson, we'll use the gcore tool to create a core file of a running process or generate-core-file filename and also update ulimit There are a few different workflows we'll go through.

Core Dumps Using gcore

  1. Start a process and obtain the process id.

  2. Then run gcore pid (where pid is the process id from step 1)

  3. Then run gdb -c corefilename (or gdb -core corefile)

    1. This will open up the core file.

  4. What you probably want though, is the actual exectuable, so run gdb ./prog corefile

    1. Now continue your debugging journey!

Sometimes you have to really take a deep dive into the code and investigate the memory. In this lesson, I'm going to show you how to examine memory.

I'll also show you one more tool as a bonus called hex that is often used to investigate memory.

Resources:

https://sourceware.org/gdb/onlinedocs/gdb/Memory.html

Save time by creating scripts for frequently executed commands.

A neat trick you can do is to execute a specific command after a breakpoint has triggered.

  1. After you create the breakpoint, type 'commands'

  2. Then proceed to type a few commands

  3. Then type end when you are finished.

On occasion it may be useful to define your own commands to use in GDB, especially if you have larger tasks that you like to perform.

  • Type define command_name

    • Then add as many commands as you like

    • (i.e. try a few commands like print $pcbt, etc.)

  • Then type end 

    • This will terminate the sequence of command that are input.

If you'd like some series of tasks to repeat when you start GDB (or otherwise to define some commands) you can do so in the .gdbinit file (either in a home directory, or the current directory you are executing GDB from)

  • Within the current directory where gdb is executing, it can load the .gdbinit file. This way you can put some common 

  • You can also use -x to execute specific scripts, so if you do a task frequently you can execute a script file.


From within GDB you can execute many commands from the shell just like you normally would. You can prefix with shell to explicitly do this, and some other commands like 'make' that are common will just execute.

  • You can run normal shell commands within gdb if you like

    • shell ls

  • You can additionally pipe the output from GDB to shell commands

    • pipe p x | nl

  • Some other common commands like 'make' will also work within GDB without having to type 'shell' beforehand (but you can also just do 'shell make')

On occasion you may want to make a change within GDB using your editor of choice. Personally, I prefer to have GDB open in another window, but on occasion you may want to make a quick change.

  • First make sure that you set your editor in an environment variable prior to using GDB

    • EDITOR=/usr/bin/vim
      export EDITOR

  • Then type 'edit linenumber' or 'edit function' to begin editing the line number or corresponding function within GDB. Changes will be saved, though you may not see the changes within GDB until you reload the compiled file.

  • Load a file with 'file' to execute a new file if you need to rebuild (make will typically reload the program)

Understand how to use Python with GDB

In this lesson we'll learn about using python within GDB. This is quite a powerful tool again for automation. Many folks will have python already available, so we'll first check to see if it is available. If you do not have Python available, we'll quickly build GDB 13 from source with Python debugging enabled.

In this quick lesson, you will see that GDB actually has a full python interpreter embedded within it. The Python API can provide you another way to pro grammatically use Python to assist with your debugging tasks.


  • Examples to try:

    • Try one command with python print("hi") to make sure it works.

    • Let's now try a series of commands

      • python starts the python script

      • print("hello from Python")

      • import gdb (This brings in the gdb api to python)

      • end to terminate the python script

    • Now try some of the API

      • python gdb.execute('start') regularr gdb commands from python

      • python gdb.execute('next')

      • python result = gdb.parse_and_eval('p') // store variable

    • Use Ctrl+C to exit the python interpreter

  • Python Interactive interpreter

    • Type pi to enter commands one at a time

  • More information on using the API

    • python help gdb

More resources:

  1. https://sourceware.org/gdb/onlinedocs/gdb/Python-API.html (Python API in GDB)

Some topics that are less common in all debuggers

I would not say 'set' is very experimental feature wise, as it has been supported in GDB for a while. However, used in combination with a feature like reverse debugging, it can be useful to run experiments to see what would happen in a program.

  • set var i=500

  • print i

Note: that reverse debugging may not necessarily reverse the execution exactly, but in some instances gdb will at least try to do something reasonable.

  • target record-full

    • nextnextnext

    • reverse-next (Goes backward)

Note: There is also reverse-step, reverse-continue, and reverse-finish. For short, you can also do set exec-direction reverse or set exec-direction forward to invert the order of commands you do.

In this lesson we take a look at debugging programs with multiple threads.

  • Display which threads are running

    • info threads (see what threads are available)

      • Apply specific commands to specific threads

        • e.g. break counter thread 1 (breakpoint on variable for thread 1 only)

        • e.g. thread apply all bt (backtrace on all threads when you pause)

      • Note: info threads 1 10 (Gives you information about specifically threads 1 and 10)

    • Sometimes you will want to work with specific threads so you can specify the thread:

      • thread 1(specifically follow execution of thread 1)

        • Then it's important to set scheduler-locking on

        • Now when you apply commands to you'll be working with a specific thread (e.g. thread 1)

        • When you are done, set scheduler-locking off

Depending on your operating system, you can actually save the state of a program in a 'snapshot' and return to it later.

This can be very powerful, and allow you to for instance share the snapshot with a colleague, or otherwise prevent you from having to re-run and re-debug a program from scratch.

  • Save a snapshot of the debugged program's execution state

    • checkpoint

    • delete checkpoint checkpoint_id will delete the checkpoint

  • List checkpoints that have been saved in the current debugging session

    • info checkpoint - lists out the checkpoints

    • restart checkpoint_id - Restarts the execution from a saved checkpoint

Learn how to speed up your iteration cycle while debugging.

In this lesson we show how to stay within GDB to iteratively debug and perform new builds.

  • Load a file with 'file' to execu

  • Rebuilding your source code in GDB with a makefile.

  • You can also use the 'shell' command within gdb

    • This will give you a shell within gdb if you need to execute some additional commands like generating some new input data for your script.

    • (That being said, I prefer to just use tmux to split my windows and do that task in a separate tmux window).


Notes to self--show how to recompile from within gdb, use 'disable' command to remove breakpoints.

Some other useful debugging tools

The Data Display Debugger (DDD) adds a graphical user interface to GDB (and other compatible debuggers).

It is especially helpful for 'visualizing' data structures if  you are on a linux system. If you don't have access to this tool don't worry, it's merely important to get exposure that such tools exist, and you can search for your platform an appropriate tool (Or, use a virtual machine if appropriate!).

Understanding what's going on at a system or library level can be important for understanding lower level bugs (e.g. embedded devices) or getting a profile of what's going on to understand performance.

  • strace a useful tool for seeing system calls

    • strace

    • strace -c

  • ltrace a useful program for seeing library calls.

    • ltrace

    • ltrace -c

In this video, we will introduce a dynamic analysis known as Valgrind to help track memory leaks in our program. Valgrind essentially shadows all of our memory allocations so that we can see if we have leaked memory anywhere.

Note: Most folks on linux machines will just want to install this using your package manager, but I'll show you how to install this tool from source, another good skill to have!

In this lesson I show you how to use Valgrind and GDB together to find bugs. This is a nice review of some of the tools that we have learned, and a reminder that you can combine as many of the tools that we learn together in order to help debug!

  • gdb

    • We learn about one new trick where we can: print sizeof(data_type)

    • Occasionally this is helpful when debugging memory errors and trying to figure out which allocation was not freed.

cppcheck is a useful static analysis tool that can help us find bugs before we even run our program.

In this lesson, I'll also show you a flag (effc++) that can find some simple stylistic errors in our code as well using a simple static analysis. These tools aren't perfect, but they may help you find some errors in especially large code bases.

  • cppcheck

    • https://cppcheck.sourceforge.io/

  • Example of effective c++ flag

    • g++ -g -Weffc++ -Wall -std=c++17 *.cpp -o prog

How to take your debugging further

In this lesson, I am going to demonstrate how to use GDB to figure out what virtual function was called from an object that has been created at run-time. In C++ this can be helpful, especially if you have complicated inheritance hierarchies, and you want to know from where your function was called, or otherwise what member functions will be called in the future based on the objects type at runtime.

  • whatis

    • This will provide the object type for which it was statically declared.

  • info vtbl object_name

    • This will provide the vtbl

Understand what the Debug information is for.

Did you know, you can use more than '-g' for debugging?

  • -g0 produces no debug information

  • -g1 produces some information, but no line numbers

  • -g2 is the default level that we have been using (same as -g). We get line numbers, symbols, file information at this level.

  • -g3 can include further information such as macros

  • -ggdb produces debug information specific to gdb. This is like -g3, and will try to generate as much information as possible that is specific to GDB. 

    • This can be very handy if you really have a tough problem to debug.

    • -ggdb3  This again produces as much information as possible.

    • Try info macro log for instance to see the macro expansion.


More Resources

A nice summary is provided here: https://web.mit.edu/rhel-doc/3/rhel-gcc-en-3/debugging-options.html

Close out the course and figure out next steps

Congratulations on completing this course--it was no small feat! Finishing the course however, I believe will save you an immense amount of your precious time!

  • Revisit the "working example of GDB" at the start of this course to review all that you have learned.

  • Your next steps are to dive into the more advanced features of GDB. 

  • Look at integrations for GDB with your favorite text editors

  • If C, C++, or perhaps DLang are not your main languages, then you should take the skills you learned, and apply them to your favorite debugger otherwise.

Congratulations again, and be sure to check out any 'Going Further Lessons' we add at the end.

And if you enjoyed the course, please share your review with a colleague!

Look at other languages with GDB

As mentioned, GDB can be used with other programming languages.

For instance, the D programming language, we can do some debugging. We often cannot use all of the features, but many of the things we have learned will transfer over. Keep an eye on your favorite programming languages to see what supported debugging features continue to come.

Good to know

Know what's good
, what to watch for
, and possible dealbreakers
Teaches debugging techniques applicable to multiple languages, which allows learners to transfer their skills beyond C and C++
Explores advanced topics like reverse-debugging and scripting, which are not typically covered in basic debugging courses
Requires familiarity with C or C++ to get the most out of the course, which may exclude learners without prior experience
Focuses on GDB, a debugger available on almost every platform, which makes it accessible to a wide range of developers
Covers memory leak detection using Valgrind, a tool that may be Linux-specific, potentially limiting its use for Windows or macOS users
Includes instruction on using older debuggers, such as DDD, which may be less relevant for developers familiar with modern IDEs

Save this course

Save Hands on Debugging in C and C++ to your list so you can find it easily later:
Save

Activities

Be better prepared before your course. Deepen your understanding during and after it. Supplement your coursework and achieve mastery of the topics covered in Hands on Debugging in C and C++ with these activities:
Review C/C++ Memory Management
Solidify your understanding of memory management in C and C++ to better identify memory-related bugs during debugging.
Browse courses on Memory
Show steps
  • Review dynamic memory allocation concepts.
  • Practice writing code with pointers.
  • Study common memory error patterns.
Read 'Effective C++'
Learn effective C++ coding practices to reduce the likelihood of introducing bugs, making debugging sessions more productive.
Show steps
  • Read and understand each item in the book.
  • Apply the principles to your C++ code.
  • Reflect on how these principles can prevent bugs.
Practice debugging with GDB exercises
Enhance your GDB proficiency by working through debugging exercises that simulate real-world scenarios.
Show steps
  • Find debugging exercises online.
  • Set up GDB and compile the exercise code.
  • Use GDB to identify and fix bugs.
  • Repeat with different exercises.
Four other activities
Expand to see all activities and additional details
Show all seven activities
Document common debugging techniques
Reinforce your understanding of debugging by creating a guide that explains common debugging techniques and GDB commands.
Show steps
  • Choose a format for your guide.
  • Describe common debugging techniques.
  • Provide examples of GDB commands.
  • Share your guide with other students.
Debug a complex C/C++ project
Apply your debugging skills to a real-world project to gain experience with complex codebases and challenging bugs.
Show steps
  • Find an open-source C/C++ project.
  • Set up the project and build it.
  • Identify and fix bugs in the project.
  • Contribute your fixes to the project.
Read 'Hacking: The Art of Exploitation'
Gain a deeper understanding of security vulnerabilities and how they can be exploited, leading to more effective debugging strategies.
Show steps
  • Read and understand the book's concepts.
  • Experiment with the provided code examples.
  • Reflect on how these concepts relate to debugging.
Help other students with debugging
Solidify your debugging knowledge by helping other students troubleshoot their code and use GDB effectively.
Show steps
  • Participate in online forums or study groups.
  • Answer questions about debugging techniques.
  • Provide guidance on using GDB.

Career center

Learners who complete Hands on Debugging in C and C++ will develop knowledge and skills that may be useful to these careers:
Operating Systems Developer
An operating system developer works on the core software that manages computer hardware and resources. This role requires a strong understanding of low-level programming and debugging. This course, with its hands-on approach to debugging in C and C++, is very relevant. The course covers how to use GDB and also introduces tools like Valgrind, which are essential for finding and resolving operating system issues such as memory leaks and crashes. These skills are imperative for an efficient and reliable experience for the end user.
Software Developer
A software developer creates applications and systems by writing code. This course, with its focus on debugging in C and C++, is directly applicable, as debugging is a critical skill for any software developer. You will learn how to use GDB and other tools to identify and correct errors in your code, which will save development time and result in more robust software. By learning how to pinpoint issues, a software developer will be more efficient in their work.
Reverse Engineer
A reverse engineer analyzes software to understand how it works, often without access to source code. The debugging skills that this course presents are crucial for this work. The course teaches you how to use debuggers like GDB, which are invaluable tools for analyzing compiled programs to find bugs and vulnerabilities. This course's focus on memory analysis, debugging tools, and dynamic analysis methods can be highly beneficial to a reverse engineer.
Systems Programmer
A systems programmer writes low-level code that interacts with the operating system and hardware. This includes work on operating systems, device drivers, and other core system components. This course, with its practical approach to debugging C and C++ code, is very relevant. The course teaches how to use GDB and other tools, allowing a systems programmer to efficiently diagnose and resolve issues. Additionally, the course goes over topics like memory leaks and reverse debugging, which may be highly beneficial to a systems programmer.
Firmware Engineer
A firmware engineer develops the low-level software that controls embedded devices. This role relies heavily on languages like C and C++, making this debugging course highly pertinent. The course's focus on using GDB, valgrind, and other debugging tools, is core to a firmware engineer's daily responsibilities. The course covers practical skills like memory leak detection and reverse debugging which are valuable for a firmware engineer. With these debugging capabilities, a firmware engineer can confidently improve the robustness and reliability of their embedded systems.
Embedded Systems Engineer
An embedded systems engineer designs, develops, and maintains software for devices that are not general purpose computers. These systems often involve low-level programming in languages like C and C++. Therefore, the debugging skills taught in this course are essential. The course teaches how to use GDB and other tools to fix bugs which is imperative when working with real-time systems and hardware constraints that an embedded systems engineer faces. Debugging embedded systems can be particularly complex, and this course can provide the targeted skills needed to address such challenges.
Robotics Engineer
A robotics engineer develops software for robots in a variety of settings. This typically involves programming in C and C++, making debugging tools essential to their daily work. This course provides the debugging methods to tackle common issues that may arise in robotics, such as memory and performance problems. Through use of valgrind, gdb, and other debugging tools, this course provides significant benefit to a robotics engineer.
Compiler Developer
A compiler developer creates the software that translates programming languages into machine code. This work involves understanding low-level details of code execution and debugging. This course's focus on debugging with GDB and other tools will provide a compiler developer a view of the compiled code output. This course will help to fix issues related to optimization and code generation that may be important to a compiler developer, allowing for more efficient code.
Performance Engineer
A performance engineer focuses on optimizing software to run faster and more efficiently. This often requires an in-depth understanding of low-level details and debugging techniques. The debugging tools and techniques taught in this course, like GDB and Valgrind, will help a performance engineer identify bottlenecks and inefficiencies in their code. The ability to analyze program behavior at a detailed level, made possible by this course, can significantly improve performance optimization and program stability.
Game Developer
A game developer is involved in building video games, which often requires a deep understanding of programming languages like C and C++. This course, focused on debugging in C and C++, may provide the foundation to fix errors in game code. The course's instruction on using GDB, Valgrind and other debugging methods helps address the unique challenges that game developers face, such as performance issues and memory management problems. With the skills learned in this course, a game developer will be more efficient, leading to an increase in productivity.
Technical Lead
A technical lead is responsible for guiding a team of developers and ensuring the quality of the software being produced. The ability to debug code is not only essential for personal work, but also for guiding others. This course helps technical leads better understand debugging techniques, like using GDB, which enables more effective code reviews and issue resolution. This course can also help you diagnose complex problems and train junior developers in efficient debugging methods. Being able to fix bugs quickly can greatly help with a technical lead's responsibilities.
Security Engineer
A security engineer is tasked with identifying and mitigating vulnerabilities in software systems. A deep understanding of how programs work and how they can fail is essential to understand potential pitfalls. This course, with a focus on debugging techniques, will assist a security engineer in analyzing software errors and potential areas of exploit. The course's practical approach to debugging C and C++ code, using GDB and other tools, can help a security engineer detect and prevent vulnerabilities.
DevOps Engineer
A DevOps engineer is responsible for the deployment and maintenance of software systems. Debugging and troubleshooting software issues is a vital part of their responsibilities. This course, with its focus on debugging in C and C++, provides an avenue to better understand issues that arise in more complex systems. The course provides a working knowledge of practical debugging and analysis tools that are essential for a DevOps engineer.
Software Architect
A software architect creates high-level designs for software systems. While not directly writing code daily, a software architect must have a strong understanding of debugging. The content of this debugging course, focused on C/C++ development, may be useful when designing robust and reliable systems. By better understanding how code works at a low level and the problems that may arise, a software architect can make better decisions about system design and code organization. A software architect may also help others in the organization improve their debugging skills.
Data Scientist
A data scientist analyzes and interprets complex data. Sometimes, data scientists will need to work with underlying code to develop data pipelines and handle large datasets. This course, with exposure to debugging principles in C and C++, helps data scientists resolve issues in their work. It can also help data scientists understand how to fix issues if they are using software libraries written in C or C++. This improves the efficiency of data analysis and project outcomes.

Reading list

We've selected two books that we think will supplement your learning. Use these to develop background knowledge, enrich your coursework, and gain a deeper understanding of the topics covered in Hands on Debugging in C and C++.
Provides valuable insights into writing robust and efficient C++ code. It covers common pitfalls and best practices that can help you avoid bugs in the first place. Understanding these principles will make debugging easier and more effective. It is commonly used as a textbook at academic institutions.
Provides a deep dive into low-level programming and security vulnerabilities. While not directly focused on debugging, it helps you understand how bugs can be exploited, leading to better debugging strategies. It provides background knowledge on how exploits work. It is more valuable as additional reading than it is as a current reference.

Share

Help others find this course page by sharing it with your friends and followers:

Similar courses

Similar courses are unavailable at this time. Please try again later.
Our mission

OpenCourser helps millions of learners each year. People visit us to learn workspace skills, ace their exams, and nurture their curiosity.

Our extensive catalog contains over 50,000 courses and twice as many books. Browse by search, by topic, or even by career interests. We'll match you to the right resources quickly.

Find this site helpful? Tell a friend about us.

Affiliate disclosure

We're supported by our community of learners. When you purchase or subscribe to courses and programs or purchase books, we may earn a commission from our partners.

Your purchases help us maintain our catalog and keep our servers humming without ads.

Thank you for supporting OpenCourser.

© 2016 - 2025 OpenCourser