文章

冗余头文件检查(四):构建可调试的 IWYU 开发环境

冗余头文件检查(四):构建可调试的 IWYU 开发环境

为什么需要可调试的开发环境

在后续的文章中,我们需要深入分析 IWYU 的源码结构和核心算法。为了能够高效地进行源码调试,首先需要搭建一个支持断点调试的开发环境。

一个好的 IWYU 开发环境应该具备以下特性:

  • 支持 VSCode 断点调试
  • 能够查看变量的实时值
  • 可以方便地修改代码并重新编译
  • 方便追踪 Clang AST 的构建过程

编译 Debug 版本的 IWYU

获取依赖

首先,确保系统已经安装了必要的依赖:

1
2
3
4
5
6
7
8
9
10
# Ubuntu/Debian
sudo apt-get install \
    cmake \
    build-essential \
    clang-10 \
    libclang-10-dev \
    libllvm10-dev

# macOS (使用 Homebrew)
brew install llvm@10 cmake

编译 Debug 版本

普通的 CMake 编译默认是 Release 模式,为了支持调试,我们需要显式指定 Debug 模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. 克隆仓库
git clone https://github.com/include-what-you-use/include-what-you-use.git
cd include-what-you-use

# 2. 创建构建目录
mkdir build-debug
cd build-debug

# 3. 配置为 Debug 模式
cmake -G Ninja \
    -DCMAKE_BUILD_TYPE=Debug \
    -DCMAKE_CXX_COMPILER=clang++ \
    -DCMAKE_C_COMPILER=clang \
    -DLLVM_DIR=/usr/lib/llvm-10/lib/cmake/llvm \
    ..

# 4. 编译
ninja -j$(nproc)

# 5. 验证编译成功
./bin/include-what-you-use --version

注意-DLLVM_DIR 的路径可能因系统而异,需要根据实际安装位置调整。

编译优化

为了加快编译速度,可以使用 ccache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 安装 ccache
sudo apt-get install ccache  # Ubuntu/Debian
brew install ccache          # macOS

# 使用 ccache 重新编译
rm -rf CMakeCache.txt
cmake -G Ninja \
    -DCMAKE_BUILD_TYPE=Debug \
    -DCMAKE_C_COMPILER_LAUNCHER=ccache \
    -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
    -DCMAKE_CXX_COMPILER=clang++ \
    -DCMAKE_C_COMPILER=clang \
    -DLLVM_DIR=/usr/lib/llvm-10/lib/cmake/llvm \
    ..

ninja -j$(nproc)

配置 VSCode 调试

安装必要的扩展

在 VSCode 中,安装以下扩展:

  1. C/C++ (ms-vscode.cpptools)
  2. CMake Tools (ms-vscode.cmake-tools)

创建 launch.json

在项目根目录下创建 .vscode/launch.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug IWYU",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/build-debug/bin/include-what-you-use",
            "args": [
                "src/example.cpp",
                "--verbose"
            ],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "lldb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for lldb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "build-iwyu"
        }
    ]
}

创建 tasks.json

同时创建 .vscode/tasks.json 用于编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build-iwyu",
            "type": "shell",
            "command": "ninja",
            "args": [
                "-C",
                "build-debug"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": []
        }
    ]
}

调试 IWYU 源码

设置断点

在 VSCode 中打开 IWYU 源码,设置断点的位置:

  1. include-what-you-use/iwyu.cc - 主入口
  2. include-what-you-use/iwyu_ast_util.cc - AST 工具函数
  3. include-what-you-use/iwyu_driver.cc - 驱动逻辑

调试会话

  1. 在 VSCode 中按 F5 启动调试
  2. 程序会在断点处暂停
  3. 使用以下快捷键进行调试操作:
    • F5 - 继续执行
    • F10 - 单步跳过
    • F11 - 单步进入
    • Shift+F11 - 单步跳出
    • Shift+F5 - 停止调试

查看变量和表达式

在调试过程中,可以:

  • 将鼠标悬停在变量上查看值
  • 在 Watch 窗口添加表达式
  • 在 Call Stack 窗口查看调用栈

调试 Clang AST

IWYU 依赖于 Clang 的 AST,要在 IWYU 内部查看 AST 结构,可以:

  1. 在 IWYU 的 iwyu_driver.cc 中找到 AST 遍历的位置
  2. 设置断点
  3. 在断点处添加输出代码,例如:
1
2
3
4
5
// 在 AST 回调函数中添加
if (const auto* decl = dyn_cast<VarDecl>(d)) {
    llvm::errs() << "Found variable: " << decl->getQualifiedNameAsString() << "\n";
    decl->dump();  // 打印完整的 AST 节点信息
}

更为高级的 Clang AST 调试

如果需要更深入地了解 Clang AST 的结构,可以独立使用 Clang 的工具:

1
2
3
4
5
6
7
8
# 生成完整的 AST 输出
clang -Xclang -ast-dump src/example.cpp

# 生成带有位置信息的 AST
clang -Xclang -ast-dump -fno-color-diagnostics src/example.cpp

# 使用 clang-check 进行更详细的分析
clang-check -ast-dump src/example.cpp --

常见调试场景

1. 调试为什么某个头文件被建议添加

iwyu.ccProcessFile 函数中设置断点,查看:

  • SymbolInfo 中记录的符号信息
  • IncludePicker 如何选择合适的头文件

2. 调试为什么某个头文件被建议移除

iwyu_ast_util.ccMarkUsedSymbols 函数中设置断点,查看:

  • 哪些符号被标记为”已使用”
  • 未被使用的 #include 是如何被识别的

3. 调试循环依赖检测

iwyu.cc 中搜索 DetectCycles 相关函数,查看 IWYU 如何构建和处理循环依赖。

快速迭代开发流程

为了提高开发效率,可以建立以下开发循环:

1
2
3
4
5
6
7
8
9
10
11
# 1. 修改源码
vim include-what-you-use/src/iwyu.cc

# 2. 编译(自动触发,如果在 VSCode 中)
# 或手动执行:
cd build-debug && ninja -j$(nproc)

# 3. 测试
./bin/include-what-you-use src/example.cpp

# 4. 如果需要调试,直接在 VSCode 中按 F5

使用 Docker 构建统一环境

为了避免本地环境差异导致的问题,可以使用 Docker 构建一个统一的开发环境(后面有介绍)。

总结

通过构建可调试的 IWYU 开发环境,我们获得了:

  1. 可视化调试能力:可以在 VSCode 中设置断点、查看变量值
  2. 快速迭代能力:Debug 模式下编译快,修改代码后可以快速重新编译测试
  3. 深入了解能力:可以追踪 IWYU 如何与 Clang AST 交互,理解其内部处理流程

这个环境将成为我们后续分析源码和扩展功能的基础。

本文由作者按照 CC BY 4.0 进行授权