熟悉深度学习框架的同学都了解,如Tensorflow,Pytorch,Mxnet等都是提供Python前端编程接口,而后端是采用C/C++实现的。想要深入了解这些框架的源码,需要一些工程实践的基础,其中有一项便是Python/C++代码的联合调试。很多Ide如Pycharm,Clion等只支持一种语言的调试,对联合调试支持不好,往上的资料也很少。Visual Studio这个宇宙第一ide倒是支持联合调试,不过VS不支持跨平台,只能在Windows运行。因此本文就选择了Vscode实现Python/C++代码的联合调试。一是它跨平台,二是通过插件支持多语言代码编辑以及调试。
测试环境Ubuntu 18.04
vscode 1.41
Python 插件 2020.2.63990
C++插件 0.26.3
Anaconda (Python 3.7)
g++ 7.40
gdb 8.1
编写一个简单的Python c++扩展
我们编写一个简单的c++扩展,实现新建文件并且往文件中写一些内容的功能。
fputsmodule.c
#define PY_SSIZE_T_CLEAN#include
static PyObject *StringTooShortError = NULL;
static PyObject *method_fputs(PyObject *self, PyObject *args, PyObject *kw) {
char *str, *filename = NULL;
int bytes_copied = -1;
/* Parse arguments */
char *kwlist[] = {"content", "filename", NULL};
if(!PyArg_ParseTupleAndKeywords(args, kw, "ss", kwlist, &str, &filename)) {
return NULL;
}
if (strlen(str) < 10) {
/* Passing custom exception */
PyErr_SetString(StringTooShortError, "String length must be greater than 10");
return NULL;
}
FILE *fp = fopen(filename, "w");
bytes_copied = fputs(str, fp);
fclose(fp);
return (PyObject *)Py_BuildValue("i", bytes_copied);
}
static PyMethodDef FputsMethods[] = {
{"fputs", method_fputs, METH_VARARGS | METH_KEYWORDS, "Python interface for fputs C library function"},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef fputsmodule = {
PyModuleDef_HEAD_INIT,
"fputs",
"Python interface for the fputs C library function",
-1,
FputsMethods
};
PyMODINIT_FUNC PyInit_fputs(void) {
/* Assign module value */
PyObject *module = PyModule_Create(&fputsmodule);
/* Initialize new exception object */
StringTooShortError = PyErr_NewException("fputs.StringTooShortError", NULL, NULL);
/* Add exception object to your module */
PyModule_AddObject(module, "StringTooShortError", StringTooShortError);
return module;
}
from distutils.core import setup, Extension
def main():
setup(name="fputs",
version="1.0.0",
description="Python interface for the fputs C library function",
author="hanbing",
author_email="beatmight@gmail.com",
ext_modules=[Extension("fputs", ["fputsmodule.c"])])
if __name__ == "__main__":
main()
具体可以参考我的另一篇文章。韩冰:使用c/c++编写python扩展(一):定义模块函数与异常zhuanlan.zhihu.com
我们使用以下命令编译我们的Python扩展
python setup.py build_ext --inplace
接下来编写一个简单的调用脚本
import fputs
fputs.fputs("hello world! You are good!", "hello.txt")
print("hello world!")
使用ptvsd启动测试程序
调试Python程序,无论是手动调试还是IDE集成,都是基于pdb调试器进行的。而对于C++的调试我们则需要gdb(Linux), LLDB(mac), Visual Studio Windows Debugger(Windows)。无论单独用哪一个,都无法实现同时在Python程序和C++程序上打断点。所以只有使用Attach的方法进行调试。好在微软已经为我们开发了现成的一套工具,来在vscode上同时对Python和C++进行调试。
ptvsd 是 Visual Studio 和 Visual Studio Code 中的 Python 调试器包,该调试引擎构建在开源 pydevd 之上。熟悉vscode python开发的同学可能都知道,ptvsd是用来进行远程调试的。服务器启动程序后,本地开发机通过ptvsd Attach到远程运行的程序上进行调试。它的共功能可不止这么简单。ptvsd同时支持pdb,gdb调试器Attach到通一个进程上,以此实现Python/C++的联合调试。
首先我们先安装ptvsd。
pip install ptvsd # 注意确认版本在4.0以上
接下来,在我们的测试程序fputs_test.py中添加以下代码
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--ptvsd", action="store_true", help="是否启动ptvsd调试。")
args = parser.parse_args()
if args.ptvsd:
import ptvsd
ptvsd.enable_attach(address =('127.0.0.1', 10010), redirect_output=True)
ptvsd.wait_for_attach()
import fputs
fputs.fputs("hello world! You are good!", "hello.txt")
print("hello world!")
这段代码的具体含义可以参考微软的官方文档Remote Debugging章节https://code.visualstudio.com/docs/python/debuggingcode.visualstudio.com
接下来我们使用--ptvsd参数运行程序
python fputs_test.py --ptvsd
配置launch.json
熟悉vscode的同学都知道,调试的关键在于配置launch.json。我们需要一个Python代码的调试配置,一个C++代码的调试配置,具体如下。
{
"version": "0.2.0",
"configurations": [
{
"name": "Python Attach (local) proc 0",
"type": "python",
"request": "attach",
"pathMappings": [
{
"localRoot": "${workspaceFolder}", // You may also manually specify the directory containing your source code.
"remoteRoot": "${workspaceFolder}", // Linux example; adjust as necessary for your OS and situation.
}
],
"port": 10010,
"host": "localhost"
},
{
"name": "GDB Attach proc 0",
"type": "cppdbg",
"request": "attach",
"program": "/home/hanbing/anaconda3/bin/python",
"processId": "${command:pickProcess}",
"MIMode": "gdb"
}
]
}
其中第一个配置与远程调试Python的配置基本相同,只是我们attach的目标进程也在本地启动,代码路径的映射都是本地路径所以配置为${workspaceFolder}即可。这其中第二个配置则是gdb调试的配置。由于我们运行的python代码本质上是运行可执行程序 python,而Python又c++编写,所以可以直接对“python”程序进行调试。由于gdb使用attach模式进行调试需要指定目标程序的进程Id,所以我们在"processId"字段指定"${command:pickProcess}"意为启动调试时进行选择。启动该调试时,只需在vscode的对话窗口中搜索 “fputs_test.py”程序对应的id就行了。
配置完成后我们只需依次启动这两个调试配置即可开始调试。
视频演示Vscode Python/C++联合调试https://www.zhihu.com/video/1213936360442515456
注意事项
gdb使用attach模式进行调试时,需要root权限,所以在启动gdb的调试配置时可能需要输入密码获得临时的root权限,请确保你的当前用户可以获得sudo的权限。我在演示的过程中直接使用了root用户。在我的测试过程当中,使用普通用户权限在启动是可能会出现无法输入密码的情况,对此我在https://github.com/microsoft/vscode-cpptoolsgithub.com
上提了一个issuehttps://github.com/microsoft/vscode-cpptools/issues/4988github.com
该issue已经有人回复,具体解决方法可以参考以下链接:https://github.com/Microsoft/MIEngine/wiki/Troubleshoot-attaching-to-processes-using-GDBgithub.com