欢迎光临
我们一直在努力

【C++ debug】在 VS Code 中无 Attach 调试 Python 调用的 C++ 扩展

系列文章目录

  • 【深度学习 DeBug 小技巧!】RuntimeError: CUDA error: device-side assert triggeredCUDA(用CPU debug 解决后再用GPU)
  • 【分布式训练 debug】VS Code Debug 技巧:launch.json实用参数
  • 【分布式训练(2)】深入理解 DeepSpeed 的 ZeRO 内存优化策略 (三阶段的区别)
  • 【分布式训练(3)】accelerator + deepspeed debug 报错 “Timed out waiting for debuggee to spawn“ 解决方法✅
  • 【分布式训练(4)】accelerator.sync_gradients 和 checkpointing 深入理解
  • 【分布式训练(5)】无法 kill PID?如何 kill 休眠中的 GPU 占用进程
  • 【分布式训练(6)】深入理解多卡训练时 training steps, epoch 的相关概念

文章目录

  • 系列文章目录
  • 在 VS Code 中无 Attach 调试 Python 调用的 C++ 扩展
    • 1. 问题背景
    • 2. 思路:用 GDB 启动 Python,让 .so 在 GDB 控制下加载
    • 3. 前置条件:扩展必须带调试符号编译
      • 3.1 在扩展的构建里加「Debug 模式」
      • 3.2 重新安装扩展(带调试符号)
      • 3.3 确认 .so 带调试信息(可选)
    • 4. VS Code launch.json 配置🔥
    • 5. 使用流程与现象
    • 6. 在 GDB 里查看 PyTorch Tensor 的“实际数据”
    • 7. 避免误入 PyTorch 内部实现
    • 8. 小结

在 VS Code 中无 Attach 调试 Python 调用的 C++ 扩展

本文总结一种在 VS Code 下无需手动 attach 即可对「Python 调用的 PyTorch C++ 扩展」进行 C++ 源码级调试的方法,适用于任何通过 torch.utils.cpp_extension 或 pybind11 构建的 .so 扩展。


1. 问题背景

典型调用链:

Python 脚本 (.py)
→ import 某包
→ 调用包里的 Python 函数(如 mesh_to_flexible_dual_grid)
→ 该函数内部调用 _C.xxx_cpu(…) # 即 .so 里的 C++ 函数
→ C++ 实现 (.cpp)

  • debugpy 只能断 .py,进不了 .cpp。
  • GDB 只能断 .cpp,不认识 .py。
  • 若用 GDB attach 到已运行的 Python 进程,需要改 ptrace 权限、选进程,流程繁琐。

目标:用 GDB 直接启动 Python,一次 F5 即可在 C++ 里断点,无需 attach。


2. 思路:用 GDB 启动 Python,让 .so 在 GDB 控制下加载

让 GDB 作为“主进程”,用 program = python、args = [你的脚本.py, …] 启动 Python;脚本里 import 到扩展时,动态库会在同一 GDB 会话中加载,因此:

  • 在 .cpp 里下的断点(或对 C++ 函数名的 pending breakpoint)会在执行到该 C++ 代码时命中。
  • 无需再 attach 到别的进程。

代价:这一次调试会话里,不能同时用 debugpy 断 Python 源码(只能二选一:本次跑要么用 GDB 断 C++,要么用 debugpy 断 Python)。


3. 前置条件:扩展必须带调试符号编译

默认的 pip install -e . 或 python setup.py build 通常用 -O3 且不带 -g,GDB 无法把断点对应到源码行。

3.1 在扩展的构建里加「Debug 模式」

例如在 setup.py 里用环境变量控制编译选项:

# 调试 C++ 时使用: DEBUG=1 pip install -e .
BUILD_DEBUG = os.environ.get("DEBUG", "0") == "1"

# 在 CUDAExtension / setuptools 的 extra_compile_args 里:
extra_compile_args={
"cxx": (["-O0", "-g", "-std=c++17"] if BUILD_DEBUG else ["-O3", "-std=c++17"]),
"nvcc": (["-O0", "-g", "-G", "-std=c++17"] if BUILD_DEBUG else ["-O3", "-std=c++17"]) + cc_flag,
}

  • Release:不加 DEBUG,用 -O3,无 -g。
  • Debug:DEBUG=1 时用 -O0 -g(及 nvcc 的 -G),方便断点与单步。

3.2 重新安装扩展(带调试符号)

