|
| Home / Docencia / I. Telecom. / Fundamentos de Ordenadores 2 | |
![]() |
|
![]() |
|
|
El programa depurador
(conocido
genéricamente como "debugger") nos permite
ver lo que está sucediendo en el interior nuestro programa mientras
está
ejecutando. Las facilidades que proporciona este programa son:
El depurador que vamos a utilizar para la realización de nuestros ejercicios es gdb que se ejecuta sobre plataformas Unix. Para que un programa genérico pueda ser procesado por gdb se necesita primero advertir al compilador y "linker" para que incluyan datos adicionales en el código. En nuestro entorno, como los programas primero se traducen por el ensamblador as y luego se procesan por el "linker", ambos programas necesitan opciones especiales para generar un ejecutable que pueda ser manipulado por gdb. En el caso del ensamblador, la opción extra que se precisa es -gstabs. Es decir, el nuevo comando para ensamblar un fichero de código será: unix$ as -gstabs -o miprograma.o miprograma.sEs preciso también añadir una nueva opción al "linker" para que esta información se propague al ejecutable final producido. El nuevo comando para obtener el ejecutable será: unix$ gcc -g -o programa programa.oNótese que el nombre de las opciones es diferente en los dos comandos. Una vez obtenido el fichero ejecutable con estos nuevos comandos podemos invocar al depurador con el comando: unix$ gdb programa Veremos que tras un mensaje de bienvenida al programa nos aparece el prompt (gdb) . El depurador es un programa que lee un fichero ejecutable y espera a que el usuario le diga mediante comandos las operaciones a hacer con el fichero. En esta sección vamos a ver los comandos más importantes para manipular la ejecución de nuestro programa. El comando info nos muestra las diferentes formas de obtener información sobre gdb desde el prompt. Para abandonar el depurador se utiliza el comando q. El primer comando que vamos a probar es list que nos "lista" el contenido de nuestro programa. Si tecleamos ese comando en el prompt vemos como se nos muestran las diez primeras líneas de nuestro programa tal y como aparece en el fichero de texto que contine el código ensamblador. El mecanismo más importante del depurador son los puntos de parada en nuestro programa. Un punto de parada es una marca en el código que hace que el programa cuando está ejecutándose y llega a ese punto, se pare y devuelva el control en ese preciso instante al depurador. El entorno de ejecución del programa se deja intacto al pasar el control al depurador. Es decir, el contenido de memoria, registros, y todo lo que pueda afectar al procesador se congela y se nos muestra de nuevo el prompt del depurador para que podamos introducir comandos. Podemos introducir un punto de parada en el código mediante el comando: (gdb) b <número de línea>El depurador tiene constancia del nombre de todas las etiquetas utilizadas en nuestro código, con lo que también podemos introducir el punto de parada en una etiqueta: (gdb) b mainSe pueden introducir varios puntos de parada en diferentes partes del código. En todo momento, el depurador mantiene una lista con los puntos de parada, y esta lista se puede visualizar mediante el comando (gdb) info breakpoints Una vez que tenemos un punto de parada introducido en nuestro código instruimos al depurador para que comience a ejecutar nuestro programa mediante el comando run. Si hemos introducido un punto de parada en la etiqueta main veremos como el depurador nos vuelve a mostrar el prompt tras mostrarnos la línea de código correspondiente a la etiqueta main. En este preciso instante nuestro programa está en mitad de ejecución a punto de ejecutar la instrucción en donde está el punto de parada. Con el comando next (abreviado con n) el programa retoma el control, ejecuta una única instrucción y devuelve el control al depurador. Veremos que de nuevo se nos muestra la línea en la que se ha quedado parado nuestro programa. Invocando repetidamente el comando next vemos como nuestro programa avanza instrucción a instrucción. En cualquier momento de la ejecución el depurador nos permite visualizar el contenido de los registros del procesador tal y como se encuentran en ese preciso instante mediante el comando info registers. El comando continue (abreviado con c) instruye al depurador a continuar la ejecución sin parar hasta que se llegue a otro punto de parada introducido por el usuario o se termine el programa. Si el programa termina el depurador nos devuelve el prompt (gdb). Si queremos podemos volver a ejecutar nuestro programa con el comando run. La principal utilidad del depurador es cuando nuestro programa tiene un error de ejecución. En este caso lo podemos ejecutar paso a paso y ver exactamente el punto en el que se produce el error, así como el entorno de ejecución para detectar la causa y corregirla. |
|
|
En este ejercicio se trabajará con un programa en ensamblador muy sencillo. Copia el siguiente código en un fichero, llamado suma.s:
El programa ensamblador se puede invocar con la opción -gstabs para que el resultado pueda ser procesado por el depurador gdb. Asimismo también hay que ejecutar el "linker" en modo depuración, con la opción -g: unix$ as -gstabs -a -o suma.o suma.sUna vez obtenido el fichero ejecutable, podemos invocar el depurador: unix$ gdb suma Se pide: Depura el programa con gdb y sigue los pasos que se indican para responder las siguientes cuestiones:
|
|
|
La instrucción imul multiplica dos números enteros con signo, mientras que la instrucción mul realiza la multiplicación de enteros pero sin signo. La intrucción imul contiene operandos implícitos. Utiliza registros que no aparecen en la instrucción para realizar la operación de multiplicación. El resultado de esta operación siempre se asume que es de tamaño doble al del operando especificado en la instrucción.
![]() Ejemplo: mov $0, %edx Esta secuencia de instrucciones multiplican ECX * EAX (0x10000000 * 0x00000010 = 0x100000000) y deposita el resultado en EDX:EAX (EDX = 0x1; EAX = 0x0). Consulta el manual de instrucciones para comprobar qué registros se utilizan en las otras variantes de esta instrucción. |
|
|
La instrucción div realiza la división de enteros sin signo, calculando el cociente y el resto de la operación. La instrucción idiv calcula la división de enteros con signo. La intrucción div (o idiv) trabaja con registros implícitos para realizar la división de enteros. Si el divisor es un entero de 32 bits (tal y como se puede ver en el dibujo):
![]() div e idiv también pueden trabajar con datos de otros tamaños. Consulta el manual de instrucciones para comprobar qué registros se utilizan en otros casos. |
|
|
Escribir el programa Figura 2.1. Sección de
datos del programa
Dado el entero de 32 bits almacenado en la etiqueta Con los datos definidos en la figura 2.1,
el programa debe mostrar por pantalla: Resultado 00000000:00042c79 |
|
|
Escribir el programa Figura 3.1. Sección de
datos del programa
Dados los enteros almacenados en las etiquetas Con los datos definidos en la figura 3.1, el programa debe mostrar por pantalla: 33245 no divide a 5. Cociente = 0, resto = 5 El programa debe funcionar para cualquier valor almacenado en
las
posiciones |
|
|
El ordenador utiliza el código ASCII para representar los caracteres. Este código asocia un número entre 0 y 255 a cada carácter, de modo que se utiliza un byte para su codificación. Este sistema permite comparar entre sí los caracteres como si fueran números enteros, puesto que se compara en realidad sus respectivos códigos ASCII. Escribe el programa comprobarCaracter.s que comprueba si el carácter almacenado en la posición de memoria caracter es una letra mayúscula, minúscula o no es un carácter alfabético, e imprime el mensaje correspondiente por pantalla. Para clasificarlo, simplemente comprueba si está:
|
|
|
Escribe el programa mayusculas.s que recorre una cadena de texto compuesta por letras minúsculas y convierte cada carácter a mayúsculas. Dada la siguiente definición de datos: .dataEl programa debe presentar la cadena de caracteres texto almacenada en memoria, convertirla a mayúsculas y presentar el resultado de nuevo por pantalla. La cadena se debe recorrer utilizando el modo de direccionamiento DESPLAZAMIENTO + INDICE. Recuerda que el final de una cadena de caracteres se marca con un byte nulo ('\0'). Sugerencia: Observa que para pasar de minúsculas a mayúsculas simplemente hay que restar 32 al código ASCII del carácter (también se puede realizar con una máscara) |
|
|