使用gdb调试Python进程

时间:2021-08-13 09:46:08

使用gdb调试Python进程

有时我们会想调试一个正在运行的Python进程,或者一个Python进程的coredump。例如现在遇到一个mod_wsgi的进程僵死了,不接受请求,想看看究竟是运行到哪行Python代码呢?这时就需要祭出gdb了。
 
主要是三步:
1)确保你的gdb版本>=7
2)安装python-debuginfo包(如:python-debuginfo-2.6.6-29.el6_2.2.x86_64.rpm,这个版本号一定要跟你所用的python版本一致(可以rpm -qa|grep python查看你安装的python的详细版本号)。找包http://debuginfo.centos.org/6/x86_64/)
3)就可以用#gdb python 进程号,进行调试了。

准备

1. 确认你的gdb版本是>=7,gdb从版本7开始支持对Python的debug

2.确认gdb连接的Python是所要debug的Python,否则请重新编译gdb。

方法:

1
2
3
4
5
6
7
$ gdb
(gdb) python
> import sys
>print sys.version
>end
2.4.3 ( #1, Sep 21 2011, 19:55:41) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-51)]

在一些追求稳定的发行版(例如CentOS),Python的版本会较低,这时都会自己编译一个Python使用。而从源里安装的gdb会连接源里Python的版本。例如在CentOS 5.4,源里的Python是2.4.3,从源安装的gdb也会连接到Python 2.4.3。

编译时注意,要把自己编译的Python路径加到PATH环境变量里,这样gdb configure的时候才会找到新版Python并连接

3.下载libpython.py

如何Debug

假设要debug的进程号为1000

1
$ gdb -p 1000

使用此命令即可使gdb附加到进程。

载入libpython脚本

  • 如果你的gdb是redhat或fedora等厂商修改过的,会有--python选项,使用此选项即可指定gdb启动时载入的Python扩展脚本(此脚本是扩展gdb的,不是我们需要debug的脚本)。
    1
    $ gdb --python /path/to/libpython .py -p 1000
  • 如果安装的是GNU的gdb,就需要打开gdb后手动载入libpython.py脚本
    1
    2
    3
    4
    5
    6
    (gdb) python
    > import sys
    >sys.path.insert(0, '/path/to/libpython.py' )
    > import libpython
    >end
    (gdb)

这时就可以使用py-bt命令打印当前线程的Python traceback了

libpython还提供很多命令,例如py-print打印变量,py-locals打印所有本地变量等等,详细可打开libpython.py查看。

一点经验

  • 在gdb可以使用generate-core-file命令生成一个coredump文件。之后可以用gdb –core来打开coredump文件进行debug。避免一直attach住进程,可以快速重启恢复服务
  • gdb-heap是gdb的一个扩展。可以打印Python的内存使用情况

参考资料

使用gdb调试python脚本

调试python脚本一般可通过记录log和使用python自带的pdb模块完成, 但凡事总有例外,在以下三种情况时上述方法就无能为力了。
   1 段错误
   2 运行中的daemon程序
   3 core dump
这个时候就需祭出gdb进行调试。python2.6的源码中提供了部分预定义函数以便大家使用gdb调试,我们只需将文件Python-2.6/Misc/gdbinit所包括的内容加入到用户目录下的.gdbinit文件中即可,这样每次启动gdb时会自动完成这些宏的定义。但可惜的是Python2.6.2 gdbini对于pylocals的定义居然有错误, 看来是没有随着代码的更新而同步更新。我们只需将 while $_i < f->f_nlocals修改为 while $_i < f->f_code->co_nlocals即可。文章后面所附的几个宏建议也加入的.gdbinit文件中,更多的宏可参考

http://web.archive.org/web/20070915134837/

http://www.mashebali.com/?Python_GDB_macros:The_Macros

我们首先需要构造一个会造成段错误的python脚本。老实说让python发生段错误并不容易,但通过其外部调用库就很简单了。我们将该文件命名为gdb_test.py 
import sys, os, libxml2

def segv_test():
    s = "<html><body><div><a><a></a></a><a></a></div></body></html>"
    options = libxml2.HTML_PARSE_RECOVER + \
              libxml2.HTML_PARSE_NOERROR + \
              libxml2.HTML_PARSE_NOWARNING
    doc = libxml2.htmlReadDoc(s, None, 'utf-8', options).doc
    ctxt = doc.xpathNewContext()
    nodes = ctxt.xpathEval('//body/node()')
    nodes.reverse()
    for note in nodes:
        nexts = note.xpathEval('node()')
        note.unlinkNode() 
        note.freeNode() //freeNode会将该节点及其子节点释放掉
        nexts[0].unlinkNode() 
        nexts[0].freeNode() //资源已经释放,再次释放会造成段错误

def main():
    segv_test()

if __name__ == "__main__":
    main()

使用gdb运行该脚本,我们会得到段错误信息。
gdb python
r gdb_test.py

*** glibc detected *** double free or corruption (fasttop): 0x08104570 ***

Program received signal SIGABRT, Aborted.
[Switching to Thread -1208260928 (LWP 26159)]
0x00b987a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2

键入bt得到如下堆栈信息: 
(gdb) bt
#0 0x00b987a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
#1 0x00c00825 in raise () from /lib/tls/libc.so.6
#2 0x00c02289 in abort () from /lib/tls/libc.so.6
#3 0x00c34cda in __libc_message () from /lib/tls/libc.so.6
#4 0x00c3b56f in _int_free () from /lib/tls/libc.so.6
#5 0x00c3b94a in free () from /lib/tls/libc.so.6
#6 0x009812c6 in xmlFreeNode () from /opt/sohumc/lib/libxml2.so.2
#7 0x0029d7f3 in libxml_xmlFreeNode () from /opt/sohumc/lib/python2.6/site-packages/libxml2mod.so
#8 0x00780bae in PyCFunction_Call (func=0x8104570, arg=0xd05820, kw=0x6) at Objects/methodobject.c:116
#9 0x007d8c79 in call_function (pp_stack=0xbff8c48c, oparg=0) at Python/ceval.c:3679
#10 0x007d6d2b in PyEval_EvalFrameEx (f=0x8124ef4, throwflag=0) at Python/ceval.c:2370
#11 0x007d8e36 in fast_function (func=0x6, pp_stack=0xbff8c5dc, n=1, na=1, nk=0) at Python/ceval.c:3765
#12 0x007d89cd in call_function (pp_stack=0xbff8c5dc, oparg=0) at Python/ceval.c:3700
#13 0x007d6d2b in PyEval_EvalFrameEx (f=0x81242fc, throwflag=0) at Python/ceval.c:2370
#14 0x007d8e36 in fast_function (func=0x6, pp_stack=0xbff8c72c, n=0, na=0, nk=0) at Python/ceval.c:3765
#15 0x007d89cd in call_function (pp_stack=0xbff8c72c, oparg=0) at Python/ceval.c:3700
#16 0x007d6d2b in PyEval_EvalFrameEx (f=0x810a7c4, throwflag=0) at Python/ceval.c:2370
#17 0x007d8e36 in fast_function (func=0x6, pp_stack=0xbff8c87c, n=0, na=0, nk=0) at Python/ceval.c:3765
#18 0x007d89cd in call_function (pp_stack=0xbff8c87c, oparg=0) at Python/ceval.c:3700
#19 0x007d6d2b in PyEval_EvalFrameEx (f=0x8091d0c, throwflag=0) at Python/ceval.c:2370
#20 0x007d76f9 in PyEval_EvalCodeEx (co=0xb7fa3728, globals=0x6, locals=0xb7f9902c, args=0x0, argcount=0, kws=0x0, kwcount=0, defs=0x0, defcount=0,
    closure=0x0) at Python/ceval.c:2942
