Anatomia de uma instrução ARM
O esqueleto de 32 bits
Uma instrução de processamento de dados (o ADD, SUB, MOV, CMP, AND... da família) tem este layout:
Decodificando à mão
Compile estas dez instruções e olhe o objdump — anat.s (.elf):
add r0, r1, r2 @ registrador
add r0, r1, #255 @ imediato pequeno
add r0, r1, #0x3F00 @ imediato com rotate
add r0, r1, r2, lsl #3 @ registrador deslocado (barrel shifter)
addeq r0, r1, r2 @ condicional (cond = EQ)
adds r0, r1, r2 @ atualiza flags (S = 1)
ldr r0, [r1, #4] @ offset, base inalterada
ldr r0, [r1, #4]! @ pre-index com writeback
ldr r0, [r1], #4 @ post-index
ldrb r0, [r1] @ acesso de 1 bytee0810002 = add r0, r1, r2
| Campo | Bits | Valor | Leitura |
|---|---|---|---|
| cond | 1110 | AL | incondicional |
| classe | 00 | — | data processing |
| I | 0 | — | Operand2 é registrador |
| opcode | 0100 | ADD | |
| S | 0 | — | não mexe nas flags |
| Rn | 0001 | r1 | |
| Rd | 0000 | r0 | |
| Operand2 | 0000 0000 0010 | r2 | Rm = r2, sem shift |
Junte os nibbles: 1110 0000 1000 0001 0000 0000 0000 0010 = 0xE0810002. Confere.
O que muda em cada variante
| Encoding | Instrução | Bit(s) que mudou |
|---|---|---|
e28100ff | add r0, r1, #255 | I = 1; Operand2 = 0x0FF (rotate 0, imm8 0xFF) |
e2810c3f | add r0, r1, #0x3F00 | I = 1; Operand2 = 0xC3F → imm8 0x3Frotacionado à direita por 2×12 = 24 bits |
e0810182 | add r0, r1, r2, lsl #3 | Operand2 = 0x182 → shift amount 00011 (3), tipo 00 (LSL), Rm 0010 (r2) |
00810002 | addeq r0, r1, r2 | cond = 0000 (EQ) — só o primeiro nibble |
e0910002 | adds r0, r1, r2 | S = 1 → bit 20 liga, o nibble vira 9 |
O Operand2 e o barrel shifter
Doze bits, dois formatos:
Imediato (I = 1):rotate[11:8] + imm8[7:0]. O valor final é imm8 rotacionado à direita por 2 × rotate. É por isso que #0x3F00 cabe (0x3F ror 24) mas #0x3F1 não — não existe rotação par de um byte que produza esse padrão. O montador resolve isso por você ou reclama.
Registrador deslocado (I = 0):shift[11:4] + Rm[3:0]. O shift pode ser por imediato (Rm, LSL #3) ou por outro registrador (Rm, LSL r4). Os tipos:
| Tipo | Bits [6:5] | Efeito | Afeta C (com S)? |
|---|---|---|---|
LSL | 00 | desloca à esquerda, entra 0 | sim (último bit que sai) |
LSR | 01 | desloca à direita lógico, entra 0 | sim |
ASR | 10 | desloca à direita aritmético, replica o sinal | sim |
ROR | 11 | rotaciona à direita | sim |
RRX | 11 + amount 0 | rotaciona 1 bit à direita através do C | sim |
O barrel shifter é um circuito dedicado no caminho de dados: ele aplica esse shift ao operando de graça, no mesmo ciclo da operação. add r0, r1, r2, lsl #3 é uma única instrução — um multiplicar-e-somar embutido.
Load/store: os bits de endereçamento
LDR/STR trocam o Operand2 por um bloco de bits de modo de endereçamento:
| Bit | Nome | 1 significa |
|---|---|---|
| P | pre/post | offset aplicado antes do acesso (pre/offset) |
| U | up/down | offset somado ao base (senão subtraído) |
| B | byte | acesso de 1 byte (senão palavra) |
| W | writeback | grava o endereço calculado de volta em Rn |
| L | load/store | carrega (senão armazena) |
As três formas do assembly:
| Assembly | P | W | Efeito |
|---|---|---|---|
ldr r0, [r1, #4] (e5910004) | 1 | 0 | lê de r1+4; r1 inalterado |
ldr r0, [r1, #4]! (e5b10004) | 1 | 1 | lê de r1+4; r1 ← r1+4 |
ldr r0, [r1], #4 (e4910004) | 0 | — | lê de r1; r1 ← r1+4 depois |
E ldrb r0, [r1] (e5d10000) é só o bit B ligado — o nibble 9 vira D. Esse post-index ([r1], #4) é o que faz um laço de varredura de vetor andar de 4 em 4 sem uma instrução de incremento separada — exatamente o padrão do artigo de carga e armazenamento.
Espelho no código: ArmDecoder
O decodificador do arm-jitter faz, em Java, o que você acabou de fazer no papel. ArmDecoder.decode(memory, address):
public DecodedInstruction decode(AddressSpace memory, int address) {
int raw = memory.fetch32(address & ~3);
Condition condition = decodeCondition(raw >>> 28); // os 4 bits de cond
// cond == 0b1111 não é "condição": é um espaço de encoding à parte
// (NEON, PLD, BLX imediato, CPS, barreiras) desde o ARMv5.
if ((raw >>> 28) == 0xF) {
return decodeUnconditional(address, raw, condition);
}
// ...
}O resultado é um record neutro, com os campos que você separou virando propriedades:
public record DecodedInstruction(
int address,
int raw, // a palavra de 32 bits, crua
InstructionSet instructionSet,
Condition condition, // <- raw >>> 28
InstructionKind kind, // <- opcode + subdispatch
int destinationRegister, // <- Rd, ou -1
int sourceRegister, // <- Rn, ou -1
int secondSourceRegister, // <- Rm, ou -1
int immediate, // <- imm8 JÁ rotacionado / offset / nº de SWI
boolean immediateOperand, // <- o bit I
boolean setFlags, // <- o bit S
boolean link, // <- BL vs B
int accessSizeBytes, // <- 1 (bit B) ou 4
boolean writeback, // <- o bit W
boolean postIndexed, // <- o bit P invertido
/* ... */) { }Repare que immediate já vem expandido: o decoder aplica a rotação do imediato de 12 bits ali, para o resto do pipeline não precisar saber do formato rotate|imm8. É a mesma decisão que o montador toma na direção oposta.
Glossário
| Termo | O que é |
|---|---|
Operand2 | o campo de 12 bits do segundo operando em data processing |
| Barrel shifter | circuito que aplica LSL/LSR/ASR/ROR ao operando no mesmo ciclo |
| Imediato rotacionado | imm8 ror 2×rotate — como o ARM cabe constantes de 32 bits em 12 bits |
| Pre-index / post-index | offset aplicado antes / depois do acesso de memória |
| Writeback | gravar o endereço calculado de volta no registrador base |
| Espaço incondicional | cond == 1111: encodings próprios, não uma condição |
Comandos
| Comando | O que faz |
|---|---|
arm-none-eabi-objdump -d x.elf | desmonta (mnemônico + bytes) |
arm-none-eabi-objdump --disassemble --show-raw-insn | força a coluna de bytes |
printf '%08x\n' $((0xE0810002 & 0x0FF00000)) | isola campos de um encoding na mão |
Próximo passo
Você já sabe ler uma instrução parada. O próximo artigo coloca ela em movimento: o ciclo fetch–decode–execute e como ele vira o laço ArmCore.step() do emulador.
Trilha: Curso de Arquitetura ARM · Módulo 1, lição 3. Ver também: Decodificando instruções ARM à mão.