Data Structures and Algorithms

Using GDB in VSCode

What is GDB?

The GNU Project debugger, abbreviated GDB, is a debugging tool for a variety of languages including C++. GDB allows you to watch how a program runs step by step, pause the program when it reaches specific places, and examine the values of variables in memory when the program is stopped. This allows you to identify when your program starts behaving incorrectly and gain more information about what is going wrong.

GDB is a separate tool from VSCode and can be executed on the command prompt. It does not rely on a graphical user interface, meaning that it can be used in environments where only a text-based terminal is available. It is also useful for a variety of other purposes than those described here. From the command prompt, GDB is executed somewhat like Valgrind: to debug a program that is normally launched by running ./foo arg1 arg2, we instead run gdb --args ./foo arg1 arg2. For more information on using GDB on the command prompt, please refer to Dr. Tia Newhall’s thorough GDB guide.

Setting Up GDB in VSCode

To use GDB in VSCode in CS35, you’ll need to begin by setting up a debugging profile. To keep things simple, you can set up a single debugging profile that you’ll use throughout the semester, changing it as you need to run different programs with different arguments. This means you should only need to follow these setup instructions once for the course. For purposes of illustration, this guide will include screenshots of a simple single-file project with a Makefile.

To begin, we should make sure we have the Microsoft C++ support tools installed. You can install this extension through the extensions button on the left side panel. You can also do this by using your CS network account to run code --install-extension ms-vscode.cpptools. You can do this from within VSCode or using a separate terminal.

To set up a GDB profile, we begin by clicking on the arrow-and-bug symbol on the left side panel. This opens the “Run and Debug” pane. In that pane, click the hyperlink which says “create a launch.json file”. You will be presented with a list of profile types to use when creating the file. You should choose “C++ (GDB/LLDB)”. If that option is not available and you just installed the C++ tools plugin, you may need to restart VSCode and try again before you see it.

The launch.json file is stored in a hidden directory named .vscode. Once you have created it, you should see open appear in your editor. This file uses a format based upon JavaScript Object Notation (JSON), which is a convenient way of using a text editor to manipulate structured data. JSON and similar formats are commonly used to configure applications like VSCode. Here is an example of how your editor might look after creating the launch.json file.

There should be a button labeled “Add Configuration…” in the bottom right of the window. Press it and choose the “C/C++: (gdb) Launch” option. Make sure to select the right option! It will be difficult to notice if you have selected a different option here until something goes wrong down the line.

Once you’ve selected this option, a new configuration will be added to your launch.json file. This will be a considerable quantity of text describing to VSCode precisely how you want it to run GDB. Here’s an example of how the file might appear after this configuration is added.

Press Ctrl+S (or Command+S on a Mac) to save the launch.json file. This will change the “Run and Debug” pane to display a series of smaller panes labeled things like “variables”, “watch”, and “call stack”.

We are almost finished setting up our debug profile! To make it easier to use VSCode in the future, especially if you use it for different courses, it will be helpful to give your profile a name. The default name is (gdb) Launch, which isn’t very informative. Find the text "name": "(gdb) Launch" in the configuration file nad change it to "name": "CS35 Debug". Make sure when you do this not to delete the quotation marks the comma at the end of the line, as these are necessary for the file to match the format used by VSCode. When you save this file, the name of the selected debug profile in the upper left of the window will change as well.

Since you’ll be using this profile throughout the semester, you shouldn’t need to follow these instructions again. You’re now ready to start using GDB in VSCode!

Configuring VSCode to Run Your Program With GDB

When you want to use GDB to debug your program, you’ll need to tell VSCode how to run it. This involves editing a couple lines in your launch.json. If you have just created your launch.json file, you might still have it open. Otherwise, you’ll need to open the launch.json file by clicking the arrow-and-bug symbol on the left side panel to open the “Run and Debug” pane. Click on the small gear icon at the top left of the window to open launch.json.

There are two things you’ll probably need to edit in your launch configuration. The first is the program setting, which should give the name of the program you want to run. Our project for this tutorial will run a program called example. To make sure that this program runs as we expect, we will give the absolute path for that program when we configure the editor. You can find the absolute path of your program by using realpath, a tool which will print the absolute path of your executable. In our demonstration, realpath ./example prints /home/zpalmer/project/example.

