[linux-riscv] Criando o projeto e um programa que sai
Aqui começaremos nossa jornada com RISC-V! Faremos o mesmo que fizemos no tutorial x86: criar um programa que sai sem crashar. Mas antes, precisamos do ambiente de desenvolvimento para RISC-V.
Como a maioria das pessoas ainda não tem um computador RISC-V (eles estão chegando!), usaremos ferramentas de compilação cruzada e o QEMU para testar. Se você tem hardware RISC-V nativo (ex: StarFive VisionFive, SiFive HiFive, ou uma FPGA com RISC-V), pode pular a parte do QEMU.
Instalando requisitos
Precisamos instalar três coisas:
- O toolchain de compilação cruzada RISC-V (GNU assembler + linker)
- O QEMU para emular RISC-V (modo usuário)
- O
gitpara versionamento
sudo apt install gcc-riscv64-linux-gnu qemu-user git
Arch Linux:
sudo pacman -S riscv64-linux-gnu-gcc qemu-userFedora:sudo dnf install riscv64-linux-gnu-gcc qemu-user
Verifique se as ferramentas estão instaladas:
riscv64-linux-gnu-as --version
qemu-riscv64 --version
Preparando nosso projeto
mkdir -p hello-asm-riscv
cd hello-asm-riscv
git init
Para facilitar a compilação, criamos um Makefile adaptado para RISC-V:
ASM_SRC=helloworld.S
ASM_OBJ=$(ASM_SRC:%.S=%.o)
PROGRAM=helloworld
AS=riscv64-linux-gnu-as
LD=riscv64-linux-gnu-ld
QEMU=qemu-riscv64
ASFLAGS=-g
LDFLAGS=
all: $(PROGRAM)
%.o: %.S
@echo "Montando $< -> $@"
@$(AS) $(ASFLAGS) -o $@ $<
clean:
@echo "Limpando projeto"
@rm -f $(PROGRAM) *.o
link: $(ASM_OBJ)
@echo "Ligando objetos $(ASM_OBJ)"
@$(LD) $(LDFLAGS) -o $(PROGRAM) $(ASM_OBJ)
$(PROGRAM): link
@echo "YEY!"
run: $(PROGRAM)
@echo "Executando no QEMU RISC-V..."
@$(QEMU) ./$(PROGRAM)
.PHONY: clean run
Diferente do Makefile x86, aqui usamos o assembler e linker GNU específicos para RISC-V e adicionamos um target run que executa via QEMU.
Salvamos o Makefile:
git add Makefile
git commit -am "Makefile adicionado para RISC-V"
Arquitetura RISC-V: primeiras diferenças
Antes de escrevermos código, vamos entender as diferenças principais entre RISC-V (64 bits) e x86-64:
| Característica | x86-64 | RISC-V 64 |
|---|---|---|
| Filosofia | CISC | RISC |
| Registradores | 16 GPRs (RAX-R15) | 31 GPRs (x1-x31) + x0=0 |
| Convenção de argumentos | RDI, RSI, RDX, RCX, R8, R9 | a0-a7 (x10-x17) |
| Chamada de sistema | syscall |
ecall |
| Número da syscall | RAX | a7 (x17) |
| Retorno | RAX | a0 (x10) |
| Stack |
push/pop
|
Manual: addi sp / sd / ld |
| Saltos condicionais |
jz, jnz, etc (baseado em flags) |
beq, bne, blt, etc (compara dois registradores) |
| Load imediato | mov reg, immed |
li reg, immed (pseudo-instrução) |
A pseudo-instrução
lié expandida pelo assembler em uma combinação deluieaddi. Você pode usar ela sem preocupações!
A tabela completa de syscalls do Linux RISC-V usa os números do conjunto asm-generic. As que usaremos:
| Syscall | Número RISC-V | x86-64 (comparação) |
|---|---|---|
| read | 63 | 0 |
| write | 64 | 1 |
| close | 57 | 3 |
| socket | 198 | 41 |
| bind | 200 | 49 |
| listen | 201 | 50 |
| accept | 202 | 43 |
| setsockopt | 208 | 54 |
| exit | 93 | 60 |
Os números são diferentes do x86! RISC-V Linux usa a tabela genérica (
asm-generic/unistd.h), então confira sempre antes de usar.
Nosso primeiro programa RISC-V
Agora ao código! Criamos helloworld.S (note a extensão .S — é a convenção do GNU assembler para assembly com pré-processador):
.section .text
.global _start
_start:
# exit(0)
li a7, 93 # syscall exit (93 no RISC-V)
li a0, 0 # status = 0 (OK)
ecall # chama o kernel
Vamos entender cada linha:
-
.section .text— equivalente aosection .textdo NASM. Declara a seção de código. -
.global _start— equivalente aoglobal _start. Torna o símbolo visível ao linker. -
_start:— label (sem dois pontos também funciona no GNU as, mas usamos:para clareza). -
li a7, 93— carrega o valor 93 no registrador a7 (syscall exit). -
li a0, 0— carrega o valor 0 no registrador a0 (status de saída). -
ecall— executa a chamada de sistema (equivalente aosyscalldo x86).
Convenção de syscall RISC-V Linux: o número da syscall vai em
a7, os argumentos ema0aa5, e o retorno vem ema0.
Compile e teste:
make clean
make
make run
Se tudo funcionou, o programa sai silenciosamente com status 0 — sem segmentation fault! Para confirmar:
make run
echo $? # deve imprimir 0
Comparação lado a lado: x86 vs RISC-V
# ──── x86-64 (NASM) ────
section .text
global _start
_start:
mov rax, 60 ; exit
mov rdi, 0 ; status
syscall
# ──── RISC-V 64 (GNU as) ────
.section .text
.global _start
_start:
li a7, 93 # exit
li a0, 0 # status
ecall
As semânticas são idênticas, mas:
- RISC-V usa
a7para o número da syscall (nãoa0como no x86 que usarax). - RISC-V usa
a0para o primeiro argumento (equivalente aordido x86). - O número 93 é o
exitno RISC-V (60 no x86).
Salvando o progresso
git add helloworld.S
git commit -am "Meu primeiro programa RISC-V sabe sair!"
Conclusão da primeira parte
Montamos nosso ambiente de compilação cruzada RISC-V, entendemos as diferenças fundamentais em relação ao x86, e escrevemos um programa que chama o exit corretamente.
Na próxima parte, vamos fazer o Hello World: usar a syscall write e criar nossa primeira função em RISC-V.