简体   繁体   English

调试:使用 gdb 单步调试 Python 脚本?

[英]Debugging: stepping through Python script using gdb?

Let's say we have the following mega-simple Python script:假设我们有以下超级简单的 Python 脚本:

print "Initializing"....
a=10
print "Variable value is %d" % (a)
print "All done!"

... and say, I'd like to debug this script by placing a breakpoint at line a=10 , and then stepping through the script. ... 并且说,我想通过在a=10行放置一个断点来调试这个脚本,然后单步执行脚本。

Now, I'd like to use gdb for this, because I'd like to debug Python bindings that may come as a part of a shared object ( .so ) library - hence, I'd ideally place a breakpoint on a Python code line, and then "step into" the C part of the shared object... ( Note that DebuggingWithGdb - PythonInfo Wiki doesn't really explicitly state that this is possible )现在,我想为此使用gdb ,因为我想调试可能作为共享对象 ( .so ) 库的一部分出现的 Python 绑定——因此,我最好在 Python 代码上放置一个断点行,然后“步入”共享对象的 C 部分...(请注意, DebuggingWithGdb - PythonInfo Wiki并未真正明确说明这是可能的

The problem is: gdb on its own cannot really recognize breakpoints, placed on a Python script line:问题是: gdb本身不能真正识别断点,放置在 Python 脚本行上:

$ gdb python
GNU gdb (GDB) 7.3.50.20110806-cvs 
...
Reading symbols from /usr/bin/python...(no debugging symbols found)...done.
(gdb) b test.py:3
No symbol table is loaded.  Use the "file" command.
Make breakpoint pending on future shared library load? (y or [n]) y

Breakpoint 1 (test.py:3) pending.
(gdb) run test.py
Starting program: /usr/bin/python test.py
...

... and while the entire Python script does run within gdb , the breakpoint is simply never reached. ...虽然整个 Python 脚本确实在gdb中运行,但根本就没有到达断点。

So - is what I want to do, at all possible with gdb ;所以 - 这就是我想要做的,完全可以使用gdb and if not, what other alternatives would I have for something similar?如果没有,对于类似的事情,我还有哪些其他选择?

Very interesting question. 非常有趣的问题。 Here's my approach. 这是我的方法。 Create signal_test.py : 创建signal_test.py

import os
import signal

PID = os.getpid()

def do_nothing(*args):
    pass

def foo():
    print "Initializing..."
    a=10
    os.kill(PID, signal.SIGUSR1)
    print "Variable value is %d" % (a)
    print "All done!"

signal.signal(signal.SIGUSR1, do_nothing)

foo()

Then you can run it under gdb: 然后,您可以在gdb下运行它:

$ gdb --args python signal_test.py
GNU gdb (GDB) Red Hat Enterprise Linux (7.0.1-37.el5_7.1)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /net/gs/vol3/software/modules-sw/python/2.7/Linux/RHEL5/x86_64/bin/python...done.

And when you run it, it will go until you reach the call to kill() : 并且当您运行它时,它将一直运行直到您达到kill()的调用:

(gdb) run
Starting program: /net/gs/vol3/software/modules-sw/python/2.7/Linux/RHEL5/x86_64/bin/python signal_test.py
warning: no loadable sections found in added symbol-file system-supplied DSO at 0x2aaaaaaab000
[Thread debugging using libthread_db enabled]
Initializing...

Program received signal SIGUSR1, User defined signal 1.
0x0000003d340306f7 in kill () from /lib64/libc.so.6

You can then look at a backtrace: 然后,您可以查看回溯:

(gdb) backtrace
#0  0x0000003d340306f7 in kill () from /lib64/libc.so.6
#1  0x00000000004d82dd in posix_kill (self=<value optimized out>, args=<value optimized out>)
    at ./Modules/posixmodule.c:4047
#2  0x000000000049b574 in call_function (f=0x8aca30, throwflag=<value optimized out>)
    at Python/ceval.c:4012
#3  PyEval_EvalFrameEx (f=0x8aca30, throwflag=<value optimized out>) at Python/ceval.c:2665
#4  0x000000000049c5cd in call_function (f=0x8ac560, throwflag=<value optimized out>)
    at Python/ceval.c:4098
#5  PyEval_EvalFrameEx (f=0x8ac560, throwflag=<value optimized out>) at Python/ceval.c:2665
#6  0x000000000049d3bb in PyEval_EvalCodeEx (co=0x2aaaae224f30, globals=<value optimized out>, 
    locals=<value optimized out>, args=0x0, argcount=0, kws=0x0, kwcount=0, defs=0x0, defcount=0, 
    closure=0x0) at Python/ceval.c:3252
#7  0x000000000049d432 in PyEval_EvalCode (co=0x1a48, globals=0xa, locals=0x0) at Python/ceval.c:666
#8  0x00000000004bf321 in run_mod (fp=0x89ad60, filename=0x7fffffffb5b4 "signal_test.py", 
    start=<value optimized out>, globals=0x7e4680, locals=0x7e4680, closeit=1, flags=0x7fffffffaee0)
    at Python/pythonrun.c:1346
#9  PyRun_FileExFlags (fp=0x89ad60, filename=0x7fffffffb5b4 "signal_test.py", 
    start=<value optimized out>, globals=0x7e4680, locals=0x7e4680, closeit=1, flags=0x7fffffffaee0)
    at Python/pythonrun.c:1332
#10 0x00000000004bf5d8 in PyRun_SimpleFileExFlags (fp=<value optimized out>, 
    filename=0x7fffffffb5b4 "signal_test.py", closeit=1, flags=0x7fffffffaee0)
    at Python/pythonrun.c:936
#11 0x00000000004148cc in Py_Main (argc=<value optimized out>, argv=<value optimized out>)
    at Modules/main.c:599
#12 0x0000003d3401d994 in __libc_start_main () from /lib64/libc.so.6
#13 0x0000000000413b19 in _start ()

If you continue on, the rest of the program will run normally. 如果继续,该程序的其余部分将正常运行。

(gdb) continue
Continuing.
Variable value is 10
All done!

Program exited normally.

You can, instead, step through in the appropriate frame until you reach the statement you're interested in. You're probably going to want to run a debugging Python for this to make much sense. 相反,您可以逐步进入适当的框架,直到找到您感兴趣的语句为止。您可能将需要运行调试Python,这在很大程度上很有意义。

Apologies for the longish post; 对冗长的帖子表示歉意; I came back again to a similar problem with debugging - a case where you take a long trip to the debugger, to finally reveal there is no actual bug - so I'd just like to post my notes and some code here (I'm still on Python 2.7, Ubuntu 11.04). 我再次遇到了类似的调试问题-您需要花很长时间去调试器,最后才发现没有实际的错误-因此,我只想在此处发布我的注释和一些代码(仍在Python 2.7,Ubuntu 11.04上)。 In respect to the OP question - in newer gdb 's, its also possible to break by using the id(...) function in the Python script, and having gdb break on builtin_id ; 关于OP问题-在较新的gdb ,也可以通过在Python脚本中使用id(...)函数并在builtin_id上使gdb中断来中断它; but here's more details: 但这里有更多详细信息:

Again, I had a problem with a C .so shared library module for Python; 同样,我遇到了一个用于Python的C .so共享库模块的问题; this time it was svn.client , which is a Swig module (see also here ); 这次是svn.client ,这是一个Swig模块(另请参见此处 ); in Debian/Ubuntu available via sudo apt-get install python-subversion ( filelist ). 在Debian / Ubuntu中可以通过sudo apt-get install python-subversionfilelist )获得。 The problem occured while trying to run the Example 8.3. 尝试运行示例8.3时出现了问题。 A Python status crawler - Using the APIs (svnbook) This example should do the same that the terminal command svn status does; Python状态搜寻器-使用API​​(svnbook)此示例应与终端命令svn status相同; but when I tried it on one of my working copies, it crashed with " Error (22): Error converting entry in directory 'path' to UTF-8 ", even if svn status has been processing the same working copy (WC) directory (for years now) - so I wanted to see where that came from. 但是,当我在其中一个工作副本上尝试使用它时,它崩溃并显示为“ 错误(22):将目录'path'中的条目转换为UTF-8时出错 ”,即使svn status已在处理相同的工作副本(WC)目录(多年以来)-所以我想看看那是哪里来的。 My version of the test script is python-subversion-test.py ; 我的测试脚本版本是python-subversion-test.py ; and my full debug log is in logsvnpy.gz (gzipped text file, ~188K uncompressed, should anyone want to wade through endless stepping and backtraces) - this being the abridged version. 而我的完整调试日志在logsvnpy.gz中 (压缩的文本文件,未压缩的〜188K,如果任何人都想涉足无休止的步进和回溯)-这是简化的版本。 I have both Python 2.7 and 3.2 installed, but the 2.7 are default on Ubuntu 11.04: 我同时安装了Python 2.7和3.2,但是2.7是Ubuntu 11.04的默认设置:

$ ls -la $(which python python-dbg)
lrwxrwxrwx 1 root root  9 2012-02-29 07:31 /usr/bin/python -> python2.7
lrwxrwxrwx 1 root root 13 2013-04-07 03:01 /usr/bin/python-dbg -> python2.7-dbg
$ apt-show-versions -r 'python[^-]+'
libpython2.7/natty uptodate 2.7.1-5ubuntu2.2
libpython3.2/natty uptodate 3.2-1ubuntu1.2
python2.7/natty uptodate 2.7.1-5ubuntu2.2
python2.7-dbg/natty uptodate 2.7.1-5ubuntu2.2
python2.7-dev/natty uptodate 2.7.1-5ubuntu2.2
python2.7-minimal/natty uptodate 2.7.1-5ubuntu2.2
python3/natty uptodate 3.2-1ubuntu1
python3-minimal/natty uptodate 3.2-1ubuntu1
python3.2/natty uptodate 3.2-1ubuntu1.2
python3.2-minimal/natty uptodate 3.2-1ubuntu1.2

The first thing to note is how the Python example functions: there, to obtain the status of all files within a directory, first svn.client.svn_client_status2 is called - aside from the path, also with _status_callback in the arguments, as a callback function in Python to be registered - and then blocks. 首先要注意的是Python示例的功能:在那里,要获取目录中所有文件的状态,首先svn.client.svn_client_status2除了路径之外,还在参数中使用_status_callback作为回调函数。在Python中要注册-然后阻止。 While status2 is blocking, the underlying module iterates through all files in the WC directory path; status2阻止状态时,底层模块将遍历WC目录路径中的所有文件; and for each file entry, it calls the registered _status_callback which should print out information about the entry. 对于每个文件条目,它将调用已注册的_status_callback ,该应当打印出有关该条目的信息。 Once this recursion is over, status2 exits. 一旦此递归结束, status2退出status2 Thus, the UTF-8 failure must come from the underlying module. 因此,UTF-8故障必须来自底层模块。 Inspecting this module further: 进一步检查该模块:

$ python -c 'import inspect,pprint,svn.client; pprint.pprint(inspect.getmembers(svn.client))' | grep status
 ('status', <function svn_client_status at 0xb7351f44>),
 ('status2', <function svn_client_status2 at 0xb7351f0c>),
 ('status3', <function svn_client_status3 at 0xb7351ed4>),
 ('status4', <function svn_client_status4 at 0xb7351e9c>),
 ('svn_client_status', <function svn_client_status at 0xb7351f44>),
 # ...

... reveals that there are other statusX functions - however, status3 failed with the same UTF-8 error; ...显示还有其他statusX函数-但是, status3因相同的UTF-8错误而失败; while status4 caused a segmentation fault (which becomes yet another problem to debug). status4导致分段错误(这又成为调试的另一个问题)。

And again, as in my comment to @EliBendersky 's answer, I wanted to issue a breakpoint in Python, so as to obtain some sort of a call stack of C functions later on, which would reveal where the problem occurs - without me getting into rebuilding the C modules from source; 再说一次,正如我对@EliBendersky的回答的评论一样 ,我想在Python中发出一个断点,以便以后获得某种C函数的调用栈,这将揭示问题出在哪里-而我却没有得到从源头重建C模块; but it didn't turn out to be that easy. 但事实并非如此简单。

Python and gdb Python和gdb

First of all, one thing that can be very confusing is the relationship between gdb and Python; 首先,可能非常令人困惑的一件事是gdb和Python之间的关系。 the typical resources coming up here are: 这里出现的典型资源是:

  • http://wiki.python.org/moin/DebuggingWithGdb - mentions a gdbinit in "GDB Macros", http://wiki.python.org/moin/DebuggingWithGdb-在“ GDB宏”中提到了gdbinit
  • That release27-maint/Misc/gdbinit is in the Python source tree; 这个release27-maint / Misc / gdbinit在Python源代码树中; defines gdb commands like pylocals and pyframe , but also mentions: 定义了pylocalspyframe类的gdb命令,但同时提到:

    # NOTE: If you have gdb 7 or later, it supports debugging of Python directly #注意:如果您具有gdb 7或更高版本,则它直接支持Python调试
    # with embedded macros that you may find superior to what is in here. #与嵌入式宏相比,您可能会发现这里的宏更好。
    # See Tools/gdb/libpython.py and http://bugs.python.org/issue8032 . #参见Tools / gdb / libpython.py和http://bugs.python.org/issue8032

  • Features/EasierPythonDebugging - FedoraProject - has an example, mentions a Fedora python-debuginfo package, and libpython 功能/ EasyPythonDebugging-FedoraProject-有一个示例,提到了Fedora python-debuginfo包和libpython

  • Tools/gdb/libpython.py is also in Python source tree, and it mentions: Tools / gdb / libpython.py也在Python源代码树中,它提到:

    From gdb 7 onwards, gdb's build can be configured --with-python, allowing gdb 从gdb 7开始,可以使用--with-python配置gdb的构建,从而允许gdb
    to be extended with Python code eg for library-specific data visualizations, 用Python代码扩展,例如用于特定于库的数据可视化,
    such as for the C++ STL types. 例如C ++ STL类型。 .... ....
    This module embeds knowledge about the implementation details of libpython so 该模块嵌入了有关libpython实现细节的知识,因此
    that we can emit useful visualizations eg a string, a list, a dict, a frame 我们可以发出有用的可视化效果,例如字符串,列表,字典,框架
    giving file/line information and the state of local variables 提供文件/行信息和局部变量的状态

  • cpython/Lib/test/test_gdb.py - apparently from cpython, seems to test gdb functionality from Python cpython / Lib / test / test_gdb.py-显然来自cpython,似乎从Python测试gdb功能

This gets a bit confusing - apart from the pointer, that one better get themselves gdb v.7; 这有点令人困惑-除了指针,还有更好的方法使自己进入gdb v.7; I managed to get for my OS: 我设法获得我的操作系统:

$ apt-show-versions gdb
gdb 7.3-50.20110806-cvs newer than version in archive

A quick way to test if gdb supports Python is this: 测试gdb支持Python的快速方法是:

$ gdb --batch --eval-command="python print gdb"
<module 'gdb' (built-in)>
$ python -c 'import gdb; print gdb'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named gdb

... but gdb supporting Python, doesn't mean Python on its own can access gdb functionality (apparently, the gdb has its own built-in separate Python interpreter). ...但是gdb支持Python,但这并不意味着Python本身可以访问gdb功能(显然, gdb具有自己的内置独立Python解释器)。

It turns out, in Ubuntu 11.04, the python2.7-dbg package installs a file libpython2.7.so.1.0-gdb.py : 事实证明,在Ubuntu 11.04中, python2.7-dbg软件包安装了一个文件libpython2.7.so.1.0-gdb.py

$ find / -xdev -name '*libpython*' 2>/dev/null | grep '\.py'
/usr/lib/debug/usr/lib/libpython2.7.so.1.0-gdb.py
$ sudo ln -s /usr/lib/debug/usr/lib/libpython2.7.so.1.0-gdb.py /usr/lib/debug/usr/lib/libpython.py

... and this is the one corresponding to the mentioned Tools/gdb/libpython.py ; ...这是与提到的Tools/gdb/libpython.py相对应的那个; the symlinking will allow us to refer to it as libpython , and use import script mentioned in Features/EasierPythonDebugging . 符号链接将使我们可以将其称为libpython ,并使用Features / EasierPythonDebugging中提到的导入脚本。

The test_gdb.py script is actually for Python 3 - I have modified it for 2.7, and posted in test_gdb2.7.py . test_gdb.py脚本实际上是针对Python 3的-我已经将其修改为2.7,并发布在test_gdb2.7.py中 This script calls gdb through an OS system call, and tests its Python functionality, with printouts to stdout; 该脚本通过OS系统调用来调用gdb ,并测试其Python功能,并将打印输出输出到stdout。 it also accepts a command line option, -imp-lp , which will import libpython in gdb before other commands are executed. 它还接受命令行选项-imp-lp ,它将在执行其他命令之前将-imp-lp import libpython gdb中。 So, for instance: 因此,例如:

$ python-dbg test_gdb2.7.py
...
*** test_prettyprint ***

42 (self=0x0, v=0x8333fc8)
[] (self=0x0, v=0xb7f7506c)
('foo', 'bar', 'baz') (self=0x0, v=0xb7f7d234)
[0, 1, 2, 3, 4] (self=0x0, v=0xb7f7506c)
...

$ python-dbg test_gdb2.7.py -imp-lp
...
*** test_prettyprint ***

42 (self=0x0, v=42)
[] (self=0x0, v=[])
('foo', 'bar', 'baz') (self=0x0, v=('foo', 'bar', 'baz'))
[0, 1, 2, 3, 4] (self=0x0, v=[0, 1, 2, 3, 4])
...

Thus, libpython.py is intended specifically for the Python interpreter inside gdb , and it helps gdb print Python representations ( v=[] ) instead of just memory addresses ( v=0xb7f7506c ) - which is only helpful, if gdb happens to debug a Python script (or rather, it will debug the Python executable, that interprets the script). 因此, libpython.py专门用于gdb内的Python解释器,它可以帮助gdb打印Python表示形式( v=[] )而不是仅打印内存地址( v=0xb7f7506c )-这仅 gdb碰巧调试a时有用。 Python脚本(或更确切地说,它将调试解释脚本的Python可执行文件)。

The test_gdb.py script also gives the pointer that you can " ... run "python -c'id(DATA)'" under gdb with a breakpoint on builtin_id "; test_gdb.py脚本也给出了指针,你可以“用下一个断点GDB“......运行”蟒蛇-c'id(DATA)” builtin_id “; for testing this, I have posted a bash script, gdb_py_so_test.sh , which creates an executable with a counting thread function, and both plain distutils and swig modules (in both debug and release versions) that interface to the same function. 为了测试这一点,我发布了一个bash脚本gdb_py_so_test.sh ,该脚本创建一个具有计数线程功能的可执行文件,以及普通distutils和swig模块(在调试版本和发行版中),它们均连接到同一功能。 It also creates a .gdbinit with both gdb and gdb 's Python class breakpoints - and finally it runs gdb on Python (loading one of the shared modules), where the user can hopefully see if the breakpoints are really triggering. 它还使用gdbgdb的Python类断点创建一个.gdbinit最后,它在Python上运行gdb (加载共享模块之一),用户可以在其中查看断点是否确实在触发。

segfault in gdb without source rebuild gdb中的segfault,无需重建源代码

First I focused on the status4 segfault, and I wanted to know exactly which module does the function come from. 首先,我专注于status4 segfault,我想确切知道该功能来自哪个模块。 I used a function, that can be found in debug_funcs.py ; 我使用了一个函数,该函数可以在debug_funcs.py中找到; which can be called with separate regex for functions and modules, and may generate something like: 可以使用单独的正则表达式来调用这些函数和模块,并且可能会生成类似以下内容的内容:

$ python python-subversion-test.py ./MyRepoWCDir
# ...
# example for debug_funcs.showLoadedModules(r'(?=.*\.(so|pyc))(?=.*svn)(?=.*client)')
#
svn.client 0xb74b83d4L <module 'svn.client' from '/usr/lib/pymodules/python2.7/svn/client.pyc'>
_client 0xb7415614L <module '_client' from '/usr/lib/pymodules/python2.7/libsvn/_client.so'>
libsvn.client 0xb74155b4L <module 'libsvn.client' from '/usr/lib/pymodules/python2.7/libsvn/client.pyc'>
#
# example for debug_funcs.showFunctionsInLoadedModules(r'status4', r'(?=.*\.(so|pyc))(?=.*svn)')
#
0xb738c4fcL libsvn.client   svn_client_status4                       libsvn/client.pyc
0xb74e9eecL _client         svn_client_status4                       libsvn/_client.so
0xb738c4fcL svn.client      status4                                  svn/client.pyc
0xb738c4fcL svn.client      svn_client_status4                       svn/client.pyc

However, note that: 但是,请注意:

$ python-dbg python-subversion-test.py ./MyRepoWCDir
# ...
0x90fc574 - _client         /usr/lib/pymodules/python2.7/libsvn/_client_d.so
# ...
0x912b30c _client         svn_client_status4                       libsvn/_client_d.so
# ...
$ apt-show-versions -r python-subversion
python-subversion/natty uptodate 1.6.12dfsg-4ubuntu2.1
python-subversion-dbg/natty uptodate 1.6.12dfsg-4ubuntu2.1

... python-dbg will load different (debug, _d ) versions of the .so modules of libsvn (or python-subversion ); ... python-dbg将加载libsvn (或python-subversion )的.so模块的不同版本(调试, _d ); and that is because I have the python-subversion-dbg package installed. 那是因为我安装了python-subversion-dbg软件包。

In any case, we may think we know the adresses where modules and respective functions are loaded upon each Python script call - which would allow us to place a gdb breakpoint on a program address ; 无论如何,我们可能认为我们知道在每个Python脚本调用中加载模块和相应函数的地址-这将使我们能够在程序地址上放置gdb断点; given that here we work with "vanilla" .so's (that haven't been rebuilt from source). 鉴于这里我们使用的是“ vanilla” .so(尚未从源代码重建)。 However, Python on its own cannot see that _client.so in fact utilizes libsvn_client-1.so : 但是,Python本身无法看到_client.so实际上使用libsvn_client-1.so

$ ls -la $(locate '*2.7*/_client*.so')  #check locations
$ ls -la $(locate 'libsvn_client')      #check locations
$ ldd /usr/lib/pyshared/python2.7/libsvn/_client.so | grep client
  libsvn_client-1.so.1 => /usr/lib/libsvn_client-1.so.1 (0x0037f000)
#
# instead of nm, also can use:
# objdump -dSlr file | grep '^[[:digit:]].*status4' | grep -v '^$\|^[[:space:]]'
#
$ nm -D /usr/lib/pyshared/python2.7/libsvn/_client.so | grep status4
         U svn_client_status4
$ nm -a /usr/lib/pyshared/python2.7/libsvn/_client_d.so | grep status4
00029a50 t _wrap_svn_client_status4
         U svn_client_status4
$ nm -D /usr/lib/libsvn_client-1.so.1 | grep status4                    # -a: no symbols
00038c10 T svn_client_status4

From within Python, we could make a system call, to query /proc/pid/maps for the address where libsvn_client-1.so is loaded, and add to it the address reported by the last nm -D command for the offset of svn_client_status4 ; 在Python中,我们可以进行系统调用,以查询/proc/pid/maps加载libsvn_client-1.so的地址, libsvn_client-1.so其添加上一个nm -D命令报告的地址以获取svn_client_status4的偏移量; and obtain the address where we could break in gdb (with the b *0xAddress syntax) - but that is not necessarry, because if nm can see the symbol, so can gdb - so we can break directly on the function name. 并获取可以在gdb中断的地址(使用b *0xAddress语法)-但这不是必需的,因为如果nm可以看到符号,则gdb也可以看到-因此我们可以直接在函数名称上中断。 Another thing is that in case of a segfault, gdb stops on its own, and we can issue a backtrace (note: use Ctrl-X A to exit the gdb TUI mode after layout asm ): 另一件事是,在发生段错误的情况下, gdb自行停止,我们可以发出回溯跟踪(注意:在layout asm之后使用Ctrl-X A退出gdb TUI模式):

$ gdb --args python python-subversion-test.py ./AudioFPGA/
(gdb) r
Starting program: /usr/bin/python python-subversion-test.py ./MyRepoWCDir
...
Program received signal SIGSEGV, Segmentation fault.
0x00000000 in ?? ()
(gdb) bt
#0  0x00000000 in ?? ()
#1  0x005a5bf3 in ?? () from /usr/lib/libsvn_client-1.so.1
#2  0x005dbf4a in ?? () from /usr/lib/libsvn_wc-1.so.1
#3  0x005dcea3 in ?? () from /usr/lib/libsvn_wc-1.so.1
#4  0x005dd240 in ?? () from /usr/lib/libsvn_wc-1.so.1
#5  0x005a5fe5 in svn_client_status4 () from /usr/lib/libsvn_client-1.so.1
#6  0x00d54dae in ?? () from /usr/lib/pymodules/python2.7/libsvn/_client.so
#7  0x080e0155 in PyEval_EvalFrameEx ()
...
(gdb) frame 1
#1  0x005a5bf3 in ?? () from /usr/lib/libsvn_client-1.so.1
(gdb) list
No symbol table is loaded.  Use the "file" command.
(gdb) disas
No function contains program counter for selected frame.
(gdb) x/10i 0x005a5bf3
=> 0x5a5bf3:    mov    -0xc(%ebp),%ebx
   0x5a5bf6:    mov    -0x8(%ebp),%esi
   0x5a5bf9:    mov    -0x4(%ebp),%edi
   0x5a5bfc:    mov    %ebp,%esp
(gdb) layout asm  # No function contains program counter for selected frame (cannot show 0x5a5bf3)
(gdb) p svn_client_status4
$1 = {<text variable, no debug info>} 0x5a5c10 <svn_client_status4>
(gdb) frame 5
#5  0x005a5fe5 in svn_client_status4 () from /usr/lib/libsvn_client-1.so.1
(gdb) list
No symbol table is loaded.  Use the "file" command.
(gdb) layout asm
 │0x5a5fd8 <svn_client_status4+968>       mov    %esi,0x4(%esp)                                |
 │0x5a5fdc <svn_client_status4+972>       mov    %eax,(%esp)                                   |
 │0x5a5fdf <svn_client_status4+975>       mov    -0x28(%ebp),%eax                              |
 │0x5a5fe2 <svn_client_status4+978>       call   *0x38(%eax)                                   |
>│0x5a5fe5 <svn_client_status4+981>       test   %eax,%eax                                     |
 │0x5a5fe7 <svn_client_status4+983>       jne    0x5a5ce3 <svn_client_status4+211>             |
 │0x5a5fed <svn_client_status4+989>       jmp    0x5a5ee3 <svn_client_status4+723>             |
 │0x5a5ff2 <svn_client_status4+994>       lea    -0x1fac(%ebx),%eax                            |
 │0x5a5ff8 <svn_client_status4+1000>      mov    %eax,(%esp)                                   |

So, our error happens somewhere in libsvn_client-1.so , but in memory area before svn_client_status4 function start; 因此,我们的错误发生在libsvn_client-1.so某个地方,但是在svn_client_status4函数启动之前的内存区域中; and since we don't have debugging symbols - we cannot say much else than that. 而且由于我们没有调试符号-我们只能说其他的话。 Using python-dbg may give bit different results: 使用python-dbg可能会产生不同的结果:

