Extending the Memory Inspector for C/C++ debugging

In Chrome 92, we introduced the Memory Inspector, a tool for inspecting linear memory buffers. In this article, we'll discuss how we've improved the Inspector for C/C++ debugging and the technical challenges encountered along the way.

These are a few relevant blog posts if you are new to C/C++ debugging and the Memory Inspector:

Introduction

The Memory Inspector provides you with more powerful debugging options for linear memory buffers. In the case of C/C++, you can inspect C/C++ memory objects in the WebAssembly Memory.

Recognizing your object’s bytes among the surrounding WebAssembly memory was a pain point. You have to know the object’s size and count bytes from the object’s start. In the screenshot below, the first byte of a 10-element int32 array is selected but it’s not immediately clear which other bytes belong to the array. Wouldn’t it be nice if you could instantly recognize all the bytes that belong to the object?

Screenshot of the original memory inspector with a single highlighted byte

Object highlighting in the Memory Inspector

Starting from Chrome 107, the Memory Inspector highlights all the bytes of a C/C++ memory object. This helps you tell them apart from the surrounding memory.

Screenshot of the updated memory inspector with a vibrantly highlighted array

Watch the video below to see the Memory Inspector in action. As you reveal the array x in the Memory Inspector, highlighted memory appears in the Memory Viewer along with a new chip right above it. This chip reminds you the name and type of the highlighted memory. Click on the chip to jump to the object’s memory. If you hover over the chip, a cross icon will appear – click on it to remove the highlight.

When you select a byte outside the object you inspect, the highlight defocuses to avoid distracting you. To refocus it again, click any of the object’s bytes or the chip again.

The support for object highlighting isn’t limited to arrays. You can also inspect structs, objects, and pointers. These changes make exploring the memory of your C/C++ apps easier than ever!

Want to give it a try? You will need to:

  • Have Chrome 107 or newer.
  • Install the C/C++ DWARF Extension.
  • Enable DWARF debugging in DevTools > Settings. Settings > Experiments > WebAssemble Debugging: Enable DWARF support.
  • Open this demo page.
  • Follow instructions on the page.

Debugging example

In this section, let’s take a look at a toy bug to illustrate how you can use the Memory Inspector for C/C++ debugging. In the code sample below, a programmer creates an integer array and decides to use pointer arithmetic to select the last element. Unfortunately, the programmer made a mistake in their pointer calculation and now instead of printing the last element, the program prints nonsense values.

#include <iostream>

int main()
{
    int numbers[] = {1, 2, 3, 4};
    int *ptr = numbers;
    int arraySize = sizeof(numbers)/sizeof(int);
    int* lastNumber = ptr + arraySize;  // Can you notice the bug here?
    std::cout <<../ *lastNumber <<../ '\n';
    return 0;
}

The programmer turns to the Memory Inspector to debug the issue. You can follow along with this demo! They first inspect the array in the Memory Inspector and see that the numbers array contains only the integers 1, 2, 3, and 4, as expected.

Screenshot of opened memory inspector with an inspected int32 array. All array elements are highlighted.

Next, they reveal the lastNumber variable from the Scope pane and notice that the pointer points to an integer outside of the array! Equipped with this knowledge, the programmer realizes that they miscounted the pointer offset at line 8. It should have been ptr + arraySize - 1.

Screenshot of opened memory inspector showing highlighted memory pointed to by a pointer named ‘lastNumber’. The highlighted memory lies right after the last byte of the previously highlighted array.

Although this is a toy example, it illustrates how object highlighting effectively conveys the size and position of memory objects, which can help you better understand what’s happening inside your C/C++ app’s memory.

How DevTools figures out what to highlight

In this section, we’ll look at the ecosystem of tools that enables C/C++ debugging. Specifically, you’ll learn how DevTools, V8, the C/C++ DWARF Extension, and Emscripten make C/C++ debugging in Chrome possible.

To unlock the full power of C/C++ debugging in DevTools, you need two things:

  • The C/C++ DWARF Extension installed in Chrome
  • C/C++ source files compiled to WebAssembly with the latest Emscripten compiler as instructed in this blog post

But why? V8 , Chrome’s JavaScript and WebAssembly engine, doesn’t know how to execute C or C++. Thanks to Emscripten, a C/C++ to WebAssembly compiler, you can compile apps built in C or C++ as WebAssembly and execute them in the browser!

During compilation, emscripten will embed DWARF debug data into your binary. On a high level, this data helps the extension to figure out which WebAssembly variables correspond to your C/C++ variables, and more. This way, DevTools can show you your C++ variables despite V8 actually running WebAssembly. If you're curious, check out out this blog post for an example of DWARF debug data.

So what actually happens when you reveal the lastNumber? As soon as you click on the memory icon, DevTools checks which variable you want to inspect. It then queries the extension on the data type and location of lastNumber. As soon as the extension responds with that info, the Memory Inspector can display the relevant slice of memory and knowing its type, it can also show you the object’s size.

If you look at lastNumber in the earlier example, you might notice that we inspected lastNumber: int *, but the chip in the Memory Inspector says *lastNumber: int, what gives? The inspector uses C++-style pointer dereferencing to indicate the type of the object shown to you! If you inspect a pointer, the inspector will show you what it points to.

Persisting highlights on debugger steps

When you reveal an object in the Memory Inspector and step with the debugger, the Inspector persists the highlight if it thinks it’s still applicable. Initially, we didn’t have this feature on our roadmap, but we quickly realized this compromises your debugging experience. Imagine having to re-inspect the array after every step like in the video below!

When the debugger hits a new breakpoint, the Memory Inspector again queries V8 and the extension for the variable associated with the previous highlight. It then compares the objects’ locations and types. If they match, the highlight persists. In the video above, there’s a for-loop writing to the array x. These operations don’t change the type or position of the array, so it stays highlighted.

You might wonder how this affects pointers. If you have a highlighted pointer and re-assign it to a different object, the old and new positions of the highlighted objects differ and the highlight disappears. Since the newly pointed-to object can live anywhere in the WebAssembly Memory and will likely have little relation to the previous memory location, removing the highlight is clearer than jumping to a new memory location. You can highlight the pointer again by clicking its memory icon in the Scope pane.

Conclusion

This article described our improvements to the Memory Inspector for C/C++ debugging. We hope the new features will simplify debugging the memory of your C/C++ apps! If you have suggestions to improve it further, let us know by filing a bug!

What’s next

To learn more, see: