Отладка: переход через Python script с помощью gdb?

Скажем, у нас есть следующий мега-простой Python script:

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

... и скажем, я бы хотел отладить этот script, поставив точку останова в строке a=10, а затем перейдя через script.

Теперь я хотел бы использовать gdb для этого, потому что я хотел бы отлаживать привязки Python, которые могут быть частью библиотеки общего объекта (.so) - следовательно, я бы идеально разместил точка останова в строке кода Python, а затем "входите" в часть C общего объекта... (Обратите внимание, что DebuggingWithGdb - PythonInfo Wiki на самом деле не явно укажите, что это возможно)

Проблема заключается в следующем: gdb сам по себе не может распознать точки останова, помещенные в строку Python script:

$ 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 script работает в пределах gdb, точка останова просто не достигается.

Итак - это то, что я хочу сделать, возможно с помощью gdb; а если нет, то какие другие альтернативы мне нужно для чего-то подобного?

Ответ 1

Очень интересный вопрос. Вот мой подход. Создайте 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, чтобы это имело смысл.

Ответ 2

Извинения за длинный пост; Я снова вернулся к аналогичной проблеме с отладкой - случай, когда вы долго отправляетесь в отладчик, чтобы, наконец, обнаружить, что нет никакой ошибки - поэтому я просто хотел бы опубликовать свои заметки и некоторый код здесь (я все еще на Python 2.7, Ubuntu 11.04). Что касается вопроса OP - в более новых gdb, его также можно разбить с помощью функции id(...) в Python script и иметь gdb break on builtin_id; но здесь более подробно:

Опять же, у меня возникла проблема с библиотечным модулем C.so для Python; на этот раз это был svn.client, который является модулем Swig (см. также здесь); в Debian/Ubuntu, доступный через sudo apt-get install python-subversion (filelist). Проблема возникла при попытке запустить Пример 8.3. Сканер состояния Python - Использование API (svnbook) В этом примере должен выполняться то же самое, что и команда терминала svn status; но когда я попробовал это на одной из моих рабочих копий, он разбился с "Ошибка (22): Ошибка преобразования записи в каталог" путь "к UTF-8", даже если svn status обрабатывает ту же рабочую копию (WC) (в течение многих лет) - так что я хотел посмотреть, откуда это взялось. Моя версия теста script python-subversion-test.py; и мой полный журнал отладки находится в logsvnpy.gz (gzipped текстовый файл, ~ 188K несжатый, если кто-то захочет пробираться через бесконечные степпинга и backtraces) - это сокращенная версия. У меня установлены оба 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 в аргументах, так как функция обратного вызова в 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 - упоминает a gdbinit в "Макросах GDB",
  • Это release27-maint/Misc/gdbinit находится в исходном дереве Python; определяет команды gdb, такие как pylocals и pyframe, но также упоминает:

    # ПРИМЕЧАНИЕ. Если у вас есть gdb 7 или более поздняя версия, он поддерживает отладку Python напрямую
    # со встроенными макросами, которые вы можете найти выше того, что здесь. # См. Инструменты /gdb/libpython.py и http://bugs.python.org/issue8032.

  • Возможности /EasierPythonDebugging - FedoraProject - имеет пример, упоминает пакет Fedora python-debuginfo и libpython

  • Tools/gdb/libpython.py также находится в исходном дереве Python, и он упоминает:

    Начиная с gdb 7, gdb build может быть сконфигурирован --with-python, что позволяет gdb для расширения с помощью кода Python, например. для визуализации данных, специфичных для библиотеки,
    например, для типов STL С++.....
    Этот модуль содержит знания о деталях реализации libpython, так что что мы можем испускать полезные визуализации, например. строка, список, dict, рамка
    предоставление информации о файле/строке и состояние локальных переменных

  • 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; symlinking позволит нам ссылаться на него как libpython и использовать import script, упомянутый в Features/EasierPythonDebugging.

test_gdb.py script на самом деле для Python 3 - я изменил его на 2.7 и разместил в test_gdb2.7.py. Этот script вызывает 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 предназначен специально для интерпретатора Python внутри gdb, и помогает gdb печатать представления Python (v=[]) вместо адресов памяти (v=0xb7f7506c) - что полезно, если gdb происходит с отладкой Python script (вернее, он будет отлаживать исполняемый файл Python, который интерпретирует script).

