Ensamblador ARM: Introducción a ARM

blog-image

Introducción

¿Por qué aprender ensamblador de ARM?

En la actualidad, existen principalmente dos arquitecturas que dominan el mercado: x86/x64 y ARM. Si bien la primera es la que principalmente se utiliza para computadoras personales y estaciones de trabajo, ARM es ampliamente utilizada en dispositivos de bajo consumo: celulares, notebooks, dispositivos IoT, sistemas embebidos. Incluso los nuevos procesadores de Apple, el M1 y M2, que se encuentran en sus nuevas computadoras están basadas en la arquitectura ARM.

Con esto a consideración, no es debe ser difícil convencer a alguien de aprender ensamblador en ARM. La siguiente serie de artículos van dirigidos hacia:

  • Personas que quieren aprender explotación binaria por primera vez y quieren hacerlo a través de una arquitectura ARM.
  • Personas que saben de explotación binaria en x86 y estén interesadas en aprenderlo para ARM.
  • Personas que están interesadas en desarrollo a bajo nivel de dispositivos de arquitectura ARM.

ARM vs. x86/x64

Empecemos comparando a la arquitectura ARM con la más conocida arquitectura x86/x64.

x86 ARM
Arquitectura CISC Arquitectura RISC
Instrucciones modifican memoria Instrucciones no modifican memoria
Duración variable de instrucciones Duración (mayoritariamente) fija
No pipeline Pipeline
Tamaño variable de instrucciones Tamaño fijo de instrucciones
Ciertas instrucciones modifican ciertas banderas. Casi todas las instrucciones pueden realizar ejecución condicional.
Little Endian Bi-endian

La diferencia entre la arquitectura CISC (Complex Instruction Set Computer) y RISC (Reduced Instruction Set Computer) es la complejidad de sus instrucciones. En una arquitectura CISC, existen más instrucciones. Por ejemplo, la arquitectura x64 cuenta con 981 instrucciones[1], mientras que la misma cifra para ARM es alrededor de 50. Esto se debe a que las instrucciones de CISC, como su nombre indica, son individualmente más complejas que las de RISC, pues cada una realiza una tarea específica.

Esta es también la razón por la que se dice que ARM es una arquitectura de LOAD/STORE. Si se requiere modificar un dato en memoria, es necesario cargarlo al procesador (LOAD), operarlo, y guardarlo de nuevo en la memoria (STORE). En una arquitectura x86, existen instrucciones que pueden modificar un dato en memoria por si solas.

La ventaja de la simplicidad de las instrucciones de ARM es que (casi) todas las instrucciones duran el mismo número de ciclos de reloj. Esto brinda la capacidad de hacer pipelining: dividir instrucciones por fases, donde cada fase puede puede ejecutarse paralelamente. De esta forma, a pesar de que una instrucción puede tardar varios ciclos de reloj en ejecutarse, de forma efectiva cada ciclo de reloj está terminando de ejecutarse una instrucción.

Todas las instrucciones en ARM tienen un tamaño fijo: 32 bits (aunque más adelante veremos que existe una forma de reducir esto a la mitad.)

El concepto de Endianness se refiere al cómo se guarda un dato en memoria. Ya que cada dato se compone de varios bytes (por ejemplo, un entero cómun se conforma de 4 bytes) y la memoria direcciona a bytes, es necesario tener un estándar del orden en que se van a guardar. Se le llama Big Endian a cuando los bytes más significativos se guardan en las direcciones más bajas de memorias (va de más significativo a menos significativo) y Little Endian es cuando el byte menos significativo se guarda en la dirección más baja (va de menos significativo a más significativo). La arquitectura ARM es capaz de cambiar de Endianness, mientras que x86 es siempre Little Endian.

Más adelante veremos a detalle cada una de estas diferencias.

Familias y Arquitecturas ARM

La siguiente tabla muestra la relación entre las familias de procesadores ARM y las arquitecturas (que pueden ser confusas).

Familia Arquitectura
ARM7 ARM v4
ARM9 ARM v5
ARM11 ARM v6
Cortex-A ARMv7-A
Cortex-R ARMv7-R
Cortex-M ARMv7-M

Para darnos alguna idea de de la capacidad de estos procesadores, véamos algunos de sus usos en la industria:

  • ARM7: Producidos entre 1993 y 2001. Utilizado en dispositivos móviles y de bajo consumo. Ejemplos: PlayStation 2, Nintendo DS, Roomba, iPod Classic.
  • ARM9: Fabricados entre 1998 y 2006. Utilizado para microcontroladores y DSPs.
  • ARM11: Producidos entre 2002 y 2011. Utilizado para procesadores de celulares como Apple y Nokia. Ejemplos: Raspberry Pi B+.
  • Cortex-A: Dedicado a dispositivos Linux y Android: celulares, tablets, smartwatches, equipo de networking. Ejemplos: iPhone,
  • Cortex-R: Utilizado para sistemas de control en tiempo real. Ejemplos: sistemas bancarios, automóviles, dispositivos biomédicos.
  • Cortex-M: Utilizado para microcontroladores, ASICs, ASSPs, FPGAs, SoCs. No suelen tener un sistema operativo, al no tener MMU.

imagen imagen

Próximamente

En la siguiente parte de esta serie, aprenderemos qué es el lenguaje ensamblador y cómo se ordena la memoria.

Bibliografía

[1] Mahoney, W. & McDonald J.T. Enumerating x86_64. https://www.unomaha.edu/college-of-information-science-and-technology/research-labs/_files/enumerating-x86-64-instructions.pdf