Decodificando Thumb à mão
16 bits, muitas famílias
O ARM de 32 bits cabe tudo num layout quase uniforme (visto no artigo de anatomia). O Thumb não tem esse luxo: com 16 bits, o conjunto é fatiado em famílias, identificadas pelos bits mais altos. O mapa (ARMv4T):
| Bits [15:11] | Família |
|---|---|
000xx | shift por imediato (LSL/LSR/ASR) |
00011 | ADD/SUB registrador ou imediato de 3 bits |
001xx | MOV/CMP/ADD/SUB com imediato de 8 bits |
010000 | operações ALU entre dois registradores |
010001 | operações com registradores altos (r8–r15) + BX |
01001 | LDR relativo ao PC (carrega constante do literal pool) |
0101x / 011xx / 100xx | load/store (registrador, imediato, halfword, relativo ao SP) |
1010x | ADD Rd, PC/SP, #imm |
1011x | miscelânea: ADD/SUB sp, PUSH, POP |
1100x | LDM/STM (transferência múltipla) |
1101x | desvio condicional (cond nos bits [11:8]) + SVC (11011111) |
11100 | desvio incondicional |
111xx | BL/BLX (par de dois halfwords) |
Restrições que caem de graça dos 16 bits: a maioria das instruções só alcança r0–r7 (3 bits de registrador), o imediato é pequeno (3 ou 8 bits), e quase tudo atualiza as flags — não há bit S opcional. Predicação some: a única forma condicional é o desvio 1101.
Sete halfwords, decodificados
thumb1.s (.elf) — um laço que soma 10+9+...+1:
.thumb
_start:
movs r0, #10
movs r1, #0
loop:
adds r1, r1, r0
subs r0, r0, #1
bne loop
lsls r2, r1, #2
bx lr200a = movs r0, #10
1809 = adds r1, r1, r0
3801 = subs r0, #1
d1fc = bne loop
O alvo é PC + 4 + (offset8 × 2). Aqui: 0x10008 + 4 + (−4 × 2) = 0x1000c − 8 = 0x10004 — o rótulo loop. O +4 é o offset arquitetural do PC em Thumb (metade do +8 do ARM; ver fetch–decode–execute).
008a = lsls r2, r1, #2
4770 = bx lr
BX pode alcançar r0–r15 (4 bits de registrador, é uma das famílias "hi register") e é a instrução que troca de estado ARM↔Thumb pelo bit 0 do endereço.
Espelho no código: ThumbDecoder
O arm-jitter tem um ThumbDecoder separado do ArmDecoder, com a mesma assinatura (decode(memory, address) → DecodedInstruction). Ele lê 16 bits (memory.fetch16), olha os bits do topo para escolher a família e preenche o mesmo record neutro — condition fica AL para tudo que não é o desvio 1101, setFlags fica true para quase tudo (o "S implícito" do Thumb), e immediateOperand/immediate seguem a mesma convenção do mundo ARM. Do ponto de vista do resto do pipeline (IR, interpretador, JIT), uma instrução Thumb decodificada é indistinguível de uma ARM — a diferença mora só no decoder.
BL/BLX legado é a exceção: são duas halfwords (prefixo + sufixo) que juntas formam um deslocamento de 22 bits. O lifter de blocos trata o par como uma instrução arquitetural que ocupa duas iterações do laço de decode.
Glossário
| Termo | O que é |
|---|---|
| Halfword | 16 bits — o tamanho de uma instrução Thumb |
| Família | grupo de encodings identificado pelos bits altos da halfword |
| S implícito | em Thumb, a maioria das instruções atualiza NZCV sem pedir |
| Literal pool | bloco de constantes de 32 bits que o LDR Rd, =valor lê via PC |
| Registrador alto | r8–r15; só algumas famílias Thumb os alcançam |
Comandos
| Comando | O que faz |
|---|---|
arm-none-eabi-as -march=armv4t -mthumb x.s | monta em Thumb |
arm-none-eabi-objdump -d --disassembler-options=force-thumb x.elf | força a desmontagem Thumb |
arm-none-eabi-objdump -d x.elf | os símbolos .thumb_func já marcam o modo |
Próximo passo
Você decodificou ARM e Thumb no papel. O próximo artigo abre o ArmDecoder do arm-jitter e segue os bytes até virarem IR.
Trilha: Curso de Arquitetura ARM · Módulo 2, lição 3. Ver também: Decodificando instruções ARM à mão, Thumb e Thumb-2.