Depuración: paso a paso a través de la secuencia de comandos de Python usando gdb?
debugging (3)
Digamos que tenemos el siguiente script mega simple de Python:
print "Initializing"....
a=10
print "Variable value is %d" % (a)
print "All done!"
... y digo, me gustaría depurar este script colocando un punto de interrupción en la línea a=10
, y luego pasar por el script.
Ahora, me gustaría usar gdb
para esto, porque me gustaría depurar los enlaces de Python que pueden venir como parte de una biblioteca de objetos compartidos ( .so
); por lo tanto, idealmente coloco un punto de interrupción en un código de Python línea, y luego "entrar" en la parte C del objeto compartido ... ( Tenga en cuenta que DebuggingWithGdb - PythonInfo Wiki no dice explícitamente que esto es posible )
El problema es: gdb
por sí solo no puede reconocer los puntos de interrupción, colocados en una línea de script de 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
...
... y mientras que todo el script de Python se ejecuta dentro de gdb
, el punto de interrupción simplemente nunca se alcanza.
Entonces, es lo que quiero hacer, en lo posible con gdb
; y si no, ¿qué otras alternativas tendría para algo similar?
Disculpas por el largo post; Regresé de nuevo a un problema similar con la depuración, un caso en el que realizas un viaje largo al depurador para finalmente revelar que no hay un error real, por lo que me gustaría publicar mis notas y algunos códigos aquí (estoy aún en Python 2.7, Ubuntu 11.04). Con respecto a la pregunta OP: en gdb
más nuevos, también es posible romper usando la función id(...)
en el script de Python, y tener gdb
break en builtin_id
; Pero aquí hay más detalles:
De nuevo, tuve un problema con un módulo de biblioteca compartida de .so para Python; esta vez fue svn.client
, que es un módulo Swig (ver también here ); en Debian / Ubuntu disponible a través de sudo apt-get install python-subversion
(lista de filelist ). El problema se produjo al intentar ejecutar el ejemplo 8.3. Un rastreador de estado de Python: uso de las API (svnbook) Este ejemplo debería hacer lo mismo que el comando de terminal svn status
; pero cuando lo probé en una de mis copias de trabajo, se bloqueó con " Error (22): Error al convertir la entrada en el directorio ''ruta'' a UTF-8 ", incluso si svn status
ha estado procesando el mismo directorio de copia de trabajo (WC) (por años) - así que quería ver de dónde venía eso. Mi versión del script de prueba es python-subversion-test.py ; y mi registro de depuración completo está en logsvnpy.gz (archivo de texto comprimido, ~ 188K sin comprimir, si alguien quiere hojear un sinfín de pasos y retrocesos) - esta es la versión abreviada. Tengo ambos Python 2.7 y 3.2 instalados, pero los 2.7 están predeterminados en 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
Lo primero a tener en cuenta es cómo funciona el ejemplo de Python: allí, para obtener el estado de todos los archivos dentro de un directorio, primero se llama svn.client.svn_client_status2
, aparte de la ruta, también con _status_callback
en los argumentos, como una función de devolución de llamada. en Python para ser registrado - y luego los bloques. Mientras status2
está bloqueando, el módulo subyacente se repite en todos los archivos en la ruta del directorio de WC; y para cada entrada de archivo, llama al _status_callback
registrado que debe imprimir información sobre la entrada. Una vez que esta recursión ha terminado, status2
sale. Por lo tanto, la falla de UTF-8 debe provenir del módulo subyacente. Inspeccionando este módulo más:
$ 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>),
# ...
... revela que hay otras funciones statusX
; sin embargo, status3
falló con el mismo error UTF-8; mientras que status4
causó un error de segmentación (que se convierte en otro problema más para depurar).
Y nuevamente, como en mi comentario a la respuesta de @EliBendersky , quise emitir un punto de interrupción en Python, para obtener una especie de pila de llamadas de funciones de C más adelante, lo que revelaría dónde ocurre el problema, sin que llegue en la reconstrucción de los módulos C desde la fuente; Pero no resultó ser tan fácil.
Python y gdb
En primer lugar, una cosa que puede ser muy confusa es la relación entre gdb
y Python; Los recursos típicos que vienen aquí son:
- http://wiki.python.org/moin/DebuggingWithGdb - menciona un
gdbinit
en "Macros de GDB", Ese release27-maint/Misc/gdbinit está en el árbol de fuentes de Python; define comandos
gdb
comopylocals
ypyframe
, pero también menciona:# NOTA: Si tiene gdb 7 o posterior, admite la depuración de Python directamente
# con macros incrustadas que puede encontrar superior a lo que hay aquí.
# Ver Herramientas / gdb / libpython.py y http://bugs.python.org/issue8032 .Features / EasierPythonDebugging - FedoraProject - tiene un ejemplo, menciona un paquete Fedora
python-debuginfo
ylibpython
- Tools/gdb/libpython.py también está en el árbol de fuentes de Python, y menciona:
Desde gdb 7 en adelante, la compilación de gdb se puede configurar --with-python, permitiendo gdb
para extenderse con el código Python, por ejemplo, para visualizaciones de datos específicas de la biblioteca
tal como para los tipos de C ++ STL. ....
Este módulo incorpora el conocimiento sobre los detalles de implementación de libpython para
que podemos emitir visualizaciones útiles, por ejemplo, una cadena, una lista, un dict, un marco
Dar información de archivo / línea y el estado de las variables locales. - cpython/Lib/test/test_gdb.py - aparentemente de cpython, parece probar la funcionalidad
gdb
de Python
Esto se vuelve un poco confuso, aparte del puntero, que es mejor obtener gdb
v.7; Me las arreglé para conseguir para mi sistema operativo:
$ apt-show-versions gdb
gdb 7.3-50.20110806-cvs newer than version in archive
Una forma rápida de probar si gdb
compatible con Python es esta:
$ 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
... pero gdb
admite Python, no significa que Python por sí solo pueda acceder a la funcionalidad de gdb
(al parecer, gdb
tiene su propio intérprete de Python integrado).
Resulta que, en Ubuntu 11.04, el paquete python2.7-dbg
instala un archivo 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
... y este es el que corresponde a las Tools/gdb/libpython.py
; el symlinking nos permitirá referirnos a él como libpython
, y usar el script de importación mencionado en Features / EasierPythonDebugging .
La test_gdb.py
comandos test_gdb.py
es en realidad para Python 3: lo he modificado para 2.7 y publicado en test_gdb2.7.py . Esta secuencia de comandos llama a gdb
través de una llamada del sistema operativo y prueba su funcionalidad Python, con impresiones en stdout; también acepta una opción de línea de comando, -imp-lp
, que import libpython
en gdb
antes de que se ejecuten otros comandos. Entonces, por ejemplo:
$ 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])
...
Por lo tanto, libpython.py
está destinado específicamente para el intérprete de Python dentro de gdb
, y ayuda a gdb
imprimir representaciones de Python ( v=[]
) en lugar de solo a direcciones de memoria ( v=0xb7f7506c
), lo cual solo es útil, si gdb
sucede para depurar una Script de Python (o más bien, depurará el ejecutable de Python, que interpreta el script).
La test_gdb.py
comandos test_gdb.py
también proporciona el puntero que puede " ... ejecutar" python -c''id (DATA) ''"bajo gdb con un punto de interrupción en builtin_id
"; Para probar esto, gdb_py_so_test.sh un script bash, gdb_py_so_test.sh , que crea un ejecutable con una función de subprocesos de conteo, y módulos de swit (tanto en versiones de depuración como de versión) que se interconectan con la misma función. También crea un .gdbinit
con los puntos de interrupción de la clase Python de gdb
y gdb
, y finalmente ejecuta gdb
en Python (cargando uno de los módulos compartidos), donde el usuario puede ver si los puntos de interrupción realmente se están disparando.
segfault en gdb sin reconstruir fuente
Primero me centré en el status4
segfault, y quería saber exactamente de qué módulo proviene la función. Utilicé una función, que se puede encontrar en debug_funcs.py ; que puede llamarse con expresiones regulares separadas para funciones y módulos, y puede generar algo como:
$ 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
Sin embargo, tenga en cuenta 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
cargará diferentes versiones (debug, _d
) de los módulos libsvn
de libsvn
(o python-subversion
); y eso es porque tengo el paquete python-subversion-dbg
instalado.
En cualquier caso, podemos pensar que conocemos las direcciones donde se cargan los módulos y las funciones respectivas en cada llamada de script de Python, lo que nos permitiría colocar un punto de interrupción gdb
en una dirección de programa ; dado que aquí trabajamos con "vanilla" .so''s (que no se han reconstruido desde la fuente). Sin embargo, Python por sí solo no puede ver que _client.so
de hecho, utiliza 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
Desde Python, podríamos hacer una llamada al sistema para consultar /proc/pid/maps
para la dirección donde está cargado libsvn_client-1.so
, y agregarle la dirección indicada por el último comando nm -D
para la compensación de svn_client_status4
; y obtenga la dirección donde podríamos dividir en gdb
(con la sintaxis b *0xAddress
), pero eso no es necesario, porque si nm
puede ver el símbolo, también puede gdb
, así que podemos separar directamente el nombre de la función. Otra cosa es que en caso de un fallo de seguridad, gdb
detiene solo, y podemos emitir un seguimiento (nota: use Ctrl-X A para salir del modo TUI de gdb después del 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) |
Por lo tanto, nuestro error ocurre en algún lugar de libsvn_client-1.so
, pero en el área de memoria antes del svn_client_status4
función svn_client_status4
; y como no tenemos símbolos de depuración, no podemos decir mucho más que eso. Usar python-dbg
puede dar resultados poco diferentes:
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.
... pero el comando list
aún nos da una línea de origen que pertenece al cuadro 5 (no al cuadro 4), y aún no sabemos más sobre svn_client_status4
: mientras que los módulos python-subversion
se cargan en sus versiones de depuración, la información de depuración es falta para libsvn_client-1.so
. Por lo tanto, es hora de reconstruir desde la fuente.
segfault en gdb con reconstruir fuente
Es la subversion
real que debemos reconstruir, o más bien es una parte de la biblioteca, ya que ya tenemos módulos de depuración de python-subversion
; el paquete en mi sistema se llama 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)
... y no hay ningún paquete de depuración para ello. Para reconstruir desde la fuente, pasé por apt-get source libsvn1
, con las dependencias encontradas manualmente a través de apt-rdepends --build-depends --follow=DEPENDS subversion
. Hay más detalles en el registro completo, pero aquí podemos observar que el paquete fuente puede crear tanto los enlaces SWIG Python (es decir, python-subversion
) como la biblioteca Subversion ( libsvn1
). Además, ejecuté make install
con una ubicación fuera del árbol del kernel principal; eso significa que uno tenía que especificar explícitamente los módulos construidos en origen a través de las variables de entorno 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
Una cosa complicada aquí es que la creación de módulos de depuración SWIG requiere una llamada con python-dbg
; aparentemente solo haciendo ./configure --enable-debug
no hace eso; y así, solo se _core.so
, etc., aunque con información de depuración. Si luego intentamos imponer su carga como con el comando anterior, pero con python-dbg
, obtendremos el undefined symbol: Py_InitModule4
, porque:
$ objdump -d $(which python) | grep ''^/w.*InitMod''
0813b770 <Py_InitModule4>:
$ objdump -d $(which python-dbg) | grep ''^/w.*InitMod''
08124740 <Py_InitModule4TraceRefs>:
... python-dbg
tiene una función diferente Py_InitModule4
. Eso, sin embargo, no fue un problema, porque simplemente se usó python
(como en la invocación anterior), y gdb
todavía permitió pasar por las funciones relevantes en el libsvn
recién creado (el script de Bash mencionado gdb_py_so_test.sh , como ejemplo construye un módulo Swig básico en las versiones de depuración y lanzamiento para confirmar el procedimiento correcto).
Con los símbolos de depuración para libsvn
, la función call stack se parece a esto (pegada un poco diferente):
#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 ?? ()
... y dado que las mismas funciones de biblioteca también son utilizadas por el svn client
línea de comandos, podemos comparar, por ejemplo, el marco 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
Entonces, en el caso de una llamada de Python a status4
, sb->real_status_func
es NULL, lo que causa un fallo de seguridad. La razón para esto se puede revelar una vez que comencemos a leer la fuente: en ./subversion/libsvn_client/deprecated.c
, la definición de status3
tiene:
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);
... es decir, cuando se llama a status3
con una función de devolución de llamada, crea una estructura y asigna la función a una de las propiedades de la estructura, y luego utiliza la estructura en la llamada adicional a status4
. Ya que status3
realmente funciona desde Python, la conclusión es que no podemos llamar correctamente status4
desde Python (ya que eso implicaría crear una estructura C en Python); y eso no importa de todos modos, porque podemos llamar a status3
desde Python, ¡lo que a su vez se llama status4
!
Entonces, ¿por qué es status4
direccionable desde Python? Probablemente porque swig
simplemente generó automáticamente una interfaz para él ... En cualquier caso, aquí hay un ejemplo en el que un viaje al depurador revela la fuente del problema, pero no es realmente un error :)
¿Solución? No utilice status4
.
Fallo de C en el módulo Python, en gdb con reconstrucción de origen
Volviendo a la falla de UTF-8, que ocurrió con status2
y status3
, fue más fácil, dado que ahora las versiones compuestas en origen de los módulos estaban disponibles. El problema era obvio en la función entry_name_to_utf8
, y al explorar su name
del argumento, uno podría darse cuenta primero de que el nombre del archivo que causaba el problema, efectivamente contenía caracteres no ASCII, pero aún así legales UTF-8 (vea el Programa para verificar / buscar ¿Caracteres UTF-8 / Unicode en la cadena en la línea de comandos? - Superusuario ). Luego utilicé este .gdbinit , para hacer un punto de interrupción de la clase Python para gdb, que imprimiera los nombres de archivo, y que solo se rompa al coincidir con el problema.
Entonces la pregunta es: ¿por qué el estado de la línea de comandos del cliente svn status
no se bloquea en el mismo nombre de archivo? Al pasar por svn status
y python python-subversion-test.py
, se pueden comparar las respectivas pilas de llamadas a funciones:
# 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
En este punto, uno se encuentra con el hecho de que Subversion usa libapr
(Apache Portable Runtime) para la asignación de memoria; y de hecho, esta parte es la causa del fallo: principalmente, la función apr_xlate_conv_buffer
comporta de manera diferente en los dos casos.
Pero, puede ser bastante difícil ver cuál es el problema real aquí, porque apr_xlate_conv_buffer
usa una codificación en node->frompage
, que se establece en la definición APR_LOCALE_CHARSET 1
, y eso no cambia entre los casos svn status
y Python. Para llegar a esto, he copiado y copiado todo lo relacionado con la copia y asignación de cadenas APR en la pila de llamadas, y he reconstruido un ejemplo simple que construye un módulo Swig, que solo debería copiar una cadena usando el tiempo de ejecución APR; ese ejemplo está en el directorio aprtest , construido con el script bash build-aprtest.sh .
Gracias a ese ejemplo, se reveló que el problema de falla de UTF se puede solucionar llamando a setlocale
en C antes de cualquier asignación de memoria de cadena APR. Para más información sobre esa prueba, consulte # 15977257 - Uso de la entrada utf-8 para el módulo Python de cmd . En consecuencia, todo lo que necesitamos hacer desde Python es ejecutar:
import locale
locale.setlocale(locale.LC_ALL, '''')
... antes de cualquier llamada a svn.client
(y por lo tanto a libsvn
, y por lo tanto a libapr
). Y aquí tenemos otro ejemplo, para un viaje al depurador, sin realmente tener un error :)
Esta es una pregunta interesante, y estoy esperando ansiosamente otras respuestas, pero por ahora:
El documento http://wiki.python.org/moin/DebuggingWithGdb se destina principalmente a la depuración de segfaults y procesos Python colgados, no a la introducción normal del código Python.
No estoy seguro de entender tu intención al 100%. ¿Desea ingresar su código C (Python C API) una vez que se alcanza una determinada línea de Python? Entonces, ¿no sería solo cuestión de hacer?
# some Python code
# some other Python code
myobj.foo()
# some other Python code
Donde myobj.foo()
llama a la API de C. Luego, solo coloque un punto de interrupción en la función adjunta a myobj.foo
y tendrá su punto de interrupción en la ubicación correcta. ¿Necesita más funcionalidad o simplemente está buscando una forma más natural de lograr lo mismo?
Pregunta muy interesante. Aquí está mi enfoque. Crear 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()
Entonces puedes ejecutarlo bajo 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.
Y cuando lo ejecutes, irá hasta que alcances la llamada a 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
Luego puedes mirar un backtrace:
(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 continúas, el resto del programa se ejecutará normalmente.
(gdb) continue
Continuing.
Variable value is 10
All done!
Program exited normally.
En su lugar, puede avanzar en el marco adecuado hasta que llegue a la declaración que le interesa. Probablemente desee ejecutar un Python de depuración para que esto tenga mucho sentido.