Tutorial de depuración de GDB para principiantes

Tutorial de depuración de GDB para principiantes

Es posible que ya esté versado en la depuración de los guiones de bash (vea cómo depurar los guiones de bash si aún no está familiarizado con la bash de depuración), pero cómo depurar C o C++? Vamos a explorar.

GDB es una utilidad de depuración de Linux de larga data e integral, que tomaría muchos años aprender si quería conocer bien la herramienta. Sin embargo, incluso para principiantes, la herramienta puede ser muy poderosa y útil cuando se trata de depurar C o C++.

Por ejemplo, si es ingeniero de control de calidad y desea depurar un programa C y binary en el que su equipo está trabajando y se bloquea, puede usar GDB para obtener una pista backtrace (una lista de funciones llamadas, como un árbol, que Finalmente llevó al accidente). O, si es un desarrollador C o C ++ y acaba de presentar un error en su código, puede usar GDB para depurar variables, código y más! Vamos a sumergirnos!

En este tutorial aprenderás:

  • Cómo instalar y usar la utilidad GDB desde la línea de comandos en Bash
  • Cómo hacer depuración básica de GDB usando la consola GDB y el aviso
  • Obtenga más información sobre la producción detallada que produce GDB
Tutorial de depuración de GDB para principiantes

Requisitos y convenciones de software utilizados

Requisitos de software y convenciones de línea de comandos de Linux
Categoría Requisitos, convenciones o versión de software utilizada
Sistema Independiente de la distribución de Linux
Software Líneas de comandos BASH y GDB, sistema basado en Linux
Otro La utilidad GDB se puede instalar utilizando los comandos proporcionados a continuación
Convenciones # - requiere que los comandos de Linux se ejecuten con privilegios raíz directamente como un usuario raíz o mediante el uso de sudo dominio
$-Requiere que los comandos de Linux se ejecuten como un usuario regular no privilegiado

Configuración de GDB y un programa de prueba

Para este artículo, veremos un pequeño prueba.C Programa en el lenguaje de desarrollo C, que introduce un error de división por cero en el código. El código es un poco más largo que lo que se necesita en la vida real (algunas líneas funcionarían, y no se requeriría ningún uso de funciones), pero esto se hizo a propósito para resaltar cómo los nombres de las funciones se pueden ver claramente dentro de GDB al depurar.

Primero instalemos las herramientas que necesitaremos usar instalación de sudo apt (o instalación de sudo yum Si usa una distribución basada en el sombrero rojo):

Sudo Apt Instalar GCC esencial de construcción GDB 

El esencial de construcción y GCC te ayudarán a compilar el prueba.C Programa C en su sistema.

A continuación, definamos el prueba.C script de la siguiente manera (puede copiar y pegar lo siguiente en su editor favorito y guardar el archivo como prueba.C)

int real_calc (int a, int b) int c; c = a/b; regresar 0;  int calc () int a; int b; a = 13; b = 0; real_calc (a, b); regresar 0;  int main () calc (); regresar 0;  


Algunas notas sobre este script: puedes ver eso cuando el principal se iniciará la función (el principal La función es siempre la función principal y la primera llamada Cuando inicia el binario compilado, esto es parte del estándar C), inmediatamente llama a la función calcón, que a su vez llama Atual_calc Después de configurar algunas variables a y b a 13 y 0 respectivamente.

Ejecución de nuestro script y configuración de volcados básicos

Vamos a compilar ahora este script usando GCC y ejecutar lo mismo:

Prueba de $ GCC -GGDB.prueba C -O.Fuera $ ./prueba.Excepción del punto flotante (núcleo arrojado) 

El -ggdb opción para GCC Se asegurará de que nuestra sesión de depuración con GDB sea amigable; Agrega información de depuración específica de GDB al prueba.afuera binario. Nombramos este archivo binario de salida usando el -O opción para GCC, y como entrada tenemos nuestro script prueba.C.

Cuando ejecutamos el script, inmediatamente recibimos un mensaje críptico Excepción del punto flotante (núcleo arrojado). La parte que estamos interesadas por el momento es el arrojado mensaje. Si no ve este mensaje (o si ve el mensaje pero no puede localizar el archivo principal), puede configurar un mejor vertido de núcleo de la siguiente manera:

si ! grep -qi 'kernel.core_pattern ' /etc /sysctl.conf; Entonces sudo sh -c 'echo "kernel.core_pattern = core.%pag.%u.%s.%mi.%t ">> /etc /sysctl.conf 'sudo sysctl -p fi ulimit -c ilimitado 

Aquí primero nos aseguramos de que no haya un patrón de núcleo del núcleo de Linux (núcleo.Core_Pattern) configuración realizada todavía en /etc/sysctl.confusión (El archivo de configuración para configurar las variables del sistema en Ubuntu y otros sistemas operativos) y, siempre que no se encontrara ningún patrón de núcleo existente, agregue un práctico patrón de nombre de archivo central (centro.%pag.%u.%s.%mi.%T) al mismo archivo.

El sysctl -p comando (para ser ejecutado como root, de ahí el sudo) Siguiente asegura que el archivo se vuelva a cargar inmediatamente sin requerir un reinicio. Para obtener más información sobre el patrón central, puede ver el Naming de archivos de volcado de núcleo sección a la que se puede acceder utilizando el núcleo de hombre dominio.

Finalmente, el ulimit -c ilimitado El comando simplemente establece el tamaño máximo del archivo del archivo para ilimitado Para esta sesión. Esta configuración es no persistente a través de reinicios. Para hacerlo permanente, puedes hacer:

sudo bash -c "gato < /etc/security/limits.conf * soft core unlimited * hard core unlimited EOF 

Que agregará * Core suave ilimitado y * Core duro ilimitado a /etc/seguridad/límites.confusión, Asegurar que no haya límites para los vertederos de núcleo.

Cuando ahora vuelva a ejecutar el prueba.afuera archivo debería ver el arrojado mensaje y debería poder ver un archivo central (con el patrón de núcleo especificado), como sigue:

$ LS Core.1341870.1000.8.prueba.afuera.15988677712 Prueba.prueba C.afuera 

A continuación, examinemos los metadatos del archivo central:

$ Archivo Core.1341870.1000.8.prueba.afuera.15988677712 núcleo.1341870.1000.8.prueba.afuera.1598867712: File LSB Core de 64 bits de 64 bits, x86-64, versión 1 (SYSV), estilo SVR4, desde './prueba.Out ', Real UID: 1000, UID efectivo: 1000, GID real: 1000, GID efectivo: 1000, execfn:'./prueba.fuera ', plataforma:' x86_64 ' 

Podemos ver que este es un archivo central de 64 bits, que ID de usuario estaba en uso, qué era la plataforma y finalmente qué ejecutable se usó. También podemos ver desde el nombre de archivo (.8.) que era una señal 8 que terminó el programa. La señal 8 es Sigfpe, una excepción de punto flotante. GDB luego nos mostrará que esta es una excepción aritmética.

Uso de GDB para analizar el volcado central

Abra el archivo central con GDB y asumamos por un segundo que no sabemos qué sucedió (si es un desarrollador experimentado, es posible que ya haya visto el error real en la fuente!)

$ GDB ./prueba.afuera ./centro.1341870.1000.8.prueba.afuera.1598867712 GNU GDB (Ubuntu 9.1-0ubuntu1) 9.1 Copyright (c) 2020 Free Software Foundation, Inc. Licencia GPLV3+: GNU GPL versión 3 o posterior Este es un software gratuito: puede cambiarlo y redistribuirlo. No hay garantía, en la medida permitida por la ley. Escriba "Mostrar copia" y "Mostrar garantía" para más detalles. Este GDB se configuró como "x86_64-linux-gnu". Escriba "Mostrar configuración" para los detalles de configuración. Para las instrucciones de informes de errores, consulte: . Encuentre el manual de GDB y otros recursos de documentación en línea en: . Para obtener ayuda, escriba "ayuda". Escriba "palabra apropiada" para buscar comandos relacionados con "palabra" ... lectura símbolos de ./prueba.Fuera ... [nuevo LWP 1341870] núcleo fue generado por './prueba.afuera'. Programa terminado con Signal SIGFPE, excepción aritmética. #0 0x0000564688444813b en real_calc (a = 13, b = 0) en la prueba.C: 3 3 C = A/B; (GDB) 