cd /path/to/your/extension # 你的 C++ 扩展工程根目录
rm -rf build/ dist/ *.egg-info */*.egg-info # 清理旧产物(重要,避免用错 .so)
DEBUG=1 pip install -e . –no-build-isolation

–no-build-isolation 可避免构建环境与当前环境不一致导致的 ABI 问题。若遇 undefined symbol: … TensorImpl::incref_pyobject … 等,多半是之前用别的 PyTorch 版本编的,清理后按上面步骤用当前环境重编即可。

3.3 确认 .so 带调试信息(可选)

readelf -S /path/to/your/_C*.so | grep debug

若有 .debug_info、.debug_line 等 section,说明带调试符号。


4. VS Code launch.json 配置🔥

在项目或工作区下 .vscode/launch.json 里增加一个 cppdbg 配置,用 GDB 启动 Python:

{
"name": "▶ GDB(C++): your_script",
"type": "cppdbg",
"request": "launch",
"program": "/absolute/path/to/your/python", // 例如 conda 环境里的 python
"args": [
"/absolute/path/to/your/script.py",
"–your-arg", "value"
],
"cwd": "/absolute/path/to/working/dir",
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"setupCommands": [
{ "text": "set breakpoint pending on", "ignoreFailures": true },
{ "text": "break your_cpp_function_name", "ignoreFailures": true }
]
}

要点:

  • program:本机 Python 解释器绝对路径(建议用当前 conda/venv 的 python)。
  • args:你的入口脚本及其参数,和平时命令行一致即可。
  • cwd:脚本的工作目录,影响 import、相对路径等。
  • set breakpoint pending on:允许在符号尚未加载时下断点(.so 是动态加载的,启动时符号还没有)。
  • break your_cpp_function_name:在 C++ 函数名上先下一个 pending 断点,等 .so 加载后会自动绑定;函数名要和 C++ 里一致(可省略,改在源码里点行号断点也行)。

再保留一个 debugpy 配置,用于只断 Python 的情况,例如:

{
"name": "▶ Python: your_script",
"type": "debugpy",
"request": "launch",
"program": "/absolute/path/to/your/script.py",
"args": ["–your-arg", "value"],
"python": "/absolute/path/to/your/python",
"cwd": "/absolute/path/to/working/dir",
"console": "integratedTerminal",
"justMyCode": false
}

日常用法:要断 C++ 选 GDB 配置,要断 Python 选 debugpy 配置。


5. 使用流程与现象

  • 在 .cpp 里需要停下的行左侧点断点(例如扩展的 xxx_cpu 函数入口)。
  • 选择 「▶ GDB(C++): your_script」,F5 启动。
  • 启动后断点可能显示为灰色(未验证):因为此时 .so 还未加载,GDB 尚未解析到该源文件,属于正常现象。
  • 继续运行(F5 或 Continue),当 Python 执行到 import 你的扩展并调用到对应 C++ 函数时,断点会命中,此时断点变为红色,光标停在 .cpp 对应行。
  • 之后即可在 C++ 中单步(F10 Step Over、F11 Step Into)、查看变量、查看调用栈等。
  • 若希望「一启动就停」,可在 setupCommands 里对 Python 入口下断点(例如 break Py_Main),再 Continue 到你的 C++ 断点;一般不必,直接等执行到 C++ 即可。


    6. 在 GDB 里查看 PyTorch Tensor 的“实际数据”

    Variables 面板里看到的往往是 torch::Tensor 的内部结构(如 impl_、target_ 等),而不是元素值。可以在调试控制台用 GDB 命令查看。

    假设 C++ 里已有:

    const float* v_ptr = vertices.data_ptr<float>();
    const int* f_ptr = faces.data_ptr<int>();

    且已单步到这两行之后,则:

    • 查看前 9 个 float(3 个顶点 × 3 分量):-exec p *v_ptr@9
    • 查看前 9 个 int(3 个面 × 3 个顶点索引):-exec p *f_ptr@9
    • 查看 tensor 的 shape(若 C++ 里有 vertices):-exec p vertices.sizes()

    语法要点:-exec 是 VS Code 里把命令发给 GDB;p 即 print;*ptr@N 表示从指针 ptr 起连续 N 个元素(类型由 ptr 推导)。这样就能在 C++ 调试时直接看到“实际数据”而不是内部结构。


    7. 避免误入 PyTorch 内部实现

    单步时若用 Step Into (F11) 进入 tensor.size()、tensor.data_ptr() 等,会进到 PyTorch 的 TensorImpl.h、intrusive_ptr.h 等内部实现,对排查业务逻辑帮助不大。

    建议:

    • 在业务 C++ 代码里尽量用 Step Over (F10) 逐行走。
    • 若已误入 PyTorch 内部,用 Step Out (Shift+F11) 返回到你的 .cpp。

    8. 小结

    项目说明
    目标 不 attach,用 GDB 直接调试 Python 所调用的 C++ 扩展
    做法 launch 配置里用 cppdbg,program 填 Python,args 填你的脚本;.so 在运行中被 GDB 加载,断点即可命中
    前提 扩展用 -O0 -g 重编(如 DEBUG=1 pip install -e .),并清理旧 build
    断点 下在 .cpp 或对 C++ 函数名 break;pending on,.so 加载后自动生效
    看数据 用 -exec p *ptr@N 等 GDB 命令在控制台查看指针指向的数组
    单步 多用 F10 避免进 PyTorch 内部;误入后用 Shift+F11 跳出

    按上述方式配置一次后,之后只需选对 launch、F5,即可在 C++ 里稳定断点和查看数据,无需再手动 attach 进程。

    赞(0)
    未经允许不得转载:171主机测评 » 【C++ debug】在 VS Code 中无 Attach 调试 Python 调用的 C++ 扩展
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址