← Voltar aos artigos

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ável
  • as resolve 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.
  • ld decide 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 _start como 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.elf
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000038  00010000  00010000  00001000  2**2

A .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:

00010000<_start>:
10000:e28f101caddr1, pc, #28@ adr r1, nums -> pc(0x10008) + 28 = 0x10024
...
00010024<nums>:
10024:00000003.word0x00000003

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:

  • FLASH em 0x0 — o Cortex-M lê o valor inicial do SP em 0x0 e o endereço de reset em 0x4 (assunto do artigo de Cortex-M). No ARM clássico (ARMv4–v7-A), 0x0 guarda instruções de branch para cada handler.
  • KEEP(...) — sem isso, a otimização --gc-sections do 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 0000

56 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

TermoO 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
Relocationmarca no .o de "aqui vai um endereço que só o linker sabe"
Linker scriptarquivo que descreve MEMORY e SECTIONS
Entry pointendereço da primeira instrução (ENTRY(...) no script)
VMA / LMAendereço de execução / de carga (divergem em firmware)
KEEPimpede o linker de descartar uma seção "não referenciada"

Comandos

ComandoO que faz
arm-none-eabi-ld -Ttext=ADDR -o x.elf x.oliga com .text em ADDR
arm-none-eabi-ld -T script.ld -o x.elf x.oliga com um linker script
arm-none-eabi-gcc -nostdlib -T s.ld -o x.elf x.smonta + liga, freestanding
arm-none-eabi-objcopy -O binary x.elf x.binextrai só os bytes
arm-none-eabi-objdump -h x.elftabela de seções (VMA/LMA/tamanho)
arm-none-eabi-readelf -l x.elfprogram 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.