Skip to content

No emulated board can boot on Windows: the chip model DLL is missing its libstdc++-6.dll dependency #632

Closed

Description

@A13xB0

Found during the #318 human test pass, scenario 9, on the v0.0.8 Windows bundled MSI (Windows 10 Home 22H2, 19045). This is not the compact-build fetcher problem in #514, and it is past the point #516 fixed — the board image downloads fine now.

What happens

No emulated board can boot. Both the internal probe and the ordinary user path fail identically:

> board.probe {"board": "Generic_E22_sx1262"}
boot     failed    engine: firmware on 2 of 3 nodes; 1 failed,
                   first: bc-under-test: firmware started but never connected within 40s
build    passed    image Generic_E22_sx1262_repeater-v1.17.1-d929643-merged.bin
fem/flood/power/radio/rx/tx   untested
> node.set_board  {"node": "Abernethy Repeater", "board": "Generic_E22_sx1262"}
> node.set_firmware {"version": "v1.17.1", "board": "Generic_E22_sx1262", "role": "simple_repeater"}
> node.start
ERROR: engine: firmware on 0 of 1 nodes; 1 failed,
       first: Abernethy Repeater: firmware started but never connected within 40s

Reproducible: 2 of 2 probes, and the user path, with sim.unverified_wiring on. The node then sits down for ever — firmware.state stays at running: 55 of 56.

The cause

nodefs/<node>/emulator.log says it exactly:

Adding SPI flash device
C:\Program Files\MeshBench\qemu\bin\qemu-system-xtensa.exe: sx1262: cannot load the chip
model at C:\Program Files\MeshBench\libvirtualsx1262.dll:
'C:\Program Files\MeshBench\libvirtualsx1262.dll': The specified module could not be found.

and the firmware's own view, from stderr.log:

radio_init: entering std_init
radio_init: std_init returned 1

The DLL is there — C:\Program Files\MeshBench\libvirtualsx1262.dll, 44,087 bytes, exactly where setup.check reports it as ready. "The specified module could not be found" from LoadLibrary means one of the DLL's own dependencies is missing, not the DLL. Parsing its import table:

import resolved
KERNEL32.dll C:\Windows\System32
msvcrt.dll C:\Windows\System32
libstdc++-6.dll not found

libstdc++-6.dll is the MinGW C++ runtime. It is not beside the binary, not under qemu\bin, not in System32, and not on PATH. libvirtualsx1262.dll is the only DLL in the install root.

So: the chip model never loads, the virtual SX1262 never appears on the bus, radio_init fails, the firmware never reaches the engine, and forty seconds later the boot is called a timeout. The timeout message is the last symptom in a chain that starts three steps earlier, which is why this reads as "emulation is flaky" rather than "a file is missing".

It is not QEMU-specific

Renode loads the same library, by path, through LoadLibraryW:

renode-support/peripherals/VirtualSX1262Lib.cs:184
    return OnWindows ? LoadLibraryW(path) : dlopen(path, 2);

So every emulated board on Windows is affected, ESP32 and nRF52 alike — the missing piece is the radio, which both MCU paths share. Both emulators themselves are fine and run standalone:

qemu-system-xtensa.exe -version -> QEMU emulator version 9.2.2 (v9.2.2_meshbench_sx1262_14)
renode.exe --version            -> Renode v1.16.1 (build 1.16.1+20260904gite7196ef71)

Where it comes from

The DLL is a prebuilt asset rather than something this repo compiles — packaging/emulator-pins.env pins CHIPMODEL_RELEASE=v1.3.0, and package.yml unpacks it and keeps one file:

mv dist/meshbench/virtual-sx1262-windows-amd64/lib/libvirtualsx1262.dll dist/meshbench/
rm -rf dist/meshbench/virtual-sx1262-windows-amd64 ...
file dist/meshbench/libvirtualsx1262.dll

I checked whether the runtime was being thrown away by that rm -rf, and it is not — virtual-sx1262-windows-amd64.tar.gz at v1.3.0 contains only:

include/virtual_sx1262.h
lib/libvirtualsx1262.a
lib/libvirtualsx1262.dll
LICENSE  NOTICE.md  README.md

No libstdc++-6.dll. So the upstream asset is incomplete, and the fix belongs in MeshBench/virtual-sx1262 rather than here. This is Windows-only because on Linux and macOS the equivalent runtime is part of the base system; Windows has no system libstdc++.

Suggested fix

Preferred, upstream in MeshBench/virtual-sx1262: link the Windows DLL with -static-libstdc++ -static-libgcc. One 44 kB file with no runtime dependencies beyond KERNEL32/msvcrt, nothing to ship alongside, and nothing to keep in step with the mingw version the asset was built with.

Otherwise: ship libstdc++-6.dll (and whatever it in turn pulls in — typically libgcc_s_seh-1.dll and libwinpthread-1.dll) beside libvirtualsx1262.dll in the asset, and have package.yml copy them across with it.

And a check worth adding here either way. file dist/meshbench/libvirtualsx1262.dll passes on a DLL that cannot load; setup.check reports the row ready on the strength of the path existing. Something that walks the import table, or simply tries to LoadLibrary it, would have failed this release rather than shipping it — the same shape as the bundle-carries-the-font check added for #608, which did exactly this job one release ago.


Filed from the #318 pass. 🤖 Generated with Claude Code

Activity

  1. added
    bugSomething isn't working
    P0Release blocking: fix before anything else
    windowsOnly reproduces on Windows, or needs a Windows machine to verify
    on Sep 6, 2026
  2. A13xB0 commented on Sep 6, 2026

    @A13xB0
    CollaboratorAuthor

    Confirmed on a second board, and here is what it looks like from the front.

    Heltec_v3 (ESP32-S3, SSD1306) fails identically to Generic_E22_sx1262 — different MCU, different flash size (8 MB against 4 MB), same wall:

    nodefs/Abernethy_Repeater/emulator.log
      Adding SPI flash device
      qemu-system-xtensa.exe: sx1262: cannot load the chip model at
      C:\Program Files\MeshBench\libvirtualsx1262.dll: The specified module could not be found.
    
    nodefs/Abernethy_Repeater/stderr.log
      radio_init: entering std_init
      radio_init: std_init returned 1
    

    So it is not board-specific, as expected for a missing radio model.

    What a user sees

    The Hardware tab is where this surfaces, and to its credit it surfaces it well — the failure is right there under last words:

    hardware tab

    The board screen panel is drawn, correctly sized and labelled, and correctly dark:

    board screen

    SSD1306 128x64 / I2C 0x3C / **not powered**, the TX lamp on pin 35, the PRG button on pin 0, the battery meter on pin 1, and the caption that the panel is what the firmware drew rather than what the glass looks like. All of that is right. It is just permanently unlit on Windows, because the firmware behind it never boots.

    So the board screen cannot be photographed on Windows at all — not for the #318 capture sweep, and not for the docs. board.screenshot and board.screen both refuse (is not running), and node.boardview refuses with "runs a host build rather than a board image", because a boot that failed never commits the board image: the node keeps firmware_board: "" and falls back.

    That last part is worth noting for whoever fixes this — the three verbs that would show a board all refuse for reasons that describe the symptom rather than the cause, so from the outside this reads as "this node is not set up" rather than "the emulator could not load its radio". The one place the real reason appears is last words on the Hardware tab, and only after you have already tried and failed to start it.

    One smaller thing found alongside

    Clicking Board view… on the Hardware tab does nothing at all — no window, and ui.said is empty afterwards. Over the socket the same request answers with a proper reason. A button that is drawn enabled, highlights on hover, and then neither acts nor explains is worse than a disabled one; it could carry the sentence node.boardview already returns.

  3. A13xB0 commented on Sep 6, 2026

    @A13xB0
    CollaboratorAuthor

    Fix opened upstream: MeshBench/virtual-sx1262#1.

    To answer the question of where this belongs, since both the bundle and the pipeline were reasonable suspects: it is neither. It is the chip model's own build flags.

    • virtual-sx1262's build.sh links the shared library's runtime dynamically on every platform. Correct on Linux and macOS, wrong on Windows, where the result only loads inside an MSYS2 environment.
    • Its release.yml stages exactly what that build produces, and this repo's package.yml ships exactly what it unpacks. Nothing is dropped anywhere along the chain. I rebuilt v1.3.0's source locally with the unmodified flags and got a DLL whose imports match the shipped one byte for byte in kind — so the released artefact is faithfully what the build asked for. The build asked for the wrong thing.

    Also worth recording, because it is the obvious first fix and it does not work: shipping the runtime beside libvirtualsx1262.dll from this repo would not help. Windows resolves a dynamically loaded DLL's imports from the loading process's directory, not from the directory of the DLL being loaded. I put libstdc++-6.dll, libgcc_s_seh-1.dll and libwinpthread-1.dll next to it in a real v0.0.8 install and the load failed identically. A copy would have to live in qemu/bin and wherever Renode is unpacked, kept in step with the toolchain that built the model. So there is no sensible package.yml-side fix, which is why the PR is upstream.

    Verified end to end on this machine: built with -static, dropped in place of the shipped v1.3.0 DLL, probe cache cleared.

    board.probe Generic_E22_sx1262
      before   boot  failed   firmware started but never connected within 40s
      after    boot  passed   attached and answering
    

    and the node then pins its board image properly — firmware_board: "Heltec_v3", which stayed empty for as long as the boot was failing — with board.screen reporting a real 128x64 SSD1306 instead of refusing.

    What this issue still needs after that PR merges, since the PR alone does not close it: a v1.3.1 release of virtual-sx1262, and CHIPMODEL_RELEASE in packaging/emulator-pins.env bumped from v1.3.0 to it. This repo pins a release rather than a commit, so nothing changes here until both happen.

    One thing I would suggest adding on this side regardless of the upstream fix. package.yml runs file on the DLL, which passes happily on one that cannot load, and setup.check reports the toolchain row ready on the strength of the path existing — both were green through this entire bug. The upstream PR adds an import check at the point of building; the equivalent here is cheap and would catch a bad artefact arriving from anywhere, not just from a build we control.

    🤖 Generated with Claude Code

  4. added a commit that references this issue on Sep 6, 2026
    bf96b95
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Before First ReleaseP0Release blocking: fix before anything elsebugSomething isn't workingwindowsOnly reproduces on Windows, or needs a Windows machine to verify

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions