Отладка: пошаговое выполнение скрипта Python с использованием gdb? - PullRequest
23 голосов
/ 14 сентября 2011

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

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

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

Теперь я хотел бы использовать gdb для этого, потому что я хотел бы отлаживать привязки Python, которые могут входить в состав библиотеки общего объекта (.so) - следовательно, я бы идеально разместил точка останова в строке кода 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; и если нет, то какие еще альтернативы я бы выбрал для чего-то подобного?

Ответы [ 3 ]

26 голосов
/ 27 октября 2011

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

22 голосов
/ 16 апреля 2013

Извинения за длинную должность;Я снова вернулся к аналогичной проблеме с отладкой - в случае, когда вы отправляетесь в долгий путь к отладчику, чтобы окончательно выявить, что в действительности нет ошибки - поэтому я просто хотел бы опубликовать свои заметки и некоторый код здесь (явсе еще на Python 2.7, Ubuntu 11.04).Что касается вопроса OP - в более новых gdb, его также можно разбить, используя функцию id(...) в скрипте Python и имея gdb break на 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) (вот уже много лет), поэтому я хотел посмотреть, откуда это взялось.Моя версия тестового скрипта python-subversion-test.py ;и мой полный журнал отладки находится в logsvnpy.gz (сжатый текстовый файл, ~ 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 в аргументах, в качестве функции обратного вызова в 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 - упоминает gdbinit в "GDB Macros",
  • That release27-maint / Misc / gdbinit находится в исходном дереве Python;определяет команды gdb, такие как pylocals и pyframe, но также упоминает:

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

  • Возможности/ 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 с выводом на 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 (точнее, он отлаживает исполняемый файл Python, который интерпретирует скрипт).

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

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

В любом случае нам может показаться, что мы знаем адреса, по которым модули и соответствующие функции загружаются при каждом вызове скрипта Python - что позволило бы нам разместить точку останова 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 останавливается сам по себе, и мы можем выдать обратную трассировку (примечание: используйте 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 * 1235).* 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, вызывая ошибку сегмента.Причину этого можно выяснить, как только мы начнем читать источник: в ./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 (см. * 1274).* Программа для проверки / поиска символов 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, которая установлена ​​в определение APR_LOCALE_CHARSET 1 - и это не меняется между svn status и Python. Чтобы вернуться к этому, я скопировал все, что связано с копированием и распределением строки APR, в стек вызовов и восстановил простой пример, который создает модуль Swig, который должен просто копировать строку с использованием среды выполнения APR; этот пример находится в каталоге aprtest , собранном с помощью скрипта bash build-aprtest.sh .

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

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

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

2 голосов
/ 14 сентября 2011

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

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

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

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

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

...