SWIG is used with different types of target languages including common scripting languages such as Javascript, Perl, PHP, Python, Tcl and Ruby.
The list of supported languages also includes non-scripting languages such as C#, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), D, Go language, Java including Android, Lua, Modula-3, OCAML, Octave, Scilab and R.
上面那段是 SWIG 的官网介绍,简单地说,SWIG 是一个用于 C/C++ 和高层脚本语言交互的工具。脚本语言在 SWIG 的辅助下可以直接调 C/C++ 的程序。
简单试了一下 SWIG 在 Python 里面的用法,然后测试了一下 gdb 以及 VS 的远程调试 (红红火火恍恍惚惚,其实想记录的重点是这个)。
以下内容均在 WSL 里面完成。
Python-SWIG 直接 apt-get
把 SWIG 装上,然后就可以开始瞎搞了。
1. 首先随便写个 C 的函数 官方的文档里面用的是个简单的阶乘的例子,这里也用这个好了。
cfact.h
cfact.c
1 2 3 4 5 6 7 8 #include "cfact.h" int fact (int n) { if (n < 0 ) return 0 ; else if (n == 0 ) return 1 ; else return n * fact(n-1 ); }
2. 写 SWIG 部分的内容,并且生成对应的封装代码 创建一个 SWIG 的文件:
cfact.i
1 2 3 4 5 6 7 8 %module cfact %{ #define SWIG_FILE_WITH_INIT #include "cfact.h" %} int fact(int n);
然后用 swig
命令生成封装代码:
调用完了之后会生成两个新的文件 cfact.py
以及 cfact_wrap.c
。
3. 编译生成 Python 模块 Python 与 C/C++ 的交互是以模块的方式进行的,C/C++ 代码编译生成运行库之后,以模块的方式链接到 Python 的运行时中。
Python 自带一个 distutils 工具用于创建扩展模块,使用这个工具也很简单,只要写一个配置文件即可:
setup.py
1 2 3 4 5 6 7 8 9 10 11 12 13 from distutils.core import setup, Extensioncfact_module = Extension('_cfact' , sources=['cfact_wrap.c' , 'cfact.c' ], ) setup (name = 'cfact' , version = '0.1' , author = "Chen Fan" , description = """Simple swig test""" , ext_modules = [cfact_module], py_modules = ["cfact" ], )
在目录下执行:
之后即可编译得到完整的 Python 模块。
1 2 3 4 5 6 7 8 9 10 11 12 $ python setup.py build running build running build_py creating build creating build/lib.linux-x86_64-2.7 copying cfact.py -> build/lib.linux-x86_64-2.7 running build_ext building '_cfact' extension creating build/temp.linux-x86_64-2.7 x86_64-linux-gnu-gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.7 -c cfact_wrap.c -o build/temp.linux-x86_64-2.7/cfact_wrap.o x86_64-linux-gnu-gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/include/python2.7 -c cfact.c -o build/temp.linux-x86_64-2.7/cfact.o x86_64-linux-gnu-gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -Wl,-z,relro -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -D_FORTIFY_SOURCE=2 -g -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security build/temp.linux-x86_64-2.7/cfact_wrap.o build/temp.linux-x86_64-2.7/cfact.o -o build/lib.linux-x86_64-2.7/_cfact.so
可以看到 cfact_wrap.c
和 cfact.c
首先被编译成 build/temp.linux-x86_64-2.7/
目录下的 cfact_wrap.o
和 cfact.o
两个文件。
之后再编译成一个完整的 _cfact.so
运行库。
切到 build/lib.linux-x86_64-2.7/
目录下就能用 Python 测试啦:
1 2 3 4 5 6 7 8 9 10 11 12 $ cd build/lib.linux-x86_64-2.7 $ python Python 2.7.6 (default, Oct 26 2016, 20:30:19) [GCC 4.8.4] on linux2 Type "help" , "copyright" , "credits" or "license" for more information. >>> import cfact >>> cfact.fact(5) 120 >>>
上 gdb 调试 Python 进程是已经在运行了的,要用 gdb 调就只能附加上进程号来启动,可以在 Python 里面 import os
然后用 os.getpid()
获取进程号,或者用 top/htop
啥的直接看一下 Python 的进程号,然后在 gdb 里面启动即可。
gdb 正常启动,然后这时候可能会报找不到库的调试信息的错误,直接添加 fact
的断点会加不上。用 info sharedlibrary
命令看一下现在追踪到的库信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 (gdb) info sharedlibrary From To Syms Read Shared Object Library 0x00007fa4d2be59f0 0x00007fa4d2bf2471 Yes /lib/x86_64-linux-gnu/libpthread.so.0 0x00007fa4d282f520 0x00007fa4d2974183 Yes /lib/x86_64-linux-gnu/libc.so.6 0x00007fa4d2600ed0 0x00007fa4d26019ce Yes /lib/x86_64-linux-gnu/libdl.so.2 0x00007fa4d23f0f10 0x00007fa4d23f1804 Yes /lib/x86_64-linux-gnu/libutil.so.1 0x00007fa4d21d1e00 0x00007fa4d21e1bf8 Yes (*) /lib/x86_64-linux-gnu/libz.so.1 0x00007fa4d1ec5610 0x00007fa4d1f34056 Yes /lib/x86_64-linux-gnu/libm.so.6 0x00007fa4d2e00ae0 0x00007fa4d2e1b490 Yes /lib64/ld-linux-x86-64.so.2 0x00007fa4d1b32720 0x00007fa4d1b33ef6 Yes (*) /usr/lib/python2.7/lib-dynload/readline.x86_64-linux-gnu.so 0x00007fa4d18f30d0 0x00007fa4d1913ee5 Yes (*) /lib/x86_64-linux-gnu/libreadline.so.6 0x00007fa4d16bc3d0 0x00007fa4d16c8028 Yes (*) /lib/x86_64-linux-gnu/libtinfo.so.5 No ./_cfact.so (*): Shared library is missing debugging information.
_cfact.so
由于不在 gdb 的默认库搜索路径下,所以找不到,需要手动加上路径。
关于 gdb 的库搜索策略,参见这篇博文 。
这里简单地在 solib-search-path
里面加上 _cfact.so
的位置就好啦:
1 2 3 4 5 6 (gdb) show solib-search-path The search path for loading non-absolute shared library symbol files is . (gdb) set solib-search-path :/home/jcf/swig_test/build/lib.linux-x86_64-2.7/ Reading symbols from /home/jcf/swig_test/build/lib.linux-x86_64-2.7/_cfact.so...done. Loaded symbols for /home/jcf/swig_test/build/lib.linux-x86_64-2.7/_cfact.so (gdb)
可以看到 set
完之后 _cfact.so
马上就被自动被载入完成了。
这时候在 fact
这个函数上设断点就能够正常找到了:
1 2 3 4 (gdb) break fact Breakpoint 1 at 0x7fa4d14a3020: file cfact.c, line 5. (gdb) c Continuing.
在 Python 窗口里面再调一次 cfact.fact
函数,gdb 成功断在了这里:
1 2 3 4 5 6 7 8 9 10 11 Breakpoint 1, fact (n=n@entry=6) at cfact.c:5 5 if (n < 0) return 0; (gdb) l 1 2 3 int fact(int n) 4 { 5 if (n < 0) return 0; 6 else if (n == 0) return 1; 7 else return n * fact(n-1); 8 }(gdb)
调用栈也是比较清晰的,Python 通过生成的封装文件 cfact_wrap.c
最后才调用到最初写的 cfact.c
文件中去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 (gdb) bt args=<optimized out>) at cfact_wrap.c:3119 argv=0x7fffcd30af38, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffcd30af28) at libc-start.c:287
接下来是 VS 的瞎搞时间~
Visual Studio 中的远程进程调试 新版 VS 中加入了看上去吊炸天的远程 ssh 调试功能 ,自然是情不自禁地想试试。
在 WSL 中开好 ssh,在 VS 里面 调试 -> 附加到进程 -> SSH -> 选择之前添加好的 WSL 的 ssh 信息 -> 选择附加到 Native(GDB)代码 -> 在下面刷新出来的列表里面直接选前面正在运行的 Python 进程即可。
调试启动!
然后打开的调试界面基本是空的,什么都没有,首先也会有与前面 gdb 相同的加不上断点的问题,原因一样,因为这里调试的原理就是 ssh 过去开一个 gdb。
开始时我在 VS 里面找了很久都没找到哪里可以添加额外 gdb 信息的地方,后再在 VS 的官方文档里面终于翻到了 gdb 调试的命令交互方法。
在 VS 的 命令窗口
中输入 Debug.MIDebugExec
后面再跟 gdb 命令即可直接发送给 ssh 那一端的服务器 gdb 来执行。
一开始直接调用可能会报错:
1 2 >Debug.MIDebugExec info sharedlibrary Error: Commands are only accepted when the process is stopped.
在调试中给上一个 中断
先进入 gdb 的交互中即可,在 Debug.MIDebugExec
后面跟上正常的 gdb 调试命令,具体的方法跟上面一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 >Debug.MIDebugExec info sharedlibrary From To Syms Read Shared Object Library 0x00007fa4d2be59f0 0x00007fa4d2bf2471 Yes /lib/x86_64-linux-gnu/libpthread.so.0 0x00007fa4d282f520 0x00007fa4d2974183 Yes /lib/x86_64-linux-gnu/libc.so.6 0x00007fa4d2600ed0 0x00007fa4d26019ce Yes /lib/x86_64-linux-gnu/libdl.so.2 0x00007fa4d23f0f10 0x00007fa4d23f1804 Yes /lib/x86_64-linux-gnu/libutil.so.1 0x00007fa4d21d1e00 0x00007fa4d21e1bf8 Yes /lib/x86_64-linux-gnu/libz.so.1 0x00007fa4d1ec5610 0x00007fa4d1f34056 Yes /lib/x86_64-linux-gnu/libm.so.6 0x00007fa4d2e00ae0 0x00007fa4d2e1b490 Yes /lib64/ld-linux-x86-64.so.2 0x00007fa4d1b32720 0x00007fa4d1b33ef6 Yes /usr/lib/python2.7/lib-dynload/readline.x86_64-linux-gnu.so 0x00007fa4d18f30d0 0x00007fa4d1913ee5 Yes /lib/x86_64-linux-gnu/libreadline.so.6 0x00007fa4d16bc3d0 0x00007fa4d16c8028 Yes /lib/x86_64-linux-gnu/libtinfo.so.5 No ./_cfact.so >Debug.MIDebugExec set solib-search-path :/home/jcf/swig_test/build/lib.linux-x86_64-2.7/ Reading symbols from /home/jcf/swig_test/build/lib.linux-x86_64-2.7/_cfact.so...done. Loaded symbols for /home/jcf/swig_test/build/lib.linux-x86_64-2.7/_cfact.so =cmd-param-changed,param="solib-search-path",value=":/home/jcf/swig_test/build/lib.linux-x86_64-2.7/"
这时候在 断点
窗口里面已经可以成功加上 fact
函数的断点了。
加好断点之后 VS 继续
,然后 Python 窗口调一次函数。
VS 的断点会马上生效,然后弹出一个窗口让选择源代码文件的位置,毕竟是远程机器上 ssh 得到的,这里的代码位置只能手动选择。
然后可以看到完整的各项信息。
用起来还是相当不错的。
我觉得 VS 调试的意义在于,Debug.MIDebugExec
命令就等于 gdb,所以能用 gdb 做的,VS 都能够做到,关键是能在这样一个方便的图形界面下调试才是最爽的。
以上。