La aplicación, inicialmente, mostrará un menú con tres opciones disponibles:
Figuras:
1. Cuadrado
2. Rectángulo
3. Triángulo
Opción:
El usuario introducirá una opción [1-3] y posteriormente se le solicitarán por teclado los datos necesarios para calcular el área seleccionada.
El dato se mostrará por pantalla, y se le dará la oportunidad al usuario de introducir una nueva opción.
Este programa se presentará tanto en ARM, como para x86.
"ÁREAS" EN x86
En primer lugar vamos a ir viendo poco a poco las diferentes partes en las que se divide el código, y ver la función que desempeña cada una de ellas.
Como bien sabemos, un programa en x86, posee una estructura bien definida. Dicha estructura se divide en tres secciones: .DATA, .CODE y .CONST. Cada una de ellas tiene un propósito bien claro y definido. En lugar de dar una definición teórica de lo que son cada una de ellas, vamos a ir viéndolas paso a paso tomando como ejemplo nuestro programa.
Sección .DATA
.Data operador DD 0, 0, 0 solucion DD 0 sino DD 's', 'n', 0
En esta sección, lo que vamos a hacer es incluir los datos que queremos definir en memoria. Definiremos tres etiquetas ('operador', 'solucion' y 'sino'), a través de las cuales, accederemos posteriormente al dato que contienen.
Todos los datos que hemos incluido en memoria tienen un tamaño de 32 bits, esto es muy importante, ya que se van a reservar en memoria tantos bits como hayamos designado. (DD = Doble palabra, 32 bits).
- Operador: Es un vector de tres posiciones. Una para la opción seleccionada y las otras dos para almacenar los parámetros que vamos a necesitar por cada operación.
- solucion: Dato donde albergaremos la solución.
- sino: Variable de tipo "vector" con tres posiciones que utilizaremos para controlar si el usuario quiere salir de la aplicación o vuelve al menú principal para seleccionar otra opción.
Sección .CODE
En esta sección escribiremos el grueso de las operaciones que se van a llevar a cabo en esta práctica. Dado que es un código bastante extenso como para verlo en un solo bloque, vamos a ir desglosando la sección .Code en otras más sencillas para comprender mejor lo que se hace en el programa.
.Code start: ;Inicio del programa principal. Invoke puts, "Figuras:" Invoke puts, "1 - cuadrado" Invoke puts, "2 - rectangulo" Invoke puts, "3 - triangulo" Invoke puts, "" Invoke printf, "Opcion: " Invoke scanf, "%d", Addr operador Jmp opciones fin: Invoke puts, "" Invoke system, "pause" Xor Eax, Eax Invoke ExitProcess, Eax ;Fin del programa principal
En este código, se crea el menú principal. Para ello hemos hecho usode la pseudoinstrucción "Invoke". Con esta pseudoinstrucción, vamos a realizar llamadas a la API de C para utilizar sus funciones. Además de pasarle como parámetro la función que queremos utilizar, también hay que pasarle los parámetros (en el mismo orden en el que se pondrían en un programa de C) que esta función recibiría.
Así, en el caso de "puts", también se le pasa la cadena que queremos que se pinte por pantalla. Lo mismo pasa con "printf" o con "scanf". Como caso especial, fijémonos en "scanf". Los parámetros que le pasamos son "%d" y "Addr operador". ¿Por qué usamos ADDR?. Porque si recordamos, "scanf" leía la dirección del dato que se pretende leer.
Una vez leído el dato, pasaríamos a procesar la opción introducida por teclado, para posteriormente, ejecutar la subrutina asociada a dicha opción.
opciones: Mov eax, [operador] Cmp eax, 1 Je > cuadrado Cmp eax, 2 Je > rectangulo Cmp eax, 3 Je > triangulo Invoke puts, "Elige una opcion valida." Jmp start
En el código de arriba, gestionamos la opción solicitada por el usuario. En primer lugar vamos a almacenar la opción en el registro EAX. Una vez que el dato ya está en el registro, vamos averiguar de que opción se trata. Comparamos si el dato almacenado en EAX (y que se corresponde con la opción seleccionada) es igual a 1, a 2 o a 3, para posteriormente hacer un salto a la subrutina asociada a dicha opción.
Una vez que la subrutina acabe su ejecución, el programa volverá al punto desde donde fue llamada dicha subrutina y se nos mostrará el menú de nuevo (Jmp start)
A continuación, vamos a ver el código que implementa las operaciones que se llevan a cabo en esta práctica:
cuadrado: Invoke printf, "Introduzca lado: " Invoke scanf, "%d", Addr operador + 4 Xor eax, eax Add eax, [operador + 4] Mul D[operador + 4] Jmp sol rectangulo: Invoke printf, "Introduzca ancho altura: " Invoke scanf, "%d %d", Addr operador + 4, Addr operador + 8 Xor eax, eax Add eax, [operador + 4] Mul D[operador + 8] Jmp sol triangulo: Invoke printf, "Introduzca base altura: " Invoke scanf, "%d %d", Addr operador + 4, Addr operador + 8 Xor eax, eax Add eax, [operador + 4] Mul D[operador + 8] Shr eax, 1 Jmp sol
En las tres subrutinas, vemos que utilizamos a "Invoke" tanto para escribir mensajes como para solicitar los datos necesarios para la correcta ejecución de las subrutinas. Veamos en detalle que hacemos en "Cuadrado", paso a paso:
- Hacemos uso de la pseudoinstrucción Invoke para llamar a la función "printf" de la API de C pasándole, además, la cadena que queremos mostrar, en este caso "Introduzca lado:"
- Volvemos a hacer uso de Invoke, en esta ocasión para leer el lado y almacenarlo en la segunda posición del vector "operador" (Addr operador + 4)
- Inicializamos el registro EAX (Xor eax, eax). No queremos obtener resultados indeseados, así que borramos el contenido que tuviese antes el registro.
- Añade el dato almacenado en [operador + 4] (segunda posición del vector) sobre el registro EAX y finalmente realiza la multiplicación (Mul D[operador + 4]), se multiplica el valor almacenado en EAX por el dato almacenado en [operador + 4].
Estos cuatro pasos son los que, a priori, se realizan en las cuatro subrutinas; lectura de datos, inicilización de registros y operar. Así mismo, al final de cada subrutina se hace un salto a la etiqueta "sol"; Esto se hace para mover el contenido del registro EAX (que almacena el resultado) a la variable "solucion", para posteriormente imprimirla por pantalla y volver pedir al usuario la opción de introducir una nueva operación de nuestra calculadora de áreas, o si prefiere salir, esto se hace en el siguiente código:
volver: Pusha Invoke scanf, "%c", Addr sino + 2 Invoke printf, "Desea volver a calcular un area (s/n): " Invoke scanf, "%c", Addr sino + 2 Popa Xor eax, eax Xor ebx, ebx Mov al, [sino + 2] Mov bl, [sino] Cmp eax, ebx Je start Mov bl, [sino + 1] Cmp eax, ebx Je fin Invoke puts, "Introduzca una opcion valida." Jmp volver
Una vez que el usuario obtiene el resultado, se le da la opción de seguir en la calculadora o volver a ejecutar una de las tres opciones disponibles inicialmente.
Finalmente, se presenta todo el código en un bloque:
;EasyCodeName=Module1,1 .Const .Data operador DD 0, 0, 0 solucion DD 0 sino DD 's', 'n', 0 .Code start: ;Inicio del programa principal. Invoke puts, "Figuras:" Invoke puts, "1 - cuadrado" Invoke puts, "2 - rectangulo" Invoke puts, "3 - triangulo" Invoke puts, "" Invoke printf, "Opcion: " Invoke scanf, "%d", Addr operador Jmp opciones fin: Invoke puts, "" Invoke system, "pause" Xor Eax, Eax Invoke ExitProcess, Eax ;Fin del programa principal ;Subprogramas: opciones: Mov eax, [operador] Cmp eax, 1 Je > cuadrado Cmp eax, 2 Je > rectangulo Cmp eax, 3 Je > triangulo Invoke puts, "Elige una opcion valida." Jmp start sol: Mov [solucion], eax Pusha Invoke printf, "solucion: %d (unidad cuadrada)", [solucion] Invoke puts, "" Popa Xor eax, eax Jmp volver volver: Pusha Invoke scanf, "%c", Addr sino + 2 Invoke printf, "Desea volver a calcular un area (s/n): " Invoke scanf, "%c", Addr sino + 2 Popa Xor eax, eax Xor ebx, ebx Mov al, [sino + 2] Mov bl, [sino] Cmp eax, ebx Je start Mov bl, [sino + 1] Cmp eax, ebx Je fin Invoke puts, "Introduzca una opcion valida." Jmp volver cuadrado: Invoke printf, "Introduzca lado: " Invoke scanf, "%d", Addr operador + 4 Xor eax, eax Add eax, [operador + 4] Mul D[operador + 4] Jmp sol rectangulo: Invoke printf, "Introduzca ancho altura: " Invoke scanf, "%d %d", Addr operador + 4, Addr operador + 8 Xor eax, eax Add eax, [operador + 4] Mul D[operador + 8] Jmp sol triangulo: Invoke printf, "Introduzca base altura: " Invoke scanf, "%d %d", Addr operador + 4, Addr operador + 8 Xor eax, eax Add eax, [operador + 4] Mul D[operador + 8] Shr eax, 1 Jmp sol
"ÁREAS" EN ARM
A continuación vamos a ver el mismo programa, pero desarrollado en ARM y comentado exhaustivamente.
AREA areas,CODE,READWRITE
SWI_EscrCar EQU &0 ; codigo de impresion de caracter (0)
; asignado a SWI_EscrCar
SWI_Salir EQU &11 ; codigo de impresion de salida del programa(11)
SWI_write0 EQU &2
SWI_ReadC EQU &4
ENTRY ; Punto de entrada del código
ADRL r0, cad1 ; Obtenemos la direccion de la cadena
SWI SWI_write0 ; Interrupcion de Soft. para mostrar la cadena
SWI SWI_ReadC ; Interrupcion de Soft. para leer un caracter de
;teclado
CMP r0, #49 ; tecla 1 ??
BLEQ CUADR ; Calcular area del cuadrado
CMP r0,#50 ; tecla 2 ??
BLEQ RECTA ; Calcular area del rectangulo
CMP r0,#51 ; tecla 3 ??
BLEQ TRIAN ; Calcular area del triangulo
DATO1 SWI SWI_write0 ; Interrupcion de Soft. para mostrar la cadena
; poner punto de interrupcion en la linea siguiente
MOV r1, r0 ; Movemos el valor introducido en r0 a r1
MOV pc, r14
DATO2 SWI SWI_write0 ; Interrupcion de Soft. para mostrar la cadena
; poner punto de interrupcion en la linea siguiente
MOV r2, r0 ; Movemos el valor introducido en r0 a r1
MOV pc, r14
SALIR ADRL r0, cad5 ; Obtenemos la direccion de la cadena
SWI SWI_write0 ; Interrupcion de Soft. para mostrar la cadena
SWI SWI_Salir ; Sale del programa
CUADR ADRL r0, cad2 ; Obtenemos la direccion de la cadena
BL DATO1 ; Pedimos el dato
MUL r3,r1,r1 ; lado * lado
B SALIR
RECTA ADRL r0, cad3 ; Obtenemos la direccion de la cadena
BL DATO1 ; Pedimos el dato
ADRL r0, cad4 ; Obtenemos la direccion de la cadena
BL DATO2 ; Pedimos el dato
MUL r3,r1,r2 ; base * altura
B SALIR
TRIAN ADRL r0, cad3 ; Obtenemos la direccion de la cadena
BL DATO1 ; Pedimos el dato
ADRL r0, cad4 ; Obtenemos la direccion de la cadena
BL DATO2 ; Pedimos el dato
MUL r4,r1,r2 ; dividendo = base * altura
B DIVID ; dividimos entre 2
DIVID MOV r2, #2 ; divisor = 2
MOV r3, #0 ; cociente = 0
BL DIVCOND ; condicion
B SALIR ; terminar
DIVBUC SUB r4, r4, r2 ; dividendo = dividendo - divisor
ADD r3, r3, #1 ; cociente++
DIVCOND CMP r4, r2 ; dividendo >= divisor
BGE DIVBUC ; hacer bucle
B SALIR
cad0 = "", 0
cad1 = "Calcular area de:", &0a, &0d, "1. CUADRADO", &0a, &0d,
"2. RECTANGULO", &0a, &0d,"3. TRIANGULO", &0a, &0d, "Opcion: ",0
cad2 = &0a, &0d, "Introduce el lado en r0 y pulsa F5", &0a, &0d, 0
cad3 = &0a, &0d, "Introduce la base en r0 y pulsa F5", &0a, &0d, 0
cad4 = "Introduce la altura en r0 y pulsa F5", &0a, &0d, 0
cad5 = &0a, &0d, "Calculo terminado. Resultado en r3", &0a, &0d, 0
END
PROS Y CONTRASUna de las cosas más atractivas de x86, es que nos da la posibilidad de poder utilizar funciones de C, usando la API de C mediante la pseudoinstrucción INVOKE. Al poder hacer esto, hemos podido "ahorrar" tiempo de desarrollo, ya que no hemos tenido que implementar dicha funcionalidad. En ARM no hemos podido llevar a cabo esta ventaja, en su lugar hay que utilizar interrupciones del sistema.
En cuanto al carácter "CISC" de la arquitectura x86, hace que esta tenga un amplio conjunto de instrucciones, y muchas de ellas son de gran complejidad, por lo cual las operaciones que realizan son mucho más completas. Es decir, lo que podemos ser capaces de hacer en x86 con una única instrucción, en ARM necesitaríamos varias líneas, lo cual hace una vez más a x86 como la opción más sencilla a la hora de elaborar un programa.