Program received signal SIGSEGV, Segmentation fault.
0x005aebf0 in ?? () from /usr/lib/libsvn_client-1.so.1
(gdb) bt
#0  0x005aebf0 in ?? () from /usr/lib/libsvn_client-1.so.1
#1  0x005e4f4a in ?? () from /usr/lib/libsvn_wc-1.so.1
#2  0x005e5ea3 in ?? () from /usr/lib/libsvn_wc-1.so.1
#3  0x005e6240 in ?? () from /usr/lib/libsvn_wc-1.so.1
#4  0x005aefe5 in svn_client_status4 () from /usr/lib/libsvn_client-1.so.1
#5  0x00d61e9e in _wrap_svn_client_status4 (self=0x0, args=0x8471214)
    at /build/buildd/subversion-1.6.12dfsg/subversion/bindings/swig/python/svn_client.c:10001
...
(gdb) frame 4
#4  0x005aefe5 in svn_client_status4 () from /usr/lib/libsvn_client-1.so.1
(gdb) list
9876    in /build/buildd/subversion-1.6.12dfsg/subversion/bindings/swig/python/svn_client.c
(gdb) p svn_client_status4
$1 = {<text variable, no debug info>} 0x5aec10 <svn_client_status4>
(gdb) info sharedlibrary
From        To          Syms Read   Shared Object Library
...
0x00497a20  0x004c8be8  Yes         /usr/lib/pymodules/python2.7/libsvn/_core_d.so
0x004e9fe0  0x004f52c8  Yes         /usr/lib/libsvn_swig_py2.7_d-1.so.1
0x004f9750  0x00501678  Yes (*)     /usr/lib/libsvn_diff-1.so.1
0x0050f3e0  0x00539d08  Yes (*)     /usr/lib/libsvn_subr-1.so.1
0x00552200  0x00572658  Yes (*)     /usr/lib/libapr-1.so.0
0x0057ddb0  0x005b14b8  Yes (*)     /usr/lib/libsvn_client-1.so.1
...
0x00c2a8f0  0x00d11cc8  Yes (*)     /usr/lib/libxml2.so.2
0x00d3f860  0x00d6dc08  Yes         /usr/lib/pymodules/python2.7/libsvn/_client_d.so
...
(*): Shared library is missing debugging information.

... but the list command still gives us a source line belonging to frame 5 (not frame 4), and we still don't know more about svn_client_status4 : while the python-subversion modules are loaded in their debug versions, debugging information is missing for libsvn_client-1.so . ...但是list命令仍然为我们提供了属于第5帧(而不是第4帧)的源代码行,并且我们仍然不了解svn_client_status4 :尽管python-subversion模块以其调试版本加载,但调试信息为libsvn_client-1.so缺少。 So, time to rebuild from source. 因此,是时候从源进行重建了。

segfault in gdb with source rebuild gdb中的segfault与源代码重建

It is the actual subversion that we need to rebuild, or rather it's library part - since we already have debug modules from python-subversion ; 这是我们需要重建的实际subversion ,或者它是库的一部分-因为我们已经有了python-subversion调试模块; the package on my system is called libsvn1 : 我系统上的软件包称为libsvn1

$ apt-show-versions -r 'libsvn'
libsvn1/natty uptodate 1.6.12dfsg-4ubuntu2.1
$ apt-cache search 'libsvn' | grep 'dbg'
python-subversion-dbg - Python bindings for Subversion (debug extension)

... and there is no debug package for it. ...并且没有调试包。 To rebuild from source, I went through apt-get source libsvn1 , with dependencies manually found via apt-rdepends --build-depends --follow=DEPENDS subversion . 要从源进行重建,我经历了apt-get source libsvn1 ,并通过apt-rdepends --build-depends --follow=DEPENDS subversion手动找到了apt-rdepends --build-depends --follow=DEPENDS subversion There are more details in the full log - but here we can note that the source package can built both the SWIG Python bindings (that is, python-subversion ) and the Subversion library ( libsvn1 ). 完整日志中有更多详细信息-但是在这里我们可以注意到源包可以构建SWIG Python绑定(即python-subversion )和Subversion库( libsvn1 )。 Also, I ran make install with a location out of the main kernel tree; 另外,我运行make install并在主内核树之外找到了一个位置。 that means, that one had to explicitly specify the source-built modules via LD environment variables: 这意味着,必须通过LD环境变量显式指定源构建的模块:

$ ELD=/path/to/src/subversion-1.6.12dfsg/tmpinst/usr/local/lib
$ LD_LIBRARY_PATH=$ELD:$ELD/svn-python/libsvn LD_PRELOAD="$ELD/libsvn_client-1.so $ELD/svn-python/libsvn/_core.so" gdb --args python python-subversion-test.py ./MyRepoWCDir

One tricky thing here is that building SWIG debug modules requires a call with python-dbg ; 这里一件棘手的事情是,构建SWIG调试模块需要使用python-dbg进行调用; apparently just doing ./configure --enable-debug doesn't do that; 显然,只是执行./configure --enable-debug不会这样做; and so, just _core.so , etc are produced, albeit with debugging information. 因此,尽管生成了调试信息,但只_core.so等。 If we then try to enforce its loading as with the above command, but with python-dbg , we will get undefined symbol: Py_InitModule4 , because: 如果我们随后尝试像上面的命令一样使用python-dbg来强制执行其加载,则会得到undefined symbol: Py_InitModule4 ,因为:

$ objdump -d $(which python) | grep '^\w.*InitMod'
0813b770 <Py_InitModule4>:
$ objdump -d $(which python-dbg) | grep '^\w.*InitMod'
08124740 <Py_InitModule4TraceRefs>:

