El depurador GNU (gdb) es una herramienta invaluable para inspeccionar procesos en ejecución y solucionar problemas mientras desarrolla programas.
Puede establecer puntos de interrupción en ubicaciones específicas (por nombre de función, número de línea, etc.), habilitar y deshabilitar esos puntos de interrupción, mostrar y modificar valores de variables y hacer todas las cosas estándar que esperaría que hiciera cualquier depurador. Pero tiene muchas otras características con las que quizás no haya experimentado. Aquí hay cinco para que pruebes.
Puntos de interrupción condicionales
Establecer un punto de interrupción es una de las primeras cosas que aprenderá a hacer con el depurador GNU. El programa se detiene cuando llega a un punto de interrupción y puede ejecutar comandos gdb para inspeccionarlo o cambiar las variables antes de permitir que el programa continúe.
Por ejemplo, es posible que sepa que una función a la que se llama con frecuencia falla a veces, pero solo cuando obtiene un determinado valor de parámetro. Puede establecer un punto de interrupción al comienzo de esa función y ejecutar el programa. Los parámetros de la función se muestran cada vez que llega al punto de interrupción y, si no se proporciona el valor del parámetro que desencadena el bloqueo, puede continuar hasta que se vuelva a llamar a la función. Cuando el parámetro problemático desencadena un bloqueo, puede revisar el código para ver qué está mal.
(gdb) break sometimes_crashes
Breakpoint 1 at 0x40110e: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5 fprintf(stderr,
(gdb) continue
Breakpoint 1, sometimes_crashes (f=0x7fffffffd1bc) at prog.c:5
5 fprintf(stderr,
(gdb) continue
Para que esto sea más repetible, puede contar cuántas veces se llama a la función antes de la llamada específica que le interesa y establecer un contador en ese punto de interrupción (por ejemplo, "continuar 30" para que ignore las próximas 29 veces que llegue el punto de interrupción).
Pero donde los puntos de interrupción se vuelven realmente poderosos es en su capacidad para evaluar expresiones en tiempo de ejecución, lo que le permite automatizar este tipo de prueba. Introduzca:puntos de interrupción condicionales.
break [LOCATION] if CONDITION
(gdb) break sometimes_crashes if !f
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) run
[...]
Breakpoint 1, sometimes_crashes (f=0x0) at prog.c:5
5 fprintf(stderr,
(gdb)
En lugar de que gdb pregunte qué hacer cada vez que se llama a la función, un punto de interrupción condicional le permite hacer que gdb se detenga en esa ubicación solo cuando una expresión particular se evalúa como verdadera. Si la ejecución alcanza la ubicación del punto de interrupción condicional, pero la expresión se evalúa como falsa, el
Más recursos de Linux
- Hoja de trucos de los comandos de Linux
- Hoja de trucos de comandos avanzados de Linux
- Curso en línea gratuito:Descripción general técnica de RHEL
- Hoja de trucos de red de Linux
- Hoja de trucos de SELinux
- Hoja de trucos de los comandos comunes de Linux
- ¿Qué son los contenedores de Linux?
- Nuestros últimos artículos sobre Linux
El depurador permite que el programa continúe automáticamente sin preguntarle al usuario qué hacer.
Comandos de punto de interrupción
Una característica aún más sofisticada de los puntos de interrupción en GNU Debugger es la capacidad de crear un script de respuesta para alcanzar un punto de interrupción. Los comandos de punto de interrupción le permiten escribir una lista de comandos de GNU Debugger para ejecutar cada vez que llegue a un punto de interrupción.
Podemos usar esto para evitar el error que ya conocemos en a veces_fallas función y hacer que regrese de esa función de manera inofensiva cuando proporciona un puntero nulo.
Podemos usar silencio como la primera línea para obtener más control sobre la salida. Sin esto, el marco de la pila se mostrará cada vez que se alcance el punto de interrupción, incluso antes de que se ejecuten nuestros comandos de punto de interrupción.
(gdb) break sometimes_crashes
Breakpoint 1 at 0x401132: file prog.c, line 5.
(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>if !f
>frame
>printf "Skipping call\n"
>return 0
>continue
>end
>printf "Continuing\n"
>continue
>end
(gdb) run
Starting program: /home/twaugh/Documents/GDB/prog
warning: Loadable section ".note.gnu.property" outside of ELF segments
Continuing
Continuing
Continuing
#0 sometimes_crashes (f=0x0) at prog.c:5
5 fprintf(stderr,
Skipping call
[Inferior 1 (process 9373) exited normally]
(gdb)
Volcar memoria binaria
GNU Debugger tiene soporte incorporado para examinar la memoria usando x comando en varios formatos, incluidos octal, hexadecimal, etc. Pero me gusta ver dos formatos uno al lado del otro:bytes hexadecimales a la izquierda y caracteres ASCII representados por esos mismos bytes a la derecha.
Cuando quiero ver el contenido de un archivo byte a byte, a menudo uso hexdump -C (hexdump proviene del paquete util-linux). Aquí está la x de gdb comando que muestra bytes hexadecimales:
(gdb) x/33xb mydata
0x404040 <mydata>: 0x02 0x01 0x00 0x02 0x00 0x00 0x00 0x01
0x404048 <mydata+8>: 0x01 0x47 0x00 0x12 0x61 0x74 0x74 0x72
0x404050 <mydata+16>: 0x69 0x62 0x75 0x74 0x65 0x73 0x2d 0x63
0x404058 <mydata+24>: 0x68 0x61 0x72 0x73 0x65 0x75 0x00 0x05
0x404060 <mydata+32>: 0x00
¿Qué pasaría si pudieras enseñarle a gdb a mostrar la memoria como lo hace hexdump? Puede, y de hecho, puede usar este método para cualquier formato que prefiera.
Combinando el volcado comando para almacenar los bytes en un archivo, el shell comando para ejecutar hexdump en el archivo, y define comando, podemos hacer nuestro propio hexdump nuevo Comando para usar hexdump para mostrar el contenido de la memoria.
(gdb) define hexdump
Type commands for definition of "hexdump".
End with a line saying just "end".
>dump binary memory /tmp/dump.bin $arg0 $arg0+$arg1
>shell hexdump -C /tmp/dump.bin
>end
Esos comandos pueden incluso ir en ~/.gdbinit para definir el comando hexdump de forma permanente. Aquí está en acción:
(gdb) hexdump mydata sizeof(mydata)
00000000 02 01 00 02 00 00 00 01 01 47 00 12 61 74 74 72 |.........G..attr|
00000010 69 62 75 74 65 73 2d 63 68 61 72 73 65 75 00 05 |ibutes-charseu..|
00000020 00 |.|
00000021
Desmontaje en línea
A veces, desea comprender más sobre lo que sucedió antes de un bloqueo, y el código fuente no es suficiente. Quiere ver lo que sucede en el nivel de instrucción de la CPU.
El desmontaje El comando le permite ver las instrucciones de la CPU que implementan una función. Pero a veces la salida puede ser difícil de seguir. Por lo general, quiero ver qué instrucciones corresponden a una determinada sección del código fuente en la función. Para lograr esto, use el /s modificador para incluir líneas de código fuente con el desensamblado.
(gdb) disassemble/s main
Dump of assembler code for function main:
prog.c:
11 {
0x0000000000401158 <+0>: push %rbp
0x0000000000401159 <+1>: mov %rsp,%rbp
0x000000000040115c <+4>: sub $0x10,%rsp
12 int n = 0;
0x0000000000401160 <+8>: movl $0x0,-0x4(%rbp)
13 sometimes_crashes(&n);
0x0000000000401167 <+15>: lea -0x4(%rbp),%rax
0x000000000040116b <+19>: mov %rax,%rdi
0x000000000040116e <+22>: callq 0x401126 <sometimes_crashes>
[...snipped...]
Esto, junto con registros de información para ver los valores actuales de todos los registros de la CPU y comandos como stepi al paso de una instrucción a la vez, le permite tener una comprensión mucho más detallada del programa.
Depuración inversa
A veces desearías poder retroceder el tiempo. Imagine que ha llegado a un punto de observación en una variable. Un punto de observación es como un punto de interrupción, pero en lugar de establecerse en una ubicación en el programa, se establece en una expresión (usando el reloj dominio). Cada vez que cambia el valor de la expresión, la ejecución se detiene y el depurador toma el control.
Así que imagine que ha llegado a este punto de observación y la memoria utilizada por una variable ha cambiado de valor. Esto puede resultar causado por algo que ocurrió mucho antes; por ejemplo, la memoria se liberó y ahora se está reutilizando. Pero, ¿cuándo y por qué fue liberado?
¡El depurador GNU puede resolver incluso este problema porque puede ejecutar su programa a la inversa!
Logra esto al registrar cuidadosamente el estado del programa en cada paso para que pueda restaurar los estados registrados previamente, dando la ilusión de que el tiempo fluye hacia atrás.
Para habilitar este registro de estado, use el registro de destino completo dominio. Entonces puedes usar comandos que suenan imposibles, como:
- paso inverso , que retrocede a la línea fuente anterior
- inverso-siguiente , que retrocede a la línea de origen anterior, retrocediendo sobre las llamadas a funciones
- acabado inverso , que retrocede hasta el punto en que la función actual estaba a punto de llamarse
- invertir-continuar , que rebobina al estado anterior en el programa que (ahora) desencadenaría un punto de interrupción (o cualquier otra cosa que haga que se detenga)
Este es un ejemplo de depuración inversa en acción:
(gdb) b main
Breakpoint 1 at 0x401160: file prog.c, line 12.
(gdb) r
Starting program: /home/twaugh/Documents/GDB/prog
[...]
Breakpoint 1, main () at prog.c:12
12 int n = 0;
(gdb) target record-full
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401154 in sometimes_crashes (f=0x0) at prog.c:7
7 return *f;
(gdb) reverse-finish
Run back to call of #0 0x0000000000401154 in sometimes_crashes (f=0x0)
at prog.c:7
0x0000000000401190 in main () at prog.c:16
16 sometimes_crashes(0);
Estas son solo algunas de las cosas útiles que puede hacer el depurador GNU. Hay muchos más por descubrir. ¿Qué característica oculta, poco conocida o simplemente sorprendente de gdb es tu favorita? Compártelo en los comentarios.