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

1 comentario:

  1. Casinos & Slot Machines | DrMCD
    With the success of online slots, you 밀양 출장샵 will 이천 출장마사지 enjoy an immersive The first casino and gaming 양산 출장안마 destination in 강원도 출장샵 the Netherlands,  Rating: 4.3 · ‎9 성남 출장안마 reviews

    ResponderEliminar