Criando um executável ARM e rodando no arm-box
Neste primeiro artigo prático do nosso curso de arquitetura ARM, você vai escrever um programa em assembly ARM do zero, montá-lo em um executável ELF real e rodá-lo no arm-box — o runner Linux user-mode construído sobre o arm-jitter. No final, você verá hello from a real ELF impresso por um núcleo ARM emulado em Java.
O que é o arm-box
O arm-box é um runner de binários ARM 32-bit no estilo do qemu-arm. Ele carrega um ELF estático, monta a pilha no formato ABI do Linux, mapeia os kuser helpers do kernel ARM e traduz as syscalls EABI para o sistema hospedeiro. O pulo do gato: a CPU que executa esse binário é o JIT do arm-jitter (por padrão ARMV5TE), então tudo que roda aqui está exercitando de verdade o decodificador, a IR e o backend de código que estudaremos ao longo do curso.
Pré-requisitos
- JDK 25 (recomendo o JBR) e Maven.
- Toolchain ARM bare-metal:
arm-none-eabi-as/arm-none-eabi-ld(vem no devkitARM). Oarm-boxusa-nostdlib, então não precisa de libc. - O próprio
arm-box:git clone https://github.com/vitorsilverio/armbox && cd armbox && mvn package.
Sem toolchain? Os testes de integração do arm-box montam ELFs sintéticos em memória, então mvn test funciona mesmo sem compilador ARM instalado. Mas para acompanhar este tutorial, o as/ld faz diferença.
O programa em assembly
Vamos escrever um "hello world" usando syscalls cruas da ABI EABI do Linux (sem libc). A convenção é simples: o número da syscall vai em r7, os argumentos em r0–r3 (e r4–r6 quando precisam), e a chamada é feita com svc #0.
.syntax unified
.text
.global _start
_start:
@ write(1, msg, msg_len) -- fd=stdout, buffer, tamanho
mov r0, #1 @ fd = 1 (stdout)
ldr r1, =msg @ ponteiro para a string
ldr r2, =msg_len @ número de bytes
mov r7, #4 @ syscall 4 = write
svc #0
@ exit(42) -- código de saída visível no shell
mov r0, #42
mov r7, #1 @ syscall 1 = exit
svc #0
.data
msg:
.ascii "hello from a real ELF\n"
msg_len = . - msg @ tamanho calculado em link-timeDecodificando o que escrevemos:
.syntax unifiedhabilita a sintaxe moderna que aceita tanto ARM quanto Thumb-2 — boa prática para todo o curso.mov r0, #1e osldrposicionam os argumentos dawrite(fd, buffer, tamanho).mov r7, #4+svc #0dispara a syscall. Repare que não hábx lr: depois deexito processo termina.
Montando e ligando
Geramos um ELF executável estático (sem dependências de biblioteca). O ponto de entrada é o símbolo _start, que o linker coloca no e_entry do ELF — é de lá que o arm-box começa a executar.
arm-none-eabi-as -march=armv5te hello.s -o hello.o
arm-none-eabi-ld -static -o hello.elf hello.o Quer conferir o binário antes de rodar? arm-none-eabi-objdump -d hello.elf mostra a desmontagem — ótimo exercício para o próximo módulo do curso, onde decodificamos essas instruções "à mão".
Rodando no arm-box
Com o arm-box já buildado:
java -jar target/armbox-1.0-SNAPSHOT.jar hello.elfSaída esperada:
hello from a real ELF O código de saída do processo é o exit() do guest — no nosso caso, 42. Você pode confirmar com echo $? (ou echo %ERRORLEVEL% no Windows).
Inspecionando a execução
O arm-box herda os backends do arm-jitter. Dois flags são ouro para aprender:
--interp: roda no interpretador IR (referência de semântica), ótimo para entender passo a passo.--check: executa JIT e interpretador em paralelo e aborta na primeira divergência — prova que os dois motores concordam.
java -jar target/armbox-1.0-SNAPSHOT.jar --check hello.elfPróximos passos
Parabéns — você acabou de fazer um binário ARM real executar dentro de um emulador escrito em Java. A partir daqui vamos descer camadas: no próximo texto veremos o banco de registradores e o CPSR, depois como ler o objdump e decodificar instruções ARM à mão, e então abrir o capô do arm-jitter para entender o ArmCore e o decodificador. Se quiser acompanhar o curso do início, confira a lista de artigos. Até a próxima!