web-dev-qa-db-fra.com

Débogage: pas à pas Python utilisant gdb?

Disons que nous avons le méga-simple suivant Python:

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

... et dis, je voudrais déboguer ce script en plaçant un point d'arrêt à la ligne a=10, puis en parcourant le script.

Maintenant, j'aimerais utiliser gdb pour cela, car je voudrais déboguer Python qui peuvent faire partie d'un objet partagé (.so) bibliothèque - par conséquent, je placerais idéalement un point d'arrêt sur une ligne de code Python, puis "entrerais" dans la partie C de l'objet partagé ... ( Notez que DebuggingWithGdb - PythonInfo Wiki ne dit pas vraiment explicitement que c'est possible )

Le problème est: gdb à lui seul ne peut pas vraiment reconnaître les points d'arrêt, placés sur une ligne de script 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
...

... et bien que l'ensemble du script Python s'exécute dans gdb, le point d'arrêt n'est tout simplement jamais atteint.

Donc - c'est ce que je veux faire, du tout possible avec gdb; et sinon, quelles autres alternatives aurais-je pour quelque chose de similaire?

27
sdaau

Question très intéressante. Voici mon approche. Créez 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()

Ensuite, vous pouvez l'exécuter sous 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.

Et lorsque vous l'exécutez, il ira jusqu'à ce que vous atteigniez l'appel à 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

Vous pouvez ensuite regarder une trace:

(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 ()

Si vous continuez, le reste du programme s'exécutera normalement.

(gdb) continue
Continuing.
Variable value is 10
All done!

Program exited normally.

Vous pouvez, à la place, parcourir le cadre approprié jusqu'à ce que vous atteigniez l'instruction qui vous intéresse. Vous voudrez probablement exécuter un débogage Python pour que cela ait beaucoup de sens.

26
Michael Hoffman

Toutes mes excuses pour le long message; Je suis revenu à nouveau sur un problème similaire avec le débogage - un cas où vous faites un long voyage vers le débogueur, pour enfin révéler qu'il n'y a pas de bug réel - alors je voudrais juste poster mes notes et du code ici (je suis toujours sur Python 2.7, Ubuntu 11.04). En ce qui concerne la question OP - dans les gdb plus récents, il est également possible de rompre en utilisant la fonction id(...) dans le script Python, et en ayant gdb break sur builtin_id; mais voici plus de détails:

Encore une fois, j'ai eu un problème avec un module de bibliothèque partagée C .so pour Python; cette fois c'était svn.client, qui est un module Swig (voir aussi ici ); dans Debian/Ubuntu disponible via Sudo apt-get install python-Subversion ( filelist ). Le problème est survenu lors de l'exécution du moteur de recherche d'état Exemple 8.3. A Python - Utilisation des API (svnbook) Cet exemple devrait faire la même chose que la commande de terminal svn status Est-ce que; mais quand je l'ai essayé sur une de mes copies de travail, il s'est écrasé avec " Erreur (22): Erreur lors de la conversion de l'entrée du répertoire 'chemin' en UTF-8", même si svn status traite le même répertoire de copie de travail (WC) (depuis des années maintenant) - je voulais donc voir d'où cela venait. Ma version du script de test est python-Subversion-test.py ; et mon journal de débogage complet se trouve dans logsvnpy.gz (fichier texte compressé, ~ 188 Ko non compressé, si quelqu'un veut parcourir des étapes et des backtraces sans fin) - ceci étant la version abrégée. J'ai les deux Python 2.7 et 3.2 installés, mais les 2.7 sont par défaut sur 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

La première chose à noter est le fonctionnement de l'exemple Python: là, pour obtenir le statut de tous les fichiers d'un répertoire, le premier svn.client.svn_client_status2 est appelé - en dehors du chemin, également avec _status_callback dans les arguments, comme une fonction de rappel dans Python à enregistrer - puis des blocs. Pendant que status2 se bloque, le module sous-jacent parcourt tous les fichiers du chemin du répertoire WC; et pour chaque entrée de fichier, il appelle le _status_callback enregistré qui doit imprimer des informations sur l'entrée. Une fois cette récursivité terminée, status2 se ferme. Ainsi, l'échec UTF-8 doit provenir du module sous-jacent. Inspecter davantage ce module:

$ 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>),
 # ...

... révèle qu'il existe d'autres fonctions statusX - cependant, status3 a échoué avec la même erreur UTF-8; tandis que status4 a provoqué une erreur de segmentation (qui devient encore un autre problème à déboguer).

