← Voltar aos artigos

Registradores e CPSR: banking, modos e SPSR

Os 16 registradores visíveis

Em qualquer instante, o programa enxerga R0–R15, todos de 32 bits:

RegistradorApelidoPapel
R0–R3—argumentos / retorno de função (AAPCS)
R4–R11—variáveis locais; callee-saved
R12ipintra-procedimento
R13spponteiro de pilha
R14lrlink register — endereço de retorno gravado por BL/BLX
R15pcprogram counter

Não há registrador de flags entre esses 16 — as flags moram no CPSR, um registrador à parte, acessível só por MRS/MSR e implicitamente pelas instruções com sufixo S e pelos desvios condicionais.

O PC não aponta para "a instrução atual"

Herança do pipeline clássico de 3 estágios: ler R15 devolve o endereço da instrução em execução + 8 em estado ARM (+ 4 em Thumb). Por isso adr r1, msg no primeiro artigo virou add r1, pc, #20 e não #28 — o montador já descontou o offset do pipeline. Escrever em R15 é um desvio.

O CPSR, bit a bit

O Current Program Status Register carrega o estado de execução. Os campos que importam nesta trilha:

BitsCampoSignificado
31Nresultado negativo (bit 31 do resultado)
30Zresultado zero
29Ccarry / borrow / último bit deslocado
28Voverflow aritmético com sinal
27Qsaturação "pegajosa" (DSP/saturating); só limpa com MSR
26–25, 15–10IT[7:0]estado do bloco IT (Thumb-2) — dividido em dois pedaços
19–16GE[3:0]greater-or-equal por lane (SIMD paralelo do ARMv6)
9Eendianness de dados (0 = little)
8Amascara aborts assíncronos
7Imascara IRQ
6Fmascara FIQ
5Testado Thumb (1) ou ARM (0)
4–0M[4:0]modo de processador (ver tabela adiante)

O bit T e o bit 0 do endereço em BX/BLX são a mesma decisão vista de dois ângulos: pular para um endereço ímpar entra em Thumb e liga o T; par entra em ARM.

Modos de processador e register banking

O ARM tem modos de execução. Cada exceção entra em um modo específico, e alguns registradores são bancados: R13/R14 (e, no FIQ, também R8–R12) têm cópias físicas distintas por modo. Trocar de modo troca qual R13 você enxerga — sem salvar nada explicitamente.

ModoM[4:0]Entra quandoBancados além de R0–R12/R15
User10000execução normal— (é o banco base)
System11111igual User, mas privilegiadocompartilha o banco de User
FIQ10001interrupção rápidaR8–R12, R13, R14, SPSR
IRQ10010interrupção normalR13, R14, SPSR
Supervisor10011reset e SVCR13, R14, SPSR
Abort10111falha de memória (data/prefetch abort)R13, R14, SPSR
Undefined11011instrução indefinida / UDFR13, R14, SPSR
Monitor10110SMC (TrustZone, ARMv6K+)R13, R14, SPSR
Hyp11010Hypervisor (ARMv7VE)R13, ELR_hyp, SPSR (LR é compartilhado com User)

R0–R7 e R15 (PC) nunca são bancados. É por isso que o handler de exceção pode ler os argumentos que estavam em R0–R3 sem mais cerimônia, mas precisa cuidar de R13/R14.

O SPSR

Ao entrar num modo de exceção, o hardware copia o CPSR de origem para o SPSR (Saved PSR) daquele modo, ajusta o CPSR (novo modo, mascara IRQ, etc.) e grava o endereço de retorno em R14. Para voltar, o handler restaura o CPSR a partir do SPSR e o PC a partir de R14 — tipicamente com SUBS pc, lr, #offset ou RFE. Os modos User e System não têm SPSR (não são alvos de exceção).

Como o arm-jitter modela isso

O núcleo (dev.vitorsilverio.armjitter.core) tem uma classe para cada peça:

CpuMode — o enum dos modos

Os cinco bits de modo, com o encoding real embutido:

public enum CpuMode {
    USER(0b10000),
    FIQ(0b10001),
    IRQ(0b10010),
    SUPERVISOR(0b10011),
    ABORT(0b10111),
    UNDEFINED(0b11011),
    SYSTEM(0b11111),
    HYP(0b11010),
    MONITOR(0b10110);

    public int bits() { /* ... */ }
    public static CpuMode fromBits(int bits) { /* lookup O(1) */ }
}

CpsrRegister — o status register

As máscaras são constantes públicas, e o acesso é tipado:

public static final int NEGATIVE_FLAG = 1 << 31;
public static final int ZERO_FLAG     = 1 << 30;
public static final int CARRY_FLAG    = 1 << 29;
public static final int OVERFLOW_FLAG = 1 << 28;
public static final int THUMB_FLAG    = 1 << 5;
public static final int IRQ_DISABLE_FLAG = 1 << 7;

int  get();
void set(int value);
boolean isThumbMode();
CpuMode mode();
void    setMode(CpuMode mode);
void    setNzcv(boolean n, boolean z, boolean c, boolean v);
boolean evalCond(Condition condition);   // usado pela predicação
boolean carry();                          // e negative()/zero()/overflow()
int     itState();                        // reconstrói IT[7:0] dos dois pedaços

O evalCond(Condition) é o coração da execução condicional: antes de executar qualquer instrução ARM, o interpretador consulta a condição de 4 bits contra as flags atuais.

ArmCore — o banco de registradores

int  register(int index);
void setRegister(int index, int value);
int  programCounter();
void setProgramCounter(int value);
CpsrRegister cpsr();
CpuMode      mode();

int  bankedRegister(CpuMode mode, int register);      // lê o R13/R14 de OUTRO modo
void setBankedRegister(CpuMode mode, int register, int value);
int  spsr(CpuMode mode);
void setSpsr(CpuMode mode, int value);
void switchMode(CpuMode mode);                        // troca o banco visível

register(13) sempre devolve o SP do modo atual; bankedRegister(CpuMode.IRQ, 13) devolve o SP_irq mesmo estando em User. É exatamente essa indireção que o silício faz em hardware.

Mão na massa: lendo o CPSR

Um programa que fotografa o CPSR em três momentos — cpsr.s (.elf pré-montado):

    .syntax unified
    .arch armv7-a
    .arm
    .text
    .global _start
_start:
    mrs     r0, cpsr           @ r0 = CPSR inicial
    movs    r1, #0             @ resultado 0  -> Z <- 1
    mrs     r2, cpsr           @ r2 = CPSR com Z setado
    subs    r3, r1, #1         @ 0 - 1        -> N <- 1, Z <- 0
    mrs     r4, cpsr           @ r4 = CPSR com N setado
    mov     r0, #0             @ exit code 0
    mov     r7, #1             @ NR_exit
    svc     #0

Montado, ligado e desmontado com o toolchain arm-none-eabi:

arm-none-eabi-as  -march=armv7-a -o cpsr.o cpsr.s
arm-none-eabi-ld  -Ttext=0x10000 -o cpsr.elf cpsr.o
arm-none-eabi-objdump -d cpsr.elf
00010000<_start>:
10000:e10f0000mrsr0, CPSR
10004:e3b01000movsr1, #0
10008:e10f2000mrsr2, CPSR
1000c:e2513001subsr3, r1, #1
10010:e10f4000mrsr4, CPSR
10014:e3a00000movr0, #0
10018:e3a07001movr7, #1
1001c:ef000000svc0x00000000

Agora sob o arm-box com o stub GDB (--gdb=PORT, ver o artigo de depuração):

# terminal 1 — sobe o emulador e trava esperando o gdb
java -jar target/armbox.jar --arch=armv7a --gdb=3333 cpsr.elf

# terminal 2 — conecta e anda instrução a instrução
arm-none-eabi-gdb -q cpsr.elf -ex "target remote :3333"
(gdb) x/1i $pc
=> 0x10000 <_start>:	mrs	r0, CPSR
(gdb) info registers cpsr
cpsr           0xdf                223

(gdb) stepi                        @ mrs r0, cpsr
(gdb) p/x $r0
$1 = 0xdf

(gdb) stepi                        @ movs r1, #0
(gdb) info registers cpsr
cpsr           0x400000df          1073742047     @ bit 30 (Z) ligado

(gdb) stepi                        @ mrs r2, cpsr  -> r2 = 0x400000df
(gdb) stepi                        @ subs r3, r1, #1
(gdb) p/x $r3
$2 = 0xffffffff
(gdb) info registers cpsr
cpsr           0x800000df          2147483871     @ bit 31 (N) ligado, Z apagado
(gdb) p/t $cpsr
$3 = 10000000000000000000000011011111

Dois detalhes que esse trace revela sobre o emulador, não só sobre o ARM:

  • O CPSR inicial é 0xdf. O byte baixo 11011111 é I=1, F=1, M[4:0]=11111: o arm-box monta o guest em modo System com IRQ e FIQ mascarados. Faz sentido para um runner user-mode — não há controlador de interrupção nem tabela de vetores. É o ArmCore sendo posto nesse estado antes do primeiro step().
  • As flags são exatamente as esperadas.movs r1, #0 liga só Z (0x40000000). subs r3, r1, #1 faz 0 - 1 = 0xFFFFFFFF: liga N (0x80000000), apaga Z, e — detalhe do ARM — na subtração C é o NOT borrow, então 0 - 1 deixa C = 0. É essa álgebra que o evalCond do arm-jitter consulta quando um BGE/BLT aparece logo depois.

Glossário

TermoO que é
CPSRCurrent Program Status Register — flags + modo + máscaras de interrupção
SPSRSaved PSR — cópia do CPSR feita ao entrar num modo de exceção
Bankingter cópias físicas de um registrador, uma por modo
Modoestado privilegiado da CPU; cada exceção entra em um
Flag pegajosabit que, uma vez ligado, só limpa por escrita explícita (Q)
MRS / MSRmover CPSR/SPSR ↔ registrador de propósito geral

Comandos

ComandoO que faz
mrs r0, cpsrcopia o CPSR para R0
msr cpsr_f, r0escreve só o campo de flags (_f) do CPSR
p $cpsr / p/t $cpsr(GDB) imprime o CPSR em hex / binário
p $spp $lrp $pc(GDB) imprime R13 / R14 / R15
info registers(GDB) todos os registradores + cpsr decodificado

Próximo passo

Com os registradores e o CPSR no lugar, o próximo artigo disseca a anatomia de uma instrução ARM — o formato {cond}{S}, o Operand2 e o barrel shifter.

Trilha: Curso de Arquitetura ARM · Módulo 1, lição 1. Ver também: Fundamentos da arquitetura ARM, Flags e desvios condicionais.