调试:如何用gdb逐步运行Python脚本?
假设我们有一个超级简单的Python脚本:
print "Initializing"....
a=10
print "Variable value is %d" % (a)
print "All done!"
... 现在,我想在代码的a=10这一行设置一个调试点,然后逐行执行这个脚本。
我想用gdb
来调试,因为我想调试一些可能作为共享库(.so
)一部分的Python绑定。因此,我理想的做法是在Python代码行上设置一个调试点,然后“进入”共享库的C部分... (注意,DebuggingWithGdb - PythonInfo Wiki并没有明确说明这是可能的)
问题是: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
...
... 尽管整个Python脚本在gdb
中运行,但调试点根本不会被触发。
所以,我想做的事情,是否在gdb
中完全不可能?如果不行,还有什么其他类似的选择吗?
4 个回答
这是个有趣的问题,我也很期待其他人的回答,不过现在我先说说我的看法:
这个文档 http://wiki.python.org/moin/DebuggingWithGdb 主要是用来调试程序崩溃和卡住的Python进程,而不是用来逐行调试普通的Python代码。
我不太确定我是否完全理解你的意思。你是想在某一行Python代码执行到的时候,暂停你的C代码(Python C API)吗?如果是这样,那其实只需要这样做:
# some Python code
# some other Python code
myobj.foo()
# some other Python code
这里的 myobj.foo()
是调用C API的地方。然后,你只需要在与 myobj.foo
关联的函数上设置一个断点,这样你就能在正确的位置暂停了。你是需要更多的功能,还是只是想找一个更自然的方法来实现同样的效果呢?
抱歉这篇文章有点长;我又遇到了一个调试的类似问题——就是你花了很长时间去调试器,最后发现根本没有实际的错误——所以我想在这里记录一下我的笔记和一些代码(我仍在使用Python 2.7和Ubuntu 11.04)。关于原帖提问的内容——在更新的gdb
中,可以通过在Python脚本中使用id(...)
函数,并让gdb
在builtin_id
处中断来实现;但这里有更多的细节:
我再次遇到了一个与Python的C .so共享库模块相关的问题;这次是svn.client
,这是一个Swig模块(也可以参考这里);在Debian/Ubuntu中可以通过sudo apt-get install python-subversion
安装(文件列表)。问题出现在尝试运行示例8.3:Python状态爬虫 - 使用API(svnbook)时。这个示例应该和终端命令svn status
做同样的事情;但是当我在我的一个工作副本上尝试时,它崩溃了,显示“Error (22): Error converting entry in directory 'path' to UTF-8”,尽管svn status
已经处理了同一个工作副本(WC)目录多年——所以我想看看问题出在哪里。我的测试脚本版本是python-subversion-test.py;我的完整调试日志在logsvnpy.gz中(gzipped文本文件,解压后约188K,如果有人想要逐步查看无尽的跟踪和回溯)——这只是简化版。我在Ubuntu 11.04上安装了Python 2.7和3.2,但2.7是默认版本:
$ 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
首先要注意的是Python示例的工作原理:在这里,要获取目录中所有文件的状态,首先调用svn.client.svn_client_status2
——除了路径,还需要在参数中提供_status_callback
,作为在Python中注册的回调函数——然后它会阻塞。虽然status2
是阻塞的,但底层模块会遍历工作副本目录路径中的所有文件;对于每个文件条目,它会调用注册的_status_callback
,该函数应该打印出有关该条目的信息。一旦这个递归结束,status2
就会退出。因此,UTF-8失败一定是来自底层模块。进一步检查这个模块:
$ 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>),
# ...
... 发现还有其他statusX
函数——然而,status3
也因同样的UTF-8错误而失败;而status4
则导致了段错误(这又是一个需要调试的问题)。
再次如我在对@EliBendersky的回答中的评论所说,我想在Python中设置一个断点,以便稍后获得某种C函数的调用栈,这将揭示问题发生的位置——而不需要我从源代码重新构建C模块;但这并没有那么简单。
Python与gdb
首先,有一点可能会让人感到困惑的是gdb
与Python之间的关系;这里通常提到的资源有:
- http://wiki.python.org/moin/DebuggingWithGdb - 提到“GDB宏”中的
gdbinit
, 那个release27-maint/Misc/gdbinit在Python源代码树中;定义了
gdb
命令,如pylocals
和pyframe
,但也提到:# 注意:如果你有gdb 7或更高版本,它支持直接调试Python
# 通过嵌入的宏,你可能会发现比这里的更好。
# 请参见Tools/gdb/libpython.py和http://bugs.python.org/issue8032。Features/EasierPythonDebugging - FedoraProject - 有一个示例,提到Fedora的
python-debuginfo
包和libpython
- Tools/gdb/libpython.py也在Python源代码树中,它提到:
从gdb 7开始,gdb的构建可以配置为--with-python,使得gdb
可以通过Python代码扩展,例如用于特定库的数据可视化,
比如C++ STL类型。....
这个模块嵌入了libpython的实现细节,以便我们可以发出有用的可视化,例如字符串、列表、字典、帧
提供文件/行信息和局部变量的状态。 - cpython/Lib/test/test_gdb.py - 显然来自cpython,似乎测试
gdb
在Python中的功能
这有点让人困惑——除了指向,最好自己获取gdb
v.7;我为我的操作系统成功获取了:
$ apt-show-versions gdb
gdb 7.3-50.20110806-cvs newer than version in archive
测试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
... 但gdb
支持Python,并不意味着Python本身可以访问gdb
的功能(显然,gdb
有自己内置的独立Python解释器)。
结果是,在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
... 这就是与提到的Tools/gdb/libpython.py
对应的文件;创建符号链接将允许我们将其称为libpython
,并使用在Features/EasierPythonDebugging中提到的导入脚本。
test_gdb.py
脚本实际上是为Python 3编写的——我已将其修改为2.7,并发布在test_gdb2.7.py。这个脚本通过操作系统的系统调用调用gdb
,并测试其Python功能,输出到标准输出;它还接受一个命令行选项-imp-lp
,该选项将在执行其他命令之前在gdb
中import libpython
。所以,例如:
$ 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])
...
因此,libpython.py
是专门为gdb
内部的Python解释器设计的,它帮助gdb
打印Python表示(v=[]
),而不是仅仅内存地址(v=0xb7f7506c
)——这只有在gdb
调试Python脚本时才有用(或者更确切地说,它将调试解释脚本的Python可执行文件)。
test_gdb.py
脚本还提供了一个指示,你可以在gdb
中运行“... run "python -c'id(DATA)'",并在builtin_id
上设置断点”;为了测试这个,我发布了一个bash脚本gdb_py_so_test.sh,它创建了一个具有计数线程功能的可执行文件,以及两个普通的distutils和swig模块(调试和发布版本),它们都与同一个功能接口。它还创建了一个.gdbinit
,其中包含gdb
和gdb
的Python类断点——最后,它在Python上运行gdb
(加载其中一个共享模块),希望用户能看到断点是否真的被触发。
gdb中的段错误而不重建源代码
首先我专注于status4
的段错误,我想知道这个函数到底来自哪个模块。我使用了一个可以在debug_funcs.py中找到的函数;它可以通过单独的正则表达式调用函数和模块,并可能生成如下内容:
$ 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
但是,请注意:
$ 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
将加载libsvn
(或python-subversion
)的不同(调试,_d
)版本的.so
模块;这是因为我安装了python-subversion-dbg
包。
无论如何,我们可能认为我们知道每次调用Python脚本时模块和相应函数加载的位置——这将允许我们在程序地址上放置gdb
断点;鉴于我们在这里使用的是“原版” .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
在Python内部,我们可以进行系统调用,查询/proc/pid/maps
以获取libsvn_client-1.so
加载的地址,并将其与最后一次nm -D
命令报告的svn_client_status4
的偏移地址相加;并获得可以在gdb
中中断的地址(使用b *0xAddress
语法)——但这并不是必要的,因为如果nm
可以看到符号,gdb
也可以看到——所以我们可以直接在函数名上设置断点。另一件事是,在发生段错误的情况下,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) |
所以,我们的错误发生在libsvn_client-1.so
中的某个地方,但在svn_client_status4
函数开始之前的内存区域;而且由于我们没有调试符号——我们无法说出更多。使用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.
... 但list
命令仍然给出属于帧5(而不是帧4)的源代码行,我们仍然对svn_client_status4
了解不多:虽然python-subversion
模块以调试版本加载,但libsvn_client-1.so
的调试信息缺失。所以,是时候从源代码重建了。
gdb中重建源代码的段错误
我们需要重建的实际上是subversion
,或者更确切地说是它的库部分——因为我们已经拥有python-subversion
的调试模块;我系统上的包叫做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)
... 并且没有调试包。为了从源代码重建,我通过apt-get source libsvn1
,手动找到依赖关系通过apt-rdepends --build-depends --follow=DEPENDS subversion
。完整日志中有更多细节——但在这里我们可以注意到,源代码包可以构建SWIG Python绑定(即python-subversion
)和Subversion库(libsvn1
)。此外,我在主内核树之外运行了make install
;这意味着必须通过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
这里有一个棘手的事情是,构建SWIG调试模块需要使用python-dbg
调用;显然,仅仅执行./configure --enable-debug
并不能做到这一点;因此,仅生成_core.so
等,尽管有调试信息。如果我们然后尝试强制加载它,如上面的命令,但使用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
有不同的Py_InitModule4
函数。然而,这并不是问题,因为简单地使用python
(如上面的调用)并且gdb
仍然允许逐步执行新构建的libsvn
中的相关函数(提到的Bash脚本gdb_py_so_test.sh,作为示例构建了一个基本的Swig模块,包含调试和发布版本以确认正确的过程)。
有了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 ?? ()
... 由于相同的库函数也被命令行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
因此,在对status4
的Python调用中,sb->real_status_func
为NULL,导致段错误。这个原因可以在我们开始阅读源代码时揭示:在./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);
... 也就是说,当status3
与回调函数一起调用时,它会创建一个结构,并将函数分配给结构的一个属性——然后在对status4
的进一步调用中使用该结构!由于status3
实际上可以从Python调用——结论是我们无法从Python正确调用status4
(因为这涉及在Python中创建一个C结构);而且无论如何,这并不重要,因为我们可以从Python调用status3
——然后它自己调用status4
!
那么,为什么status4
可以从Python访问?可能是因为swig
简单地为它自动生成了一个接口……无论如何,这里是一个示例,调试器之旅揭示了问题的根源——但并不是真正的错误:)
解决方案?不要使用status4
。
Python模块中的C失败,在gdb中重建源代码
回到UTF-8失败,这发生在status2
和status3
中——鉴于现在有源构建版本的模块,问题显而易见,出现在函数entry_name_to_utf8
中,通过检查它的参数name
,可以首先意识到导致问题的文件名确实包含非ASCII但仍然合法的UTF-8字符(见命令行中检查/查找UTF-8/Unicode字符的程序? - 超级用户)。然后我使用这个.gdbinit,为gdb创建了一个Python类断点,它将打印出文件名,并仅在与有问题的文件名匹配时中断。
然后问题是——为什么命令行客户端svn status
在同一个文件名上不会崩溃?通过逐步执行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
此时,人们会发现Subversion使用libapr
(Apache Portable Runtime)进行内存分配;实际上,正是这一部分导致了失败——原则上,函数apr_xlate_conv_buffer
在这两种情况下的行为不同。
但是,看到这里的实际问题可能会相当困难,因为apr_xlate_conv_buffer
使用node->frompage
中的编码,而该编码设置为定义APR_LOCALE_CHARSET 1
——而这在svn status
和Python情况下并没有改变。为了弄清楚这一点,我将与APR字符串复制和分配相关的所有内容从调用栈中复制粘贴,并重建了一个简单的示例,构建一个Swig模块,该模块应该仅使用APR运行时复制字符串;该示例位于aprtest目录中,使用bash脚本build-aprtest.sh构建。
多亏了这个示例,揭示了UTF失败问题可以通过在任何APR字符串内存分配之前在C中调用setlocale
来修复——有关该测试的更多信息,请参见#15977257 - 使用utf-8输入进行cmd Python模块。相应地,我们需要从Python执行:
import locale
locale.setlocale(locale.LC_ALL, '')
... 在任何对svn.client
(因此对libsvn
,因此对libapr
)的调用之前。在这里,我们又有一个示例,调试器之旅,没有真正的错误:)
这个问题很有意思。下面是我的做法。首先,创建一个叫做 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()
然后你可以在 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.
当你运行它时,它会一直执行,直到你到达调用 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
接下来,你可以查看一个回溯信息:
(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 ()
如果你继续执行,程序的其余部分会正常运行。
(gdb) continue
Continuing.
Variable value is 10
All done!
Program exited normally.
你也可以在合适的地方逐步执行,直到到达你感兴趣的那条语句。为了更好地理解这些内容,你可能需要使用调试版本的 Python。