扩展内存检查器以进行 C/C++ 调试

在 Chrome 92 中,我们推出了内存检查器,这是一款用于检查线性内存缓冲区的工具。在本文中,我们将讨论如何改进 C/C++ 调试检查器,以及在此过程中遇到的技术难题。

如果您刚开始接触 C/C++ 调试和内存检查器,请参阅以下几篇博文:

简介

内存检查器为线性内存缓冲区提供了更强大的调试选项。对于 C/C++,您可以检查 WebAssembly Memory 中的 C/C++ 内存对象。

在周围的 WebAssembly 内存中识别对象的字节是一个痛点。从对象开始,您必须知道对象的大小和计数字节数。在下面的屏幕截图中,我们选择了一个包含 10 个元素的 int32 数组的第一个字节,但是您无法马上清楚还有哪些其他字节属于该数组。如果您可以立即识别属于对象的所有字节,不是很不错吗?

原始内存检查器的屏幕截图,其中突出显示了一个字节

内存检查器中的对象突出显示

从 Chrome 107 开始,内存检查器会突出显示 C/C++ 内存对象的所有字节。这有助于您将它们与周围的内存区分开来。

更新后的内存检查器的屏幕截图,其中有一个以鲜明突出显示的数组

观看下面的视频,了解内存检查器的实际运用。在内存检查器中显示数组 x 时,内存查看器中会显示突出显示的内存,并在其正上方显示一个新条状标签。此条状标签会提醒您突出显示的内存的名称和类型。点击条状标签即可跳转到对象的内存。如果您将鼠标悬停在条状标签上,将会显示一个十字图标,点击该图标即可取消突出显示。

当您选择检查对象外的字节时,突出显示效果将会散焦,以免分散您的注意力。如需重新聚焦于该对象,请再次点击该对象的任一字节或条状标签。

对对象突出显示的支持不仅限于数组。您还可以检查结构体、对象和指针。这些更改让您可以更轻松地探索 C/C++ 应用的内存!

想试试吗?您需要执行以下操作:

  • 使用的是 Chrome 107 或更高版本。
  • 安装 C/C++ DWARF 扩展程序。
  • DevTools 启用 DWARF 调试 >设置。 设置 >实验 >WebAssemble 调试:启用 DWARF 支持
  • 打开此演示页面
  • 请按照页面上的说明操作。

调试示例

在本部分中,我们来看一个玩具 bug,说明如何使用内存检查器进行 C/C++ 调试。在以下代码示例中,程序员创建了一个整数数组,并决定使用指针算法选择最后一个元素。遗憾的是,该程序员在指针计算中出错,现在,程序输出的是无意义值,而不是输出最后一个元素。

#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;
}

程序员依靠内存检查器调试问题。您可以按照此演示操作!他们首先在内存检查器中检查该数组,并看到 numbers 数组仅包含整数 1234,如预期一样。

屏幕截图:包含已检查的 int32 数组的已打开内存检查器。所有数组元素均突出显示。

接下来,他们从 Scope 窗格中显示 lastNumber 变量,并发现指针指向了数组之外的一个整数!掌握了这些信息后,程序员意识到他们记错了第 8 行的指针偏移量。应该是 ptr + arraySize - 1

打开的内存检查器的屏幕截图,显示由名为“lastNumber”的指针所指向的突出显示的内存。突出显示的内存位于之前突出显示的数组的最后一个字节之后。

虽然这是一个玩具示例,但它说明了对象突出显示如何有效传达内存对象的大小和位置,从而帮助您更好地了解 C/C++ 应用内存中发生的情况。

开发者工具如何确定要突出显示的内容

在本部分,我们将介绍支持 C/C++ 调试的工具生态系统。具体来说,您将了解开发者工具、V8、C/C++ DWARF 扩展程序和 Emscripten 如何使 Chrome 中的 C/C++ 调试成为可能。

如需在开发者工具中充分发挥 C/C++ 调试的强大作用,您需要做好两件事:

  • Chrome 中安装的 C/C++ DWARF 扩展程序
  • 按照这篇博文中的说明,使用最新的 Emscripten 编译器编译为 WebAssembly 的 C/C++ 源文件

但这是为什么呢?V8 是 Chrome 的 JavaScript 和 WebAssembly 引擎,不知道如何执行 C 或 C++。得益于 Emscripten(一种从 C/C++ 转成 WebAssembly 的编译器),您可以将使用 C 或 C++ 构建的应用编译为 WebAssembly,然后在浏览器中执行它们!

在编译过程中,emscripten 会将 DWARF 调试数据嵌入您的二进制文件。概括来讲,这些数据有助于扩展程序找出哪些 WebAssembly 变量与 C/C++ 变量相对应等。这样,即使 V8 实际运行了 WebAssembly,DevTools 也可以向您显示 C++ 变量。如果您想查看 DWARF 调试数据示例,请参阅这篇博文

那么,当您显示 lastNumber 后,实际会发生什么呢?在您点击内存图标后,DevTools 便会立即检查您想要检查的变量。然后,它会针对 lastNumber 的数据类型和位置查询该扩展程序。一旦扩展程序用该信息进行响应,内存检查器就可以显示相关的内存切片并知道其类型,还可以向您显示对象的大小。

如果查看前面示例中的 lastNumber,您可能会注意到我们检查了 lastNumber: int *,但内存检查器中的芯片显示 *lastNumber: int,是什么原因呢?检查器使用 C++ 样式的指针解引用来指明向您展示的对象类型!如果您检查指针,检查器会向您显示指针所指向的内容。

对调试程序步骤保留突出显示效果

内存检查器中显示某个对象并使用调试程序单步调试时,如果检查器认为它仍然适用,则会保留突出显示的内容。最初,我们并未在路线图中添加此功能,但我们很快意识到,这会损害您的调试体验。想象一下,在执行每个步骤后,您都必须重新检查该阵列,如下面的视频所示!

当调试程序遇到新的断点时,内存检查器会再次查询 V8 和扩展,以获取与之前突出显示相关的变量。然后比较这些对象的位置和类型。如果匹配,突出显示的内容会继续显示。在上面的视频中,有一个 for 循环写入到数组 x。这些操作不会更改数组的类型或位置,因此会保持突出显示状态。

您可能想知道这对指针有何影响。如果您有突出显示的指针,并将其重新分配给其他对象,则突出显示对象的旧位置和新位置会有所不同,并且突出显示效果会消失。由于新指向的对象可以位于 WebAssembly 内存中的任何位置,并且可能与之前的内存位置几乎没有关系,因此移除突出显示比跳转到新的内存位置更清晰。您可以在 Scope 窗格中点击指针的内存图标,再次突出显示该指针。

总结

本文介绍了我们对用于 C/C++ 调试的内存检查器所做的改进。我们希望这些新功能可以简化 C/C++ 应用内存的调试过程!如果您对进一步改进有任何建议,请提交 bug 告诉我们!

后续步骤

如需了解详情,请参阅: