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

时间:2011-09-14 07:26:55

标签: python debugging gdb

假设我们有以下超级简单的Python脚本:

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

...并且说,我想通过在第a=10行放置断点来调试此脚本,然后逐步完成脚本。

现在,我想使用gdb,因为我想调试可能作为共享对象(.so)库的一部分的Python绑定 - 因此,我理想情况下,在Python代码行上放置一个断点,然后“进入”共享对象的C部分...(注意DebuggingWithGdb - PythonInfo Wiki并没有明确说明这是可能的< / em>的)

问题是: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;如果没有,我会对类似的东西有什么其他选择?

3 个答案:

答案 0 :(得分:26)

非常有趣的问题。这是我的方法。创建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以使其更有意义。

答案 1 :(得分:22)

道歉的长篇大论;我再次回到类似的调试问题 - 你需要长途跋涉到调试器,最后才发现没有实际的错误 - 所以我只想在这里发布我的笔记和一些代码(我和#39; m仍然在Python 2.7,Ubuntu 11.04)。关于OP问题 - 在更新的gdb中,它也可以通过在Python脚本中使用id(...)函数来中断,并使gdb中断builtin_id 1}};但是这里有更多细节:

同样,我遇到了一个用于Python的C .so共享库模块的问题;这次是svn.client,这是一个Swig模块(另见here);在Debian / Ubuntu中可以通过sudo apt-get install python-subversionfilelist)获得。尝试运行Example 8.3. A Python status crawler - Using the APIs (svnbook)时出现问题此示例应与终端命令svn status执行相同的操作;但是当我在我的一个工作副本上尝试它时,它崩溃了&#34; 错误(22):错误转换目录中的条目&#39;路径&#39;到UTF-8 &#34;,即使svn status已处理相同的工作副本(WC)目录(现在已有好几年) - 所以我想看看它来自哪里。我的测试脚本版本是python-subversion-test.py;我的完整调试日志在logsvnpy.gz(gzip文本文件,~188K未压缩,如果有人想通过无休止的步进和回溯跋涉) - 这是删节版本。我安装了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

首先要注意的是Python示例的功能:在那里,为了获取目录中所有文件的状态,首先调用svn.client.svn_client_status2 - 除了路径,还有_status_callback in参数,作为Python中的回调函数进行注册 - 然后阻止。当status2阻塞时,底层模块遍历WC目录路径中的所有文件;对于每个文件条目,它调用已注册的_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 - 在&#34; GDB宏&#34;中提及gdbinit
  • release27-maint/Misc/gdbinit位于Python源代码树中;定义gdbpylocalspyframe命令,但也提到:

      

    #注意:如果你有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,似乎在测试来自Python的gdb功能

这有点令人困惑 - 除了指针之外,最好让自己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中。该脚本通过OS系统调用调用gdb,并测试其Python功能,打印输出到stdout;它还接受命令行选项-imp-lp,在执行其他命令之前import libpython gdb。所以,例如:

$ 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=[])而不仅仅是内存地址({{ 1}}) - 这只是有用的,如果 v=0xb7f7506c碰巧调试Python脚本(或者更确切地说,它将调试解释脚本的Python可执行文件。)

gdb脚本还提供指针,您可以&#34; ...运行&#34; python -c&#39; id(DATA)&#39;&#34;在gdb下,test_gdb.py上有一个断点&#34 ;;为了测试这个,我发布了一个bash脚本gdb_py_so_test.sh,它创建了一个带有计数线程函数的可执行文件,以及与同一函数接口的普通distutils和swig模块(在调试和发布版本中)。它还创建了builtin_id同时包含.gdbinitgdb的Python类断点 - 最后它在Python上运行gdb(加载其中一个共享模块) ,用户可以希望看到断点是否真正触发。

在没有源重建的gdb中

segfault

首先我专注于gdb段错误,我想确切地知道该函数来自哪个模块。我使用了一个函数,可以在debug_funcs.py中找到;可以使用单独的正则表达式为函数和模块调用,并可以生成类似:

status4

但请注意:

