Skip to content

Repository files navigation

DinoRISC

RISC-V 64-bit to ARM64 dynamic binary translator.

Overview

DinoRISC translates and executes RV64I ELF binaries on ARM64 hardware. It reads a RISC-V ELF executable, decodes instructions into a block-local SSA intermediate representation, lowers through instruction selection and register allocation to ARM64 machine code, and executes it natively via a block-based dispatch loop.

Dependencies

  • C++17 compiler (Clang or GCC)
  • CMake >= 3.20
  • ELFIO and Catch2 are submodules under third_party/

For end-to-end tests:

  • clang with RISC-V target support
  • Python 3 with pytest

Building

cmake -G Ninja -B build
ninja -C build

Usage

dinorisc <riscv_binary> <function_name> [arg1] [arg2] ...

Executes a named function from a RISC-V ELF binary. Up to 8 integer arguments can be passed and are mapped to registers a0a7. The function's return value (from a0) is printed to stdout.

./build/bin/dinorisc program.elf main
./build/bin/dinorisc math.elf add 3 5

Architecture

The translation pipeline processes one basic block at a time:

StageDescription
ELF ReaderParses RV64 ELF binaries (ELFIO), extracts .text section and symbol table
DecoderDecodes 32-bit RISC-V instructions, extracting opcodes, registers, and immediates
LifterConverts decoded instructions into a block-local SSA intermediate representation
Instruction SelectorTranslates IR operations to ARM64 instructions with virtual registers
Liveness AnalysisComputes live intervals for virtual registers within each block
Register AllocatorLinear scan allocation over ARM64 general-purpose registers (X1–X28)
EncoderEmits raw ARM64 machine code bytes from instruction objects
Execution EngineMaps code into executable memory (mmap/mprotect), dispatches blocks in a loop

Guest state is maintained in a GuestState struct (32 integer registers + PC) with an 8 MB shadow memory region for loads and stores.

Supported Instructions

The following RV64I instruction categories are supported:

  • ArithmeticADD, ADDI, ADDW, ADDIW, SUB
  • BitwiseAND, ANDI, OR, ORI, XOR, XORI
  • ShiftsSLL, SLLI, SLLIW, SRL, SRLI, SRA, SRAI
  • ComparisonsSLT, SLTU, SLTI, SLTIU
  • LoadsLB, LH, LW, LWU, LD
  • StoresSW, SD
  • BranchesBEQ, BNE, BLT, BGE, BLTU, BGEU
  • JumpsJAL, JALR (including RET recognition)
  • Upper immediateLUI, AUIPC

Compiling RISC-V Binaries

DinoRISC only supports the base RV64I integer instruction set — no M (multiply/divide), A (atomics), F/D (floating point), or C (compressed) extensions. Binaries must be statically linked, freestanding ELF executables. Linker relaxations must be disabled since they can rewrite instructions into forms we don't handle.

clang \
-target riscv64-unknown-elf \
-march=rv64i \
-mabi=lp64 \
-nostdlib \
-ffreestanding \
-static \
-Wl,-Ttext=0x10000 \
-Wl,--no-relax \
-o program.elf program.c
FlagWhy
-target riscv64-unknown-elfBare-metal RV64 ELF target (no OS runtime)
-march=rv64iBase integer ISA only — no extensions
-mabi=lp64LP64 soft-float ABI (no floating-point registers)
-nostdlib -ffreestandingNo standard library or C runtime; we execute individual functions directly
-staticStatically linked — no dynamic loader
-Wl,-Ttext=0x10000Place .text at a known base address
-Wl,--no-relaxDisable linker relaxations (e.g. converting call sequences to jal)

See tests/e2e/samples/ for example C programs that work with DinoRISC.

Testing

# all tests
ninja -C build && ctest --test-dir build --output-on-failure
# run a specific test
ctest --test-dir build -R DecoderUnitTest --output-on-failure
# end-to-end tests only
ctest --test-dir build -R E2ETests --output-on-failure

E2E tests require a clang with RISC-V target support on your PATH. On macOS with Homebrew, the default Apple Clang doesn't include it — use the LLVM formula instead:

export PATH="/opt/homebrew/opt/llvm/bin:$PATH"

About

A RISC-V to ARM dynamic binary translator

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages