← Voltar aos artigos

Por que emular ARM (e por que isso ensina arquitetura)

A ideia da trilha

Dá para aprender arquitetura ARM lendo o Architecture Reference Manual de capa a capa. São umas 8000 páginas. A alternativa desta trilha é outra: aprender implementando. Cada conceito — um registrador, uma flag, um modo de exceção — vira código que decide o que acontece quando aquela instrução executa. Não tem como "mais ou menos entender" o carry do ADCS quando o seu emulador precisa produzir o bit certo.

O material de apoio é um conjunto de projetos Java que eu mantenho:

  • arm-jitter — a biblioteca-núcleo. Executa, depura e compila (JIT) blocos ARM/THUMB. Não tem main: é um core para ser embutido.
  • armbox — um runner Linux user-mode (estilo qemu-arm) sobre o arm-jitter. Carrega um ELF estático, monta a pilha ABI do Linux e traduz syscalls. É onde a maioria dos exemplos da trilha roda.
  • gbaemu / ndsemu / n3dsemu — emuladores de Game Boy Advance, Nintendo DS e 3DS. Cada console fixa uma geração do ARM.
  • virtual-arm-box — máquina ARM completa (CPU + MMU + periféricos) que boota um Linux real até o shell.

A trilha inteira está indexada na página do Curso de Arquitetura ARM.

Por que um emulador ensina o que um livro não ensina

Um livro descreve o comportamento. Um emulador é o comportamento. Três coisas ficam concretas quando você implementa:

  1. Não existe caso omisso. O manual pode dizer "o resultado é UNPREDICTABLE". O emulador tem que fazer alguma coisa — e escolher o quê te força a entender por que a arquitetura deixou aquilo em aberto.
  2. Estado é tudo. Registradores bancados por modo, SPSR, o bit T do CPSR, o bit 0 do PC que seleciona ARM ou Thumb — tudo isso deixa de ser trivia e vira uma variável que você lê e escreve na ordem certa, ou o programa convidado quebra.
  3. O binário é a verdade. Você para de pensar em mov r0, #1 e passa a pensar em 0xE3A00001. Decodificar esse número — separar cond, opcode, Rd, operando — é o exercício central dos módulos 2 e 3.

O ecossistema, em uma tabela

ProjetoPapelO que executa
arm-jitterBiblioteca-núcleo (core + decoder + IR + JIT + GDB)blocos ARM/THUMB, embutido por um hospedeiro
armboxRunner Linux user-modeELF estático 32-bit + syscalls EABI
gbaemuEmulador de GBAARM7TDMI (ARMv4T)
ndsemuEmulador de NDSARM9 + ARM7 (ARMv5TE)
n3dsemuEmulador de 3DSARM11 MPCore (ARMv6K)
virtual-arm-boxMáquina ARM completa (softmmu)kernel Linux ARM até shell busybox

A linha do tempo do ARM que a trilha percorre

Os emuladores da coleção cobrem, sem querer, quase toda a evolução do ARM de 32 bits. É essa a espinha do módulo 5:

ArquiteturaNúcleoGuest típicoO que ela adiciona
ARMv4TARM7TDMIGBAARM + Thumb, o conjunto base
ARMv5TEARM9ENDSBLX, CLZ, multiplicações DSP/saturating, LDRD/STRD
ARMv6KARM113DS, Raspberry Pi 1LDREX/STREX, SIMD paralelo, CPS/SETEND/WFI
ARMv6-M / v7-MCortex-Mfirmwareperfil M: MSP/PSP, xPSR, NVIC, sem modos bancados clássicos
ARMv7-ACortex-ALinux/Android 32-bitMOVW/MOVT, SDIV/UDIV, bitfield, barreiras, VFPv2
AArch64ARMv8-ALinux arm6464 bits, X0–X30, sem banking por modo, exceções por Exception Level

Começar no ARMv4T não é nostalgia: é o subconjunto menor. Tudo que vem depois é acréscimo sobre essa base, e é mais fácil sentir por que uma extensão existe depois de ter passado sem ela.

Um gostinho do que vem por aí

Este é o menor programa Linux ARM que dá para escrever — sem libc, só syscalls cruas (armbox/testdata/hello.s):

    .arch armv5te
    .arm
    .text
    .global _start
_start:
    mov     r0, #1              @ fd = stdout
    adr     r1, msg
    mov     r2, #(msg_end - msg)
    mov     r7, #4              @ NR_write
    svc     #0
    mov     r0, #42             @ código de saída
    mov     r7, #1              @ NR_exit
    svc     #0
msg:
    .ascii  "hello from a real ELF\n"
msg_end:

Montado e desmontado com o toolchain arm-none-eabi, ele vira isto:

00010000<_start>:
10000:e3a00001movr0, #1
10004:e28f1014addr1, pc, #20
10008:e3a02016movr2, #22
1000c:e3a07004movr7, #4
10010:ef000000svc0x00000000
10014:e3a0002amovr0, #42
10018:e3a07001movr7, #1
1001c:ef000000svc0x00000000

Reparou que adr r1, msg virou add r1, pc, #20? Esse tipo de tradução — do que você escreve para o que a máquina realmente executa — é o assunto da trilha. No artigo de decodificação a gente pega números como 0xe3a00001 e separa campo por campo.

Glossário

TermoO que é
ISAInstruction Set Architecture — o contrato entre o binário e o hardware
EmuladorPrograma que implementa a semântica de uma ISA em outra máquina
CoreA parte do emulador que executa instruções (registradores + decode + execute)
Guest / hostO sistema emulado / a máquina que roda o emulador
JITJust-In-Time — compilar blocos do guest para código nativo do host em tempo de execução
InterpretadorExecutar instrução por instrução, sem compilar; a referência de semântica
Bare-metalCódigo sem sistema operacional por baixo

Próximo passo

O próximo artigo monta a bancada: JDK, Maven, o toolchain arm-none-eabi e os repositórios, para você conseguir compilar e rodar cada exemplo daqui pra frente.

Trilha: Curso de Arquitetura ARM · Módulo 0, lição 1.