RvRun banner

RvRun

17 devlogs
42h 33m 58s

RvRun is an emulator for RISC-V 64bit Linux programs. It is meant to run under Linux and Linux only. Tested under Gentoo, Archlinux, and Ubuntu 25.10

Demo Repository

Loading README...

foo-V

on commit c6f8e8f:

  • I added division support, finishing work on the M extension.
  • I tried to inspect which system calls were made by libc, but I found out that not only they are not the same in all architectures, but libc may have instructions from extensions that were not specified in the -march argument while compiling. This sets my new goal not at running basic (non-freestanding) C programs, but at supporting Rv64GC.

Therefore, I will probably only do some small refactoring/optimizations before getting work started on the F and D extensions. I’m curious on how I can implement the floating point environment instructions, and if simply affecting the emulator’s environment is enough (after all, that’s considerably faster than saving/fetching bit masks and doing floating point operations by hand). Regardless, I probably won’t be coding in the next 4 days for personal reasons, so I’ll have lots of time to think about a good design.

It is also worth mentioning that these instructions were not included in my first ship (which while currently pending, was done at the 15th devlog). The binary on the v1.0 release only supports the base ISA (Rv64I). I’ll be making a second ship later this month, perhaps in one or two weeks, that supports all of these instructions, and maybe a few more system calls.

Attachment
0
foo-V

on commit 493c809:

  • I have started work on the M extension, added all the multiplication instructions. To get the upper XLEN bits of multiplications, I wanted to use the __int128_t non-standard feature that both gcc and clang have, but after trying for some time, I decided to just use inline assembly with both the imulq and mulq x86-64 instructions.
  • The next step is to add the division operations to finish implementing the M extension, then I’ll either do some optimizations/refactoring, or add more system calls, before working on the F and D extensions. I intend to inspect the system calls made by libc’s initialization so I can eventually get basic C programs running (64bit C only requires Rv64IMD)

I had some problems with shipping, but it should hopefully be fixed by now too! The people on slack are really helpful.

Attachment
0
foo-V

on commit 0616547:

  • I did more touch-ups, added a –version option, re-wrote the README.md, added asciinema recordings, and tested the code under my system (gentoo), an archlinux VM, and a Ubuntu 25.10 VM. Couldn’t find any bugs, and I hope the people testing don’t find any either
  • I also added a pixel art! The resolution is pretty low, but I haven’t found a way to fix it. It took easily over 3 hours to do, as one may realize, I’m not very good when it comes to art.

The project has now had it’s first release (on github), and I’m going to ship it here, on flavortown, too!

Attachment
0
foo-V

on commit 5bbcc22:

  • I wrote three different example programs (echo, cat, and xxd), all in pure assembly. During testing, I found quite a lot of bugs and ways to improve RvRun, by the time I was writing xxd, no problems appeared.

I am now taking a look at packaging, I added install and uninstall targets to my Makefile, and will rewrite the README to better reflect where the project now is. I’m going to put a statically compiled binary on the github releases page, to make sure no problems can happen, it is about 15 times bigger than the dynamically compiled one, but still under 1Mb (there are no dependencies other than libc, after all), so no problems.

I also realized that I need a banner, so I’m thinking of making a pixelart for a logo.

Attachment
0
foo-V

on commit c1bf362:

  • Added argv and envp support for processes. Not sure how useful envp is, most programs don’t use it for compatability reasons, and it seems that it’s empty on my system (the process inherits it from RvRun). Regardless, the implementation is almost identical to the one of argv so I just went ahead and added it. Having argv support is extremely useful tho, it will be used on most, if not all of the example programs I write. (edit: Fixed the loading on fcad00d, was previously loading on argument register, but after inspecting libc’s startup code, I realized they’re supposed to be loaded right after SP. I think envp is still wrong but it wouldn’t usable anyways)
    I’m thinking of touching up the logging system, add some more info_log statements, maybe flush stdout/stderr on panics, etc.

I’m also thinking of what programs I should write as examples, they’ll probably be recreations of classical UNIX utilities, like cat(1), ls(1), echo(1), etc.

Attachment
0
foo-V

I had some problems with hacktime’s time tracking, but am finally back!
on commit fbcaefb:

  • I Finished implementing the I extension (aka the base specification), by adding Load, Store, Memory Ordering (FENCE), and Environment Breakpoint (EBREAK) instructions.
  • Made logs better (ABI names are now used for registers)
    I will now add argc/argv/envp support and write some example programs, before doing some final touch-ups required for a release. The projcet should be shipped by tomorrow!
Attachment
0
foo-V

on commit 4f3ebba:
Added a README, should have done this some time ago, but only now the project is starting to get usable. I’ll finish implementing the base ISA, perhaps add some system calls, and then release the version one.

Attachment
0
foo-V

on commit 1883f67:
RvRun can now interpret arguments, this might have been a bit late, but there are actual things to be set as arguments now. The currently added ones are:

  • –stdin=FILE, –stdout=FILE, and –stderr=FILE, these redirect the I/O of the emulated process,
    stdout and stderr always truncate FILE, I’ll add an option to append later
  • –log-file=FILE, and –log-level=LEVEL, these allow you to manipulate the logging of RvRun,
    FILE is where they’re written to (deafults to stderr), and LEVEL is which messages (aka DEBUG,
    WARN, etc) get written
    * –help, just shows you a help message, the standard stuff
    I’m using getopt_long() for these, with the GNU extension. I thought of enabling the POSIXLY_COMPATIBLE feature test macro for better compatibility, but RvRun is only meant to run on Linux anyways, so it just wasn’t worth it.
    The next thing I’ll do today is write a README.md, and then I’ll work on more instructions and system calls tomorrow.
Attachment
0
foo-V

on commit74aaa7b :
I Added all the W-type instructions, some other ones I had forgotten, and added support for file descriptor related system calls, like openat(), close(), read(), and write(). There is no open() system call on RISC-V, it is only a libc wrapper around open() (no __NR_open macro in include/uapi/asm-generic/unitsd.h under the linux kernel source code).
I am working on passing options to the emulator, writing a README, and making some examples, I’ll probably do that over the weekend. After that, there shouldn’t be that much work other than simple repetitive stuff (aka adding more instructions and more system calls)
I forgot to do the devlog earlier, so about one hour of the time being logged was actually spent adding flag support, haha.

Attachment
0
foo-V

on commit 1eb5f81:
I implemented ECALL, and can now emulate both exit(2) and exit_group(2) (they are both the same, as I don’t have thread support yet), with now a proper way to decide when a process exits and when it doesn’t, I implemented an actual Von Neumann cycle too.
I am going to iterate back between finishing the implementation of all Rv64I instructions, and supporting more system calls (most likely, file descriptor related ones). My objective is to support all the instructions from Rv64IMFD, and a handful of system calls, in about 2 weeks and a half.

Attachment
0
foo-V

on commit b264055:
I am finally back! I did some refactoring on src/proc.c yesterday, and implemented the I-type from Rv32I instructions today, I am ignoring the W variations that Rv64I adds for simplicity, I’d like to cover the most important instructions first.
I will probably start basic work on ecall next, so that I can do a real von neumann execution cycle by having an exit system call, after that, I’ll go on with the U-type, B-type, and J-type instructions, before going back to the things I ignored, and continuing with all the system calls.
I will probably change the license to GPLv2, too, and perhaps make some artwork.

Attachment
0
foo-V

on commit 6423204:
I can now execute all R instructions from Rv64I, except for the “*W” ones. I am downloading and building https://github.com/riscv/riscv-opcodes.git at compile time, setting this in the Makefile was a bit of a pain but I eventually got it. I will now work on some logging/debugging functions, so that I can test the instructions I add properly, and then work on the Register-Immediate instructions.
I’m not sure if I’m going to “speedrun” a hello-world, or try to support most instructions from the base ISA first, but they should both lead to the same outcome.
All of this is being done while I read the specification, and while it can get quite tedious, I feel way better in RISC-V now, I might even write my next project in raw assembly.

Attachment
0
foo-V

on commit 9611fd6:
I can now load and store values into memory, it respects the permissions set when loading the ELF binary, and fail gracefully both when trying to access out of bound memory or when doing invalid operations (aka writing to a read-only address). Unaligned memory access is also supported, as RISC-V allows that, even if the host machine does not support it. I decided to use C11 to allow for the _Generic macro, and enabled the _GNU_SOURCE macro to use some extensions (they weren’t needed, but I’ll be using other ones further down the road anyways).
I will now implement instruction fetching, and later the decoding

Attachment
0
foo-V

on commit 0e626b8:
I ended up spending the whole day thinking about how to support both 32bit and 64bit applications, I went over C11’s _Generic, void pointers, wider types + type casts, etc, but ended up realizing the best choice was to only support one. Some time was thrown away, but at the very least, I think I made a really good and important design decision, one I could not go back later.

Attachment
0
foo-V

on commit d7c2c71:
Finished implementing the loading of ELF segments, it felt a bit tricky at first but it turns out the format is beautifully designed.
I am using a linked list to represent the segments, it saves the RISC-V addresses it maps, a pointer to the memory that represents it, and the flags (Read/Write/Exec). I might change this design by using mmap, but I’m not sure how I would mark the address space as executable or not (since it should be executable by the emulator, not by the host OS itself). But it seems to work now, therefore I’ll leave this to a future refactor when I have a better idea of how the project as a whole should be designed.

Attachment
0
foo-V

Started working on my project, created the directory structure, added a license, and a Makefile. Will add README further down the road.

Had some problems making GNU Make automatically deduce prerequisites, but it eventually worked.

Attachment
0