调试:如何用gdb逐步运行Python脚本?

41 投票
4 回答
63181 浏览
提问于 2025-04-17 02:15

假设我们有一个超级简单的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 个回答

2

这是个有趣的问题,我也很期待其他人的回答,不过现在我先说说我的看法:

这个文档 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 关联的函数上设置一个断点,这样你就能在正确的位置暂停了。你是需要更多的功能,还是只是想找一个更自然的方法来实现同样的效果呢?

26

抱歉这篇文章有点长;我又遇到了一个调试的类似问题——就是你花了很长时间去调试器,最后发现根本没有实际的错误——所以我想在这里记录一下我的笔记和一些代码(我仍在使用Python 2.7和Ubuntu 11.04)。关于原帖提问的内容——在更新的gdb中,可以通过在Python脚本中使用id(...)函数,并让gdbbuiltin_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命令,如pylocalspyframe,但也提到:

    # 注意:如果你有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,该选项将在执行其他命令之前在gdbimport 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,其中包含gdbgdb的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失败,这发生在status2status3中——鉴于现在有源构建版本的模块,问题显而易见,出现在函数entry_name_to_utf8中,通过检查它的参数name,可以首先意识到导致问题的文件名确实包含非ASCII但仍然合法的UTF-8字符(见命令行中检查/查找UTF-8/Unicode字符的程序? - 超级用户)。然后我使用这个.gdbinit,为gdb创建了一个Python类断点,它将打印出文件名,并仅在与有问题的文件名匹配时中断。

然后问题是——为什么命令行客户端svn status在同一个文件名上不会崩溃?通过逐步执行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

此时,人们会发现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)的调用之前。在这里,我们又有一个示例,调试器之旅,没有真正的错误:)

37

这个问题很有意思。下面是我的做法。首先,创建一个叫做 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。

撰写回答