All Projects → fwsGonzo → Libriscv

fwsGonzo / Libriscv

C++17 RISC-V RV32GC / RV64GC userspace emulator library

Programming Languages

cpp
1120 projects
cpp17
186 projects

Projects that are alternatives of or similar to Libriscv

Riscv Fs
F# RISC-V Instruction Set formal specification
Stars: ✭ 173 (+39.52%)
Mutual labels:  risc-v, library
Math Engine
Mathematical expression parsing and calculation engine library. 数学表达式解析计算引擎库
Stars: ✭ 123 (-0.81%)
Mutual labels:  library
Tik4net
Manage mikrotik routers with .NET C# code via ADO.NET like API or enjoy O/R mapper like highlevel api.
Stars: ✭ 118 (-4.84%)
Mutual labels:  library
Commonmark Java
Java library for parsing and rendering CommonMark (Markdown)
Stars: ✭ 1,675 (+1250.81%)
Mutual labels:  library
Wearable Reply
Simplify text input for Android Wear 2.0, by voice, keyboard, or canned response.
Stars: ✭ 119 (-4.03%)
Mutual labels:  library
Imagezipper
An image compresssion library in android.
Stars: ✭ 121 (-2.42%)
Mutual labels:  library
Best Of Python
🏆 A ranked list of awesome Python open-source libraries and tools. Updated weekly.
Stars: ✭ 1,869 (+1407.26%)
Mutual labels:  library
Colore
A powerful C# library for Razer Chroma's SDK
Stars: ✭ 121 (-2.42%)
Mutual labels:  library
Belvedere
An image picker library for Android
Stars: ✭ 122 (-1.61%)
Mutual labels:  library
Auth
Authenticator via oauth2
Stars: ✭ 118 (-4.84%)
Mutual labels:  library
Dateutil
Useful extensions to the standard Python datetime features
Stars: ✭ 1,706 (+1275.81%)
Mutual labels:  library
Angular Open Source Starter
This is a starter project for creating open-source libraries for Angular. It is a full fledged Angular workspace with demo application and easy library addition. It is designed to be used for open-sourcing libraries on Github and has everything you'd need ready for CI, code coverage, SSR testing, StackBlitz demo deployment and more.
Stars: ✭ 120 (-3.23%)
Mutual labels:  library
Hubspot3
python3.5+ hubspot client based on hapipy, but modified to use the newer endpoints and non-legacy python
Stars: ✭ 121 (-2.42%)
Mutual labels:  library
Vue Ts Lib
Vue 3 library starter in TS with lint, auto release, changelog and tests
Stars: ✭ 119 (-4.03%)
Mutual labels:  library
Angular Feather
A-la-carte integration of Feather Icons in Angular applications
Stars: ✭ 123 (-0.81%)
Mutual labels:  library
Dry Logic
Predicate logic with rule composition
Stars: ✭ 118 (-4.84%)
Mutual labels:  library
Superfine
Absolutely minimal view layer for building web interfaces.
Stars: ✭ 1,542 (+1143.55%)
Mutual labels:  library
React Rainbow
🌈 React Rainbow Components. Build your web application in a snap.
Stars: ✭ 1,662 (+1240.32%)
Mutual labels:  library
Riko
A Python stream processing engine modeled after Yahoo! Pipes
Stars: ✭ 1,571 (+1166.94%)
Mutual labels:  library
Ratifier
Ratifier is a form validation library for Android.
Stars: ✭ 123 (-0.81%)
Mutual labels:  library

RISC-V userspace emulator library

libriscv is a RISC-V emulator that is highly embeddable and configurable. This project is intended to be included in a CMake build system, and should not be installed anywhere. There are several CMake options that control RISC-V extensions and how the emulator behaves.

The emulator has a binary translation mode that has currently only been tested on Linux.

While this emulator has a focus on performance, one higher priority is the ability to map any memory anywhere with permissions, custom fault handlers and such things. This allows you to take the memory of one machine and map it into another, and does have a slight performance penalty compared to an emulator that can only have sequential memory.

Benchmarks against Lua

My primary motivation when writing this emulator was to use it in a game engine, and so it felt natural to compare against Lua, which I was already using.

Benchmarks with binary translation enabled

STREAM memory benchmark

LuaJIT benchmarks

Lua 5.4 benchmarks

Installing a RISC-V GCC compiler

To get C++ exceptions and other things, you will need a (limited) Linux userspace environment. You will need to build this cross-compiler yourself:

git clone https://github.com/riscv/riscv-gnu-toolchain.git
cd riscv-gnu-toolchain
./configure --prefix=$HOME/riscv --with-arch=rv32g --with-abi=ilp32d
make -j4

This will build a newlib cross-compiler with C++ exception support. The ABI is ilp32d, which is for 32-bit and 64-bit floating-point instruction set support. It is much faster than software implementations of binary IEEE floating-point arithmetic.

Note that if you want a full glibc cross-compiler instead, simply appending linux to the make command will suffice, like so: make linux. Glibc is harder to support, and produces larger binaries, but will be more performant. It also supports threads, which is awesome to play around with.

git clone https://github.com/riscv/riscv-gnu-toolchain.git
cd riscv-gnu-toolchain
./configure --prefix=$HOME/riscv --with-arch=rv64g --with-abi=lp64d
make -j4

The incantation for 64-bit RISC-V.

The last step is to add your compiler to PATH so that it becomes visible to build systems. So, add this at the bottom of your .bashrc file in the home (~) directory:

export PATH=$PATH:$HOME/riscv/bin

Installing a RISC-V GCC embedded compiler

$ xpm install --global @xpack-dev-tools/[email protected]

See more here: https://gnu-mcu-eclipse.github.io/install/

This compiler will build the smallest executables. See the micro examples in the binaries folder. Don't start with this unless you already know how to write startup code.

Building and running a test program

From one of the binary subfolders:

$ ./build.sh

Which will produce a hello_world binary in the sub-projects build folder.

Building the emulator and booting the newlib hello_world:

cd emulator
mkdir -p build && cd build
cmake .. && make -j4
./rvnewlib ../../binaries/newlib/build/hello_world

The emulator is built 3 times for different purposes. rvmicro is built for micro-environments with custom heap and threads. rvnewlib has hooked up enough system calls to run newlib. rvlinux has all the system calls necessary to run a normal userspace linux binary.

Building and running your own ELF files that can run in freestanding RV32GC is quite challenging, so consult the barebones example! It's a bit like booting on bare metal, except you can more easily implement system functions. The fun part is of course the extremely small binaries and total control over the environment.

The newlib example project have much more C and C++ support, but still misses things like environment variables and such. This is a deliberate design as newlib is intended for embedded development. It supports C++ RTTI and exceptions, and is the best middle-ground for running a fuller C++ environment that still produces small binaries.

The full example project uses the Linux-configured cross compiler and will expect you to implement quite a few system calls just to get into int main(). In addition, you will have to setup argv, env and the aux-vector. There is a helper method to do this in the src folder. There is also basic pthreads support.

And finally, the micro project implements the absolutely minimal freestanding RV32GC C/C++ environment. You won't have a heap implementation, so no new/delete. And you can't printf values because you don't have a C standard library, so you can only write strings and buffers using the write system call. Still, the stripped binary is only 784 bytes, and will execute only ~120 instructions running the whole program! The micro project actually initializes zero-initialized memory, calls global constructors and passes program arguments to main.

Instruction set support

The emulator currently supports RV32GC, and RV64GC (IMAFDC). The F and D-extensions should be 100% supported (32- and 64-bit floating point instructions), and there is a test-suite for these instructions, however they haven't been extensively tested as there are generally few FP-instructions in normal programs.

Note: There is no support for the B-, E- and Q-extensions.

Usage

Load a binary and let the machine simulate from _start (ELF entry-point):

#include <libriscv/machine.hpp>

int main(int /*argc*/, const char** /*argv*/)
{
	// load binary from file
	const std::vector<uint8_t> binary /* = ... */;

	using namespace riscv;
	// create a 64-bit machine
	Machine<RISCV64> machine { binary };

	// install the `exit` system call handler
	machine.install_syscall_handler(93,
	 [] (auto& machine) {
		 const auto [code] = machine.template sysargs <int> ();
		 printf(">>> Program exited, exit code = %d\n", code);
		 machine.stop();
	 });

	// add program arguments on the stack
	machine.setup_argv({"emulator", "test!"});

	// this function will run until the exit syscall has stopped the
	// machine, an exception happens which stops execution, or the
	// instruction counter reaches the given limit (1M):
	try {
		machine.simulate(1'000'000);
	} catch (const std::exception& e) {
		fprintf(stderr, ">>> Runtime exception: %s\n", e.what());
	}
}

You can find the example above in the emulator/minimal folder. It's a normal CMake project, so you can build it like so:

mkdir -p build && cd build
cmake .. && make -j4
./emulator

If you run the program as-is with no program loaded, you will get an Execution space protection fault, which means the emulator tried to execute on non-executable memory.

$ ./emulator
>>> Runtime exception: Execution space protection fault

You can limit the amount of (virtual) memory the machine can use like so:

	const uint32_t memsize = 1024 * 1024 * 64;
	riscv::Machine<riscv::RISCV32> machine { binary, { .memory_max = memsize } };

You can limit the amount of instructions to simulate at a time like so:

	const uint64_t max_instructions = 1000;
	machine.simulate(max_instructions);

Similarly, when making a function call into the VM you can also add this limit as a template parameter to the vmcall() function.

You can find details on the Linux system call ABI online as well as in the syscalls.hpp, and syscalls.cpp files in the src folder. You can use these examples to handle system calls in your RISC-V programs. The system calls is emulate normal Linux system calls, and is compatible with a normal Linux RISC-V compiler.

Setting up your own machine environment

You can create a 64kb machine without a binary, and no ELF loader will be invoked.

	std::vector<uint8_t> nothing; // taken as reference
	riscv::Machine<riscv::RISCV32> machine { nothing, { .memory_max = 65536 }};

Now you can copy your machine code directly into memory:

	std::vector<uint8_t> my_program_data;
	const uint32_t dst = 0x1000;
	machine.copy_to_guest(dst, my_program_data.data(), my_program_data.size());

Finally, let's jump to the program entry, and start execution:

	// example PC start address
	const uint32_t entry_point = 0x1068;
	machine.cpu.jump(entry_point);

	// geronimo!
	machine.simulate(5'000);

The fuzzing program does this, so have a look at that.

Documentation

System calls

Freestanding environment

Function calls into the VM

Why a RISC-V library

It's a drop-in sandbox. Perhaps you want someone to be able to execute C/C++ code on a website, safely?

See the webapi folder for an example web-server that compiles and runs limited C/C++ code in a relatively safe manner. Ping me or create a PR if you notice something is exploitable.

Note that the web API demo uses a docker container to build RISC-V binaries, for security reasons. You can build the container with docker build -t newlib-rv32gc . -f newlib.Dockerfile from the docker folder. Alternatively, you could build a more full-fledged Linux environment using docker build -t linux-rv32gc . -f linux.Dockerfile. There is a test-script to see that it works called dbuild.sh which takes an input code file and output binary as parameters.

It can also be used as a script backend for a game engine, as it's quite a bit faster than LuaJIT, although it requires you to compile the scripts ahead of time as binaries using any computer language which can output RISC-V.

What to use for performance

Use Clang (newer is better) to compile the emulator with. It is somewhere between 20-25% faster on most everything.

Use GCC to build the RISC-V binaries. Use -O2 or -O3 and use the regular standard extensions: -march=rv32gc -mabi=ilp32d. Enable the RISCV_EXPERIMENTAL option for the best performance unless you are using libriscv as a sandbox. Use -march=rv32g for the absolute best performance, if you have that choice. Difference is minimal so don't go out of your way to build everything yourself. Always enable the instruction decoder cache as it makes decoding much faster at the cost of extra memory. Always enable LTO and -march=native if you can.

Building the fastest possible RISC-V binaries for libriscv is a hard problem, but I am working on that in my rvscript repository. It's a complex topic that cannot be explained in one paragraph.

If you have arenas available you can replace the default page fault handler with your that allocates faster than regular heap. If you intend to use many (read hundreds, thousands) of machines in parallel, you absolutely must use the forking constructor option. It will apply copy-on-write to all pages on the newly created machine and share text and rodata. Also, enable RISCV_EXPERIMENTAL so that the decoder cache will be generated ahead of time.

Binary translation

Instead of JIT, the emulator supports translating binaries to native code using any local C or C++ compiler. You can control compilation by passing CC and CFLAGS environment variables to the program that runs the emulator. You can show the compiler arguments using VERBOSE=1. Example: CFLAGS=-O2 VERBOSE=1 ./myemulator.

The binary translation feature (accessible by enabling RISCV_EXPERIMENTAL) can greatly improve performance in some cases, but requires compiling the program on the first run. The RISC-V binary is scanned for code blocks that are safe to translate, and then a C compiler is invoked on the generated code. This step takes a long time. The resulting code is then dynamically loaded and ready to use. The feature is a work in progress.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].