$ 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 {或_d的{​​{1}}个模块的不同版本(调试,.so);那是因为我安装了libsvn包。

在任何情况下,我们都可能认为我们知道每个Python脚本调用时加载模块和相应函数的地址 - 这将允许我们在program address上放置python-subversion断点;鉴于我们在这里与&#34; vanilla&#34; .so(没有从源头重建过)。但是,Python本身无法看到python-subversion-dbg实际上使用gdb

_client.so

在Python中,我们可以进行系统调用,向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 的地址,并将最后/proc/pid/maps命令报告的地址添加到其中libsvn_client-1.so的偏移量;并获取我们可以在nm -D(使用svn_client_status4语法)中断的地址 - 但这不是必需的,因为如果gdb可以看到符号,那么b *0xAddress也可以 - 所以我们可以直接打破函数名称。另一件事是,如果是段错误,nm自行停止,我们可以发出回溯(注意:在gdb之后使用Ctrl-X A退出gdb TUI模式):

gdb

因此,我们的错误发生在layout asm的某处,但是在$ 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命令仍然为我们提供了属于第5帧(不是第4帧)的源代码行,我们仍然不了解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. :{{ {1}}模块在​​其调试版本中加载,list缺少调试信息。所以,是时候从源头重建了。

使用源重建的gdb中的段错误

这是我们需要重建的实际svn_client_status4,或者更确切地说是它的库部分 - 因为我们已经有来自python-subversion的调试模块;我系统上的软件包名为libsvn_client-1.so

subversion

...并且没有调试包。为了从源代码重建,我浏览了python-subversion,并通过libsvn1手动找到了依赖项。完整日志中有更多详细信息 - 但是在这里我们可以注意到源包可以构建SWIG Python绑定(即$ 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) )和Subversion库(apt-get source libsvn1)。另外,我使用主内核树中的位置运行apt-rdepends --build-depends --follow=DEPENDS subversion;这意味着,必须通过LD环境变量显式指定源构建的模块:

python-subversion

这里有一个棘手的问题是构建SWIG调试模块需要使用libsvn1进行调用;显然只是做make install并没有这样做;因此,只有$ 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 等产生,尽管有调试信息。如果我们尝试按照上述命令强制执行加载,但使用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中的相关功能(提到的Bash脚本gdb_py_so_test.sh,作为一个例子,在调试和发布版本中构建一个基本的Swig模块,以确认正确的过程。)

使用python的调试符号,函数调用堆栈看起来像这样(粘贴方式有点不同):

gdb

...由于命令行libsvn也使用相同的库函数,我们可以比较第5帧:

libsvn

因此,如果Python调用#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为NULL,会导致段错误。一旦我们开始阅读来源,就可以揭示这一点:在# `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的定义包含:

sb->real_status_func

...也就是说,当使用回调函数调用./subversion/libsvn_client/deprecated.c时,它会创建一个结构,并将该函数分配给其中一个结构属性 - 然后在进一步调用{{}时使用该结构。 1}}!由于status3实际上来自Python - 结论是我们无法从Python正确调用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); (因为这将涉及在Python中创建C结构);并且这无关紧要,因为我们可以从Python调用status3 - 然后它自己调用status4

那么为什么status3可以从Python中找到?可能是因为status4只是为它自动生成了一个接口......无论如何,这是一个例子,调试器的行程揭示了问题的根源 - 但实际上并不是一个bug status3解决方案?请勿使用status4

Python模块中的C失败,在带有源重建的gdb中

回到使用status4swig发生的UTF-8故障 - 这更容易,因为现在可以使用源代码构建版本的模块。问题在函数:)中显而易见,并且通过探索它的参数status4,人们可以首先意识到导致问题的文件名,确实包含非ascii - 但仍然合法UTF-8字符(请参阅Program to check/look up UTF-8/Unicode characters in string on command line? - Super User)。然后我使用这个.gdbinit来为gdb创建一个Python类断点,它会打印出文件名,并且仅在与有问题的匹配时打破。

然后问题是 - 为什么命令行客户端status2不会在同一文件名上崩溃?通过单步执行status3entry_name_to_utf8,可以比较各自的函数调用堆栈:

name

此时,人们遇到Subversion使用svn status(Apache Portable Runtime)进行内存分配的事实;实际上这部分导致了失败 - 主要是,函数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 中的编码,该编码设置为定义libapr - 并且不会#39;在apr_xlate_conv_buffer和Python案例之间进行更改。为了解决这个问题,我已经将与APR字符串复制和分配相关的所有内容复制粘贴到调用堆栈中,并重新构建了一个构建Swig模块的简单示例,该模块应该只使用APR运行时复制字符串;该示例位于目录aprtest中,使用bash脚本build-aprtest.sh构建。

由于这个例子,我们发现可以通过在任何APR字符串内存分配之前在C中调用apr_xlate_conv_buffer来修复UTF故障问题 - 有关该测试的更多信息,请参阅#15977257 - Using utf-8 input for cmd Python module。相应地,我们需要从Python执行的只是执行:

node->frompage

...在拨打APR_LOCALE_CHARSET 1之前(以及svn status,然后拨打setlocale)之前。在这里,我们有另一个例子,一个调试器之旅,没有真正的错误import locale locale.setlocale(locale.LC_ALL, '')

答案 2 :(得分:2)

这是一个有趣的问题,我急切地等待其他答案,但现在:

文档http://wiki.python.org/moin/DebuggingWithGdb主要用于调试段错误和挂起的Python进程,而不是用于正常单步执行Python代码。

我不确定我100%理解你的意图。一旦达到某个Python行,您想要破解C(Python C API)代码吗?那不就是做的事情:

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

myobj.foo()调用C API的位置。然后,只需在附加到myobj.foo的函数上放置一个断点,就可以在正确的位置找到断点。您是否需要更多功能,或者您只是想找到更自然的方法来实现相同的目标?