Hello lads and lasses! This project features a RV64I emulator. It is a learning project that seeks to emulate a simple linux runtime environment for the RISC-V 64bit base instruction set. The project is built to run code compiled for the risc-v integer architecture using any compiler toolchain of your choosing assuming you have implemented the syscalls and other runtime dependencies that the toolchain.
It should be easy to extend the project to other use cases that require an emulator, and this is something I hope to explore in the near future. Right now I'm still learning and trying to get the simple use case of executing programs right.
If you want to hack on this project or on something similar to this, you can hit me up. I would like any criticism and help I can get. Even better if you wanna join me on the journey of learning how computers work.
To build the project, clone this repository and run make build
After that you can take the built emulator for a spin by running make
or make run
, which
runs a test hello world program.
The emulator supports commandline options for tweaking the runtime environment it provides
to the programs that it executes. To see those, run the emulator without any arguments.
You can find other sample test programs in the testdata
directory.
Executing a helloworld program is as simple as doing the following:
joe@debian:~/dev/emulator$ ./simpmulator testdata/musl/hello/hello
Hello World
The rest of the sections below contain guides on how to build your own rv64i
program to run
against the emulator. And how to extend the emulator if you want to.
The musl toolchain for building test applications for this project can be installed using instructions from here. After installing and building the toolchain checkout this project, it contains instructions on how to generate makefiles for generating binaries that can be executed in the emulator.
The testdata/musl/
directory contains sample source files, makefiles and binaries
you can try out before going out to build your own.
The newlib toolchain used in this project
can be installed by doing as follows:
A precompiled riscv64-gcc-unknown-elf
with newlib
toolchain for embedded systems
can be found here. Download the
the tar archive, unarchive it into a /opt/risc-newlib-toolchain
directory and
have fun hacking.
Took me some doing to get it to work because I'm new to the stuff.
If you don't know what you are doing just like me and you want to use the toolchain to build your
own binaries, copy any of the makefiles from the
newlibc testdata/newlibc
directory, tweak it if need be: if you can't, fuck around a lil bit, you'll figure it out!.
Or hit me up, with our collective energies we might be able to fuck around faster and figure it out
If you succesfully compile a program with the toolchain, execute the program with the emulator to see if it works. If you encounter any hiccups try to figure it out or reach out to me let's figure it out together.
Everything else should work fine (LOL!), the only difference is if you are using
musl_libc
you have more syscalls to implement and the many problems that come
with doing it. It's LINUX baby!
It doesn't really matter the C library you use, so long as you are willing, and you have time to make it work, have a go at it.
I am able to run simple programs in the emulator (atleast I was at some point). You will hit a panic when you encounter a syscall that is not yet implemented, which contains info on the syscall that is causing the error and the contents of its argument registers.
PATH: /home/joe/go/src/github.com/Joe-Degs/emulator/testdata/hello/hello
FILENAME: hello
CURRENT ALLOCATION: 0x140b0
STACK [0x240b0 -> 0x140b0]
panic: Syscall {num: 64, a0: 1, a1: 77416, a2: 12, a3: 0, a4: 7, a5: 70816}
goroutine 1 [running]:
main.main()
/home/joe/go/src/github.com/Joe-Degs/emulator/main.go:62 +0x5c5
exit status 2
The panic up above is caused by the absense of syscall number 64, there is also
the values of argument register a0-a5
. Syscalls are implemented in the file
syscall.go
and all syscalls are functions of type;
func(*Emulator, SysCall) error
Implementing the syscalls should be trivial (LOL!), but I'll be doing it
incrementally, i.e implementing syscalls only when I have need of them.
Syscall names and their respective numbers can be found in *syscalls*.txt
So implement a syscall, stick in the syscall table syscalls
in the syscall.go
and you might be good to go.
- Memory mapping unit for mapping programs into memory
- Memory permissions to ensure secured access.
- Ability to reset/clone/fork the execution context provided by the emulator.
- Ability to dump execution context for easy debugging of issues.
joe@debian:~/dev/emulator$ ./simpmulator -v -elf-info testdata/newlibc/newtool/test
PATH: /home/joe/go/src/github.com/Joe-Degs/emulator/testdata/newtool/test
FILENAME: test
MEM SIZE: 0x1fffff
STACK [0x1fff00 -> 0x1fef00]
HEAP [0x17990 -> 0x18990]
CURRENT ALLOCATION: 0x18990
EmuExit {
zero: 0000000000000000 ra: 0000000000012304 sp: 00000000001ffdcc gp: 0000000000015188
tp: 0000000000000000 t0: 00000000000102d4 t1: 000000000000000f t2: 0000000000000000
s0/fp: 0000000000014968 s1: 0000000000000020 a0: 0000000000000020 a1: 0000000000013920
a2: 0000000000000020 a3: 0000000000000000 a4: 0000000000000000 a5: 0000000000000000
a6: 0000000000018dc0 a7: 0000000000000040 s2: 00000000001ffe9c s3: 0000000000013920
s4: 00000000001ffe74 s5: 0000000000014968 s6: 000000007ffffc00 s7: 0000000000000000
s8: 0000000000000000 s9: 0000000000000000 s10: 0000000000000000 s11: 0000000000000000
t3: 0000000000000000 t4: 0000000000000000 t5: 0000000000000000 t6: 0000000000000000
pc: 0000000000013750
Syscall {num: 64, a0: 1, a1: 80160, a2: 32, a3: 0, a4: 0, a5: 0},
opcode: 0b01110011
}
This project is inspired by gamozolabs' Fuzz Week 2020. Check out his youtube.