不知各位有木有遇到这样的情况,生产环境下的Python进程突然卡死了,所有其他线程都无法调度,如果我们kill掉重启,通常会丧失掉当前报错的上下文信息,失去这些信息,对后续报错定位不太友好,能不能在不关停Python程序的情况下,看看程序发生了什么问题?
首先,你可以尝试一下,此前我提过的py-spy(漫画:如何分析运行中的 Python 程序?),py-spy可以打印出简单的调用信息,但很多时候不够用,这里我们通过gdb来调试Python程序,同时打印c栈和py栈的信息,调试起来,一目了然。
gdb主要用于调试c/c++程序的,因为Python是使用c写的(cpython,当然还有其他语言实现的),对gdb而言,将python当成普通的c程序调试则可。
当我们使用gdb调试python进程时,我们会获得解释器级别的调试信息和内存状态,而不是应用程序级别的(我们使用pdb便是应用程序基本的),这样我们可以看到Python完整的执行流程,包括解释器上函数与变量的信息。
我的服务器是Ubuntu 20.04,所以本文的操作都在Ubuntu 20.04上进行。
首先,需要安装一下gdb。
sudo apt-get install gdb
光安装gdb,在调试python时,虽然可以用,但不太友好,为了更好的浏览调试信息,我们为cpython安装gdb debugging Symbols。
gdb中的debugging Symbols的主要作用是将程序编译后的二进制指令映射到源代码相应的变量、函数和行中,这样在调试时可以很好的浏览调试信息。
在Ubuntu中,安装python-dbg则可。
sudo apt-get install python-dbg
python-dbg提供了gdb Debugging Symbols和一些调试python的命令,如py-bt、py-list,本文后续会使用。
python-dbg之所以可以发挥作用,是因为它将python gdb相关的内容自动复制到gdb auto-load目录下了,这样gdb在启动时,会自动加载这些内容。
你可以在进入gdb交互命令后,通过info auto-load来查看相关信息。
安装好gdb调试环境后,准备一段代码,简单使用一下gdb。
我在当前用户目录下,创建了play_gdb目录并在其中创建了虚拟环境,然后在目录中创建了play_gdb.py,里面就是一段简单的计算斐波那契数列的代码。
在日常开发中,我们经常使用python虚拟环境,所以这里想测试一下,如果我们使用python虚拟环境运行程序时,gdb是否可以正常调试。
在使用python venv前,先通过系统的python来试试,不进入虚拟环境,直接运行。
从上图可知,我们使用系统python运行程序,开启新窗口,通过gdb调试一下,如下图:
上图命令为:
ps -x | grep python
sudo gdb -p 1199469
先找到pid,然后再让gdb直接附加到运行中的python进程上。
通过bt命令,查一下当前程序的调用栈。
从上图可以看出,有很多python解释器级别的打印,我们看到程序目前在python的timemodule.c的pysleep方法中,最终调用了linux系统的select.c(即通过I/O复用相关的逻辑来实现python进程中主线程的sleep)。
gdb的bt命令可以将c调用栈完整打印出来,如果我们只想看python调用栈,可以使用py-bt(你需要安装python-dbg才能用),此外,如果想查看当前程序的py代码,可以使用py-list(等价pdb的ll命令),如下图:
从上图看,py-bt和py-list都没有正常运行,这是因为我们使用gdb时,没有在当前项目的根目录,通过q命令退出一下,然后进入项目根目录,再用gdb开启调试。
简单列一下gdb调试时的常用命令:
bt # 当前C调用栈
py-bt # 当前Py调用栈
py-list # 当前py代码位置
py-up # 上一帧(py级别的帧)
py-down # 下一帧(py级别的帧)
info thread # 线程信息
thread # 切换到某个线程
thread apply all py-list # 查看所有线程的py代码位置
ctrl-c # 中断
更多用法可以看pthon官方关于gdb的文档:https://devguide.python.org/gdb/
如果利用python虚拟环境中的python解释器来执行py程序,gdb也可以正常调试,没有什么使用上的差异。
通过gdb-dashboard(https://github.com/cyrus-and/gdb-dashboard)可以美化gdb调试信息,从而增加效率。
从gdb7开始,gdb便支持使用Python代码来扩展gdb,gdb-dashboard的原理正是如此。
在当前使用gdb的用户目录下,下载gdb-dashboard提供的.gdbinit文件,你可以直接从github中拉取,然后复制到相应的用户目录下。
然后你再安装一下pygments,用于启用语法高亮显示的效果(选择性安装):
pip install pygments
因为我是通过root用户来使用gdb的,所以需要将.gdbinit放置在root用户目录下。
然后正常使用gdb,会获得下图效果。
上图中,可以很直观地看到调用栈(Stack)、变量(Variables)、寄存器(Registers)等信息,都是c层面的。
在gdb中,通过help dashboard可以查看dashboard更多的用法。
掌握gdb调试,可以更好地理解python源码,对于一些比较难搞的线上情况,也更加游刃有余,学起来呗。
使用gdb调试CPython进程 (https://github.com/ictar/python-doc/blob/master/Others/%E4%BD%BF%E7%94%A8gdb%E8%B0%83%E8%AF%95CPython%E8%BF%9B%E7%A8%8B.md)
gdb调试cpython(https://meteorix.github.io/2019/02/13/gdbpython/)