test_gdb.py script также дает указателю, что вы можете "запустить" python -c'id (DATA) "под gdb с точкой останова на builtin_id"; для тестирования этого я опубликовал bash script, gdb_py_so_test.sh, который создает исполняемый файл с функцией потока подсчета и как простые distutils, так и модули swig (как в версиях отладки, так и в версии выпуска), которые взаимодействуют с одной и той же функцией. Он также создает .gdbinit с gdb и gdb точками прерывания класса Python - и, наконец, он запускает gdb на Python (загрузка одного из разделяемых модулей), где пользователь может надеяться увидеть, действительно ли точки останова запускаются.

segfault в gdb без восстановления источника

Сначала я сосредоточился на segfault 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 будет загружать разные версии (_d) .so модулей libsvn (или python-subversion); и это потому, что у меня установлен пакет python-subversion-dbg.

В любом случае мы можем думать, что мы знаем адреса, где модули и соответствующие функции загружаются при каждом вызове Python script, что позволит нам разместить точку останова gdb на адрес программы; учитывая, что здесь мы работаем с "vanilla".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 - чтобы мы могли разбить непосредственно на имя функции. Другое дело, что в случае segfault gdb останавливается сам по себе, и мы можем выдать backtrace (обратите внимание: используйте Ctrl-X A для выхода из режима 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 может дать бит разные результаты:

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. Итак, время для восстановления из источника.

segfault в 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 script 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

Итак, в случае вызова Python на status4, sb->real_status_func равен NULL, вызывая segfault. Причина этого может быть обнаружена после начала чтения источника: в ./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 - вывод состоит в том, что мы не можем корректно вызывать status4 из Python (так как это связано с созданием C-структуры в Python); и это не имеет никакого значения, потому что мы можем вызывать status3 из Python, который затем сам вызывает status4!

Тогда почему status4 адресуется из Python? Вероятно, потому что swig просто автогенерировал для него интерфейс... В любом случае, вот пример, когда поездка в отладчик обнаруживает источник проблемы - но на самом деле не ошибка :) Решение? Не используйте status4.

Сбой C в модуле Python, в gdb с исходной перестройкой

Возвращаясь к ошибке UTF-8, которая произошла с status2 и status3, было проще, учитывая, что теперь доступны исходные версии модулей. Проблема была очевидна в функции entry_name_to_utf8, и, исследуя ее аргумент name, можно было сначала понять, что имя файла, вызывающее проблему, действительно содержало не-ascii-но все еще законные символы UTF-8 (см. Программа для проверки/поиска символов UTF-8/Unicode в строке в командной строке? - Суперпользователь). Затем я использовал этот . Gdbinit, чтобы создать контрольную точку класса Python для gdb, которая будет печатать имена файлов и ломаться только по совпадению с проблематичный.

Тогда возникает вопрос: почему клиент командной строки 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, которая установлена ​​в define APR_LOCALE_CHARSET 1 - и которая не изменяется между svn status и Python. Чтобы дойти до этого, я скопировал все, что связано с строковым копированием и распределением APR по стеку вызовов, и восстановил простой пример, который создает модуль Swig, который должен просто скопировать строку с использованием времени выполнения APR; этот пример находится в каталоге aprtest, построенном с bash script build-aprtest.sh.

Благодаря этому примеру было обнаружено, что проблема сбоя UTF может быть исправлена ​​путем вызова setlocale в C перед любым распределением памяти строки APR - для получения дополнительной информации об этом тесте см. # 15977257 - Использование ввода utf-8 для модуля cmd Python. Соответственно, все, что нам нужно сделать из Python, выполняется:

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

... перед любыми вызовами svn.client (и, следовательно, до libsvn и, следовательно, до libapr). И здесь у нас есть еще один пример, для поездки в отладчик, без лишней ошибки :)

Ответ 3

Это интересный вопрос, и я с нетерпением жду других ответов, но пока:

Документ http://wiki.python.org/moin/DebuggingWithGdb в основном предназначен для отладки segfaults и зависания процессов Python, а не для нормального перехода через код Python.

Я не уверен, что понимаю ваше намерение на 100%. Вы хотите разбить код C (Python C API), как только будет достигнута определенная линия Python? Тогда не будет ли это просто вопросом:

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

Где myobj.foo() вызывает API C. Затем просто поместите точку останова на функцию, прикрепленную к myobj.foo, и у вас есть точка останова в нужном месте. Вам нужно больше функциональности, или вы просто ищете более естественный способ добиться того же?