Como puede ver, en la primera línea llamamos gdb con como primera opción nuestro binario y como segunda opción el archivo principal. Simplemente recuerda binario y núcleo. A continuación, vemos que GDB se inicializa, y se nos presenta información.

Si ves un Advertencia: tamaño inesperado de la sección.reg-xstate/1341870 'en el archivo central.'O un mensaje similar, puede ignorarlo por el momento.

Vemos que el volcado de núcleo fue generado por prueba.afuera y se les dice que la señal era una excepción aritmética sigfpe. Excelente; Ya sabemos que algo está mal con nuestras matemáticas, y tal vez no con nuestro código!

A continuación vemos el marco (piense en un marco como un procedimiento en código por el momento) en el que terminó el programa: marco #0. GDB agrega todo tipo de información útil a esto: la dirección de memoria, el nombre del procedimiento real_calc, cuáles fueron nuestros valores variables, e incluso en una línea (3) de qué archivo (prueba.C) El problema sucedió.

A continuación vemos la línea de código (línea 3) Nuevamente, esta vez con el código real (c = a/b;) de esa línea incluida. Finalmente se nos presenta un mensaje GDB.

Es probable que el problema ya esté muy claro; lo hicimos C = A/B, o con variables llenas C = 13/0. Pero el humano no puede dividirse por cero, y una computadora tampoco puede. Como nadie le dijo a una computadora cómo dividirse por cero, ocurrió una excepción, una excepción aritmética, una excepción / error de punto flotante.

Retroceso

Así que veamos qué más podemos descubrir sobre GDB. Veamos algunos comandos básicos. El puño es el que es más probable que use con más frecuencia: bt:

(GDB) Bt #0 0x0000564688444813b en REAL_CALC (A = 13, B = 0) En la prueba.C: 3 #1 0x00005646884448171 en calc () en la prueba.C: 12 #2 0x0000564688444818a en main () en la prueba.C: 17 

Este comando es una taquigrafía para retroceso y básicamente nos da un rastro del estado actual (procedimiento después del procedimiento llamado) Del programa. Piénselo como un orden inverso de cosas que sucedieron; marco #0 (el primer cuadro) es la última función que estaba siendo ejecutada por el programa cuando se bloqueó, y el marco #2 fue el primer cuadro llamado cuando se inició el programa.

Podemos analizar lo que sucedió: el programa comenzó y principal() fue llamado automáticamente. Próximo, principal() llamado calc () (y podemos confirmar esto en el código fuente anterior), y finalmente calc () llamado real_calc Y allí las cosas salieron mal.

Muy bien, podemos ver cada línea en la que sucedió algo. Por ejemplo, el REAL_CALC () la función se llamó desde la línea 12 en prueba.C. Tenga en cuenta que no es calc () que fue llamado desde la línea 12 pero más bien REAL_CALC () que tiene sentido; prueba.C terminó ejecutándose en la línea 12 hasta el calc () se trata de la función, ya que aquí es donde el calc () función llamada REAL_CALC ().

Consejo de usuario de alimentación: si usa múltiples subprocesos, puede usar el comando hilo aplicar todo bt Para obtener una traje retroceso para todos los hilos que se ejecutaban a medida que el programa se bloqueaba!

Inspección de marco

Si queremos, podemos inspeccionar cada cuadro, el código fuente coincidente (si está disponible) y cada variable paso a paso:

(GDB) F 2 #2 0x0000555fa2323318a en main () en la prueba.c: 17 17 calc (); (GDB) Lista 12 REAL_CALC (A, B); 13 regreso 0; 14 15 16 int main () 17 calc (); 18 regreso 0; 19 (GDB) P A sin símbolo "A" en el contexto actual. 

Aquí 'saltamos al marco 2 usando el F 2 dominio. F es una mano corta para el marco dominio. A continuación, enumeramos el código fuente utilizando el lista comando y finalmente intente imprimir (usando el pag Comando de taquigrafía) el valor del a variable, que falla, como en este punto a aún no se definió en este punto del código; Tenga en cuenta que estamos trabajando en la línea 17 en la función principal(), y el contexto real en el que existía dentro de los límites de esta función/marco.

Tenga en cuenta que la función de visualización del código fuente, incluido algunos de los códigos fuente que se muestran en las salidas anteriores anteriores, solo está disponible si el código fuente real está disponible.

Aquí inmediatamente también vemos una gotcha; Si el código fuente es diferente, entonces el código del que se compiló el binario, se puede engañar fácilmente; La salida puede mostrar una fuente no aplicable / cambiada. GDB lo hace no Compruebe si hay una coincidencia de revisión del código fuente! Por lo tanto, es de suma importancia que use exactamente la misma revisión del código fuente que el que se compiló su binario.

Una alternativa es no usar el código fuente en absoluto, y simplemente depurar una situación particular en una función particular, utilizando una nueva revisión del código fuente. Esto a menudo sucede para desarrolladores y depugadores avanzados que probablemente no necesitan demasiadas pistas sobre dónde puede estar el problema en una función determinada y con valores variables proporcionados.

Luego examinemos el marco 1:

(GDB) F 1 #1 0x0000555fa23233171 en calc () en la prueba.C: 12 12 REAL_CALC (A, B); (GDB) Lista 7 int calc () 8 int a; 9 int b; 10 a = 13; 11 b = 0; 12 REAL_CALC (A, B); 13 regreso 0; 14 15 16 int main ()  

Aquí podemos ver nuevamente que GDB obtiene mucha información que ayudará al desarrollador a depurar el tema en cuestión. Dado que ahora estamos en calcón (en la línea 12), y ya hemos inicializado y posteriormente establecemos las variables a y b a 13 y 0 respectivamente, ahora podemos imprimir sus valores:

(GDB) P A $ 1 = 13 (GDB) P B $ 2 = 0 (GDB) P C No hay símbolo "C" en el contexto actual. (GDB) PA A/B División por cero 


Tenga en cuenta que cuando intentamos imprimir el valor de C, todavía falla como otra vez C no se define hasta este punto (los desarrolladores pueden hablar "en este contexto") todavía.

Finalmente, miramos al marco #0, Nuestro marco de bloqueo:

(GDB) F 0 #0 0x0000555FA2323313B en REAL_CALC (A = 13, B = 0) AL TEST.C: 3 3 C = A/B; (GDB) P A $ 3 = 13 (GDB) P B $ 4 = 0 (GDB) P C $ 5 = 22010 

Todo lo evidente, excepto por el valor reportado para C. Tenga en cuenta que habíamos definido la variable C, pero aún no le había dado un valor inicial. Como tal C está realmente indefinido (y no fue llenado por la ecuación C = A/B Sin embargo, como ese falló) y el valor resultante probablemente se lee desde algún espacio de direcciones al que la variable C fue asignado (y ese espacio de memoria aún no se inicializó/se borró).

Conclusión

Excelente. Pudimos depurar un vertedero central para un programa C, y mientras inclinamos los conceptos básicos de la depuración de GDB mientras tanto. Si usted es un ingeniero de control de calidad, o un desarrollador junior, y ha entendido y aprendido todo en este tutorial, ya está bastante por delante de la mayoría de los ingenieros de control de calidad, y potencialmente otros desarrolladores a su alrededor.

Y la próxima vez que vea a Star Trek y el Capitán Janeway o el Capitán Picard quieran 'volcar el núcleo', seguro que harás una sonrisa más amplia. Disfruta de depuración de tu próximo núcleo abandonado y déjanos un comentario a continuación con tus aventuras de depuración.

Tutoriales de Linux relacionados:

  • Una introducción a la automatización, herramientas y técnicas de Linux
  • Cosas para instalar en Ubuntu 20.04
  • Mastering Bash Script Loops
  • Cosas que hacer después de instalar Ubuntu 20.04 fossa focal Linux
  • Mint 20: Mejor que Ubuntu y Microsoft Windows?
  • Ubuntu 20.04 Guía
  • Cosas para instalar en Ubuntu 22.04
  • Bucles anidados en guiones Bash
  • Cómo arrancar dual Kali Linux y Windows 10
  • Sistema colgado de Linux? Cómo escapar a la línea de comando y ..