... python-dbg has a different Py_InitModule4 function. ... python-dbg具有不同的Py_InitModule4函数。 That, however, wasn't a problem, because simply python was used (as in the above invocation), and gdb still allowed stepping through the relevant functions in the newly built libsvn (the mentioned Bash script gdb_py_so_test.sh , as an example builds a basic Swig module in both debug and release versions to confirm the right procedure). 但是,这并不是问题,因为仅使用了python (如上述调用中所述),并且gdb仍允许单步执行新构建的libsvn的相关功能(例如,所提到的Bash脚本gdb_py_so_test.sh调试和发行版中的基本Swig模块,以确认正确的过程)。

With debugging symbols for libsvn , the function call stack looks like this (pasted a bit differently): 使用libsvn调试符号,函数调用堆栈如下所示(粘贴有所不同):

#5  0x0016e654 in svn_client_status4 (...,    libsvn_client/status.c:369
  #4  0x007fd209 in close_edit (...,            libsvn_wc/status.c:2144
    #3  0x007fafaa in get_dir_status (...,        libsvn_wc/status.c:1033
      #2  0x007fa4e7 in send_unversioned_item (..., libsvn_wc/status.c:722
        #1  0x0016dd17 in tweak_status (...,          libsvn_client/status.c:81
          #0 0x00000000 in ?? ()

... and since the same library functions are also used by command line svn client , we can compare, in say, frame 5: ...并且由于命令行svn client也使用相同的库函数,因此可以比较一下第5帧:

# `svn status`:
(gdb) p *(sb->real_status_func)
$3 = {svn_error_t *(void *, const char *, svn_wc_status2_t *, apr_pool_t *)} 0x805e199 <print_status>
...
# `python python-subversion-test.py`
(gdb) p *(svn_wc_status_func3_t*)sb->real_status_func
Cannot access memory at address 0x0

So, in case of a Python call to status4 , sb->real_status_func is NULL, causing a segfault. 因此,在Python调用status4情况下, sb->real_status_func为NULL,从而导致段错误。 The reason for this can be revealed once we start reading the source: in ./subversion/libsvn_client/deprecated.c , the definition for status3 has: 一旦我们开始阅读源代码,就可以揭露其原因:在./subversion/libsvn_client/deprecated.cstatus3的定义为:

svn_client_status3(svn_revnum_t *result_rev,
                   const char *path,
                   const svn_opt_revision_t *revision,
                   svn_wc_status_func2_t status_func,
                   void *status_baton,
....
  struct status3_wrapper_baton swb = { 0 };
  swb.old_func = status_func;
  swb.old_baton = status_baton;
  return svn_client_status4(result_rev, path, revision, status3_wrapper_func,
                            &swb, depth, get_all, update, no_ignore,
                            ignore_externals, changelists, ctx, pool);

... that is, when status3 is called with a callback function, it creates a struct, and assigns the function to one of the struct properties - and then uses the struct in the further call to status4 ! ... ...也就是说,当使用回调函数调用status3时,它会创建一个结构,然后将该函数分配给其中一个结构属性-然后在对status4的进一步调用中使用该结构! Since status3 actually works from Python - the conclusion is that we cannot correctly call status4 from Python (since that would involve creating a C struct in Python); 由于status3实际上可以在Python中运行-结论是我们无法从Python正确调用status4 (因为这涉及在Python中创建C结构); and that doesn't matter anyways, because we can call status3 from Python - which then itself calls status4 ! 但这没关系,因为我们可以从Python调用status3 ,然后它本身就调用status4

Then why is status4 addressible from Python? 那么为什么status4从Python寻址呢? Probably because swig simply autogenerated an interface for it... In any case, here is an example, where a trip to the debugger reveals the source of the problem - but not really a bug :) Solution? 可能是因为swig只是自动为其生成了一个接口...无论如何,这是一个示例,在该示例中,调试器之旅揭示了问题的根源-但实际上不是bug :)解决方案? Don't use status4 . 不要使用status4

C failure in Python module, in gdb with source rebuild 带有源代码重建的gdb中的Python模块中的C失败

Going back to the UTF-8 failure, which occured with status2 and status3 - it was easier, given that now source built versions of the modules were available. 回到UTF-8故障,该故障与status2status3一起发生-更加容易,因为现在可以使用源代码构建的模块版本。 The problem was obvious in the function entry_name_to_utf8 , and by exploring it's argument name , one could first realize that the file name causing the problem, did indeed contain non-ascii - but still legal UTF-8 characters (see Program to check/look up UTF-8/Unicode characters in string on command line? - Super User ). 这个问题在函数entry_name_to_utf8很明显,通过探究它的参数name ,人们可以首先意识到引起问题的文件名确实包含非ASCII字符-但仍然是合法的UTF-8字符(请参阅程序以检查/查找)命令行中的字符串中包含UTF-8 / Unicode字符?-超级用户 )。 I have then used this .gdbinit , to make a Python class breakpoint for gdb, that would print out the filenames, and break only on match with the problematic one. 然后,我使用了此.gdbinit ,为gdb创建了一个Python类断点,该断点将打印出文件名,并且仅在与有问题的文件名匹配时中断。

Then the question is - how come, the command line client svn status does not crash on the same filename? 然后的问题是-为什么命令行客户端的svn status不会在同一文件名上崩溃? By stepping through both svn status and python python-subversion-test.py , one can compare the respective function call stacks: 通过逐步检查svn statuspython python-subversion-test.py ,可以比较相应的函数调用堆栈:

# call stack Python module:
#
_wrap_svn_client_status3    subversion/bindings/swig/python/svn_client.c * allocs:
(svn_swig_py_get_pool_arg(args, SWIGTYPE_p_apr_pool_t, &_global_py_pool, &_global_pool))
  svn_client_status3    subversion/libsvn_client/deprecated.c
    svn_client_status4    subversion/libsvn_client/status.c
      close_edit    subversion/libsvn_wc/status.c
        get_dir_status    subversion/libsvn_wc/status.c

