关于调试:显示正在运行的python应用程序的堆栈跟踪

Showing the stack trace from a running Python application

我有一个python应用程序经常被卡住,我不知道在哪里。

有没有什么方法可以向python解释器发出信号来向您显示正在运行的确切代码?

某种即时的stacktrace?

相关问题:

  • 从python代码中的方法打印当前调用堆栈
  • 检查正在运行的进程正在做什么:打印未构造的python程序的堆栈跟踪


我有我在这种情况下使用的模块-在这种情况下,一个进程将运行很长时间,但有时会因为未知和不可恢复的原因而卡住。它有点黑客,只在Unix上工作(需要信号):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.
Traceback:
"

    message += ''.join(traceback.format_stack(frame))
    i.interact(message)

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

要使用,只需在程序启动时的某个时刻调用listen()函数(甚至可以将其插入site.py中,让所有的python程序都使用它),然后让它运行。在任何时候,使用kill或python向进程发送sigusr1信号:

1
    os.kill(pid, signal.SIGUSR1)

这将导致程序在当前位置中断到Python控制台,向您显示堆栈跟踪,并允许您操作变量。使用control-d(eof)继续运行(不过请注意,在您发出信号的时候,您可能会中断任何I/O等,因此它不是完全非侵入性的。

我有另一个脚本,它做同样的事情,除了通过管道与正在运行的进程通信(允许调试后台进程等)。它有点大,张贴在这里,但我添加它作为一个Python食谱。


安装信号处理器的建议很好,我经常使用它。例如,bzr默认安装一个sigquit处理程序,它调用pdb.set_trace()立即将您放入pdb提示。(有关详细信息,请参阅bzrlib.breakin模块的源代码。)使用pdb,您不仅可以获取当前堆栈跟踪,还可以检查变量等。

但是,有时我需要调试一个我没有预见性的进程来安装信号处理程序。在Linux上,可以将gdb附加到进程中,并使用一些gdb宏获取python堆栈跟踪。把http://svn.python.org/projects/python/trunk/misc/gdbinit放在~/.gdbinit中,然后:

  • 附gdb:gdb -pPID
  • 获取python堆栈跟踪:pystack

不幸的是,它并不完全可靠,但大多数时候都能工作。

最后,附加strace通常可以让您更好地了解流程正在做什么。


我几乎总是处理多个线程,而主线程通常不怎么做,所以最有趣的是转储所有栈(这更像是Java的转储)。以下是基于此博客的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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("
# 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"
"
.join(code)

import signal
signal.signal(signal.SIGQUIT, dumpstacks)

获取未准备好的python程序的堆栈跟踪,在没有调试符号的普通python中运行,可以用pyrasite完成。在Ubuntu Trusty上为我工作得很有魅力:

1
2
3
$ sudo pip install pyrasite
$ echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
$ sudo pyrasite 16262 dump_stacks.py # dumps stacks to stdout/stderr of the python program

(给@albert的帽子贴士,他的答案包括一个指向这个的指针,以及其他工具。)


1
2
3
4
5
6
>>> import traceback
>>> def x():
>>>    print traceback.extract_stack()

>>> x()
[('<stdin>', 1, '<module>', None), ('<stdin>', 2, 'x', None)]

您还可以很好地格式化堆栈跟踪,查看文档。

编辑:为了模拟Java的行为,如@ Douglas Leeder所建议的,添加:

1
2
3
4
import signal
import traceback

signal.signal(signal.SIGUSR1, lambda sig, stack: traceback.print_stack(stack))

应用程序中的启动代码。然后您可以通过发送SIGUSR1到正在运行的python进程来打印堆栈。


回溯模块有一些很好的功能,其中包括:打印堆栈:

1
2
3
import traceback

traceback.print_stack()


您可以尝试FaultHandler模块。使用pip install faulthandler安装并添加:

1
2
import faulthandler, signal
faulthandler.register(signal.SIGUSR1)

在程序开始时。然后将sigusr1发送到您的进程(例如:kill -USR1 42以显示所有线程到标准输出的python回溯。阅读文档了解更多选项(例如:登录到一个文件)和显示回溯的其他方法。

该模块现在是Python3.3的一部分。关于python 2,请参见http://faulthandler.readthedocs.org。/


在这里真正帮助我的是SPIV的提示(如果我有信誉点的话,我会投票并评论)从一个未准备好的python进程中获取堆栈跟踪。但直到我修改了gdbinit脚本它才工作。所以:

  • 下载http://svn.python.org/projects/python/trunk/misc/gdbinit,放到~/.gdbinit中。

  • 编辑它,将PyEval_EvalFrame更改为PyEval_EvalFrameEx[编辑:不再需要;链接文件已经有了这个更改,从2010-01-14开始]

  • 附gdb:EDOCX1[3]

  • 获取python堆栈跟踪:pystack


我会在haridsv的回复中加上一句话作为评论,但我缺乏这样做的声誉:

我们中的一些人仍然坚持使用比2.6更旧的Python版本(thread.ident需要),所以我让代码在Python2.5中工作(尽管没有显示线程名称),如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import traceback
import sys
def dumpstacks(signal, frame):
    code = []
    for threadId, stack in sys._current_frames().items():
            code.append("
# Thread: %d"
% (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"
"
.join(code)

import signal
signal.signal(signal.SIGQUIT, dumpstacks)


python-dv yourscript.py版

这将使解释器在调试模式下运行,并为您提供解释器正在执行的操作的跟踪。

如果要以交互方式调试代码,应按如下方式运行:

python-m pdb yourscript.py版

这就告诉python解释器使用模块"pdb"(即python调试器)运行脚本,如果您像这样运行脚本,解释器将以交互模式执行,就像gdb一样。


请看一下Python3.3中新增的faulthandler模块。pypi上提供了用于python 2的faulthandler后端。


在Solaris上,可以使用pstack(1),不需要更改python代码。如。

1
2
3
4
5
6
7
8
9
10
11
12
# pstack 16000 | grep : | head
16000: /usr/bin/python2.6 /usr/lib/pkg.depotd --cfg svc:/application/pkg/serv
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:282 (_wait) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:295 (wait) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/process/wspbus.py:242 (block) ]
[ /usr/lib/python2.6/vendor-packages/cherrypy/_init_.py:249 (quickstart) ]
[ /usr/lib/pkg.depotd:890 (<module>) ]
[ /usr/lib/python2.6/threading.py:256 (wait) ]
[ /usr/lib/python2.6/Queue.py:177 (get) ]
[ /usr/lib/python2.6/vendor-packages/pkg/server/depot.py:2142 (run) ]
[ /usr/lib/python2.6/threading.py:477 (run)
etc.


我正在寻找一个解决方案来调试我的线程,我在这里找到了它,感谢haridsv。我使用稍微简化的版本,使用traceback.print_stack():

1
2
3
4
5
6
7
8
9
10
11
12
13
import sys, traceback, signal
import threading
import os

def dumpstacks(signal, frame):
  id2name = dict((th.ident, th.name) for th in threading.enumerate())
  for threadId, stack in sys._current_frames().items():
    print(id2name[threadId])
    traceback.print_stack(f=stack)

signal.signal(signal.SIGQUIT, dumpstacks)

os.killpg(os.getpgid(0), signal.SIGQUIT)

为了我的需要,我还按名称筛选线程。


如果您使用的是Linux系统,请在python调试扩展(可以在python-dbgpython-debuginfo包中)中使用gdb的出色功能。它还可以帮助处理多线程应用程序、GUI应用程序和C模块。

运行程序时使用:

1
$ gdb -ex r --args python <programname>.py [arguments]

这就要求gdb准备python .py r联合起来。

现在,当程序挂起时,切换到gdb控制台,按ctr+cabbkbd执行:

1
(gdb) thread apply all py-list

请在这里和这里查看示例会话和更多信息。


我破解了一些附加到正在运行的Python进程中的工具,并注入了一些代码以获得Python外壳。

请参见:https://github.com/albertz/pydbattach


值得一看的是pydb,"一个基于gdb命令集的python调试器的扩展版本"。它包括信号管理器,可以在发送指定信号时启动调试器。

2006年夏天的一个代码项目研究了在名为mpdb的模块中向pydb添加远程调试功能。


pyrange是一个调试器,它可以与运行python进程、打印堆栈跟踪、变量等交互,而无需预先设置。

虽然在过去我经常使用信号处理程序解决方案,但在某些环境中重现问题仍然很困难。


如何调试控制台中的任何函数:

在使用pdb.set_trace()的地方创建函数,然后创建要调试的函数。

1
2
3
4
5
6
7
>>> import pdb
>>> import my_function

>>> def f():
...     pdb.set_trace()
...     my_function()
...

然后调用创建的函数:

1
2
3
4
5
6
>>> f()
> <stdin>(3)f()
(Pdb) s
--Call--
> <stdin>(1)my_function()
(Pdb)

调试愉快:)


您可以使用pudb,一个带有curses接口的python调试器来执行此操作。只要添加

1
from pudb import set_interrupt_handler; set_interrupt_handler()

到您的代码,并在您想要中断时使用ctrl-c。您可以继续使用c,如果您错过了它并想再试一次,可以多次中断。


无法钩住正在运行的Python进程并获得合理的结果。如果进程锁定,我要做的就是将strace连接起来,并试图找出到底发生了什么。

不幸的是,strace通常是"修复"争用条件的观察者,因此输出在那里也是无用的。


使用检查模块。

import inspect
help(inspect.stack)
Help on function stack in module inspect:

< /块引用>< /块引用>

堆栈(上下文=1)返回调用方帧上方堆栈的记录列表。

我觉得这确实很有帮助。


在python 3中,当您第一次在调试器中使用c(ont(inue))时,pdb将自动安装一个信号处理程序。之后按control-c会把你放回那里。在python 2中,这里有一个一行程序,即使在相对较旧的版本中也可以使用(在2.7中测试过,但我将python源代码检查回2.4,它看起来还不错):

1
2
import pdb, signal
signal.signal(signal.SIGINT, lambda sig, frame: pdb.Pdb().set_trace(frame))

如果您花费大量时间调试Python,那么PDB值得学习。这个接口有点迟钝,但是对于使用类似工具的人来说应该是熟悉的,比如gdb。


我不知道类似于Java对SigDug的任何响应,所以您可能需要将其构建到应用程序中。也许您可以让另一个线程中的服务器对某种类型的消息做出stacktrace响应?


如果您需要使用uwsgi进行此操作,它内置了python tracebacker,只需在配置中启用它(编号附加到每个工作人员的名称上):

1
py-tracebacker=/var/run/uwsgi/pytrace

完成此操作后,只需连接到套接字即可打印回溯:

1
uwsgi --connect-and-read /var/run/uwsgi/pytrace1


我在gdb阵营中使用了python扩展。遵循https://wiki.python.org/moin/debuggingwithgdb,这意味着

  • dnf install gdb python-debuginfosudo apt-get install gdb python2.7-dbg
  • gdb python
  • py-bt
  • 也可以考虑info threadsthread apply all py-bt