Gentoo Logo

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.



Print

Page updated June 16, 2010

Summary: Esta guía pretende ofrecer a los usuarios una explicación simple de porqué una instalación por defecto de Gentoo no ofrece unas trazas íntegras y cómo realizar ajustes para obtenerlas.

Diego E. Pettenò
Autor

Ned Ludd
Información sobre la cadena de herramientas Hardened

Kevin Quinn
Información sobre la cadena de herramientas Hardened y la arquitectura x86

Donnie Berkholz
Revisor

Donate to support our development efforts.

Copyright 2001-2014 Gentoo Foundation, Inc. Questions, Comments? Contact us.