Et encore une fois, comme dans mon commentaire à la réponse de @ EliBendersky , je voulais émettre un point d'arrêt en Python, afin d'obtenir une sorte de pile d'appels de fonctions C plus tard, qui révélerait où le problème se produit - sans que j'entre dans la reconstruction des modules C à partir de la source; mais cela ne s'est pas avéré si simple.

Python et gdb

Tout d'abord, une chose qui peut être très déroutante est la relation entre gdb et Python; les ressources typiques à venir ici sont:

  • http://wiki.python.org/moin/DebuggingWithGdb - mentionne un gdbinit dans "GDB Macros",
  • Cela release27-maint/Misc/gdbinit est dans l'arborescence source Python; définit les commandes gdb comme pylocals et pyframe, mais mentionne également:

    # REMARQUE: si vous avez gdb 7 ou version ultérieure, il prend en charge le débogage de Python directement
    # avec des macros intégrées que vous trouverez peut-être supérieures à ce qui se trouve ici.
    # Voir Tools/gdb/libpython.py et http://bugs.python.org/issue8032 .

  • Features/EasierPythonDebugging - FedoraProject - contient un exemple, mentionne un package Fedora python-debuginfo et libpython

  • Tools/gdb/libpython.py est également dans l'arborescence source de Python, et il mentionne:

    A partir de gdb 7, la construction de gdb peut être configurée - avec-python, permettant à gdb
    à étendre avec le code Python par exemple pour les visualisations de données spécifiques à la bibliothèque,
    comme pour les types C++ STL. ....
    Ce module intègre des connaissances sur les détails d'implémentation de libpython afin
    que nous pouvons émettre des visualisations utiles, par exemple une chaîne, une liste, un dict, un cadre
    donnant des informations sur le fichier/la ligne et l'état des variables locales

  • cpython/Lib/test/test_gdb.py - apparemment de cpython, semble tester la fonctionnalité gdb de Python

Cela devient un peu déroutant - à part le pointeur, il vaut mieux se procurer gdb v.7; J'ai réussi à obtenir pour mon OS:

$ apt-show-versions gdb
gdb 7.3-50.20110806-cvs newer than version in archive

Un moyen rapide de tester si gdb prend en charge Python est le suivant:

$ 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

... mais gdb prenant en charge Python, ne signifie pas que Python à lui seul peut accéder à la fonctionnalité gdb (apparemment, le gdb a sa propre fonction intégrée séparée Python interprète).

Il s'avère, dans Ubuntu 11.04, que le package python2.7-dbg installe un fichier 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

... et c'est celui correspondant au Tools/gdb/libpython.py mentionné; le lien symbolique nous permettra de l'appeler libpython, et d'utiliser le script d'importation mentionné dans Features/EasierPythonDebugging .

Le script test_gdb.py est en fait pour Python 3 - Je l'ai modifié pour 2.7 et publié dans test_gdb2.7.py . Ce script appelle gdb via un appel système OS et teste sa fonctionnalité Python, avec des impressions vers stdout; il accepte également une option de ligne de commande, -imp-lp, qui sera import libpython dans gdb avant l'exécution d'autres commandes. Ainsi, par exemple:

$ 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])
...

Ainsi, libpython.py est destiné spécifiquement à l'interpréteur Python dans gdb, et il aide gdb print Python représentations (v=[]) au lieu de simplement des adresses mémoire (v=0xb7f7506c) - ce qui n'est utile que, sigdb arrive à déboguer un Python script (ou plutôt, il déboguera l'exécutable Python, qui interprète le script).

Le script test_gdb.py donne également le pointeur que vous pouvez "... exécuter" python -c'id (DATA) '"sous gdb avec un point d'arrêt sur builtin_id "; pour tester cela, j'ai posté un script bash, gdb_py_so_test.sh , qui crée un exécutable avec une fonction de thread de comptage, ainsi que des modules distutils et swig simples (dans les versions de débogage et de publication) qui s'interfacent avec la même fonction. Il crée également un .gdbinit avec à la fois gdb et gdb's Python points d'arrêt de classe - et enfin il exécute gdb sur Python (chargement de l'un des modules partagés), où l'utilisateur peut, espérons-le, voir si les points d'arrêt se déclenchent réellement.

segfault dans gdb sans reconstruction de source

Je me suis d'abord concentré sur le status4 segfault, et je voulais savoir exactement de quel module venait la fonction. J'ai utilisé une fonction, qui peut être trouvée dans debug_funcs.py ; qui peut être appelé avec une expression régulière distincte pour les fonctions et les modules, et peut générer quelque chose comme:

$ 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

Cependant, notez que:

$ 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 chargera différentes versions (débogage, _d) des modules .so de libsvn (ou python-Subversion ); et c'est parce que j'ai installé le package python-Subversion-dbg.

Dans tous les cas, nous pouvons penser que nous connaissons les adresses où les modules et les fonctions respectives sont chargés sur chaque appel de script Python - ce qui nous permettrait de placer un point d'arrêt gdb sur un programme adresse ; étant donné qu'ici nous travaillons avec des "Vanilla" .so (qui n'ont pas été reconstruits à partir de la source). Cependant, Python seul ne peut pas voir que _client.so utilise en fait 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

À partir de Python, nous pourrions effectuer un appel système, pour interroger /proc/pid/maps sur l'adresse où libsvn_client-1.so est chargé, et y ajouter l'adresse indiquée par le dernier nm -D commande pour le décalage de svn_client_status4; et obtenir l'adresse où nous pourrions entrer gdb (avec la syntaxe b *0xAddress) - mais ce n'est pas nécessaire, car si nm peut voir le symbole, _ gdb - pour que nous puissions nous arrêter directement sur le nom de la fonction. Une autre chose est qu'en cas de panne, gdb s'arrête de lui-même, et nous pouvons émettre une trace (note: utilisez Ctrl-X A pour quitter le mode gdb TUI après 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)                                   |

Donc, notre erreur se produit quelque part dans libsvn_client-1.so, mais dans la zone de mémoire avant le début de la fonction svn_client_status4; et puisque nous n'avons pas de symboles de débogage - nous ne pouvons pas dire grand-chose d'autre que cela. L'utilisation de python-dbg peut donner des résultats légèrement différents:

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.

... mais la commande list nous donne toujours une ligne source appartenant à l'image 5 (pas l'image 4), et nous n'en savons toujours pas plus sur svn_client_status4: tandis que la python-Subversion les modules sont chargés dans leurs versions de débogage, les informations de débogage sont manquantes pour libsvn_client-1.so. Donc, il est temps de reconstruire à partir de la source.

segfault dans gdb avec la reconstruction de la source

C'est le Subversion réel que nous devons reconstruire, ou plutôt sa partie bibliothèque - puisque nous avons déjà des modules de débogage de python-Subversion; le package sur mon système s'appelle 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)

... et il n'y a pas de package de débogage pour cela. Pour reconstruire à partir des sources, je suis passé par apt-get source libsvn1, avec les dépendances trouvées manuellement via apt-rdepends --build-depends --follow=DEPENDS Subversion. Il y a plus de détails dans le journal complet - mais ici, nous pouvons noter que le paquet source peut construire à la fois les liaisons SWIG Python (c'est-à-dire python-Subversion) et la bibliothèque Subversion (libsvn1). De plus, j'ai exécuté make install avec un emplacement hors de l'arborescence principale du noyau; cela signifie qu'il fallait spécifier explicitement les modules construits par la source via les variables d'environnement 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

Une chose délicate ici est que la construction de modules de débogage SWIG nécessite un appel avec python-dbg; apparemment, faire simplement ./configure --enable-debug ne fait pas cela; et ainsi, seulement _core.so, etc. sont produits, mais avec des informations de débogage. Si nous essayons ensuite d'imposer son chargement comme avec la commande ci-dessus, mais avec python-dbg, nous obtiendrons undefined symbol: Py_InitModule4, car:

$ objdump -d $(which python) | grep '^\w.*InitMod'
0813b770 <Py_InitModule4>:
$ objdump -d $(which python-dbg) | grep '^\w.*InitMod'
08124740 <Py_InitModule4TraceRefs>:

... python-dbg a une fonction Py_InitModule4 différente. Cependant, cela ne posait pas de problème, car simplement python était utilisé (comme dans l'invocation ci-dessus), et gdb permettait toujours de parcourir les fonctions pertinentes dans la nouvelle libsvn ( le script Bash mentionné gdb_py_so_test.sh , à titre d'exemple, construit un module Swig de base dans les versions de débogage et de publication pour confirmer la bonne procédure).

Avec les symboles de débogage pour libsvn, la pile d'appels de fonction ressemble à ceci (collée un peu différemment):

#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 ?? ()

... et comme les mêmes fonctions de bibliothèque sont également utilisées par la ligne de commande svn client, nous pouvons comparer, disons, l'image 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

