jueves, 15 de enero de 2015

Contraste entre las arquitecturas ARM/x86

La primera distinción que podemos hacer es que la arquitectura x86 está basada en arquitectura CISC, con soporte para instrucciones complejas, cuyo conjunto de instrucciones se caracteriza por ser muy amplio y permitir operaciones complejas entre operandos situados en la memoria o en el banco de registros. La estructura de dicha instrucción es más simple, pero tiene un mayor consumo de energía y de espacio físico. Podemos resumir algunas características de la arquitectura CISC en las siguientes:

- Instrucciones de longitud variable, la longitud depende del modo de direccionamiento usado en los operandos.
- Las instrucciones requieren múltiples ciclos de reloj para ejecutar una instrucción, puesto a que los operandos se buscan desde diferentes ubicación en memoria.
- Soportan instrucciones con dos operandos.
Múltiples modos de direccionamientos. 
  • Ventajas e inconvenientes de la Arquitectura CISC:
- Ventajas: Facilidad de implementación del conjunto de instrucciones y facilidad de programación.
- Inconvenientes: Aumenta la complejidad de las instrucciones.


En cuanto a la arquitectura ARM está basada en la arquitectura RISC, con soporte para instrucciones simples, generalmente utilizado en microprocesadores con las siguientes características:

- Instrucciones de tamaño fijo y presentadas en un reducido número de formatos.
- Sólo las instrucciones de carga y almacenamiento acceden a la memoria de datos.
- Predominan las instrucciones que se ejecutan en un ciclo de reloj, por lo que permite la implementación de la segmentación, manejando varias instrucciones al mismo tiempo.
  • Ventajas e inconvenientes de la Arquitectura RISC:
- Ventajas: Se incrementa la velocidad debido al conjunto de instrucciones simples.
- Inconvenientes: Incrementa el tamaño de código del lenguaje máquina.





Intel y AMD son dos fabricantes que han hecho evolucionar los procesadores x86 en entornos de escritorio. En cuanto a los procesadores ARM han evolucionado gracias a fabricantes como Qualcomm, Apple o Samsung, entre otros. ARM Holdings, es la empresa que desarrolló la arquitectura y además decidió licenciarla por lo que se hizo accesible a los fabricantes por una pequeña tasa. Por esto se ha permitido que los proceadores ARM se encuentren en una gran variedad de dispositivos, mientras que la arquitectura x86 de Intel solo se encuentra en sus procesadores y en los de AMD.

ARM ha evolucionado mucho en estos últimos años y está unida al segmento de bajo consumo, y por tanto está presente en dispositivos móviles como teléfonos o tablets, aunque Intel está preparando tecnologías para llevar x86 a dispositivos móviles de bajo consumo. ARM puede llegar a los niveles actuales de un x86 con la implementación de más núcleos y otras tecnologías, puesto que ARM tiene una gran ventaja frente a x86 en el menor consumo de energía. En cuanto a los procesadores x86 destacan en un buen rendimiento comparado con los procesadores ARM, al ser una arquitectura más compleja permite más optimizaciones que se hacen mientras se ejecuta la aplicación, como por ejemplo el intercambio de orden de instrucciones para mejorar el tiempo de ejecución.

Por José Joaquín Sánchez.

miércoles, 14 de enero de 2015

Ejecución de un programa en ensamblador ARM en Android




Nuestro objetivo es ejecutar un programa escrito en lenguaje ensamblador (x86 o ARM) en un dispositivo que tenga esa arquitectura.

El primer paso a la hora de realizar esta tarea es tomar unas cuantas decisiones, siendo la primera de ellas la arquitectura con la que vamos a trabajar. En este artículo trabajaremos con la arquitectura ARM.

La arquitectura ARM es empleada hoy día por multitud de dispositivos: móviles, tablets, sistemas empotrados, microcontroladores, etc. El dispositivo que vamos a utilizar es nuestro smartphone, por dos motivos:
  • Disponibilidad: en la actualidad, casi todo el mundo tiene un smartphone a su alcance
  • Documentación: la programación para dispositivos móviles está en su punto álgido de popularidad, por lo que la cantidad de documentación e información que nos facilita trabajar con smartphones es considerablemente mayor que con otros dispositivos o plataformas.

Lo primero que deberíamos hacer es comprobar que nuestro smartphone utiliza una arquitectura ARM. Ésto lo podemos consultar en la documentación de nuestro smartphone o buscando sus especificaciones técnicas en internet. En mi caso, voy a utilizar un Huawei Ascend Y300, que utiliza un procesador Qualcomm MSM8225. Wikipedia nos confirma que, efectivamente....



...éste procesador trabaja con arquitectura ARM, más concretamente el juego de instrucciones ARMv7. Comprobado esto, vamos a preparar nuestro smartphone (y nuestro ordenador) para poder testear nuestros programas en él.

En este artículo vamos a trabajar con un móvil equipado con el sistema operativo Android. Sin embargo, no vamos a trabajar sobre Android, sino sobre una distribución de Linux (Debian en mi caso). ¿Por qué?

La programación en Android se centra, sobre todo, en el desarrollo de aplicaciones, es decir, la programación en alto nivel, sobre todo en lenguaje JAVA, utilizando la plataforma de desarrollo de Android (SDK). Existe una “expansión” de esta plataforma, el NDK o “Native Development Kit” (kit de desarrollo nativo), para implementar a nuestras aplicaciones código en lenguaje de más bajo nivel que JAVA, como pueden ser C, C++, o incluso lenguajes ensamblador como ARM, MIPS o x86. Para ejecutar nuestro código ensamblador, necesitamos apoyarnos en código C y JAVA.

