Tutorial: Getting Started with UDB in VS Code

Tutorial: Getting Started with UDB in VS Code

Overview

This quick start tutorial will walk you through how to use the Time Travel Debug extension in VS Code to identify the root cause of a small bug.

You will:

  • set up and run UDB (time travel debugger)
  • install the Time Travel Debug VS Code extension
  • locate and use a provided example program
  • learn the basics of time travelling backward and forward through code in order to inspect and easily understand program state

The principles learned by following this guide can be used on on complex codebases with millions of lines of code.

Unpack the Tar file

If you have just downloaded UDB, you will need to install it to get going.
If you have already done this, you can skip this step by clicking ‘Next’ to continue.

It’s easy to install UDB. Here’s what you need to do:

  1. If you’re debugging remotely via the Remote – ContainersRemote – SSH or Remote – WSL Visual Studio Code extensions, copy the .tar.tgz file that you just downloaded onto the remote system.
  2. Open the Terminal app.
  3. Unpack the .tar.tgz file that you just downloaded with the following command:
    tar -xzf UDB-Individual-Evaluation-<version>.tgz

  4. This will create a folder named UDB-Individual-Evaluation-<version>

Install the VS Code extension

  1. Run Visual Studio Code
  2. If you’re debugging remotely via the Remote – ContainersRemote – SSH or Remote – WSL extensions, connect to the remote system.
  3. Bring up the Extensions view by clicking on the Extensions icon in the Activity Bar on the left hand side of the window, type undo.udb into the search box and press Install.

Install_Code_002.gif

Open the examples

We will use the sample program cache-cpp.c (cache calculate) that can be found in the examples directory of the UDB-Individual-Evaluation-<version> folder you just created.

This sample program maintains a square root cache data structure in memory and validates it through repeatedly looking up values, caching additional new values on a cache miss.

  1. Choose File → Open Folder …
    Code_014
  2. Navigate to the examples folder inside the UDB-Individual-Evaluation-<version> folder that you created in the last step and press OK. 
    Code_012
  3. You will be building and running the example programs in this folder, so press “Yes, I trust the authors” to allow debugging 
    Code_001
  4. Click on cache-cpp.cpp in the sidebar to open the cache calculate sample program
    Code_004

Build & run the example program

To build and run the examples you’ll need a C++ compiler and the make program.

On Debian/Ubuntu you can install these with sudo apt install build-essential.

  1. Bring up the Run view by clicking on the Run icon in the Activity Bar on the left hand side of the window
  2. Press the the green Run button to build and debug
    Code_006
  3. View the UDB license and press “Accept the license”
    Code_008
  4. UDB stops the program at main(). Let’s run it to the end. Press the Continue button to do this Code_010

Run UDB & diagnose the problem

  1. We can see from the Call Stack that the program has crashed out with an abort():
    Code_015
    Note: unless you have libc sources installed on your machine you’ll see messages from VSCode about not being able to display source code – you can safely dismiss these messages as they occur.
  2. Let’s analyze the program’s execution and diagnose the reason for the failure.
    By selecting the Terminal tab we can see the output of the program – the number that the program is pulling out of the cache is not the expected number.
    Code_019
  3. Because UDB is a time travel debugger we can run the execution of the program in reverse.
    Press the Reverse Step Out button six times to reverse up the stack to the throw() statement in cache-cpp.cpp.
    Code_025
  4. With a time travel debugger you can go back to any line of code that executed and see the complete program state.
    So we are currently looking at the state of the variables at the point that the exception was thrown.
    Looking in the Variables window we can see that the code is querying the cache for the square root of the number 255.
    The integer square root of the number 255 is 15 (in sqroot_correct). But sqroot_cache is 0; which is the wrong value.
    Code_026
  5. This is the point where the defect manifests, but it’s not the root cause of the defect. We need to find the point where the cache is populated with the incorrect value. Line 123 is where the sqroot_correct variable is set.
    Use the Reverse Step Over button 3 times to step back in time to line 123.
    Code_027
  6. The previous line is where the sqroot_cache variable is set to its incorrect value. Use the Reverse Step Into button to step back into the function which returns this value.
    Code_028
  7. Press Reverse Step Over to step a little further back in time to where this function returns the incorrect value
    Code_029
  8. Switch to the Debug Console, type f->second and press Enter. We can confirm that the number being pulled out of the cache is 0; which is incorrect.
    Code_030
  9. Now we need to find out where this cache entry was populated with this incorrect value.
    We can do this by setting a watchpoint (a.k.a. a data breakpoint) on the incorrect entry in the cache and running back in time to where it was set.
    In the Debug Console type: `watch -l f->second (note the backtick) and press Enter.
    Code_031
  10. Press the Reverse Continue button to run backwards in time and stop at the moment this cache entry was written to.
    Code_032
    Note: VS Code reports hitting a watchpoint that was set from the Debug Console as an “Unknown watchpoint”.
  11. We’re in the depths of the C++ STL. Press Reverse Step Out four times until we’re back in our code, just before the incorrect value is placed in the cache.
    Code_033
  12. Looking at number_adj in the Variables window we can see that the code is trying to calculate the square root of -1.
    And looking at sqroot_adj we can see that it’s storing the square root of -1 as 0.
    So there are two problems – why does the code think the square root of -1 is 0, and why is it trying to calculate the square root of -1 in the first place?
    Code_033
  13. sqroot_adj is calculated on the previous line; line 69. Because UDB is a time travel debugger we can call arbitrary functions at any time in the program’s execution history.
    So in the Debug Console type sqroot(-1) and press Enter to see what the function returns.
    Code_034
    The sqroot() function returns a special value that indicates “not a number”, which on line 69 is statically cast to an int to fit into the data container which is expecting integers.
    This explains why the code is storing 0 in the cache; there’s no check that the number being stored is representable as an int.
  14. You’ve discovered that the root cause of this program failure happens as a result of attempting to put the square root of -1 into the cache, which was not intended.
    But why is it trying to calculate the square root of -1 in the first place?
    Code_035
    That happens because the for loop in line 66 loops from number-1 to number+1, but there is no protection anywhere to deal with the special case that we just hit where the number is zero.
    A simple if (number_adj < 0) continue; at the start of the for loop would have avoided this error.

Tutorial Complete

Congratulations! you have successfully used time travel debugging in VS Code to diagnose the root cause of an error!

You’re now a time travelling Bug Hunter!

Next steps

Do more with time travel debugging in VS Code.

  1. VS Code docs
  2. Get started with your own project
  3. Operation
  4. Launch configurations
  5. Limitations

Help and Support

If you get stuck, help is always at hand.

UDB Documentation
Community – ask a question

Stay informed. Get the latest in your inbox.