ABC del GDB (y II)
Conociendo el depurador o debugger
Vamos a revisar algunas ordenes útiles del depurador gdb. Supongamos que tenemos este programa:
void saludar (char *q) { char saludo[10] = “Hola “; char quien[15] = ” don “; printf(”%s, %s %s ”, saludo, quien, q); } int main(int argc, char * argv[]) { int entero; entero = 0; saludar(argv[1]); entero = 1; printf(”Ok, valor del entero %d ”, entero); return 0; }
Para poder depurarlo mejor, debemos compilarlo con el flag ggdb:
gcc -ggdb -o ejemplo ejemplo.c
Ahora ya podemos iniciar el depurador gdb:
gdb Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type “show copying” to see the conditions. There is absolutely no warranty for GDB. Type “show warranty” for details. This GDB was configured as “i386-linux”. (gdb)
Podemos invocarlo sin más y para cargar el programa que queremos depurar haríamos algo así:
(gdb) file ejemplo Reading symbols from /home/pello/b0f/dia2/ejemplo…done. Using host libthread_db library “/lib/tls/libthread_db.so.1″. (gdb)
Y si no podríamos invocar el debugger directamente con el programa que queremos depurar:
gdb ejemplo … (gdb)
list Bien. Con el comando list o l podemos echar un ojo al código fuente. Esto es posible gracias a que está compilado con -ggdb Se le pueden añadir números para indicar las lineas que queremos ver: list 1 : para ver a partir de la 1ª linea list 3,6 : para ver de la linea 3 a la 6
(gdb) list 3 char saludo[10] = “Hola “; 4 char quien[15] = ” don “; 5 6 printf(”%s, %s %s ”, saludo, quien, q); 7 } 8 9 int main(int argc, char * argv[]) 10 { 11 int entero; 12 entero = 0; (gdb) list 1,5 1 void saludar (char *q) 2 { 3 char saludo[10] = “Hola “; 4 char quien[15] = ” don “; 5 (gdb)
disassemble Esta opción desensambla el binario y muestra las instrucciones de ensamblador que componen el programa. Se le indica el nombre de función, y puede ser la función main o principal
(gdb) disas main Dump of assembler code for function main: 0×0804840c : push %ebp 0×0804840d : mov %esp,%ebp 0×0804840f : sub $0×18,%esp 0×08048412 : and $0xfffffff0,%esp 0×08048415 : mov $0×0,%eax 0×0804841a : sub %eax,%esp 0×0804841c : movl $0×0,0xfffffffc(%ebp) 0×08048423 : mov 0xc(%ebp),%eax 0×08048426 : add $0×4,%eax 0×08048429 : mov (%eax),%eax 0×0804842b : mov %eax,(%esp) 0×0804842e : call 0×80483a4 0×08048433 : movl $0×1,0xfffffffc(%ebp) 0×0804843a : mov 0xfffffffc(%ebp),%eax 0×0804843d : mov %eax,0×4(%esp) 0×08048441 : movl $0×8048588,(%esp) 0×08048448 : call 0×80482b8 <_init+56> 0×0804844d : mov $0×0,%eax 0×08048452 : leave 0×08048453 : ret End of assembler dump. (gdb)
En esta especie de batiburrillo se puede aprender a distinguir algunas cosas: los valores más a la izquierda son las direcciones de memoria en la que se encuentran las instrucciones primero en hexadecimal y luego en una notación relativa al inicio de main. Si desensamblamos la función saludar:
(gdb) disas saludar Dump of assembler code for function saludar: 0×080483a4 : push %ebp 0×080483a5 : mov %esp,%ebp 0×080483a7 : push %edi 0×080483a8 : sub $0×34,%esp 0×080483ab : mov 0×8048564,%eax 0×080483b0 : mov %eax,0xffffffe8(%ebp) 0×080483b3 : movzwl 0×8048568,%eax 0×080483ba : mov %ax,0xffffffec(%ebp) 0×080483be : movl $0×0,0xffffffee(%ebp)
run Para ejecutar el programa usamos la instrucción run o r. Se le pueden pasar parámetros como si estuvieramos en la linea de comandos.
(gdb) run Juan Starting program: /home/64kbytes/b0f/dia2/ejemplo Hola , don Juan Ok, valor del entero 1 Program exited normally. (gdb)
Pero claro, puede que nos interese ejecutar paso a paso. Para eso establecemos un punto de ruptura o break point
break Con break o b establecemos dónde debe detenerse la ejecución para que se siga paso a paso. Podemos establecerlo por nombre de función o por linea: break main : para depurarlo desde el principio break 10 : para depurar desde la linea 10
(gdb) break main Breakpoint 2 at 0×804841c: file ejemplo.c, line 12. (gdb)
A partir de ahí podemos iniciar la ejecución paso a paso con distintas instrucciones:
step Mediante step o s ejecutamos el programa linea a linea. En caso de pasarlo un parámetro numérico repetiría el código de la linea ese número de veces. stepi o si e similar pero en ese caso solo se ejecuta una instrucción al margen de lo que haya en toda la linea. Vamos a ejecutar paso a paso:
(gdb) break main Note: breakpoints 2, 3 and 4 also set at pc 0×804841c. Breakpoint 5 at 0×804841c: file ejemplo.c, line 12. (gdb) run Starting program: /home/pello/b0f/dia2/ejemplo Juan Breakpoint 2, main (argc=2, argv=0xbffff9b4) at ejemplo.c:12 12 entero = 0; (gdb) s 13 saludar(argv[1]); (gdb) s Breakpoint 1, saludar (q=0×2 ) at ejemplo.c:2 2 { (gdb) s 3 char saludo[10] = “Hola “; (gdb)
…y seguiría. Existen otras instrucciones similares a step.
next Next y nexti tratan las llamadas a funciones como una instrucción¿ más sin meterse a depurar dentro de ellas. kill Podemos interrumpir la ejecución con la instrucción kill o k.
(gdb) k Kill the program being debugged? (y or n) y (gdb)
info registers Este comando te da información sobre el estado de los registros. Muy útil en la depuración y en los overflows ya que son valores muy indicativos:
(gdb) info registers eax 0×0 0 ecx 0×4003de6d 1073995373 edx 0×2 2 ebx 0×40156ff4 1075146740 esp 0xbffff920 0xbffff920 ebp 0xbffff938 0xbffff938 esi 0×0 0 edi 0×40015cc0 1073831104 eip 0×804841c 0×804841c eflags 0×282 642 cs 0×73 115 ss 0×7b 123 ds 0×7b 123 es 0×7b 123 fs 0×0 0 gs 0×33 51 (gdb)
info frame Con este comando podemos ver el contenido del frame actual de la pila. Muy útil para ver los vlaores del EIP por ejemplo:
(gdb) info frame Stack level 0, frame at 0xbffff950: eip = 0×804841e in main (pila.c:34); saved eip 0×4003dea8 source language c. Arglist at 0xbffff948, args: argc=2, argv=0xbffff9c4 Locals at 0xbffff948, Previous frame’s sp is 0xbffff950 Saved registers: ebp at 0xbffff948, eip at 0xbffff94c (gdb) n
info stack Con info stack podemos ver las llamadas acumuladas en la pila.
(gdb) info stack #0 0×080483f0 in cambiar (valor=4) at pila.c:21 #1 0×08048436 in main (argc=2, argv=0xbffff9c4) at pila.c:34 (gdb)
x: examinando la memoria Para saber qué narices se guarda en memoria nada mejor que el comando x con sus muchas opciones. El formato general del comando es: x/[formato] dirección Donde el [formato] puede ser, además de un número, o de octal, x de hexadecimal, d de decimal e u de decimal sin signo, y otros como t de binario, f de float, a para direcciones, i para instrucciones, c caracteres and s para cadenas. Y además se le añade el tamaño: b para bytes, h para medio word, w para word, g para 8 bytes. Una combinación útil para examinar la memoria en bytes
(gdb) x/bx main 0×80483e6 : 0×55 (gdb) [enter] 0×80483e7 : 0×89 (gdb) [enter] 0×80483e8 : 0xe5 (gdb)
Ahí vemos cada instrucción del programa: 0×55, 0×89, 0xe5 en hexadecimal. Por ejemplo, para ver la parte de memoria a la que apunta el $esp
(gdb) x/100 $esp 0xbffffc50: “fþÿ¿@_25@” 0xbffffc59: “” 0xbffffc5a: “” 0xbffffc5b: “” 0xbffffc5c: “@_25@@e01@” 0xbffffc65: “” 0xbffffc66: “” 0xbffffc67: “” …
print Para sacar los valores concretos de variables y registros podemos usar la función print o p, indicándole lo que queremos mostrar. Por ejemplo el valor de un registro:
(gdb) print $ebx $1 = 1075146740 (gdb)
O el valor de una variable
(gdb) print saludo $3 = “Hola 00000000″ (gdb)
Y de momento es suficiente para poder empezar