展示 HN:用 C 编写的微型 VM 沙箱,应用程序用 Rust、C 和 Zig 编写
Show HN: Tiny VM sandbox in C with apps in Rust, C and Zig

原始链接: https://github.com/ringtailsoftware/uvm32

## uvm32:适用于资源受限系统的微型虚拟机 uvm32是一个极简、无依赖的虚拟机,专为微控制器和嵌入式设备设计。它以单个C99文件实现,不进行动态内存分配,在STM32L0上拥有小于4KB flash/1KB RAM的占用空间。 它充当RISC-V模拟器,并提供管理接口,能够执行用C、Zig、Rust和汇编编写的代码。uvm32为不可信代码提供安全的沙箱,为无法使用原生编译器的情况提供开发环境,并提供“一次编写,随处运行”的解决方案。 与传统的脚本引擎不同,uvm32优先考虑**安全性和小尺寸**,而非速度和易用性。它专为定制逻辑而设计,而非完整的硬件模拟,并防止恶意字节码导致主机崩溃。 主要特性包括非阻塞设计、简单的FFI以及对各种编程范式的灵活性。示例应用程序展示了其功能,并提供了一个最小的主机示例,方便集成。该项目采用MIT许可,鼓励在研究、产品和嵌入式系统中使用。

## C语言实现的微型虚拟机沙箱 - 摘要 一个名为`uvm32` (github.com/ringtailsoftware)的新项目在Hacker News上分享:一个用C语言编写的微型虚拟机沙箱,能够运行用Rust、C和Zig构建的应用程序。它基于RISC-V指令集架构。 讨论集中在其与WebAssembly (WASM) 的关系上,两者都旨在创建通用的执行环境。虽然WASM受益于硬件加速,但`uvm32`的RISC-V基础提供了稳定性和潜在的简单性,但解码速度可能较慢。 有人将其与谷歌的Wasefire项目进行了比较,后者似乎代码量更小。项目作者指出设计目标不同。一个有趣的细节:该项目包含一个用Zig编写的“rats”应用程序。最终,`uvm32` 贡献于为通用虚拟机构建应用程序的持续趋势,现代网络已经体现了这一方向。
相关文章

原文

uvm32 is a minimalist, dependency-free virtual machine sandbox designed for microcontrollers and other resource-constrained devices. Single C file, no dynamic memory allocations, asynchronous design, pure C99.

On an STM32L0 (ARM Cortex-M0+) the required footprint is under 4KB flash/1KB RAM.

uvm32 is a RISC-V emulator, wrapped in a management interface and provided with tools to build efficient code to run in it.

  • As a no-frills alternative to embedded script engines (Lua, Duktape, MicroPython, etc)
  • As a sandbox to isolate untrusted or unreliable elements of a system
  • As a way to allow development in modern systems programming languages where a compiler for the target may not be available (rust-hello)
  • As a way to write once, run anywhere and avoid maintaining multiple software variants
  • Bytecode example apps written in C, Zig, Rust and assembly
  • Non-blocking design, preventing misbehaving bytecode from stalling the host
  • No assumptions about host IO capabilities (no stdio)
  • Simple, opinionated execution model
  • Safe minimally typed FFI
  • Small enough for "if this then that" scripts/plugins, capable enough for much more
  • Aims for safety over speed, bad code running in the VM should never be able to crash the host

Although based on a fully fledged CPU emulator, uvm32 is intended for executing custom script like logic, not for simulating hardware.

How does it compare to the alternatives?

Many scripting languages and virtual machines are available for embedding in small systems and they all make tradeoffs in different dimensions.

uvm32 aims for:

  • Small footprint (suitable for embedded devices, games and apps)
  • Support well-known programming languages for VM code (with high quality dev tools)
  • Ease of integration into existing software
  • Flexibility of paradigm (event driven, polling, multi-processor)
  • Robustness against misbehaving VM code

uvm32 does not aim for:

  • Frictionless FFI (no direct function calls between host and VM code)
  • Maximum possible efficiency
  • The simplest scripting experience for VM code (a develop-compile-run cycle is expected)
  • "Batteries included" libraries to do stdio, networking, etc

uvm32 is a tiny virtual machine, all of the code is in uvm32.

A minimal example of a host to run code in is at host-mini.

Everything else is a more advanced host example, or a sample application which could be run in a host.

A simple VM host from host-mini

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "uvm32.h"
#include "uvm32_common_custom.h"

uint8_t rom[] = { // mandel.bin
  0x23, 0x26, 0x11, 0x00, 0xef, 0x00, 0xc0, 0x00, 0xb7, 0x08, 0x00, 0x01,
  ...
  ...
};

int main(int argc, char *argv[]) {
    uvm32_state_t vmst;
    uvm32_evt_t evt;
    bool isrunning = true;

    uvm32_init(&vmst);
    uvm32_load(&vmst, rom, sizeof(rom));

    while(isrunning) {
        uvm32_run(&vmst, &evt, 100);   // num instructions before vm considered hung

        switch(evt.typ) {
            case UVM32_EVT_END:
                isrunning = false;
            break;
            case UVM32_EVT_SYSCALL:    // vm has paused to handle UVM32_SYSCALL
                switch(evt.data.syscall.code) {
                    case UVM32_SYSCALL_PUTC:
                        printf("%c", uvm32_arg_getval(&vmst, &evt, ARG0));
                    break;
                    case UVM32_SYSCALL_PRINTLN: {
                        const char *str = uvm32_arg_getcstr(&vmst, &evt, ARG0);
                        printf("%s\n", str);
                    } break;
                    case UVM32_SYSCALL_YIELD:
                    break;
                    default:
                        printf("Unhandled syscall 0x%08x\n", evt.data.syscall.code);
                    break;
                }
            break;
            case UVM32_EVT_ERR:
                printf("UVM32_EVT_ERR '%s' (%d)\n", evt.data.err.errstr, (int)evt.data.err.errcode);
            break;
            default:
            break;
        }
    }

    return 0;
}
  • VM hosts
    • host vm host which loads a binary and runs to completion, handling multiple syscall types
    • host-mini minimal vm host (shown above), with baked in bytecode
    • host-parallel parallel vm host running multiple vm instances concurrently, with baked in bytecode
    • host-arduino vm host as Arduino sketch (make test to run AVR code in qemu)
  • C sample apps
    • apps/helloworld C hello world program
    • apps/heap Demonstration of malloc() on extram in C
    • apps/conio C console IO demo
    • apps/lissajous C console lissajous curve (showing softfp, floating point)
    • apps/maze C ASCII art recursive maze generation
    • apps/fib C fibonacci series program (iterative and recursive)
    • apps/sketch C Arduino/Wiring/Processing type program in setup() and loop() style
  • Rust sample apps
  • Zig sample apps
  • Assembly sample apps
  • VM host as an app
    • apps/self host-mini with embedded mandelbrot generation program, compiled as an app (VM running VM)

The code in uvm32 to build a VM host is very portable and requires only a C compiler. However, many of the examples provided show how to build target code with different languages and tools. A Dockerfile is provided to set up the required environment.

make dockerbuild
make dockershell

Then, from inside the docker shell

make

./hosts/host/host apps/helloworld/helloworld.bin

host is the command line test VM for running samples. Run host -h for a full list of options.

The best source of information is the header file uvm32/uvm32.h and the tests.

Also see doc/README.md

This project is licensed under the MIT License. Feel free to use in research, products and embedded devices.

联系我们 contact @ memedata.com