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