← Voltar aos artigos

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
000xxshift por imediato (LSL/LSR/ASR)
00011ADD/SUB registrador ou imediato de 3 bits
001xxMOV/CMP/ADD/SUB com imediato de 8 bits
010000operações ALU entre dois registradores
010001operações com registradores altos (r8–r15) + BX
01001LDR relativo ao PC (carrega constante do literal pool)
0101x / 011xx / 100xxload/store (registrador, imediato, halfword, relativo ao SP)
1010xADD Rd, PC/SP, #imm
1011xmiscelânea: ADD/SUB sp, PUSH, POP
1100xLDM/STM (transferência múltipla)
1101xdesvio condicional (cond nos bits [11:8]) + SVC (11011111)
11100desvio incondicional
111xxBL/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      lr
00010000<_start>:
10000:200amovsr0, #10
10002:2100movsr1, #0
00010004<loop>:
10004:1809addsr1, r1, r0
10006:3801subsr0, #1
10008:d1fcbne.n10004
1000a:008alslsr2, r1, #2
1000c:4770bxlr

200a = movs r0, #10

200a = movs r0, #10

1809 = adds r1, r1, r0

1809 = adds r1, r1, r0

3801 = subs r0, #1

3801 = subs r0, #1

d1fc = bne loop

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

008a = lsls r2, r1, #2

4770 = bx lr

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

TermoO que é
Halfword16 bits — o tamanho de uma instrução Thumb
Famíliagrupo de encodings identificado pelos bits altos da halfword
S implícitoem Thumb, a maioria das instruções atualiza NZCV sem pedir
Literal poolbloco de constantes de 32 bits que o LDR Rd, =valor lê via PC
Registrador altor8–r15; só algumas famílias Thumb os alcançam

Comandos

ComandoO que faz
arm-none-eabi-as -march=armv4t -mthumb x.smonta em Thumb
arm-none-eabi-objdump -d --disassembler-options=force-thumb x.elfforça a desmontagem Thumb
arm-none-eabi-objdump -d x.elfos 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.