[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-94894":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":16,"stars30d":16,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":15,"starSnapshotCount":15,"syncStatus":26,"lastSyncTime":27,"discoverSource":28},94894,"solo","pg83\u002Fsolo","pg83","Portable Linux binaries, solved","",null,"C++",510,11,1,0,373,77.24,"MIT License",false,"main",true,[],"2026-08-24 04:01:22","# SoLo — a `.so` loader for static Linux binaries\n\n[![CI](https:\u002F\u002Fgithub.com\u002Fpg83\u002Fsolo\u002Factions\u002Fworkflows\u002Fci.yml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fpg83\u002Fsolo\u002Factions\u002Fworkflows\u002Fci.yml)\n[![codecov](https:\u002F\u002Fcodecov.io\u002Fgh\u002Fpg83\u002Fsolo\u002Fgraph\u002Fbadge.svg)](https:\u002F\u002Fapp.codecov.io\u002Fgh\u002Fpg83\u002Fsolo)\n\n**Ship one musl-linked executable. At runtime, load the user's existing\nglibc-linked GPU driver. No container, no AppImage, and no second libc in the\nprocess.**\n\nStatic binaries are a wonderfully boring way to deploy software on Linux. We\nbuild ours with [IX](https:\u002F\u002Fgithub.com\u002Fpg83\u002Fix), a source-first build system for\nproducing fully static Linux binaries. The story stays wonderfully boring—right\nup until the application needs the GPU. Vulkan and OpenGL drivers are supplied\nby the host as shared objects, usually built against glibc. A fully static musl\nbinary cannot normally `dlopen()` them.\n\nSoLo crosses that boundary. It provides a `dlfcn`-style source API backed by\nits own x86-64 ELF loader and a glibc ABI bridge implemented on top of musl.\nThe result is still one ordinary static executable, but it can use the graphics\ndriver already installed on the machine.\n\nThe repository includes an end-to-end Vulkan proof: a static executable with\nno `PT_INTERP` and no `DT_NEEDED` loads an unmodified system Mesa ICD, runs a\ncompute shader, and writes the result to a PNG.\n\n**The host keeps the hardware-specific code. You ship everything else.**\n\n## See it work\n\nOn x86-64 Linux, with Python 3 and a C\u002FC++ compiler in `PATH`:\n\n```sh\ngit clone https:\u002F\u002Fgithub.com\u002Fpg83\u002Fsolo.git\ncd solo\n.\u002Fbuild vulkan\n.\u002Fvulkan hello.png\n```\n\nThe last command discovers the distro-installed Vulkan ICD in the usual way\nand produces a 512×512 RGBA image. This is how we build the\n[Shitty release binaries](https:\u002F\u002Fgithub.com\u002Fpg83\u002Fshitty\u002Freleases)—a blazingly\nfast terminal emulator, BTW! To force a particular driver:\n\n```sh\n.\u002Fvulkan --driver \u002Fusr\u002Fshare\u002Fvulkan\u002Ficd.d\u002Fradeon_icd.x86_64.json radeon.png\n.\u002Fvulkan --driver \u002Fusr\u002Fshare\u002Fvulkan\u002Ficd.d\u002Flvp_icd.json lavapipe.png\n```\n\nICD manifest names vary slightly between distributions. Passing no `--driver`\nlets the embedded Khronos loader perform its normal discovery.\n\nYou can verify that the executable itself is not dynamically linked:\n\n```sh\nreadelf -lW .\u002Fvulkan | grep INTERP       # no output\nreadelf -dW .\u002Fvulkan                     # \"There is no dynamic section\"\n```\n\nThis is not a toy call to `vkCreateInstance`. The demo:\n\n1. enters the statically linked Khronos Vulkan loader;\n2. loads the host's Vulkan ICD and its non-glibc dependencies through SoLo;\n3. creates a device, storage buffer, descriptor set, and compute pipeline;\n4. dispatches a checked-in SPIR-V shader;\n5. maps the result and writes it through statically linked libpng.\n\nThe complete example is in [`bin\u002Fvulkan`](bin\u002Fvulkan), and the Vulkan program\nitself is in [`main.cpp`](bin\u002Fvulkan\u002Fmain.cpp).\n\n## How it works\n\n```text\n┌──────────────────── fully static executable ────────────────────┐\n│                                                                 │\n│  application → embedded Vulkan loader → SoLo dlopen\u002Fdlsym       │\n│                                           ├─ x86-64 ELF mapper  │\n│                                           └─ glibc ABI → musl   │\n│                                           │                     │\n└───────────────────────────────────────────┬─────────────────────┘\n                                            │ maps at runtime\n                                            ▼\n                              system Mesa\u002FVulkan ICD.so + DSOs\n```\n\n[`elf_loader.cpp`](lib\u002Felf_loader.cpp) maps ELF segments, walks `DT_NEEDED`,\nresolves versioned symbols, applies x86-64 relocations, supports ELF TLS and\nTLSDESC, materializes IFUNCs, applies RELRO, and runs initializers. Dependencies\nthat are themselves ELF DSOs are loaded recursively.\n\nglibc is deliberately *not* loaded. Imports such as `malloc@GLIBC_2.2.5` are\nresolved by [`glibc_shim.cpp`](lib\u002Fglibc_shim.cpp) to ABI-correct adapters over\nthe process's existing musl runtime. Unsupported glibc functions have unique\ngenerated stubs that fail loudly with the exact symbol and version if they are\never called, instead of silently corrupting the process.\n\nBecause musl sizes its synchronization objects to the glibc ABI of each\narchitecture, the bridge does not shadow them: a `pthread_mutex_t` a driver\ncreates is used in place. A lock is therefore one lock for both the loaded DSO\nand the static executable that may share it, and glibc's static recursive and\nerror-check initializers are adopted on first use.\n\nBefore loading a DSO from disk, SoLo checks its static provider registry. This\nlets an application satisfy a dependency—Wayland, for example—with functions\nalready linked into the executable. `LD_LIBRARY_PATH` and\n`DL_ELF_LIBRARY_PATH` are honored for libraries outside the standard system\ndirectories.\n\nThe interesting pieces are small enough to read:\n\n- [`lib\u002Fdlfcn.cpp`](lib\u002Fdlfcn.cpp) — `dlopen`, `dlsym`, errors, and static providers\n- [`lib\u002Felf_loader.cpp`](lib\u002Felf_loader.cpp) — ELF mapping, symbols, relocations, and TLS\n- [`lib\u002Fglibc_shim.cpp`](lib\u002Fglibc_shim.cpp) — implemented glibc ABI adapters\n- [`lib\u002Fglibc_stubs.cpp`](lib\u002Fglibc_stubs.cpp) — explicit fallbacks for the rest of the ABI\n\n## Use it as a library\n\nThe default target builds the standalone archive:\n\n```sh\n.\u002Fbuild\n```\n\nThe published `.\u002Fdlfcn` symlink points to the resulting `libdlfcn.a`. Include\n[`lib\u002Fdlfcn.h`](lib\u002Fdlfcn.h), link the archive into a musl-static application,\nand ordinary `dlopen()`\u002F`dlsym()` calls are redirected to SoLo. The source tree\nis intentionally self-contained and suitable for copying into another static\nbuild graph.\n\n## Reproduce the experiment\n\n```sh\n.\u002Fbuild test          # load an Arch glibc DSO closure in the smoke test\n.\u002Fbuild vulkan_test   # build the static demo and verify a native Lavapipe PNG\n```\n\nCI performs the native build and test on Alpine\u002Fmusl with GCC, Fedora with GCC,\nand Ubuntu with Clang. The Vulkan test installs each distribution's own\nLavapipe package; it does not run the driver from an Arch sysroot.\n\nEvery build input for the standalone Vulkan executable is vendored under\n`bin\u002Fvulkan`. [`build.py`](build.py) compiles those sources directly: upstream\nCMake, Meson, configure, and Make build systems are not invoked.\n\n\u003Cdetails>\n\u003Csummary>Vendored versions\u003C\u002Fsummary>\n\n- musl 1.2.5 (`0784374d561435f7c787a555aeab8ede699ed298`)\n- LLVM runtimes 15.0.7: libc++, libc++abi, libunwind, and compiler-rt builtins\n  (`8dfdcc7b7bf66834a761bd8de445840ef68e4d1a`)\n- Vulkan Headers 1.4.357 (`e3b1eec08173d6b825cd3ac88c885a63b621504a`)\n- Vulkan Loader 1.4.357 (`5f157b62e333c63260d05d81bf66faa216ab0fb8`)\n- zlib 1.3.2 (`da607da739fa6047df13e66a2af6b8bec7c2a498`)\n- libpng 1.6.50 (`2b978915d82377df13fcbb1fb56660195ded868a`)\n\nLicense files are retained beside the corresponding sources. `shader.inc` is\nthe checked-in SPIR-V form of `shader.comp`, so no shader compiler is required.\n\n\u003C\u002Fdetails>\n\n## How this differs from prior work\n\nIn the general case, only SoLo lets a static application tell the dynamic\nloader: \"for this system DSO's `libwayland` dependency, use the symbols already\nlinked into my executable.\" This lets the application embed the newest\n`libwayland` instead of targeting the oldest version available on every\nsupported system.\n\n- [gcompat](https:\u002F\u002Fgithub.com\u002FStantheman\u002Fgcompat) is a distribution-level\n  glibc API shim for running prebuilt glibc binaries on musl. Its loader stub\n  re-executes the program through musl's dynamic linker with `libgcompat.so`\n  preloaded; using it from a musl program requires linking that shared library\n  or adding it to the loaded DSO's `DT_NEEDED`. It does not give a fully static\n  musl process a dynamic loader. SoLo's self-contained model is stronger: the\n  executable embeds both the ELF loader and ABI bridge, loads unchanged host\n  DSOs without a system compatibility package, preserves the versions of their\n  glibc imports, and lets unused unsupported functions remain behind\n  symbol-specific, fail-loud stubs instead of blocking the entire DSO.\n- [Detour](https:\u002F\u002Fgithub.com\u002Fgraphitemaster\u002Fdetour) bootstraps the system's\n  `ld-linux` and allows multiple C runtimes to coexist. SoLo takes the opposite\n  route: it maps the required DSOs itself and translates their glibc imports\n  onto musl, so a second libc and its TLS state never enter the process.\n- [Cosmopolitan Libc's `cosmo_dlopen()`](https:\u002F\u002Fgithub.com\u002Fjart\u002Fcosmopolitan\u002Fblob\u002Fmaster\u002Flibc\u002Fdlopen\u002Fdlopen.c)\n  follows the same split-runtime scheme as Detour, with all of its advantages\n  and drawbacks: it bootstraps the host's ELF interpreter and libc, then\n  delegates loading the target DSO to the host's `dlopen()`.\n- ClickHouse's experimental [userspace dynamic loader](https:\u002F\u002Fgithub.com\u002FClickHouse\u002FClickHouse\u002Fpull\u002F110125)\n  currently maps ELF objects itself, but stops short of loading glibc. Its\n  proposed path to real-world system libraries such as CUDA is Detour-like:\n  bootstrap the system's `ld.so`, keep a second libc runtime, and swap the\n  musl\u002Fglibc thread pointer at every boundary. SoLo instead implements the\n  glibc ABI over the host's musl runtime and can satisfy DSO dependencies from\n  providers already linked into the static executable.\n- [graphics.gd's `musl` + `dlopen` experiment](https:\u002F\u002Fgithub.com\u002Fquaadgras\u002Fgraphics.gd\u002Fdiscussions\u002F242)\n  follows the same split-runtime model as Detour: an embedded helper brings in\n  the host's glibc loader, and assembly trampolines switch between musl and\n  glibc TLS around foreign calls. This leaves two independent TLS worlds: every\n  boundary crossing needs a trampoline, and a callback implemented in musl\n  cannot be passed safely to glibc code because glibc invokes it while its own\n  TLS is active. SoLo keeps a single musl TLS world instead.\n- Flatpak, AppImage, and containers solve the problem by hiding a small Linux\n  distribution inside or around your program. This works in roughly the same\n  way that moving house solves a missing power adapter. The result is a huge\n  blob full of duplicated libraries, mounts, namespaces, extraction tricks,\n  and runtime indirection—all of which make profiling, debugging, and basic\n  introspection worse. Shipping a distro because you need one system `.so` is\n  not portability. SoLo ships one normal, inspectable executable and borrows\n  the only component that genuinely belongs to the host: its hardware driver.\n\n## Scope\n\n- Linux only, on x86-64 and aarch64. The loader, the TLSDESC and lazy-PLT\n  resolvers, and the initial-exec arena cover both; the glibc symbol\n  inventories are generated per architecture, so `printf@GLIBC_2.2.5` on one\n  is `printf@GLIBC_2.17` on the other without a single translation rule in\n  the code;\n- focused on real Mesa\u002FVulkan ICD dependency closures, and driven by the top\n  1000 Debian library packages by popcon votes: the 885 of them that ship\n  glibc-linked shared objects — about 2100 objects — all load through SoLo\n  in CI on both architectures. Loading is the floor, not the claim: calls\n  into the symbols the bridge still stubs abort loudly, and\n  [dev\u002Fabi-demand.txt](dev\u002Fabi-demand.txt) is the remaining work, ranked by\n  how many installations demand each symbol;\n- a load-once runtime (`dlclose` succeeds but does not unload an image);\n- supporting all four TLS models. Initial-exec variables are placed in a\n  16 KiB surplus arena that rides in the executable's own static TLS, so one\n  process-wide offset is valid in every thread without patching musl. The one\n  restriction: threads created *before* a `dlopen` see zero-initialized TLS\n  for the modules it loaded, so load initial-exec libraries before spawning\n  the threads that use them. An initial-exec module that does not fit the\n  arena fails to load with an error naming the image and the byte counts;\n- explicit about missing ABI coverage: an unimplemented glibc call aborts and\n  names itself.\n\nThe goal is to turn the hard wall between “fully static” and “uses the system\nGPU” into a finite, testable compatibility layer. The Vulkan PNG is the first\nproof that the wall has a door.\n","SoLo 是一个用于静态 Linux 二进制程序的动态库加载器，使 musl 静态链接的可执行文件能在运行时安全加载主机系统中 glibc 编译的 GPU 驱动（如 Vulkan\u002FOpenGL ICD）。其核心功能包括自研 x86-64 ELF 加载器、glibc ABI 兼容桥接层，以及与标准 dlfcn.h 接口兼容的 API；不依赖容器、AppImage 或双 libc 运行环境。适用于需要分发单文件静态程序但又必须调用系统图形驱动的场景，例如跨发行版部署终端模拟器、图形工具或计算应用。",2,"2026-08-17 02:30:12","CREATED_QUERY"]