使用 Visual Studio 调试共享库

在前面的示例中,我们讨论了如何在 Visual Studio 中调试依赖项,但使用 Conan 依赖项的项目可能不存在原始的构建文件夹和构建文件。 Conan 包默认情况下不包含调试库所需的必要信息,这些信息存储在编译库时生成的 PDB 文件中。 使用 Conan 时,这些 PDB 文件是在构建文件夹中生成的,而构建文件夹仅在构建库期间需要。 因此,使用 conan cache clean 清理 Conan 缓存以删除构建文件夹并节省磁盘空间是一种常见操作。

对于构建文件夹不存在的情况,我们创建了一个钩子,将构建文件夹中生成的 PDB 文件复制到包文件夹中。 默认情况下无法强制执行此行为,因为 PDB 文件通常比整个包大,并且会大大增加包的大小。

本节将介绍一些关于如何在不同情况下调试项目的示例,以展示用户如何利用 PDB 钩子。

创建项目并像往常一样调试

首先,我们将像往常一样调试我们的项目,如在 前面的示例 中更详细地解释的那样。 我们可以像上一节一样从源代码开始构建我们的依赖项,只是这次我们将它们构建为共享库。 首先,从 GitHub 的 examples2 仓库 克隆示例所需的源代码并创建项目。

$ git clone https://github.com/conan-io/examples2.git
$ cd examples2/tutorial/consuming_packages/simple_cmake_project
$ conan install . -o="*:shared=True" -s build_type=Debug --build="zlib/*"
...
Install finished successfully

# CMake presets require CMake>=3.23
$ cmake --preset=conan-default

注意

我们将仅涵盖依赖项构建为共享库的情况,因为 PDB 及其与库的链接方式对于静态库而言有所不同。

现在我们可以打开解决方案 compressor.sln 在 Visual Studio 中打开我们的项目并像在前面的示例中解释的那样对其进行调试。 在第 22 行设置断点,运行调试器并使用“步入”将允许我们在依赖项文件 deflate.c 中进行调试。

Debugging with build files in cache

在这种情况下,原始构建文件都存在,因此调试器像往常一样工作。 接下来,我们将看到在从 Conan 缓存中删除构建文件后调试器的工作方式。

从 Conan 缓存中删除构建文件

有多种原因可能导致依赖项编译后构建文件不存在。 我们将从缓存中清理我们的构建文件,以使用 conan cache clean 模拟其中一种情况。 --build 标志确保我们仅删除构建文件,因为我们将需要此示例的源代码文件。

$ conan list "zlib/1.2.11:*"
$ conan cache path --folder build zlib/1.2.11:17b26a16efb893750e4481f98a154db2934ead88
$ conan cache clean zlib/1.2.11 --build
$ conan cache path --folder build zlib/1.2.11:17b26a16efb893750e4481f98a154db2934ead88

在 Visual Studio 中关闭并重新打开我们的解决方案后,我们可以尝试再次调试。 如果您尝试在第 22 行的断点处“步入”依赖项,您会注意到它会直接跳过到下一行,因为 Visual Studio 没有关于要调试的依赖项的任何信息。

安装一个钩子将 PDB 文件复制到包文件夹

为了解决包文件夹中没有 PDB 文件的问题,我们创建了一个钩子,将 PDB 文件从构建文件夹复制到包文件夹中。 该钩子在 conan-extensions 仓库 中可用。 安装整个仓库是可以的,但我们建议仅从 conan-extensions 仓库安装 hooks 文件夹,使用

$ conan config install https://github.com/conan-io/conan-extensions.git -sf=extensions/hooks -tf=extensions/hooks

该钩子被设计为默认情况下不会运行,因为它会显著增加包的大小。 如 hooks 文档 中解释的那样,我们需要将钩子的名称更改为以 hook_ 开头。 要找到钩子放置的路径,请运行命令 conan config home 以找到您的本地缓存路径,然后转到 extensions/hooks 文件夹以重命名 _hook_copy_pdbs_to_package.py 文件。 请注意,此钩子将在每次运行 package() 方法时运行,要禁用钩子只需将钩子重命名为以 _hook_ 开头即可。

该钩子被实现为后包钩子,这意味着它将在通过配方的 package() 方法创建包之后执行。 这避免了任何潜在问题,因为顺序将如下所示

  • 配方的 build() 方法被执行,生成 DLL 和 PDB 文件

  • 配方的 package() 方法被执行,将必要的文件复制到包文件夹(在本例中为 DLL 但不包括 PDB 文件)

  • 钩子被执行,将 PDB 文件从构建文件夹复制到包中每个 DLL 旁边的位置

该钩子利用 Visual Studio 安装中包含的 dumpbin 工具。 此工具允许我们获取 DLL 的信息,在本例中是其关联 PDB 文件的位置。 它将用于包中的每个 DLL 以找到其 PDB 文件并将其复制到包文件夹中。

有关 PDB 文件如何与 Visual 配合使用以及我们如何使用它来创建钩子的更多信息,请参阅 hook readme

在没有构建文件的情况下调试

安装钩子后,我们将再次从源代码创建项目,以便钩子现在可以将 PDB 文件复制到包文件夹以及包 DLL 中,以便调试器可以找到它们。

$ conan install . -o="*:shared=True" -s build_type=Debug --build="zlib/*"
...
zlib/1.2.11: Calling package()
...
[HOOK - hook_copy_pdbs_to_package.py] post_package(): PDBs post package hook running
...
Install finished successfully

# CMake presets require CMake>=3.23
$ cmake --preset=conan-default

请注意,当您现在运行 conan install 时,您将在 package() 调用之后看到钩子运行的输出。 为了测试钩子,我们可以再次清理缓存以删除构建文件,其中包括用于构建库的源代码和最初生成的 PDB 文件。

$ conan cache clean zlib/1.2.11 --build

再次在 Visual Studio 中打开解决方案并启动调试器。 当您尝试在第 22 行的断点处“步入”依赖项时,将弹出一个错误消息,告诉我们找不到该文件,并会询问文件的位置。 我们可以关闭此窗口,它将提供查看反汇编的选项,这可以由于 PDB 的存在而进行调试。 PDB 仅包含调试信息,但 Visual Studio 缺少源代码文件,因此它将无法像最初那样在这些文件上进行调试。

Debugging without build files in cache

为调试器定位源代码路径

在删除原始构建文件后,Visual Studio 无法自行找到源代码文件。 为了能够在源代码文件上进行调试,有一个选项可以手动设置源代码文件夹路径,以便可以对其进行调试。 这要求依赖项的源代码文件存在。 在我们的例子中,我们可以通过运行 conan cache path 来获取此源代码文件的位置。

$ conan cache path --folder source zlib/1.2.11

如果此源代码路径不存在,我们可以使用配置再次下载源代码。

$ conan install . -o="*:shared=True" -s build_type=Debug -c:a="tools.build:download_source=True"

一旦我们有了源代码路径,我们就可以在 Visual Studio 中设置它,以便调试器可以找到源代码文件。 右键单击解决方案资源管理器中的解决方案并选择“属性”。 转到“常用属性”部分中的“调试源文件”,然后添加我们的源代码路径。

Setting source path

再次启动调试器将允许我们像在第一个示例中所做的那样“步入”依赖项的代码。

注意

如果对源代码文件有补丁,我们将无法在修改后的文件上进行调试,因为我们使用的是来自源代码文件夹的文件,并且补丁是在构建文件夹中编译之前应用的后期步骤中应用的。

对源代码的任何修改将不允许对其进行调试,因为 Visual Studio 会执行校验和检查,因此它们需要与库编译时完全相同的文件。