Python 调试方法

FROM http://kamushin.github.io/debug/python.htmlhtml

背景

这几天一直在查一个线上程序 hang 住的问题. 这个程序老是在运行50分钟后 hang 住, 经过如下的一些调试手段,发现是打日志的时候由于 buffer 满被 block 了.
Python 日志是默认打到 stderr 的, 不管日志级别. 而我这个程序是被另外一个程序调起的, 父进程没有接收子进程的 stderr, 致使了 buffer 被打满.
在调试的过程当中, 用到了如下几种 Python 调试手段, 因而记录如下.python

GDB

GDB是一个广为人知的调试器, 并且线上可用, 很是赞. 可是默认配置的 GDB 并不能打印 Python 当前调用栈. 咱们须要对其作些配置.
首先进行gdb的安装, 须要gdb7以上版本
sudo yum install gdb python-debuginfo
而后下载这份 gdb 配置文件 http://svn.python.org/projects/python/trunk/Misc/gdbinit ~/.gdbinit
对于一个线上已经hang住的程序来讲, 能够用 gdb -p pid 的形式进行 attach, 打印出当前调用栈.
通常来讲, 必须是带debug symbol的Python 编译版本才能打印出足够多的信息, 可是线上的 Python 版本每每是不带debug symbol 的, 因而咱们要修改下上述的配置文件git

<<<<         if $pc > PyEval_EvalFrameEx && $pc < PyEval_EvalCodeEx
    >>>>         if $pc > PyEval_EvalFrameEx && $pc < PyEval_EvalCodeEx && $fp != 0

~/.gdbinit 进行上述修改, 便可成功打印出当前 hang住进程的调用栈.
具体到我此次遇到的问题, 在打出调用栈后发现是卡死在 log 模块的 emit 上, 因而 strace 下看到果真是卡死在 write 的系统调用上, 顺利找到了缘由.
更多的用法能够看https://wiki.python.org/moin/DebuggingWithGdb, 不过大部分的用法依然须要debug symbol, 按照 wiki 来,不必定能够顺利实现.github

PDB

PDB是 Python 自带的一个调试模块. 能够以python -m pdf xxx.py 的形式, 以调试模式启动一个 Python 进程.
虽然彷佛不能 attach 到已运行的进程上, 可是提供了一个简单快速的调试方式.shell

Singal AND InteractiveConsole

上述的方式都是不须要侵入代码的, 这里再提供一种侵入代码的方式.less

import code, traceback, signal

def debug(sig, frame):
    """Interrupt running process, and provide a python prompt for
    interactive debugging."""
    d={'_frame':frame}         # Allow access to frame object.
    d.update(frame.f_globals)  # Unless shadowed by global
    d.update(frame.f_locals)

    i = code.InteractiveConsole(d)
    message  = "Signal received : entering python shell.\nTraceback:\n"
    message += ''.join(traceback.format_stack(frame))
    i.interact(message)

def listen():
    signal.signal(signal.SIGUSR1, debug)  # Register handler

基本原理是给SIGUSR1信号加上一个handler, handler 执行时会把当前的变量加载到一个交互式窗口, 而后开启交互式console, 接下来就像打开一个 REPL 同样了, 能够查看当前的变量值, 能够改变变量值, 能够调用函数看看结果是什么, 查看完后^d离开, 就可让程序继续执行下去.
在加好 handler 后, 咱们能够用os.kill(pid, signal.SIGUSR1)的方式, 调起 handler, 进行调试.
值得注意的是, 因为和console 的交互须要 stdout 的支持, 而父子进程默认是不共享 stdout 的,因此当要调试子进程的时候, 须要重定向子进程的 stdout 到父进程的 stdout, 这个很简单,就不贴代码了.ide

相关文章
相关标签/搜索