Montando e ligando um executável ARM
Dois passos, dois programas
soma5.s --(arm-none-eabi-as)--> soma5.o --(arm-none-eabi-ld)--> soma5.elf
texto monta objeto liga executávelasresolve mnemônicos em bytes de instrução, mas deixa os endereços relativos ao início de cada seção e marca com relocations tudo que depende de um endereço absoluto.lddecide onde cada seção vai na memória, soma os offsets, resolve os símbolos entre objetos e grava um ELF com um entry point.
O atalho: -Ttext
Nos artigos anteriores a ligação foi uma linha:
arm-none-eabi-ld -Ttext=0x10000 -o soma5.elf soma5.o-Ttext=0x10000 diz: "ponha a seção .text a partir de 0x10000". Funciona para exemplos de uma seção só. Para qualquer coisa maior, você escreve um linker script.
Um linker script explícito
user.ld — o mesmo efeito do -Ttext, mas dizendo tudo:
ENTRY(_start)
MEMORY
{
RAM (rwx) : ORIGIN = 0x00010000, LENGTH = 64K
}
SECTIONS
{
.text : {
*(.text*)
*(.rodata*)
} > RAM
.data : { *(.data*) } > RAM
.bss : { *(.bss*) } > RAM
}ENTRY(_start)— grava_startcomo o endereço de entrada no cabeçalho do ELF.MEMORY— as regiões físicas: nome, permissões (rwx), origem, tamanho.SECTIONS— para onde vai cada seção de entrada.*(.text*)= "a seção.text(e.text.foo, ...) de todos os objetos".
Ligando com ele:
arm-none-eabi-ld -T user.ld -o soma5.elf soma5.o
arm-none-eabi-objdump -h soma5.elfIdx Name Size VMA LMA File off Algn
0 .text 00000038 00010000 00010000 00001000 2**2A .text caiu exatamente em 0x00010000 — a ORIGIN que o script pediu. O VMA (onde roda) e o LMA (de onde é carregada) coincidem porque é RAM; em firmware que copia dados da flash para a RAM no boot, eles divergem.
E a desmontagem mostra o _start e o vetor nums já com endereços absolutos:
Bare-metal: a tabela de vetores
Em Linux user-mode o kernel entrega o controle em _start. Em bare-metal (firmware, sem SO), o hardware começa lendo uma tabela de vetores num endereço fixo. O arm-box usa este script para os testes Cortex-M (armbox/testdata/flash.ld):
MEMORY
{
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 1M
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 1M
}
SECTIONS
{
.text : {
KEEP(*(.isr_vector)) /* a tabela de vetores, na origem da FLASH */
*(.text*)
*(.rodata*)
} > FLASH
}Dois detalhes:
FLASHem0x0— o Cortex-M lê o valor inicial doSPem0x0e o endereço de reset em0x4(assunto do artigo de Cortex-M). No ARM clássico (ARMv4–v7-A),0x0guarda instruções de branch para cada handler.KEEP(...)— sem isso, a otimização--gc-sectionsdo linker vê que ninguém "chama" a tabela de vetores e a descarta. O boot então lê lixo.
gcc como motorista
arm-none-eabi-gcc sabe chamar as e ld na ordem certa:
# monta + liga em um passo (sem libc), com um linker script:
arm-none-eabi-gcc -nostdlib -T user.ld -o soma5.elf soma5.s
# só monta (para no .o):
arm-none-eabi-gcc -c -o soma5.o soma5.s-nostdlib é o que torna o resultado freestanding — sem o crt0 que normalmente roda antes do seu main e chama exit depois.
objcopy: só os bytes
Um ELF tem cabeçalhos, tabela de seções, símbolos. Um chip de flash quer só as instruções e dados:
arm-none-eabi-objcopy -O binary soma5.elf soma5.bin$ xxd soma5.bin
00000000: 1c10 8fe2 0520 a0e3 0000 a0e3 0430 91e4 ..... .......0..
00000010: 0300 80e0 0120 52e2 fbff ff1a 0170 a0e3 ..... R......p..
00000020: 0000 00ef 0300 0000 0700 0000 0100 0000 ................
00000030: 0900 0000 0400 000056 bytes: as 9 instruções (1c10 8fe2 é 0xe28f101c em little-endian) seguidas dos 5 .word do vetor. É esse .bin que um gravador de flash ou o arm-box em modo bare-metal consome.
Glossário
| Termo | O que é |
|---|---|
| Montar (assemble) | .s → .o: mnemônicos viram bytes, endereços ficam relativos |
| Ligar (link) | .o(s) → executável: endereços absolutos, símbolos resolvidos |
| Relocation | marca no .o de "aqui vai um endereço que só o linker sabe" |
| Linker script | arquivo que descreve MEMORY e SECTIONS |
| Entry point | endereço da primeira instrução (ENTRY(...) no script) |
| VMA / LMA | endereço de execução / de carga (divergem em firmware) |
KEEP | impede o linker de descartar uma seção "não referenciada" |
Comandos
| Comando | O que faz |
|---|---|
arm-none-eabi-ld -Ttext=ADDR -o x.elf x.o | liga com .text em ADDR |
arm-none-eabi-ld -T script.ld -o x.elf x.o | liga com um linker script |
arm-none-eabi-gcc -nostdlib -T s.ld -o x.elf x.s | monta + liga, freestanding |
arm-none-eabi-objcopy -O binary x.elf x.bin | extrai só os bytes |
arm-none-eabi-objdump -h x.elf | tabela de seções (VMA/LMA/tamanho) |
arm-none-eabi-readelf -l x.elf | program headers (o que o loader mapeia) |
Próximo passo
Com o binário no lugar certo, o artigo de API Java roda ele por dentro do arm-jitter — montando um AddressSpace, passando por ArmCore e lendo os registradores a cada instrução.
Trilha: Curso de Arquitetura ARM · Módulo 3, lição 2. Ver também: Setup do ambiente ARM, Criando um executável ARM e rodando no arm-box.