Cómo obtener trazas íntegras en Gentoo
1.
Trazas en Gentoo
¿Qué son las trazas?
Una traza, también llamada bt, trace, o stack trace (traza de pila)
es un informe legible por un humano de la pila de llamadas realizadas por un
programa. Informa en qué punto de un programa se encuentra y cómo se llegó
a ese punto a través de todas las funciones hasta la función
main() (por lo menos esta es la teoría). Las trazas son
normalmente analizadas cuando acontecen condiciones de error como fallos de
segmento o abortos en la ejecución. Para su análisis se usan depuradores
como gdb (GNU debugger), que ayudan a encontrar la causa del error.
Una traza íntegra, contiene no sólo los objetos compartidos (shared objects)
en los que se generó la llamada, también el nombre de la función, el nombre
del fichero y el la línea en la que se detuvo la ejecución. Desgraciadamente
en un sistema optimizado para el rendimiento y el ahorro de espacio, las
trazas no tienen ninguna utilidad y muestran únicamente los punteros a la
pila y una serie de caracteres ?? en lugar del nombre de las funciones y su
posición.
Esta guía le mostrará cómo es posible obtener trazas útiles e íntegras en
Gentoo usando características de Portage.
Ajustes del compilador
Por defecto gcc no incluye información de depuración dentro de los
objetos (librerías y programas) que construye, ya que ésto crearía objetos
más grandes. También muchas optimizaciones interfieren en cómo la
información de depuración es guardada. Por estas razones, la primera
cuestión a tener en cuenta es que los ajustes CFLAGS estén preparados para
generar información de depuración útil.
El ajuste básico a añadir en este caso es -g. Ésto hace que el
compilador incluya información extra en los objetos, como los nombres de
fichero y los números de línea. Esto normalmente es suficiente para tener
trazas básicas. Sin embargo el ajuste -ggdb añade más información. De
hecho existe otro ajuste (-g3) pero su uso no está
recomendado. Parece que rompe las interfaces binarias y puede llevar a algún
error de ejecución inesperado. Por ejemplo glibc falla cuando
se construye usando este ajuste. Si quiere ofrecer la mayor información
posible, debe usar el ajuste -ggdb.
Code Listing 1.1: Ejemplo de CFLAGS con información de depuración |
CFLAGS="-march=k8 -O2 -ggdb"
CXXFLAGS="${CFLAGS}"
|
Altos niveles de optimización como -O3 pueden causar que la traza sea
menos fiable o incluso incorrecta. Generalmente hablando, -O2 y
-Os pueden usarse de forma segura para obtener una traza aproximada,
bajando hasta la función llamada y el área del fichero fuente dónde se
produjo el error de ejecución. Para trazas más precisas se puede usar
-O1.
Note:
El uso de -O0 es sugerido a menudo cuando se intenta producir una
traza completa. Desgraciadamente esto no funciona siempre bien con el
software, ya que la desactivación de las optimizaciones cambia la
implementación de las funciones en la librería GNU C (sys-libs/glibc), hasta
el punto de que se pueden considerar dos librerías diferentes, una para
construcciones optimizadas y otra para no optimizadas. También, algún
software puede fallar a la hora de ser construido cuando se usa -O0,
debido a los cambios en los ficheros de inclusión de cabeceras y la falta de
características como la propagación constante en la eliminación de código
muerto.
|
Aviso para los usuarios de arquitecturas x86: Los usuarios de x86
normalmente tienen -fomit-frame-pointer en su CFLAGS. La arquitectura
x86 tiene un juego limitado de registros generales, y este ajuste puede
hacer que esté disponible un nuevo registro que mejora el rendimiento. Sin
embargo, esto tiene un coste: hace imposible a gdb "caminar por la
pila" — en otras palabras: generar la traza de forma
confiable. Elimine este ajuste de CFLAGS para construir algo más fácil de
comprender para gdb. En otras plataformas no hay que preocuparse por
esto; generalmente el ajuste -fomit-frame-pointer no está activado, o
el código generado por gcc no confunde a gdb (en cuyo caso el
ajuste está activado por el nivel de optimización -O2).
Los usuarios de hardened tienen otras cosas de las que preocuparse. Las
Preguntas de Uso Frecuente de Gentoo Hardened ofrecen consejos y
trucos extra que necesitará saber.
Recortando (haciendo stripping)
Simplemente cambiando sus CFLAGS y haciendo emerge world de nuevo no le dará
trazas íntegras, ya que tiene que solventar el problema del recorte. Por
defecto Portage recorta los binarios que genera. En otras palabras, elimina
las secciones innecesarias para el funcionamiento reduciendo el tamaño de
los ficheros instalados. Esto es bueno para el usuario medio, el cual no
necesita generar trazas, sin embargo, elimina toda la información de
depuración generada por los ajustes -g* al igual que las tablas de
símbolos que son necesarias para encontrar la información base para mostrar
las trazas de una forma legible por los humanos.
Hay dos formas de hacer que el recorte no afecte a la depuración y a la
generación de trazas. En primer lugar, se debe indicar a Portage que no
recorte binarios de ningún modo, añadiendo nostrip a FEATURES. Esto
dejará instalados los ficheros exactamante donde gcc los creó, con
toda la información de depuración y las tablas de símbolos, lo cual
incrementa el espacio en disco ocupado por los ejecutables y las
librerías. Para evitar este problema, en la versión 2.0.54-r1 uy en las
series 2.1 de Portage, es posible usar la FEATURE splitdebug en su
lugar.
Con splitdebug activado, Portage recortará los binarios instalados en
el sistema. Pero antes de hacer esto, toda la información de depuración que
sea de utilidad es copiada al fichero ".debug", que es luego instalado
dentro de /usr/lib/debug (el nombre completo del fichero se
obtendría añadiendo a éste el camino donde el fichero es instalado
realmente). El camino a este fichero es entonces guardado en el fichero
original dentro de una sección ELF llamada ".gnu_debuglink", de modo que
gdb sepa desde que fichero tiene que cargar los símbolos.
Important:
Si se activan ambas características nostrip y splitdebug,
Portage no recortará los binarios de ninguna forma, de modo que se debe que
prestar especial atención a lo que se quiere hacer.
|
Otra ventaja de splitdebug es que no requiere la reconstrucción de
los paquetes para deshacerse de la información de depuración. Esto es útil
cuando se construyen algunos paquetes con depuración para obtener una traza
de un sólo error. Una vez este error está subsanado, simplemente se necesita
eliminar el directorio /usr/lib/debug.
Para asegurarse de que no se recortan los binarios, deberá también
asegurarse de que tiene activado el ajuste -s en su LDFLAGS. Esto le
indica al enlazador que debe recortar los binarios resultantes en la fase de
enlazado. También note que usando este ajuste puede desembocar en problemas
futuros. No se respetarán las restricciones impuestas por algunos paquetes
que dejan de funcionar cuando son recortados completamente.
Note:
Algunos paquetes desafortunadamente manejan el recorte por sí mismos, dentro
de los ficheros makefile suministrados por el equipo de desarrollo. Esto es
un error y debe ser notificado. Todos los paquetes deben dejar a Portage la
tarea de hacer el recorte, o no permitir el recorte de ningún modo. La
principal excepción a esto son los paquetes binarios. Son normalmente
recortados por el equipo de desarrollo del paquete fuera del control de
Portage.
|
Ajuste USE debug
Algunos ebuild proporcionan un ajuste USE debug. Aunque algunos lo
usan de forma incorrecta para ofrecer información de depuración y juegan con
los ajustes del compilador cuando el ajuste está activado, éste no es su
propósito.
Si está tratando de depurar un error de ejecución que se puede reproducir,
se deberá dejar sólo este ajuste USE, ya que estará construyendo una fuente
diferente de la que tenía previamente. Es más eficiente obtener en primer
lugar una traza sin cambiar el código, simplemente omitiendo la información
de símbolos y justo después de activar las características de depuración
para hacer un seguimiento del problema más adelante.
La características de depuración son activadas mediante las aserciones en
los ficheros de cabecera, registros de depuración en la pantalla, ficheros
de depuración, detección de goteras (leaks) y operaciones con seguridad
extra (como por ejemplo la limpieza de memoria antes de su uso). Algunos
pueden ser complicados, especialmente en software complejo o en el cual el
rendimiento es una cuestión importante.
Por estas razones, por favor, tenga cuidado cuando active el ajuste USE
debug, y considérelo como el último recurso.
Introducción a gdb
Una vez construidos los paquetes con información de depuración y no son
recortados, simplemente necesita obtener la traza. Para ello, necesitará el
paquete sys-devel/gdb. Contiene el depurador GNU
(gdb). Después de instalarlo, podrá obtener la traza. La forma más
simple de obtnerla es ejecutar el programa dentro de gdb. Para ello,
necesitará apuntar gdb al camino del programa a ejecutar, darle los
argumentos que necesite y ejecutarlo:
Code Listing 1.2: Corriendo ls a través de gdb |
$ gdb /bin/ls
GNU gdb 6.4
[...]
(gdb) set args /usr/share/fonts
(gdb) run
Starting program: /bin/ls /usr/share/fonts
[Thread debugging using libthread_db enabled]
[New Thread 47467411020832 (LWP 11100)]
100dpi aquafont baekmuk-fonts cyrillic dejavu fonts.cache-1 kochi-substitute misc xdtv
75dpi arphicfonts CID default encodings fonts.dir mikachan-font util
Program exited normally.
(gdb)
|
El mensaje "Program exited normally" (el programa terminó correctamente),
indica que el programa acabó devolviendo el código de salida 0. Esto
significa que no se produjo ningún error. No debe fiarse mucho de esto ya
que hay programas que devuelven 0 cuando alcanzan una condición de
error. Otro mensaje común es "Program exited with code nn" (el
programa terminó con código nn). Esto simplemente le indica que
código de estado distinto de cero ha devuelto el programa. Esto puede
implicar una condición de error manejada o esperada. Para fallos de segmento
y abortos en la ejecución puede que obtenga el mensaje "Program received
signal SIGsomething" (el programa recibió la señal SIGalgo).
Cuando un programa recibe una señal, puede ser por varias razones. En el
caso de SIGSEGV y SIGABRT (fallo de segmento y aborto en la ejecución
respectivamente), normalmente significa que el código está haciendo algo
incorrecto como una llamada al sistema inválida o acceder a memoria a través
de un puntero roto. Otras señales comunes son: SIGTERM, SIGQUIT y SIGINT (la
última se recibe cuando se envía CTRL-C al programa y normalmente es
capturada por gdb e ignorada por el programa).
Finalmente hay una serie de "Eventos de tiempo real". Se nombran como
SIGnn siendo nn un número mayor que 31. La implementación
pthread normalmente usa estos eventos para sincronizar los diferentes hilos
del programa, y así no se presentan condiciones de error de ningún tipo. Es
fácil ofrecer trazas sin sentido cuando se confunden las señales de tiempo
real con las condiciones de error. Para evitar esto, puede indicar a
gdb que no pare cuando éstas se reciben, y en lugar de ésto, pasarlas
directamente al programa como en el ejemplo siguiente.
Code Listing 1.3: Corriendo xine-ui a través de gdb, ignorando las señales de tiempo real. |
$ gdb /usr/bin/xine
GNU gdb 6.4
[...]
(gdb) run
Starting program: /usr/bin/xine
[...]
Program received signal SIG33, Real-time event 33.
[Switching to Thread 1182845264 (LWP 11543)]
0x00002b661d87d536 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib/libpthread.so.0
(gdb) handle SIG33 nostop noprint noignore pass
Signal Stop Print Pass to program Description
SIG33 No No Yes Real-time event 33
(gdb) kill
Kill the program being debugged? (y or n) y
(gdb) run
|
El comando handle indica a gdb qué debe hacer cuando la señal
dada es enviada al programa; en este caso los ajustes son nostop (no
parar el programa, devolviendo el comando al depurador), noprint (no
molestarse en imprimir la recepción de la señal recibida), noignore
(no ignorar la señal — ignorar señales es peligroso, ya que implica
descartarlas sin pasarlas al programa), pass (pasar las señal al
programa en depuración).
Después de que los eventuales eventos en tiempo real son ignorados por
gdb, deberá intentar reproducir el error que desea reportar. si lo
puede reproducir de forma sistemática, entonces es muy fácil. Cuando
gdb indica que el programa ha recibido las señales SIGSEGV o SIGABRT
(o cualquier otra señal que pueda representar una condición de error en el
programa), tendrá que generar la traza, posiblemente guardándola en algún
lugar. El comando básico para hacerlo es bt, que es la abreviatura de
backtrace, este comando muestra la traza del hilo de ejecución actual
(si el programa tiene un solo hilo, entonces hay un solo hilo de ejecución).
Un comando alternativo para obtener una traza más detallada es bt
full. Este comando ofrece información sobre parámetros y variables
locales de la función en la que las llamadas son realizadas (siempre que
estén disponibles y no hayan sido eliminadas por el uso de
optimizaciones). Esto hace que la traza sea más larga, pero más útil a la
hora de encontrar, por ejemplo, porqué un puntero no se ha inicializado.
Finalmente, no es extraño que incluso los programas más simples sean
escritos con múltiples hilos de ejecución. En estos casos usar la salida de
un simple bt, aunque tiene sentido, no tiene ninguna utilidad, ya que
puede representar el estado de un hilo de ejecución diferente al hilo en el
que se ha generado la señal o en el que la condición de error se ha
manifestado (en caso que haya otro hilo responsable de generar señales). Por
esta razón, debe en su lugar obtener la traza con el comando thread apply
all bt full (más largo), que indica a la depuración que imprima la traza
completa de todos los hilos que se estén ejecutando en ese momento.
Si la traza es corta, es fácil copiar y pegarla fuera del terminal (a menos
que el fallo suceda en un terminal sin X). En la mayoría de los casos es tan
larga que no puede ser copiada fácilmente ya que se extiende a través de
varias páginas. Para poder obtener trazas en un fichero con la intención de
adjuntarlo a un informe de error, se debe usar la característica
logging:
Code Listing 1.4: Usando la característica logging para guardar la traza a un fichero. |
$ gdb /usr/bin/xine
GNU gdb 6.5
[...]
(gdb) run
[...]
(gdb) set logging file backtrace.log
(gdb) set logging on
Copying output to backtrace.log.
(gdb) bt
#0 0x0000003000eb7472 in __select_nocancel () from /lib/libc.so.6
...
(gdb) set logging off
Done logging to backtrace.log.
(gdb) quit
|
Ahora puede obtener la traza en el fichero backtrace.log y
simplemente enviarla por correo electrónico o adjuntar el fichero al informe
de error relacionado.
Volcados Core
En algunas ocasiones, los errores de ejecución son difíciles de reproducir,
el programa tienes muchos hilos y es muy lento ejecutarlo en gdb o
está embrollado cuando se realiza la ejecución con el depurador (no debería
sorprender a nadie que al realizar las ejecuciones dentro del depurador se
muestran más errores que al tratar de reproducirlos sin el depurador). En
estos casos hay una herramienta que se muestra muy útil: el volcado core.
Un volcado core es un fichero que contiene todo el área de memoria de un
programa cuando éste ha fallado. Usando este fichero es posible extraer la
traza de la pila, incluso si el programa ha fallado fuera de gdb,
asumiendo que los volcados core están activados. Por defecto, los volcados
core no están activados en Gentoo Linux (sin embargo, son activados por
defecto en
Gentoo/FreeBSD), por lo que tendrá que activarlos.
Los ficheros de volcado core son generados directamente por el núcleo; por
esta razón, el núcleo necesita tener esta característica activada en el
momento de su construcción para que funcione correctamente. Normalmente las
configuraciones por defecto del núcleo activan los ficheros de volcado
core. Si está corriendo un núcleo incrustado, o ha configurado la sección
standard kernel features (características estándar del núcleo), debería
verificar las siguientes opciones:
Note:
Puede saltarse este paso si no ha activado la opción “Configure standard
kernel features en ningún momento, lo cual no debería hacerlo si no sabe si
lo hizo o no.
|
Code Listing 1.5: Opciones del núcleo para activar los volcados core |
General Setup --->
Configure standard kernel features --->
Enable ELF core dumps
|
Los volcados core pueden ser activados al nivel de sistema o al nivel de
sesión del intérprete de comandos. En el primer caso, todo en el sistema que
produzca un error de ejecución y no tenga un manejador de ese error (mire
más abajo algunas notas sobre el manejador de errores de KDE) será
volcado. Cuando se activa al nivel de sesión, únicamente los programas que
sean ejecutados en esa sesión generarán un volcado.
Para activar los volcados core al nivel de sistema, tendrá que editar, bien
/etc/security/limits.conf (si está usando PAM, situación por
defecto), bien /etc/limits.conf. En el primer caso, debe
definir un límite (hardware o más comúnmente software; para ficheros core,
el cual puede ser cualquiera entre 0 y ningún límite). En el último caso,
simplemente necesita definir la variable C al tamaño límite de un fichero
core (aquí no existe "ilimitado").
Code Listing 1.6: Ejemplo de regla para obtener ficheros core ilimitados cuando se usa PAM |
# /etc/security/limits.conf
* soft core unlimited
|
Code Listing 1.7: Ejemplo de regla para obtener ficheros core de hasta 20 MB cuando no se usa PAM |
# /etc/limits.conf
* C20480
|
Para activar fichero core en una sesión simple del intérprete de comandos
puede usar el comando ulimit con la opción -c. 0 indica
desactivado; cualquier otro número positivo es el tamaño en KB del fichero
core generado, mientras que unlimited simplemente elimina el límite
en las dimensiones de los ficheros core. A partir de ese momento, todos los
programas que terminen como resultado de una señal como SIGABRT or SIGSEGV
dejarán un fichero core que puede llamarse "core" o "core.pid" (donde
pid es reemplazado por el identificador de proceso del programa que murió).
Code Listing 1.8: Ejemplo de uso de ulimit |
$ ulimit -c unlimited
$ crashing-program
[...]
Abort (Core Dumped)
$
|
Note:
El comando ulimit es un comando interno en bash y zsh. En otros
intérpretes de comandos puede tener otro nombre o incluso no estar
disponible.
|
Después de obtener un volcado core, puede ejecutar gdb sobre él,
especificando tanto el fichero que generó el volcado core (tiene que ser
exactamente el mismo binario, por lo que si recompila, el volcado core es
inútil) y el camino al fichero core. Una vez que haya abierto gdb
sobre él, puede seguir las mismas instrucciones detalladas arriba tal y como
si se hubiera recibido la señal que mata el proceso en ese preciso instante.
Code Listing 1.9: Arrancado gdb con un fichero core |
$ gdb $(el programa que murió) --core core
|
Como alternativa, puede usar la capacidades de línea de comandos de
gdb para obtener la traza sin entrar en modo interactivo. Esto
también hace más fácil guardar la traza en un fichero o enviarla a una
tubería (pipe) de cualquier tipo. El truco están en las opciones
--batch y -ex que son aceptadas por gdb. Puede usar la
siguiente función bash para obtener la traza completa de un volcado core
(incluyendo todos los hilos de ejecución) en la salida estándar.
Code Listing 1.10: Función para obtener la traza completa de un volcado core |
gdb_get_backtrace() {
local exe=$1
local core=$2
gdb ${exe} \
--core ${core} \
--batch \
--quiet \
-ex "thread apply all bt full" \
-ex "quit"
}
|
Notas acerca del manejador de errores de ejecución de KDE
La aplicaciones basadas en KDE se ejecutan por defecto con su propio
manejador de errores de ejecución, el cual es presentado al usuario como
"Dr. Konqi" si está instalado (el paquete es kde-base/kdebase o
kde-base/drkonqi (incluido en kdebase-meta). Este
manejador de errores de ejecución muestra al usuario un diálogo informativo
de que el programa ha fallado. En este diálogo hay una pestaña "Backtrace"
(traza) que cuando es cargada llama a gdb y hace que éste cargue los
datos y genere la traza completa en representación del usuario, mostrándola
en la caja de texto principal y permitiendo que ésta sea salvada
directamente a un fichero. Esa traza, normalmente, es suficientemente buena
para informar de un problema.
Cuando drkonqi no está instalado, los errores de ejecución no generarán un
volcado core, y el usuario no recibirá ninguna información por defecto. Para
evitar esto, es posible usar el argumento --nocrashhandler en todas
las aplicaciones basadas en KDE. Esto desactivará el gestor de errores de
ejecución completamente y dejará que las señales sean gestionadas por el
sistema operativo como es normal. Esto es útil para generar ficheros core
cuando drkonqi no está disponible o cuando se desea inspeccionar las trazas
de la pila a mano.
The contents of this document, unless otherwise expressly stated, are licensed under the CC-BY-SA-2.5 license. The Gentoo Name and Logo Usage Guidelines apply.
|