使用gdb調試Python進程


使用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解釋器本身要做很多工作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM