调试Python在线程序

907 阅读4分钟

GDB 7+ 支持使用 Python 语言扩展 GDB 功能 - Extending GDB using Python , 比如,可以通过编写代码可视化展现某些库独有的数据结构(比如 C++ STL 类型)。

通用调试方法

以下命令不限于调试Python程序。

查看进程号:

ps auxf

查看此进程卡在哪个系统回调:

strace -p *pid*

pid替换为实际进程号。此命令会返回一个系统回调和文件操作符。

检查上一条命令系统回调的文件操作符的含义:

ls -l /proc/*pid*/fd

数字就是文件操作符。

/proc/pid 中还包含其他很多进程信息,有兴趣可自行研究。

列出此进程当前的文件操作:

lsof -p *pid*

GDB调试

安装依赖

目前来说调试python 的条件比较苛刻, 只针对特定版本的python版本 和 centos 系统。
对于 centos6.x 系统, 目前已经开发出的 debuginfo 包是针对python3.4 版本
对于 centos7.x 系统, 目前针对python3.4 和python3.6 版本

安装debuginfo包

yum install python36-debug && debuginfo-install python36-debug-3.6.3-7.el7.x86_64

或者

sudo yum install gdb
sudo yum install yum-utils
sudo debuginfo-install glibc
sudo yum install python-debuginfo

下载libpython.py脚本  

地址:github.com/python/cpyt…
注:不同的系统该脚本不一样,比如有的可能是叫pythonx.x-gdb.py

进入gdb

执行以下命令

$gdb  -p 1000     #使用此命令即可使gdb附加到进程

其中1000是进程号

载入libpython脚本

如果gdb是redhat或fedora等厂商修改过的,会有--python选项,使用此选项即可指定gdb启动时载入的Python扩展脚本

$ gdb --python /path/to/libpython.py -p 1000

如果安装的是GNU的gdb,就需要打开gdb后手动载入libpython.py脚本  

(gdb) python
> import sys  
>sys.path.insert(0, '/path/to/libpython.py' ) # 这里是libpython.py的目录,不包含该文件
> import libpython
>end
(gdb)

这时就可以使用py-bt等命令了

常用命令

bt    # 当前C调用栈
py-bt  # 当前Py调用栈
py-list  # 当前py代码位置
info thread   # 线程信息
thread <id>   # 切换到某个线程
thread apply all py-list  # 查看所有线程的py代码位置
ctrl-c  # 中断

生成 core file

为了不影响运行中的进程,可以通过生成 core file 的方式来保存进程的当前信息:

(gdb) generate-core-file
warning: target file /proc/6489/cmdline contained unexpected null characters
Saved corefile core.6489
(gdb) quit
A debugging session is active.

    Inferior 1 [process 6489] will be detached.

Quit anyway? (y or n) y

可以通过 gdb python core.PID 的方式来读取 core file:

$ gdb python core.6489
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
...
Type "apropos word" to search for commands related to "word"...
Reading symbols from python...Reading symbols from /usr/lib/debug/.build-id/90/d1300febaeb0a626baa2540d19df2416cd3361.debug...done.
done.

warning: core file may not match specified executable file.
[New LWP 6489]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Core was generated by `python'.
#0  0xb778fc31 in __kernel_vsyscall ()
(gdb)

查看变量的值

(gdb) py-list
   1    # -*- coding: utf-8 -*-
   2    import time
   3
   4
   5    def do(x):
  >6        time.sleep(10)
   7
   8
   9    def main():
  10        for x in range(10000):
  11            do(x)
(gdb) py-print x
local 'x' = 12
(gdb)
(gdb) py-locals
x = 12
(gdb)

查看上层调用方的信息

(gdb) py-up
#9 Frame 0xb74c0994, for file test.py, line 11, in main (x=12)
    do(x)
(gdb) py-list
   6        time.sleep(10)
   7
   8
   9    def main():
  10        for x in range(10000):
 >11            do(x)
  12
  13
  14    if __name__ == '__main__':
  15        main()
(gdb) py-print x
local 'x' = 12
(gdb)

可以通过 py-down 回去:

(gdb) py-down
#6 Frame 0xb74926e4, for file test.py, line 6, in do (x=12)
    time.sleep(10)
(gdb) py-list
   1    # -*- coding: utf-8 -*-
   2    import time
   3
   4
   5    def do(x):
  >6        time.sleep(10)
   7
   8
   9    def main():
  10        for x in range(10000):
  11            do(x)
(gdb)

查看所有线程

(gdb) info threads
  Id   Target Id         Frame
* 1    Thread 0xb74b9700 (LWP 11039) 0xb7711c31 in __kernel_vsyscall ()
  2    Thread 0xb73b8b40 (LWP 11040) 0xb7711c31 in __kernel_vsyscall ()
  3    Thread 0xb69ffb40 (LWP 11041) 0xb7711c31 in __kernel_vsyscall ()
(gdb)

可以看到这个程序当前有 3 个线程, 当前进入的是 1 号线程。

切换线程

(gdb) thread 3
[Switching to thread 3 (Thread 0xb69ffb40 (LWP 11041))]
#0  0xb7711c31 in __kernel_vsyscall ()
(gdb) info threads
  Id   Target Id         Frame
  1    Thread 0xb74b9700 (LWP 11039) 0xb7711c31 in __kernel_vsyscall ()
  2    Thread 0xb73b8b40 (LWP 11040) 0xb7711c31 in __kernel_vsyscall ()
* 3    Thread 0xb69ffb40 (LWP 11041) 0xb7711c31 in __kernel_vsyscall ()
(gdb)

现在切换到了 3 号线程.

参考链接

ialloc.org/blog/python…
sourceware.org/gdb/current…
wiki.python.org/moin/Debugg…
mozillazg.com/2017/07/deb…