Repository navigation
build: the Windows DLL carries its own runtime - #1
Conversation
The shared build linked libstdc++ dynamically everywhere. On Linux and
macOS that is right - the runtime is part of the base system and a plugin
should share the host's copy. On Windows it produced a DLL that only
loads inside an MSYS2 environment, and nothing that loads it has one.
Both emulators open this library at run time on an ordinary machine: QEMU
through LoadLibrary, Renode through LoadLibraryW behind a P/Invoke. There
the load fails, and the message names us rather than what we are missing:
qemu-system-xtensa.exe: sx1262: cannot load the chip model at
...\libvirtualsx1262.dll: The specified module could not be found.
which is why it reads as a corrupt or absent library rather than an
absent runtime. Downstream it surfaces three steps later still, as
"firmware started but never connected within 40s" - a radio that never
answers, because the model behind it was never loaded. MeshBench#632.
Shipping libstdc++-6.dll beside this file does not fix it, which is worth
recording because it is the obvious first try: Windows resolves a
dynamically loaded DLL's imports from the loading *process's* directory,
not from the directory of the DLL being loaded. Tested - the runtime sat
next to libvirtualsx1262.dll and the load failed identically. A copy would
have to live beside every host that opens us, in QEMU's directory and
wherever Renode is unpacked, and be kept in step with the toolchain that
built us. Static is the only version that travels.
-static rather than -static-libstdc++, because -static-libstdc++ alone
leaves libwinpthread-1.dll behind it: libstdc++'s threading support pulls
it in, so the two have to go together. Imports before and after, on
MINGW64 gcc 16.1.0:
before KERNEL32.dll msvcrt.dll libstdc++-6.dll
after KERNEL32.dll msvcrt.dll
Both of those are on every supported Windows. The DLL goes from 112 kB to
666 kB, which is the whole of the fix's cost.
And the build now checks. What this looked like from the outside was a
green build, passing tests, an artefact of the right size, and a library
nothing could open - so the one thing that would have caught it is the
build saying what its own product depends on. check_shared_imports fails
the build on any import beyond kernel32 and msvcrt, and was tested in
both directions: it passes on the fixed link and fails on the old one,
naming libstdc++-6.dll.
Verified end to end against MeshBench v0.0.8 on Windows 10 22H2. Built
with this change, dropped in place of the shipped v1.3.0 DLL, and the
board that could not boot boots:
board.probe Generic_E22_sx1262
before boot failed firmware started but never connected within 40s
after boot passed attached and answering
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSb8bN9gtL72cXAxtyTbAG
|
CI: everything passes including windows / mingw, and the new guard ran there on your own runner rather than only on my machine:
That is the check working as written: The same argument applies more strongly to a pull request than to a push, so as it stands the job goes red on every PR to this repository regardless of what the PR does — a check that cannot pass is one people learn to scroll past, which is the opposite of what it is for. Two options, neither of which belongs in this PR:
The follow-up this is correctly reminding us about is real either way: after this merges, |
v1.3.0's Windows DLL imported libstdc++-6.dll. That exists inside the MSYS2 environment it was built in and almost nowhere else, so on any ordinary Windows machine neither emulator could open the chip model: qemu-system-xtensa.exe: sx1262: cannot load the chip model at ...\libvirtualsx1262.dll: The specified module could not be found. which names the DLL rather than the dependency behind it. Renode reaches the same library through LoadLibraryW and failed the same way, so this was every emulated board on Windows, ESP32 and nRF52 alike. It surfaced three steps downstream as "firmware started but never connected within 40s" - a radio that never answers, because the model behind it never loaded. Nothing was lost in packaging: the release staged what build.sh produced and we shipped what we unpacked. The build asked for a dynamic runtime on a platform that has none. MeshBench/virtual-sx1262#1 links it statically; v1.3.1 is that build, same ABI and same model, and imports only kernel32 and msvcrt. Verified against the published artefact rather than a local build: the v1.3.1 windows tarball dropped into a v0.0.8 install, probe cache cleared. board.probe Generic_E22_sx1262 v1.3.0 boot failed firmware started but never connected within 40s v1.3.1 boot passed attached and answering The Windows tarball grows from 28 kB to 143 kB, which is the static runtime and the whole cost of the fix. The catalogue's digests and sizes move with the pin, as the rule at the top of emulator-pins.env requires. Closes #632. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RSb8bN9gtL72cXAxtyTbAG
The pin was two commits behind and the consumers check in that repository was saying so on every run. It now names the merge of MeshBench/virtual-sx1262#1. What that commit changes is the upstream build.sh: the Windows shared library is linked statically, because linked the default way it imported libstdc++-6.dll and neither emulator could open it on an ordinary Windows machine (MeshBench#632). Nothing in src/ moved. So this bump is a no-op for what this repository produces. build.sh here compiles the model from vendor/virtual-sx1262/src rather than loading the shared library, and already links -static -static-libgcc -static-libstdc++ for Windows, so the fault never reached the native firmware and the fix does not change it either. It is worth taking anyway. A submodule is a commit and nothing moves it on its own, so a pin left behind stays behind - and the next change upstream that does touch src/ should arrive on its own rather than stacked behind an unrelated lag nobody has looked at. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RSb8bN9gtL72cXAxtyTbAG
Fixes the root cause of MeshBench#632: no emulated board can boot on Windows, because the chip model cannot be loaded.
What is wrong
build_sharedlinked the runtime dynamically on every platform. On Linux and macOS that is correct — libstdc++ is part of the base system and a plugin is better off sharing the host's copy. On Windows it produces a DLL that only loads inside an MSYS2 environment, and nothing that opens it has one.Both emulators open this library at run time on an ordinary machine — QEMU through
LoadLibrary, Renode throughLoadLibraryWbehind a P/Invoke — and there it fails:The message names us rather than the dependency we are missing, which is why it reads as a corrupt or absent library. Downstream in MeshBench it surfaces three steps later still, as
firmware started but never connected within 40s— a radio that never answers, because the model behind it was never loaded.Why the artefact is not at fault, and neither is MeshBench's pipeline
Worth stating, because both were suspected first.
release.ymlstages exactly whatbuild.shproduces, and MeshBench'spackage.ymlships exactly what it unpacks. Nothing is dropped along the way. I rebuilt v1.3.0's source locally with the unmodified flags and got a DLL with imports identical to the shipped one, so the released file is faithfully what this repo asked for. The build asked for the wrong thing.Why not ship the runtime instead
The obvious first try, and it does not work — recorded here so nobody spends the afternoon on it. 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.dllandlibwinpthread-1.dllnext tolibvirtualsx1262.dllin a real MeshBench install and the load failed identically. A copy would have to live beside every host that opens us — QEMU'sbin, wherever Renode is unpacked — and be kept in step with the toolchain that built us.-staticrather than-static-libstdc++: the latter alone leaveslibwinpthread-1.dllbehind it, because libstdc++'s threading support pulls it in.The change
One line of build flags, scoped to the platform that needs it, plus a guard.
KERNEL32.dll,msvcrt.dll,libstdc++-6.dllKERNEL32.dll,msvcrt.dllBoth remaining ones are on every supported Windows. The DLL goes from 112 kB to 666 kB, which is the whole cost.
check_shared_importsfails the build on any import beyond those two. This bug looked like a green build, passing tests, an artefact of the right size and a library nothing could open — so the build saying what its own product depends on is the one thing that would have caught it. Tested in both directions, since a check nothing tests can quietly stop checking:Verification
On Windows 10 Home 22H2 (19045), MSYS2 MINGW64 gcc 16.1.0:
STRICT=1 ./build.sh— full build and both test binaries, 0 failures.boot:The node then pins its board image properly (
firmware_board: "Heltec_v3", which stayed empty while the boot was failing) andboard.screenreports a real 128x64 SSD1306 rather than refusing.After this merges
MeshBench needs a release of this repo to pin:
CHIPMODEL_RELEASEinpackaging/emulator-pins.envisv1.3.0today. Cutting v1.3.1 and bumping that pin is what actually closes MeshBench#632 — this PR only makes the artefact correct.Not addressed here: the same
-staticquestion for the Linux.soand macOS.dylib. I left both dynamic deliberately, but if a host is ever expected to load us somewhere without a system libstdc++, that is the conversation to have.🤖 Generated with Claude Code
https://claude.ai/code/session_01RSb8bN9gtL72cXAxtyTbAG