Python Runtime Debugging
使用 GDB 调试 Python 程序
GDB 7+ 支持使用 Python 语言扩展 GDB 功能 - Extending GDB using Python , 比如,可以通过编写代码可视化展现某些库独有的数据结构(比如 C++ STL 类型)。
Python 提供的 python-gdb.py 模块就属于这类扩展,它可以用在调试对象(the inferior process)是 Python 进程或者链接了 libpython 库进程的场合。它为 GDB 提供了如下功能:
默认配置下,GDB 将进程中的数据元素(几乎所有元素)都作为 PyObject * 类型处理。这时,GDB 打印 出的指针地址对用户来说并没有什么启发性。本模块使用 libpython 的实现细节,识别诸如 string , list , dict 等语言类型,并为用户提供直观的输出信息。
In particular, given a ``gdb.Value`` corresponding to a ``PyObject *`` in the inferior process, we can generate a "proxy value" within the gdb process. For example, given a ``PyObject*`` in the inferior process that is in fact a ``PyListObject*`` holding three ``PyObject*`` that turn out to be ``PyStringObject*`` instances, we can generate a proxy value within the gdb process that is a list of strings: ``["foo", "bar", "baz"]``.
本模块除了为 PyObject* 提供了易于理解的输出,还为 GDB 增加了几个命令: py-list, py-up, py-down, py-bt, py-print, py-locals 。详细使用方式参数 这里
这个模块的使用步骤如下:
升级 GDB 版本为 7+;
安装 CPython 解释器调试符号(The installed debugging symbols will be used by the CPython script for gdb in order to analyze the PyEval_EvalFrameEx frames (A frame essentially is a function call and the associated state in a form of local variables and CPU registers, etc) and map those application level functions in your code.);
# Ubuntu apt install python-dbg # CentOS / RHEL yum install python-debuginfo debuginfo-install python # Pyenv # 自带调试符号
使 GDB 加载 python-gdb.py (或者名为 libpython.py) ;
- 调试系统安装的 Python 时,使用 GDB 加载 Python 进程时,该文件自动加载;
- 调试使用 Pyenv 安装的 Python 时,设置 GDB 的 safe-path, scripts-directory 等等都无法正 常加载此扩展脚本。目前只能通过 source 命令手动加载;
使用 GDB 中断运行中的进程;
单步调试工具
这类工具会中断 Python 进程执行流程。
- almighty GDB
- 使用信号触发 pdb/pudb 断点(http://stackoverflow.com/questions/132058/showing-the-stack-trace-from-a-running-python-application)
- from pudb import set_interrupt_handler; set_interrupt_handler()
CPU/Memory Profiler
stackimpact - 使用定时器定期触发信号处理函 数,然后使用 sys._current_frames() 函数收集 Python 代码上下文,并计算函数 CPU 占用。
pyflame - 使用 Linux 系统调用 ptrace(2) 收集 Python 程序 调用信息,然后将 profiling 数据输出为 Flame Graph 图示。该程序不能使用 pip 安装,需要手动编译。
# compile and install *pyflame* first. # 该程序依赖 Python 头文件和库文件,在编译时需提供正确的文件路径 git clone https://github.com/uber/pyflame cd pyflame; ./autogen.sh; PKG_CONFIG_PATH=/usr/local/pyenv/versions/2.7.14/lib/pkgconfig ./configure make; make install # 指定进程 pyflame -p 3166 -r 0.01 -s 10 -o output.txt -p <PID> # 生成 flamegraph: https://github.com/brendangregg/FlameGraph /path/to/FlameGraph/flamegraph.pl output.txt>flaskt.svg
进程状态收集
lptrace - 基于 GDB 7+。它可以输出类似 strace 格式的 Python 代码级的调用信息。
pyrasite - 基于 GDB 7+。它向运行中的 Python 注入代码并获取执行结果。该软件包附带提供了几种方便获取某些信息的脚本。
# Requirements: gdb 7.3+ pip install pyrasite # See https://pyrasite.readthedocs.io/en/latest/Payloads.html for Payload examples
使用 faulthandler 模块,在它注册的信号处理函数中打印栈信息。Python 3 将此模块作为标准模块提供。
使用 Python 源代码提供 GDB Python Macros 打印应用调用栈等信息
Put http://svn.python.org/projects/python/trunk/Misc/gdbinit into ~/.gdbinit 然后进入 GDB 后,就可以使用 ``pystack`` 等命令了
使用和 StackImpact 类似的方式打印各线程的调用栈:
# https://stackoverflow.com/questions/132058/showing-the-stack-trace-from-a-running-python-application import threading, sys, traceback def dumpstacks(signal, frame): id2name = dict([(th.ident, th.name) for th in threading.enumerate()]) code = [] for threadId, stack in sys._current_frames().items(): code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), threadId)) for filename, lineno, name, line in traceback.extract_stack(stack): code.append('File: "%s", line %d, in %s' % (filename, lineno, name)) if line: code.append(" %s" % (line.strip())) print "\n".join(code) import signal signal.signal(signal.SIGQUIT, dumpstacks)
通用调试工具
SystemTap, DTrace, KTap 等。
Comments
不要轻轻地离开我,请留下点什么...