[英]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-subversion
( filelist )获得。 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.
但事实并非如此简单。
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: 这里出现的典型资源是:
gdbinit
in "GDB Macros", 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: 定义了
pylocals
和pyframe
类的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
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提供文件/行信息和局部变量的状态
gdb
functionality from 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. 它还使用
gdb
和gdb
的Python类断点创建一个.gdbinit
最后,它在Python上运行gdb
(加载共享模块之一),用户可以在其中查看断点是否确实在触发。
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. 因此,是时候从源进行重建了。
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.c
, status3
的定义为:
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
。
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故障,该故障与
status2
和status3
一起发生-更加容易,因为现在可以使用源代码构建的模块版本。 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 status
和python 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_buffer
在node->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.