#21 0x007d47cb in PyEval_EvalCode (co=0xb7fa3728, globals=0xb7f9902c, locals=0xb7f9902c) at Python/ceval.c:515
#22 0x007fbbce in run_mod (mod=0x80ea780, filename=0xbffc6be6 "gdb_test.py", globals=0xb7f9902c, locals=0xb7f9902c, flags=0xbff8ca8c, arena=0x807ef28)
    at Python/pythonrun.c:1330
#23 0x007fbb58 in PyRun_FileExFlags (fp=0x8091d00, filename=0xbffc6be6 "gdb_test.py", start=257, globals=0xb7f9902c, locals=0xb7f9902c, closeit=1,
    flags=0xbff8ca8c) at Python/pythonrun.c:1316
#24 0x007fb22d in PyRun_SimpleFileExFlags (fp=0x8091d00, filename=0xbffc6be6 "gdb_test.py", closeit=1, flags=0xbff8ca8c) at Python/pythonrun.c:926
#25 0x007facc9 in PyRun_AnyFileExFlags (fp=0x8091d00, filename=0xbffc6be6 "gdb_test.py", closeit=1, flags=0xbff8ca8c) at Python/pythonrun.c:731
#26 0x00808fea in Py_Main (argc=1, argv=0xbff8cbb4) at Modules/main.c:597
#27 0x080486ae in main (argc=2, argv=0xbff8cbb4) at Modules/python.c:23
   
   pystack和pystackv两个宏可用来查看python内部的栈情况;可以看到程序时执行到freeNode函数时结束, 该函数位于libxml2.py的3141行。
(gdb) pystack
/opt/lib/python2.6/site-packages/libxml2.py (3141): freeNode
gdb_test.py (17): segv_test
gdb_test.py (21): main
gdb_test.py (24): <module>

通过堆栈我们可以看到脚本内部各函数的调用关系, 那么我们如何查看函数内变量情况呢? 正如大家所, python内部堆栈和函数的调用由PyEval_EvalFrameEx完成的, 一次PyEval_EvalFrameEx意味着一次函数调用,象上面的第19,13,10行分别对应于main, segv_test, freeNode函数, 将gdb定位到对应行后,使用pylocals宏即可查看该函数内部变量的详细情况。
(gdb) up 13
#13 0x007d6d2b in PyEval_EvalFrameEx (f=0x81242fc, throwflag=0) at Python/ceval.c:2370
2370    in Python/ceval.c
(gdb) pylocals
s:
object : '<html><body><div><a><a></a></a><a></a></div></body></html>'
type    : str
refcount: 3
address : 0xb7f64440
options:
object : 97
type    : int
refcount: 7
address : 0x8082c20
doc:
object : <xmlDoc (None) object at 0xb7cc04ec>
type    : instance
refcount: 1
address : 0xb7cc04ec
ctxt:
object : <libxml2.xpathContext instance at 0xb7f70ccc>
type    : instance
refcount: 1
address : 0xb7f70ccc
nodes:
object : [<xmlNode ((儓X? object at 0xb7cc0cac>]
type    : list
refcount: 2
address : 0xb7f70a8c
note:
object : <xmlNode ((?圶? object at 0xb7cc0cac>
type    : instance
refcount: 2
address : 0xb7cc0cac
nexts:
object : [<xmlNode (hhX? object at 0xb7cc750c>, <xmlNode (HXX? object at 0xb7cc76cc>, <xmlNode (@XX? object at 0xb7c9348c>]
type    : list
refcount: 1
address : 0xb7f4ce4c
    
   脚本调试时断点的设置是个很麻烦的东西,我所能想到的有两种方法:1 根据函数的python源码进行断点设置;2 采用sleep函数和ctrl+c来中断程序的运行。无论怎么样使用逐条执行进行调试都是很痛苦的事情,因为这个时候python解释器本身要做很多工作

使用gdb调试Python进程的更多相关文章

  1. gdb调试python

    一.概述 有时我们会想调试一个正在运行的Python进程,或者一个Python进程的coredump.例如现在遇到一个mod_wsgi的进程僵死了,不接受请求,想看看究竟是运行到哪行Python代码呢 ...

  2. 用gdb调试python多线程代码-记一次死锁的发现

    | 版权:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接.如有问题,可以邮件:wangxu198709@gmail.com 前言 相信很多人都有 ...

  3. gdb 调试 python

    gdb 版本 >7 的 对python调试有特别支持,参考: https://docs.python.org/devguide/gdb.html?highlight=gdb https://bl ...

  4. &lbrack;gdb&rsqb;&lbrack;python&rsqb;&lbrack;libpython&rsqb; 使用gdb调试python脚本

    https://devguide.python.org/gdb/ https://sourceware.org/gdb/current/onlinedocs/gdb/Python.html#Pytho ...

  5. 使用gdb调试python程序

    参考文章:https://mozillazg.com/2017/07/debug-running-python-process-with-gdb.html https://blog.alswl.com ...

  6. GDB 调试PYTHON

    http://www.cnblogs.com/dkblog/p/3806277.html

  7. 用GDB排查Python程序故障

        某Team在用Python开发一些代码,涉及子进程以及设法消除僵尸进程的需求.实践中他们碰上Python程序非预期退出的现象.最初他们决定用GDB调试Python解释器,查看exit()的源头 ...

  8. 通过GDB重新获得进程的输出

    有时通过SecureCRT或者Putty远程ssh到主机上执行某个进程,因长时间没有交互导致ssh断链,此时该进程由init进程收留.该进程的输出也就无法获得了. 这种情况下,可以利用gdb重新获得该 ...

  9. 使用 GDB 调试多进程程序

    使用 GDB 调试多进程程序 GDB 是 linux 系统上常用的调试工具,本文介绍了使用 GDB 调试多进程程序的几种方法,并对各种方法进行比较. 3 评论 田 强 (tianq@cn.ibm.co ...

随机推荐

  1. 【转载】C&plus;&plus; 与&OpenCurlyDoubleQuote;类”有关的注意事项总结(十二):按成员初始化 与 按成员赋值

    原文:C++ 与"类"有关的注意事项总结(十二):按成员初始化 与 按成员赋值 一.按成员初始化(与构造函数和拷贝构造函数有关) 用一个类对象初始化另一个类对象,比如: Accou ...

  2. 黄聪:WordPress 备案期间临时关闭站点设置404

    在header.php文件顶部添加如下代码即可: <? if( !is_user_logged_in() ){ wp_die('网站备案审核中……', '网站备案', array('respon ...

  3. Javascript中Array&period;prototype&period;map&lpar;&rpar;详解

    map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数.callback 每次执行后的返回值组合起来形成一个新数组. callback 函数只会在有值的索引上被调用:那些从来没被赋 ...

  4. C&num;反射 获取程序集信息和通过类名创建类实例(转载)

    C#反射获取程序集信息和通过类名创建类实例 . System.Reflection 命名空间:包含通过检查托管代码中程序集.模块.成员.参数和其他实体的元数据来检索其相关信息的类型. Assembly ...

  5. javascript正则多次调用test 结果交替出现

    现在需要一个正则验证小数点后保留一到三位数,小数点前只能两位或一位整数的这么一个数. 正则如: var reg = /^\d{,}\.\d{,}$/g; 验证如下: 因为我们用1.23符合规则的数据去 ...

  6. 实现WebSocket和WAMP协议的开源库WampSharp

    Websocket Application Messaging Protocol 协议:https://github.com/wamp-proto/wamp-proto 1. 基础档案 引入: WAM ...

  7. 为什么对string调用swap会导致迭代器失效

    一般来说,swap操作将容器内容交换不会导致容器的指针.引用.迭代器失效. 但当容器类型为array和string时除外. 原因在于:SSO  (Short String Optimization 指 ...

  8. zabbix 自定义 nginx 监控模板

    打开zabbix首页→配置→模板→创建模板模板名称:Template App NGINXagent 需添加自定义监控项:UserParameter=nginx.status[*],/bin/bash ...

  9. &lbrack;codevs3342&rsqb;&lbrack;绿色通道&rsqb;

    codevs3342 思路: 既然是要求最小化最长空题段,直接二分答案.然后就是check函数的写法. 先考虑n方转移,假设当前二分的答案是x,用f[i]表示前i个题,第i道题写的最小花费时间.最后再 ...

  10. shell脚本之正则表达式

    具体参考: www.jb51.net/tools/shell_regex.html 正则表达式常用于grep AWK 等工具中