Ainsi, en cas d'appel Python à status4, sb->real_status_func est NULL, provoquant une erreur de segmentation. La raison de ceci peut être révélée une fois que nous commençons à lire la source: dans ./Subversion/libsvn_client/deprecated.c, la définition de status3 a:

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);

... c'est-à-dire que lorsque status3 est appelé avec une fonction de rappel, il crée une structure et attribue la fonction à l'une des propriétés de la structure - puis utilise la structure dans l'appel ultérieur à status4! Puisque status3 fonctionne réellement à partir de Python - la conclusion est que nous ne pouvons pas appeler correctement status4 à partir de Python (car cela impliquerait la création d'un C struct en Python); et cela n'a pas d'importance de toute façon, car nous pouvons appeler status3 depuis Python - qui appelle alors lui-même status4!

Alors pourquoi status4 est-il adressable depuis Python? Probablement parce que swig a simplement généré automatiquement une interface pour elle ... Dans tous les cas, voici un exemple, où un voyage vers le débogueur révèle la source du problème - mais pas vraiment un bogue :) Solution? N'utilisez pas status4.

Échec C dans le module Python, dans gdb avec la reconstruction de la source

Revenons à l'échec UTF-8, qui s'est produit avec status2 et status3 - c'était plus facile, étant donné que des versions des modules construites à la source étaient désormais disponibles. Le problème était évident dans la fonction entry_name_to_utf8, et en explorant son argument name, on pouvait d'abord se rendre compte que le nom du fichier à l'origine du problème contenait en effet non ascii - mais toujours légal UTF- 8 caractères (voir Programme pour vérifier/rechercher des caractères UTF-8/Unicode en chaîne sur la ligne de commande? - Super User ). J'ai ensuite utilisé ceci . Gdbinit , pour créer un point d'arrêt de classe Python pour gdb, qui afficherait les noms de fichiers et ne se briserait qu'en cas de correspondance avec celui problématique.

Alors la question est - comment se fait-il que le client de ligne de commande svn status ne plante pas sur le même nom de fichier? En parcourant à la fois svn status et python python-Subversion-test.py, on peut comparer les piles d'appels de fonction respectives:

# 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

À ce stade, on rencontre le fait que Subversion utilise libapr (Apache Portable Runtime) pour l'allocation de mémoire; et c'est en fait cette partie qui provoque l'échec - principalement, la fonction apr_xlate_conv_buffer se comporte différemment dans les deux cas.

Mais, il peut être assez difficile de voir quel est le problème réel ici, parce que apr_xlate_conv_buffer utilise un codage dans node->frompage, qui est défini sur la définition APR_LOCALE_CHARSET 1 - et cela ne change pas entre les cas svn status et Python. Pour en venir à cela, j'ai copié-collé tout ce qui concerne la copie et l'allocation de chaînes APR dans la pile d'appels, et reconstruit un exemple simple qui construit un module Swig, qui devrait simplement copier une chaîne à l'aide de l'exécution APR; cet exemple se trouve dans le répertoire aprtest , construit avec le script bash build-aprtest.sh .

Grâce à cet exemple, il a été révélé que le problème de défaillance UTF peut être résolu en appelant setlocale en C avant toute allocation de mémoire de chaîne APR - pour en savoir plus sur ce test, voir # 15977257 - Utilisation d'utf-8 entrée pour cmd Python module . En conséquence, tout ce que nous devons faire de Python est d'exécuter:

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

... avant tout appel à svn.client (et donc à libsvn, et donc à libapr). Et ici, nous avons encore un autre exemple, pour un voyage vers le débogueur, sans vraiment avoir de bug :)

22
sdaau

C'est une question intéressante, et j'attends avec impatience d'autres réponses, mais pour l'instant:

Le document http://wiki.python.org/moin/DebuggingWithGdb est principalement destiné au débogage des erreurs de segmentation et au blocage Python, pas pour les étapes normales Python.

Je ne suis pas sûr de comprendre votre intention à 100%. Voulez-vous casser votre code C (Python C API) une fois qu'une certaine ligne Python est atteinte? Ne serait-ce pas simplement une question de faire:

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

myobj.foo() appelle dans l'API C. Ensuite, placez simplement un point d'arrêt sur la fonction attachée à myobj.foo Et vous avez votre point d'arrêt au bon endroit. Avez-vous besoin de plus de fonctionnalités ou cherchez-vous simplement un moyen plus naturel de réaliser les mêmes?

2
Eli Bendersky