# call stack svn client:
#
main    subversion/svn/main.c
  svn_cl__status    subversion/svn/status-cmd.c * allocs
  (subpool = svn_pool_create(pool))
    svn_client_status4    subversion/libsvn_client/status.c
      close_edit    subversion/libsvn_delta/cancel.c
        close_edit    subversion/libsvn_wc/status.c
          get_dir_status    subversion/libsvn_wc/status.c


# svn call stack:
# ... svn_client_status4 - starts pool
#
get_dir_status    subversion/libsvn_wc/status.c
  handle_dir_entry    subversion/libsvn_wc/status.c
    get_dir_status    subversion/libsvn_wc/status.c
      svn_io_get_dirents2    subversion/libsvn_subr/io.c
        entry_name_to_utf8    subversion/libsvn_subr/io.c
          svn_path_cstring_to_utf8    subversion/libsvn_subr/path.c
            svn_utf_cstring_to_utf8    subversion/libsvn_subr/utf.c   * from here, bad node->handle
              convert_cstring    subversion/libsvn_subr/utf.c
                convert_to_stringbuf    subversion/libsvn_subr/utf.c  * here, bad node => fail

At this point, one encounters the fact that Subversion uses libapr (Apache Portable Runtime) for memory allocation; 在这一点上,人们遇到一个事实,即Subversion使用libapr (Apache可移植运行时)进行内存分配。 and it is in fact this part causing the failure - principally, the function apr_xlate_conv_buffer behaves differently in the two cases. 实际上这是导致失败的部分-原则上,函数apr_xlate_conv_buffer在两种情况下的行为不同。

But, it can be rather difficult to see what the actual problem is here, because apr_xlate_conv_buffer uses an encoding in node->frompage , which is set to the define APR_LOCALE_CHARSET 1 - and that doesn't change between svn status and Python cases. 但是,要弄清楚实际问题出在哪里可能非常困难,因为apr_xlate_conv_buffernode->frompage apr_xlate_conv_buffer使用了一种编码,该编码设置为定义APR_LOCALE_CHARSET 1并且在svn status和Python情况之间不会改变。 To come down to this, I've copy-pasted everything related to APR string copying and allocation down the call stack, and reconstructed a simple example that builds a Swig module, that should just copy a string using APR runtime; 归根结底,我将所有与APR字符串复制和分配相关的内容复制粘贴到了调用堆栈中,并重建了一个构建Swig模块的简单示例,该示例应该仅使用APR运行时复制字符串。 that example is in the directory aprtest , built with the bash script build-aprtest.sh . 这个例子是在目录aprtest ,与bash脚本建build-aprtest.sh

Thanks to that example, it was revealed that the UTF failure problem can be fixed by calling setlocale in C before any APR string memory allocation - for more about that test, see #15977257 - Using utf-8 input for cmd Python module . 多亏了该示例,我们发现可以通过在APR字符串内存分配之前在C中调用setlocale来解决UTF故障问题-有关该测试的更多信息,请参见#15977257-使用cmd Python模块的utf-8输入 Correspondingly, all we need to do from Python is execute: 相应地,我们需要从Python做的就是执行:

import locale
locale.setlocale(locale.LC_ALL, '')

... before any calls to svn.client (and thus to libsvn , and thus to libapr ). ......任何调用之前svn.client (从而libsvn ,从而给libapr )。 And here we have yet another example, for a trip to the debugger, without really having a bug :) 在这里,我们还有另一个示例,介绍了调试器之旅,而没有真正的错误:)

This is an interesting question, and I'm eagerly waiting for other answers, but for now: 这是一个有趣的问题,我急切地等待其他答案,但是现在:

The document http://wiki.python.org/moin/DebuggingWithGdb is mainly for debugging segfaults and hung Python processes, not for normal stepping through Python code. http://wiki.python.org/moin/DebuggingWithGdb文档主要用于调试段错误和挂起的Python进程,而不用于正常地逐步处理Python代码。

I'm not sure I understand your intention 100%. 我不确定我是否100%理解您的意图。 Do you want to break in your C (Python C API) code once a certain Python line is reached? 一旦到达特定的Python行,您是否要破坏C(Python C API)代码? Then wouldn't it be just a matter of doing: 然后,这不是一件要做的事:

# some Python code
# some other Python code
myobj.foo()
# some other Python code

Where myobj.foo() calls into the C API. myobj.foo()调用C API的位置。 Then, just place a breakpoint on the function attached to myobj.foo and you have your breakpoint at the right location. 然后,将断点放在附加到myobj.foo的函数上, myobj.foo将断点放在正确的位置。 Do you need more functionality, or are you simply looking for a more natural way to achieve the same? 您是否需要更多功能,还是只是在寻找一种更自然的方法来实现相同目的?

I recently faced the same problem of debugging an already running python script.我最近在调试已经运行的 python 脚本时遇到了同样的问题。 I used PyCharm's "Attach to process" ( Attach a process ) utility to set breakpoints and was actually able to set breakpoints and dump the variables I was interested in. The main challenge was the script was already running for last 20hrs and I didn't want to modify that script and start again.我使用 PyCharm 的“附加到进程”( 附加进程)实用程序来设置断点,并且实际上能够设置断点并转储我感兴趣的变量。主要挑战是脚本已经运行了过去 20 小时,而我没有想要修改该脚本并重新开始。 Hence, I attached an already running process.因此,我附上了一个已经在运行的进程。

For production cases where using an IDE is not possible, one may try to remotely debug the scripts Remote Debugging对于无法使用 IDE 的生产情况,可以尝试远程调试脚本Remote Debugging

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM