2020-08-08 python linux 调试(使用PDB
python也有类似C语言gdb的工具pdb,记录下pdb的使用方法和心得。
基础使用
先找了段简单的测试程序:
#!/usr/bin/python
from ftplib import FTP
import sys
import socket
import pdb
def passwordCorrect(ip,port,username,password):
try:
client = FTP()
pdb.set_trace()
client.connect(ip,port)
client.login(username,password)
client.close()
except Exception, e:
pdb.set_trace()
client.close()
if str(e).find('unknown IP address')!=-1:
return 2
return 0
print "correct"
return 1
if __name__ == '__main__':
socket.setdefaulttimeout(3)
ret = passwordCorrect('127.0.0.1',21,'test','test')
print "return is ",ret
pdb的常用命令说明:
l #查看运行到哪行代码
n #单步运行,跳过函数
s #单步运行,可进入函数
p 变量 #查看变量值
b 行号 #断点设置到第几行
b #显示所有断点列表
cl 断点号 #删除某个断点
cl #删除所有断点
c #跳到下一个断点
r #return当前函数
exit #退出
调试记录:
pdb设置断点可以在程序里加入:
import pdb
在需要设置断点的地方加入pdb.set_trace()
执行 python pdb_test.py
1 [root@wh practice]# vim test.py
2 [root@wh practice]# python -m pdb test.py
3 > /home/practice/test.py(2)<module>()
4 -> from ftplib import FTP
5 (Pdb) c
6 > /home/practice/test.py(10)passwordCorrect()
7 -> client.connect(ip,port)
按c逐个执行到下一个断点,按p ip 就可以查看变量ip的值
exit退出当前函数
补充
使用 PDB 调试
python -m pdb 1.py
-m 的话,就会默认断在第一行代码
-
h(elp),会打印当前版本Pdb可用的命令,如果要查询某个命令,可以输入 h [command],例如:“h l” — 查看list命令
-
l(ist),可以列出当前将要运行的代码块
(Pdb) l
497 pdb.set_trace()
498 base_data = {}
499 new_data = {}
500 try:
501 execfile(base_file_name,{},base_data)
502 -> execfile(new_file_name,{},new_data)
503 except:
504 logger.writeLog(“error! load result log error!”)
505 print “load cmp logs error!”
506 raise Exception, “load cmp logs error!”
507
断点设置
(Pdb)b 10 #断点设置在本py的第10行
或(Pdb)b ots.py:20 #断点设置到 ots.py第20行
删除断点(Pdb)b #查看断点编号
(Pdb)cl 2 #删除第2个断点
运行
(Pdb)n #单步运行
(Pdb)s #细点运行 也就是会下到,方法
(Pdb)c #跳到下个断点
查看
(Pdb)p param #查看当前 变量值
(Pdb)l #查看运行到某处代码
(Pdb)a #查看全部栈内变量
- b(reak), 设置断点,例如 “b 77″,就是在当前脚本的77行打上断点,还能输入函数名作为参数,断点就打到具体的函数入口,如果只敲b,会显示现有的全部断点
(Pdb) b 504
Breakpoint 4 at /home/jchen/regression/regressionLogCMP.py:504
- condition bpnumber [condition],设置条件断点,下面语句就是对第4个断点加上条件“a==3”
(Pdb) condition 4 a==3
(Pdb) b
Num Type Disp Enb Where
4 breakpoint keep yes at /home/jchen/regression/regressionLogCMP.py:504
stop only if a==3
- cl(ear),如果后面带有参数,就是清除指定的断点(我在Python2.4上从来没成功过!!!);如果不带参数就是清除所有的断点
(Pdb) cl
Clear all breaks? y
- disable/enable,禁用/激活断点
(Pdb) disable 3
(Pdb) b
Num Type Disp Enb Where
3 breakpoint keep no at /home/jchen/regression/regressionLogCMP.py:505
-
n(ext),让程序运行下一行,如果当前语句有一个函数调用,用n是不会进入被调用的函数体中的
-
s(tep),跟n相似,但是如果当前有一个函数调用,那么s会进入被调用的函数体中
-
c(ont(inue)),让程序正常运行,直到遇到断点
-
j(ump),让程序跳转到指定的行数
(Pdb) j 497
/home/jchen/regression/regressionLogCMP.py(497)compareLog()
-> pdb.set_trace()
- a(rgs),打印当前函数的参数
(Pdb) a
_logger =
_base = ./base/MRM-8137.log
_new = ./new/MRM-8137.log
_caseid = 5550001
_toStepNum = 10
_cmpMap = {‘_bcmpbinarylog’: ‘True’, ‘_bcmpLog’: ‘True’, ‘_bcmpresp’: ‘True’}
- p,最有用的命令之一,打印某个变量
(Pdb) p _new
u’./new/MRM-8137.log’
-
!,感叹号后面跟着语句,可以直接改变某个变量
-
q(uit),退出调试
使用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 进程号,进行调试了。需要在系统安装 gdb
和 Python debug 扩展。Python debug 扩展包含了一些 debug 命令,并且添加了一些 Python 特定的命令到gdb
。在主流的 Linux 发行版中,你可以轻松的安装他们:
Fedora:
sudo yum install gdb python-debuginfo
Ubunt:
sudo apt-get install gdb python2.7-dbg
在一些老系统上面,也一样可以使用 gdb
,具体看文章末尾。
准备
1. 确认你的gdb版本是>=7,gdb从版本7开始支持对Python的debug。
2.确认gdb连接的Python是所要debug的Python,否则请重新编译gdb。
方法:
$ 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。
使用 GDB
有两种可行的方法:
- 一开始就使用
gdb
来启动应用 - 连接到一个已经运行的 Python 进程
在 gdb
下面启动 Python 同样有两种方式:
交互式:
...
(gdb) run <programname>.py <arguments>
自动:
$ gdb -ex r --args python <programname>.py <arguments>
这样的话,它会一直运行直到退出、段错误、或者人为的停止(使用 Ctrl+C
)。
如果进程已经开始运行,你可以通过 PID 来接入它:
$ gdb python <pid of running process>
调试进程
如果你的程序段错误了, gdb
会自动暂停程序,这样你可以切换到 gdb
命令行来检查状态。你也可以人为地使用Ctrl+C
来暂停程序运行。
查看 EasierPythonDebugging获得 gdb
里面的 Python 命令列表。
查看 C 调用栈
如果你在 debug 段错误,你最想做的可能就是查看 C 调用栈。
在 gdb
的命令行里面,只要运行一下命令:
(gdb) bt
#0 0x0000002a95b3b705 in raise () from /lib/libc.so.6
#1 0x0000002a95b3ce8e in abort () from /lib/libc.so.6
#2 0x00000000004c164f in posix_abort (self=0x0, noargs=0x0)
at ../Modules/posixmodule.c:7158
#3 0x0000000000489fac in call_function (pp_stack=0x7fbffff110, oparg=0)
at ../Python/ceval.c:3531
#4 0x0000000000485fc2 in PyEval_EvalFrame (f=0x66ccd8)
at ../Python/ceval.c:2163
...
运气好的话,你可以直接看到问题出现在什么地方。如果它提供的信息不能直接帮你解决问题,你可以尝试继续追踪调用栈。调式的结果取决于 debug 信息的有效程度。
查看 Python 调用栈
如果你安装了 Python 扩展,你可以使用
(gdb) py-bt
可以获取熟悉的 Python 源代码。
对挂住的进程开刀
如果一个进程看上去挂住了,他可能在等待什么东西(比如锁、IO 等等)。也有可能在拼命的跑循环。连接上这个进程,然后检查调用栈也许可以帮上忙。
如果进程在疯狂循环,你可以先让它运行一会,使用 cont
命令,然后使用 Ctrl+C
来暂停,并且打印出调用栈。
如果一些线程卡住了,下面的命令可能会帮上忙:
(gdb) info threads
Id Target Id Frame
37 Thread 0xa29feb40 (LWP 17914) "NotificationThr" 0xb7fdd424 in __kernel_vsyscall ()
36 Thread 0xa03fcb40 (LWP 17913) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()
35 Thread 0xa0bfdb40 (LWP 17911) "QProcessManager" 0xb7fdd424 in __kernel_vsyscall ()
34 Thread 0xa13feb40 (LWP 17910) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()
33 Thread 0xa1bffb40 (LWP 17909) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()
31 Thread 0xa31ffb40 (LWP 17907) "QFileInfoGather" 0xb7fdd424 in __kernel_vsyscall ()
30 Thread 0xa3fdfb40 (LWP 17906) "QInotifyFileSys" 0xb7fdd424 in __kernel_vsyscall ()
29 Thread 0xa481cb40 (LWP 17905) "QFileInfoGather" 0xb7fdd424 in __kernel_vsyscall ()
7 Thread 0xa508db40 (LWP 17883) "QThread" 0xb7fdd424 in __kernel_vsyscall ()
6 Thread 0xa5cebb40 (LWP 17882) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()
5 Thread 0xa660cb40 (LWP 17881) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()
3 Thread 0xabdffb40 (LWP 17876) "gdbus" 0xb7fdd424 in __kernel_vsyscall ()
2 Thread 0xac7b7b40 (LWP 17875) "dconf worker" 0xb7fdd424 in __kernel_vsyscall ()
* 1 Thread 0xb7d876c0 (LWP 17863) "python2.7" 0xb7fdd424 in __kernel_vsyscall ()
当前运行的线程被标记为 *
,要查看 Python 代码运行到哪里,使用 py-list
查看:
(gdb) py-list
2025 # Open external files with our Mac app
2026 if sys.platform == "darwin" and 'Spyder.app' in __file__:
2027 main.connect(app, SIGNAL('open_external_file(QString)'),
2028 lambda fname: main.open_external_file(fname))
2029
>2030 app.exec_()
2031 return main
2032
2033
2034 def __remove_temp_session():
2035 if osp.isfile(TEMP_SESSION_PATH):
查看所有进程的 Python 代码位置,可以使用:
(gdb) thread apply all py-list
...
200
201 def accept(self):
>202 sock, addr = self._sock.accept()
203 return _socketobject(_sock=sock), addr
204 accept.__doc__ = _realsocket.accept.__doc__
205
206 def dup(self):
207 """dup() -> socket object
Thread 35 (Thread 0xa0bfdb40 (LWP 17911)):
Unable to locate python frame
Thread 34 (Thread 0xa13feb40 (LWP 17910)):
197 for method in _delegate_methods:
198 setattr(self, method, dummy)
199 close.__doc__ = _realsocket.close.__doc__
200
201 def accept(self):
>202 sock, addr = self._sock.accept()
203 return _socketobject(_sock=sock), addr
...
引用
- http://fedoraproject.org/wiki/Features/EasierPythonDebugging
- https://code.google.com/p/spyderlib/wiki/HowToDebugDeadlock
老系统上的 GDB
有时候你需要在老系统上面安装 gdb
,这时候你可能需要下列信息:
GDB Macros
一些随着 Python 发布的 GDB 脚本可以用来调试 Python 进程。你可以把 Python 源码里面的 Misc/gdbinit
拷贝到~/.gdbinit
,或者从Subversion来拷贝他们。请注意你的 Python,确保使用正确的代码版本,否则有些功能可能无法工作。
请注意有些新的 GDB 命令只有在 debug 需要的库存在才能正常工作。
这个脚本在 Ubuntu 上面的 gcc 4.5.2 工作时,会爆出错误No symbol "co" in current context.
,是因为call_function
在PyEval_EvalFrameEx 和PyEval_EvalCodeEx 之间。重新使用make "CFLAGS=-g -fno-inline -fno-strict-aliasing"
编译 Python 可以解决这个问题。
使用 Python Stack Traces GDB 脚本
在 gdb 命令行里,可以这样查看 Python stack trace:
(gdb) pystack
同样的,可以获取一列 stack frame 的 Python 变量:
(gdb) pystackv