6.2 KiB
Building QEMU from source for Velxio
The Velxio docker image ships with prebuilt libqemu-xtensa and
libqemu-riscv32 libraries so ESP32 / ESP32-S3 / ESP32-C3 simulation
works out of the box. If you'd rather not use the prebuilts —
whether for licensing reasons, audit requirements, or simply because
you prefer to compile what you run — this guide walks you through
building both libraries from the upstream
lcgamboa/qemu fork.
Velxio itself is AGPLv3, and that includes the binary linker contract: you have the source, you can rebuild, you can verify the chain end-to-end.
The prebuilts at
velxio.dev/license/signupand on GitHub Releases are a convenience, not a gate. Everything in this guide produces the exact same.so/.dll/.dylibfiles those bundles contain, byte-for-byte modulo timestamps.
Audience
This guide assumes a Linux or macOS workstation with a working C
toolchain. Windows builds are possible via MSYS2 / MinGW but are
out of scope here — start from lcgamboa/qemu's own README if you
need them.
What you'll produce
| File | Architecture | Used for |
|---|---|---|
libqemu-xtensa.so |
Xtensa LX6 / LX7 | ESP32, ESP32-S3, ESP32-CAM, Arduino Nano ESP32 |
libqemu-riscv32.so |
RISC-V RV32IMC | ESP32-C3, XIAO ESP32-C3, CH32V003 |
The Velxio backend dlopen's these at runtime through the dynamic
linker (backend/app/services/qemu_runtime.py). Drop the freshly
built files into /app/lib/ inside the container (or the host
path that mounts to it) and Velxio will use them on the next
simulation start.
1. Clone the fork
git clone https://github.com/lcgamboa/qemu.git
cd qemu
git checkout 822927b6 # the commit Velxio's prebuilts are anchored to
The exact commit ID may move forward over time. The Velxio docker
image's Dockerfile.standalone records the canonical SHA — grep -E 'qemu.*checkout|QEMU_REF' Dockerfile.standalone to read it.
2. Install build dependencies
Debian / Ubuntu:
sudo apt-get update
sudo apt-get install -y \
git ninja-build pkg-config \
libglib2.0-dev libpixman-1-dev \
python3 python3-venv python3-pip \
flex bison
Arch / Manjaro:
sudo pacman -S --needed \
git ninja pkgconf \
glib2 pixman \
python flex bison
macOS (Homebrew):
brew install ninja pkg-config glib pixman
3. Configure and build — Xtensa (ESP32 / ESP32-S3)
mkdir build-xtensa && cd build-xtensa
../configure \
--target-list=xtensa-softmmu \
--disable-werror \
--enable-shared-lib \
--disable-tools \
--disable-docs
ninja
This produces libqemu-xtensa.so under build-xtensa/. Verify:
file libqemu-xtensa.so
# ELF 64-bit LSB shared object, x86-64, dynamically linked
ls -lh libqemu-xtensa.so
# ~46 MB on Linux x86_64
Go back to the repo root:
cd ..
4. Configure and build — RISC-V (ESP32-C3)
mkdir build-riscv32 && cd build-riscv32
../configure \
--target-list=riscv32-softmmu \
--disable-werror \
--enable-shared-lib \
--disable-tools \
--disable-docs
ninja
cd ..
You now have build-riscv32/libqemu-riscv32.so.
5. Drop the binaries into Velxio
If you self-host Velxio via the official docker image:
docker cp build-xtensa/libqemu-xtensa.so velxio:/app/lib/libqemu-xtensa.so
docker cp build-riscv32/libqemu-riscv32.so velxio:/app/lib/libqemu-riscv32.so
docker restart velxio
If you're running Velxio from source:
cp build-xtensa/libqemu-xtensa.so /path/to/velxio/backend/app/lib/
cp build-riscv32/libqemu-riscv32.so /path/to/velxio/backend/app/lib/
# restart the backend
The next ESP32 / ESP32-C3 simulation start will use your libraries. Check the backend logs for a line like:
[qemu_runtime] loaded libqemu-xtensa.so build=<your-hash>
6. ESP32 ROM blobs
The QEMU build does not produce the ESP32 ROM dumps Velxio also
needs (esp32-v3-rom.bin, esp32-v3-rom-app.bin, esp32c3-rom.bin).
Those come straight from Espressif's open-source toolchain and are
redistributable verbatim. The image already includes them at
/app/lib/; you only need to replace them if you're working from a
custom esp-idf version.
7. Troubleshooting
No such file or directory: glib-2.0 — apt missed
libglib2.0-dev. Re-run the dependencies step.
error: ‘CONFIG_USER_ONLY’ is not defined — you forgot
--target-list=. The softmmu suffix is required for the system
emulator Velxio uses.
Shared library is too small (a few hundred KB) — you built
without --enable-shared-lib. The default QEMU output is the
qemu-system-* binary, not the library Velxio loads.
Simulation starts but the board boots into "qemu: fatal: Trying to
execute code outside RAM or ROM" — wrong commit. lcgamboa/qemu
master moves; Velxio is pinned. Check out the commit listed in
Dockerfile.standalone.
License notes
lcgamboa/qemu is GPL-2.0 (same as upstream QEMU). Velxio is
AGPLv3. The dlopen boundary keeps the two licenses orthogonal:
QEMU stays GPL'd, Velxio stays AGPL'd, neither contaminates the other.
If you distribute a modified Velxio image with self-built QEMU
binaries, you owe the QEMU sources to your recipients (GPL-2.0
obligation), and you owe the Velxio modifications under AGPLv3 if
the deployment is networked.
Why use the prebuilts at all?
For ~99% of self-hosters the prebuilts at
velxio.dev/license/signup
or the GitHub release are the path of least resistance — they're
the same files this guide produces, signed-by-sha256 in their
manifest, ready to drop in. The build itself takes 15-30 minutes
on a modern laptop and ~3 GB of disk for the build tree.
Building from source matters when:
- You're auditing the supply chain for a regulated deployment.
- You need to patch QEMU (e.g. add a peripheral the fork doesn't emulate) and want to ship the patched library.
- You don't want any third-party prebuilts on your machine.
- You're on a platform we don't ship a binary for (e.g. BSD, ARM on macOS Intel boxes, exotic libc).
All four are legitimate. The license module on velxio.dev exists to distribute prebuilts conveniently and to detect bulk-abuse patterns; it never tries to be the only path.