Edit your launch.json file to change your program setting to equal the absolute path of your program. In this case, we are editing our launch.json file to include the text "program": "/home/zpalmer/project/example". Your program setting will differ based upon the absolute path of your program’s executable.

Next, determine what command line arguments you want your program to receive. In the case of this tutorial, the example program expects a single command-line argument. For instance, we might run our program by entering ./example clouds on the command line. Because we have a single additional argument – clouds – we will edit the args entry of launch.json to read "args": ["clouds"]. The value after "args": is expected to be a list of strings. If you have no command line arguments, use an empty list by writing []. If you have three command line arguments, you might write "args": ["arg1", "arg2", "arg3"]; this would be like running ./example arg1 arg2 arg3 in your terminal.

Make sure to save your launch.json file once you have made these changes.

Debugging Your Program in VSCode

Once you have configured your program to run in VSCode’s debugger, you can tell the debugger you’d like it to pause at the top of your main function by setting a breakpoint. Click the area just to the left of the line number at the beginning of your main function; you’ll see a red dot appear and it will brighten when you click it.

Once you have set the breakpoint, you can run the program through the debugger by pressing the green arrow in the upper-left of the “Run and Debug” pane.

This will open a new terminal window that shows the output of the debugger, which might be either very verbose or completely blank (depending upon your situation). We can largely ignore that window for now, though. The line containing the breakpoint will be highlighted because GDB started running the program but stopped when it saw a breakpoint. Importantly, the highlighted line is the next line to run which has not run yet.

We can see the initial values of the variables in the upper left of the “Run and Debug” pane under “Variables”. Note, for instance, that argc is 2 in this example (because we configured VSCode to run ./example clouds).

We control the program by pressing the buttons which are, by default, docked in the top middle of the screen. (Hover over these buttons to see their keyboard shortcuts.) The second button in that row – the dot with an arrow arcing over it – will be particularly useful. It instructs GDB to run the next line of code and then pause again. In this example, pressing it causes the if condition to run and GDB pauses on the next line, an assignment to the string variable word.

If we press that same button again, GDB will run this line, which assigns argv[1] (a C-style string) to word (a C++ string variable). VSCode highlights the new value of word in the Variables section to draw our attention to the fact that it changed.

Note again that the highlighted line has not yet been executed. As a result, we have not yet seen anything printed. If we step forward again, we can see this output printed to the debugging terminal.

The next line to be executed calls a function. The third button in our control bar is a downward-pointing arrow over a dot, which denotes stepping into a function. Let’s consider pressing this button to enter the beExcited function and watch it run. Here’s what we see after we press that button:

This may be quite surprising! The code which has appeared here looks nothing like the code we were running (and isn’t code that we wrote at all). That’s because this code was running automatically as a result of passing the string object to the beExcited function. This is a consequence of how strings (as objects) in C++ are copied from one variable into another: this copying function runs implicitly before the beExcited function actually runs.

We don’t want to watch this function do whatever it does, so we can press the fourth button on the control bar (an upward arrow pointing away from the dot) to finish running the function we’re in and go back to where it was called. We find ourselves at the beExcited call again.

Even though it looks like we’re back where we started before we pressed the “Step Into” button, we’ve made progress: the parameter of beExcited has been filled. So we press the “Step Into” button again in order to see the beExcited function run.

The bottom left of the “Run and Debug” pane shows us information about the current call stack. The beExcited function is running now and it was called by the main function. In general, this section shows us our call stack while the Variables section shows us the variables in the currently-selected stack frame. Note that the s variable, the parameter to beExcited, is shown here rather than the word variable from main.

Summary

These three buttons – “Step Over”, “Step Into”, and “Step Out” – are the primary controls you use to run your program while debugging. As you do so, you can see the variables and call stack of your program change. You can use this tool to find bugs by running your program in a way that you know isn’t working correctly and stepping through its execution until you see something wrong: a variable with an incorrect value, a condition not running the way you expected, a loop never finishing, etc. Then, you can use the Variables view to inspect the situation and come to understand why your program behaved the way it did. This perspective can be invaluable in identifying the source of problematic program behavior and determining how to correct it.