En conclusión, trabajar sobre Android es considerablemente más engorroso que trabajar sobre Linux, donde podemos compilar y ejecutar nuestro código en ensamblador introduciendo una sóla línea en el terminal. Aprovechando que existe un port de la distribución Debian de Linux para el set de instrucciones ARMv7, es más cómodo instalar Debian en nuestro smartphone y trabajar directamente sobre él.

Existen diversas maneras de montar en nuestra tarjeta SD una imagen de la distribución de Linux que elijamos. Yo personalmente me decanté por la aplicación Debian Kit, que provee facilidad de instalación y uso, una guía de instalación y uso muy detallada, y además trabaja paralelamente con Android. Es importante aclarar que esta aplicación requiere acceder a nuestro smartphone como superusuario o “root”: debemos hacer lo que se conoce popularmente como “rootear” nuestro teléfono, tarea que hoy día es completamente trivial y no requiere, dependiendo del sistema que utilicemos, más de 5 minutos y un par de clicks. Yo utilicé la aplicación Kingo (http://www.kingoapp.com/).

Una vez rooteado el terminal, podemos montar nuestra imagen de Debian en él. Existen multiples maneras de implementar un sistema operativo Linux funcional en nuestro smartphone. Yo elegí Debian Kit, junto con la aplicación ConnectBot para hacer de shell local, por los motivos antes mencionados. El creador de esta aplicación detalla estupendamente el proceso de instalación y de uso de esta aplicación aquí: http://sven-ola.dyndns.org/repo/debian-kit-en.html. Recordad que debemos disponer de al menos medio gigabyte de espacio libre en la tarjeta MicroSD de nuestro móvil.

ConnectBot es el cliente SecureShell para Android más utilizado y valorado. Un cliente SSH (Secure Shell o “intérprete de ordenes seguro) es, citando a la Wikipedia, “el nombre de un protocolo y del programa que lo implementa, y sirve para acceder a máquinas remotas a través de una red”. En nuestro caso, y si hemos seguido el tutorial, estaremos utilizando una shell local; es decir, utilizaremos ConnectBot para comunicarnos, mendiante línea de comandos, con el sistema Debian que acabamos de instalar.



Ya disponemos de Debian en nuestro móvil. Es posible, llegados a este punto, crear una shell remota ssh para poder manejar nuestro móvil sin tener que lidiar con el incómodo teclado táctil del móvil, usando android bridge (adb) y ssh (secure shell); no obstante, no es realmente necesario (aunque sí bastante práctico)



El siguiente paso es instalar un compilador/ensamblador. Yo he optado por el compilador de propósito múltiple, GNU GCC, puesto que es el más ampliamente usado y el estándar. La orden "apt-get install gcc" instala el compilador.

Llegados a este punto, ya podríamos ensamblar y ejecutar un archivo .s de código en ensamblador ARM. Con la instrucción:

gcc -o prueba codigo.s && ./prueba

Generaríamos un archivo objeto “prueba” a partir de nuestro archivo de código ensamblador “codigo.s”, y lo ejecutaríamos

Vamos a usar el ensamblador GNU. GNU es un ensamblador que soporta múltiples arquitecturas; por lo tanto, la sintaxis es distinta a la del ensamblador armasm de ARM, y debemos adaptar nuestro programa a dicha sintaxis.
  • Los comentarios son precedidos por el símbolo '@' o '#', no ';'
  • Las etiquetas deben terminar con el símbolo ':'
  • Las directivas son diferentes.



A continuación muestro el programa que he ejecutado: un simple Hola Mundo



Aquí vemos varias notables diferencias con lo visto en prácticas. La primera y mas obvia son las directivas y su sintaxis; aunque, como vemos, las instrucciones apenas varían. 
Digo apenas porque hay un par de pequeñas variaciones. En las prácticas empleabamos ADR para asignar una etiqueta a un registro, mientras que aquí usamos LDR, y ponemos un signo = antes de la etiqueta. También usábamos SWI para las llamadas al sistema; SWC ya está obsoleto, siendo ahora el estándar SVC o "supervisor call".

La segunda son las llamadas al sistema. Cada sistema operativo tiene su propia convención de llamadas al sistema. En el caso de Linux, cargamos en r7 el código de la llamada al sistema; en nuestro caso la llamada 4, asignada a la función write(). Los argumentos que le pasamos a la función se almacenan en r0, r1, r2 y r3. En r0 establecemos el "file descriptor": 0 para entrada estandar, 1 para salida estandar, y 2 para salida de errores estandar. En r1 establecemos la cadena a imprimir, y en r2, su longitud en bytes (como bien sabemos, 1 caracter = 1 byte). Aquí vemos cómo ensamblar y ejecutar, con una simple línea, nuestro código: (la opción nostdlib evita que se enlace nuestro programa con librerías estandar; por algún motivo, esa fase de enlace me estaba provocando errores, por eso la deshabilité. También es posible usar -c con un fin parecido).



Parece fácil en teoría; sin embargo, aun así, todavía hay dificultades. El procesador, el tipo de memoria donde está montada la imagen de Linux, el compilador, las directivas (incluyendo algunas como .cpu para indicar exactamente qué procesador va a tratar), el “toolchain” que utilicemos, el método de llamadas al sistema, etc. etc... Hay un millón de variables a la hora de compilar y ejecutar código ensamblador en este entorno, y basta que sólo una de ellas falle para que no se ejecute nuestro código. Es, en definitiva, una tarea difícil y tan didáctica como poco práctica. Otra dificultad es la imposibilidad de debuggear, al estar trabajando con el móvil utilizando únicamente el modo texto.

Por Santiago Zaldívar Lavalle