Author and run QEMU system emulation as an embedded-test target - qemu-system-arm / qemu-system-aarch64 / qemu-system-riscv32 launching cross-compiled ELF binaries on virtual MCUs and SoCs. Covers machine selection (-M virt / mps2-an385 / mps2-an386 / mps2-an500 / mps2-an511 / mps3-an524 / lm3s6965evb / raspi3b / xilinx-zynq-a9), CPU selection (-cpu cortex-m0 / cortex-m3 / cortex-m4 / cortex-m33 / cortex-a15 / cortex-a57 / max), -kernel ELF load, -nographic + -serial stdio, ARM semihosting via -semihosting-config enable=on,target=native (so cross-compiled GoogleTest / Unity binaries print to host stdio and exit with the test return code), GDB stub via -S -gdb tcp::1234, QMP monitor via -qmp tcp:host:port for automated test orchestration, and CI wiring. Use when host-only test runs are insufficient and the team wants arch-correct (endianness / alignment / interrupt-vector) behaviour on a virtual MCU without committing to physical hardware-in-loop.
65
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
QEMU system emulation (per
qemu.org system docs)
runs cross-compiled ELF test binaries on virtual MCUs and SoCs.
The embedded-relevant binaries are qemu-system-arm,
qemu-system-aarch64, qemu-system-riscv32, and
qemu-system-riscv64.
In an embedded test pipeline QEMU sits between the host build
(fast, but misses arch-specific behaviour) and the physical
hardware-in-loop rig (slow, expensive). A cross-compiled
GoogleTest or Unity binary runs under QEMU with semihosting; the
test's main() returns the failure count; QEMU exits with that
code; CI gates on it.
Composes with:
googletest-embedded-arm - cross-compiled C++ test binary.unity-test-framework-c - cross-compiled C test binary.ceedling-build-runner - Ceedling project configured for ARM cross-compile.hardware-in-loop-reference - the next rung up the V-cycle (PIL / HIL).QEMU does not emulate analog I/O, sensor wiring, or
real-time timing precisely - for that, escalate to a real HIL
rig per
hardware-in-loop-reference.
Match the machine to the CPU profile: mps2-* boards for
M-profile cores, virt for A-profile and RISC-V. The common
Cortex-M mapping:
| Test target | QEMU command | Typical -cpu |
|---|---|---|
| Cortex-M0 / M0+ | qemu-system-arm -M mps2-an385 | cortex-m0 |
| Cortex-M3 | qemu-system-arm -M mps2-an385 | cortex-m3 (board default) |
| Cortex-M4 | qemu-system-arm -M mps2-an386 | cortex-m4 |
The full machine list, the A-profile / Raspberry Pi / Xilinx
targets, and the max CPU note are in
references/boards.md.
For an embedded test, load the ELF with -kernel and send serial
to the console with -nographic. The full invocation flag table
(-M, -cpu, -smp, -m, -bios, -serial, -monitor,
-qmp), per
invocation.html,
is in references/flags.md.
The critical flag for cross-compiled test binaries:
-semihosting-config [enable=on|off][,target=native|gdb|auto][,chardev=name][,arg=string]Per the same invocation page, semihosting "allows direct host
system calls and I/O operations". When the test ELF was linked
with --specs=rdimon.specs, printf and exit() from the
binary go through ARM semihosting calls; with
-semihosting-config enable=on,target=native QEMU services those
calls against the host.
Practically: the test binary's main() returns the failure
count; the C runtime calls _exit(rc); QEMU exits with rc. CI
gates on $?.
The test side is covered in
googletest-embedded-arm
and
unity-test-framework-c;
the canonical Cortex-M4 build:
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -O0 -g \
--specs=rdimon.specs \
test/test_ringbuffer.c test/test_ringbuffer_Runner.c \
src/ringbuffer.c ext/Unity/src/unity.c \
-o build/test.elf -lrdimon-lrdimon links the ARM semihosting library
(developer.arm.com GNU Toolchain).
--specs=rdimon.specs pulls in startup code that wires printf
/ _write / _exit to the semihosting interface.
qemu-system-arm -M mps2-an386 -cpu cortex-m4 \
-nographic \
-semihosting-config enable=on,target=native \
-kernel build/test.elfOutput (Unity-style):
test/test_ringbuffer.c:34:test_ringbuffer_initially_empty:PASS
test/test_ringbuffer.c:42:test_ringbuffer_push_increments_size:PASS
-----------------------
2 Tests 0 Failures 0 Ignored
OKExit code: 0 (or the failure count for Unity / GoogleTest).
Per the invocation page, -d "activates debug logging for
specified subsystems":
qemu-system-arm -M mps2-an386 -cpu cortex-m4 \
-nographic -semihosting-config enable=on,target=native \
-d int,cpu_reset,unimp \
-kernel build/test.elf 2> qemu-debug.log-d help lists the available items. int traces interrupts;
cpu_reset traces resets; unimp traces unimplemented
features (board-quirk hunts).
# Terminal 1
qemu-system-arm -M mps2-an386 -cpu cortex-m4 -nographic \
-semihosting-config enable=on,target=native \
-S -gdb tcp::1234 \
-kernel build/test.elf
# Terminal 2
arm-none-eabi-gdb build/test.elf \
-ex "target remote :1234" \
-ex "b main" \
-ex "continue"-S "freezes the CPU at startup"; -gdb tcp::1234 "opens a GDB
stub on the specified device" (per the invocation page).
Per the same invocation reference, -qmp tcp:host:port[,server, nowait] exposes the QEMU Machine Protocol over TCP as JSON-RPC.
For a CI orchestration that needs to inject faults mid-test:
qemu-system-arm -M mps2-an386 -cpu cortex-m4 -nographic \
-semihosting-config enable=on,target=native \
-qmp tcp:localhost:4444,server,nowait \
-kernel build/test.elfA test harness then connects to localhost:4444 and issues
{"execute":"query-status"}, {"execute":"stop"},
{"execute":"cont"} - useful for staged fault injection that
mirrors the HIL fault-injection patterns.
Semihosting's _exit(rc) propagates rc through QEMU. A test
binary linked with --specs=rdimon.specs and ending in:
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_x);
return UNITY_END(); /* failure count */
}…makes qemu-system-arm ... -kernel test.elf; echo $? return
the failure count. CI gates on the exit code directly.
For Unity / GoogleTest, the canonical line format reaches host
stdout via semihosting → QEMU stdio. Grep / tee / pipe-to-JUnit
the same way as a host run - see
unity-test-framework-c
and
googletest-embedded-arm.
QEMU emulation is not real-time. For a test that asserts on
wall-clock latency, the result is wrong - QEMU executes
faster than physical hardware for compute-bound code and slower
for I/O-heavy code. For real-time-sensitive tests, escalate to
hardware-in-loop-reference.
The canonical GitHub Actions pipeline (install toolchain, cross-build, run under two CPU profiles) is in references/ci.md. The "run under multiple CPUs" pattern is the cheap way to catch CPU-feature regressions - float vs no-float, ARMv6-M vs ARMv7-M.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Forgetting -semihosting-config enable=on,target=native | Test prints nothing; QEMU never exits | Always set both -semihosting-config enable=on and target=native (or auto) |
-kernel pointed at a stripped binary | QEMU loads but symbol info gone for debug | Keep -g debug info; strip only the release binary |
| Asserting on wall-clock timing under QEMU | QEMU is not real-time | Move timing assertions to HIL; QEMU asserts only logical correctness |
Using -M virt for a Cortex-M test | virt is Cortex-A - wrong instruction set | Match machine to CPU profile (mps2-* for M-profile, virt for A-profile) |
| Not pinning the QEMU version | Newer QEMU may emulate differently; CI flakes | Pin qemu-system-arm version in the runner image |
Skipping -cpu and relying on board default | Board defaults shift between QEMU versions | Always specify -cpu explicitly |
Reading -monitor stdio and -nographic together without -serial mon:stdio | Serial and monitor share stdio; output garbles | Use -monitor none -serial stdio for clean output |
| Running QEMU in CI without a sane timeout | A hung test ELF runs forever | Wrap in timeout 60 qemu-system-arm ... |
max CPU diverges from production. The max CPU is per
the cpu-features page "available for comprehensive feature
testing"; passing on max does not mean passing on the
production part.--specs=rdimon.specs; the
semihosting bkpt is a debug-only path.Cited inline. Foundational documents:
max, named CPUs) - www.qemu.org/docs/master/system/arm/cpu-features.html.--specs=rdimon.specs, semihosting) - developer.arm.com Tools and Software / GNU Toolchain.googletest-embedded-arm,
unity-test-framework-c,
ceedling-build-runner,
hardware-in-loop-reference,
embedded-coverage-strategy-reference.