# SoLo: loading glibc-linked GPU drivers from a static musl binary

> SoLo is an ELF loader and glibc ABI bridge for fully static Linux binaries. It lets a musl-linked executable dlopen the host's Vulkan driver without pulling a second libc into the process.

**pg83/solo** — Portable Linux binaries, solved

- Repository: https://github.com/pg83/solo
- Stars: 641 · Forks: 14
- Language: C++
- License: MIT
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/pg83-solo

## The static binary that needs a driver it cannot link

A static musl executable is one file with no dependency resolution at startup. That property survives right up to the point where the program wants the GPU. Vulkan and OpenGL drivers arrive as shared objects supplied by the host, usually built against glibc, and a static binary has no dynamic linker to map them. The usual escapes are a container, an AppImage, or shipping a second libc inside the process. SoLo takes a fourth route: it embeds its own ELF loader plus a glibc ABI bridge implemented on top of the musl runtime already in the process.

The target reader is the person packaging a Linux application who has already chosen static linking, often through the IX build system the README names, and now has to reach hardware-specific code that the vendor keeps on the machine. The README states the split plainly: the host keeps the hardware-specific code, you ship everything else.

## Inside the loader: ELF mapping, DT_NEEDED walking, and the glibc shim

The data flow in the README's diagram runs from the application through an embedded Vulkan loader into SoLo's dlopen and dlsym, then splits into an x86-64 ELF mapper and a glibc ABI layer over musl, and finally maps the system Mesa or Vulkan ICD .so at runtime.

lib/elf_loader.cpp is where the work happens. According to the README it maps ELF segments, walks DT_NEEDED, resolves versioned symbols, applies x86-64 relocations, supports ELF TLS and TLSDESC, materializes IFUNCs, applies RELRO, and runs initializers. Dependencies that are themselves ELF DSOs load recursively. glibc is deliberately not loaded. An import such as malloc@GLIBC_2.2.5 is resolved by lib/glibc_shim.cpp to an adapter over the existing musl runtime. Functions that have no adapter get generated stubs that fail with the exact symbol and version when called, which is a better failure than silent corruption.

The synchronization decision is the part worth pausing on. musl sizes its synchronization objects to the glibc ABI of each architecture, so the bridge does not shadow them. A pthread_mutex_t created by a driver is used in place, meaning one lock serves both the loaded DSO and the static executable that may share it, and glibc's static recursive and error-check initializers are adopted on first use. Before touching disk, SoLo consults a static provider registry, so an application can satisfy a dependency such as Wayland with functions already linked into the executable.

## Installing SoLo and running the Vulkan proof

There is nothing to install for the demo. The README gives a prebuilt x86-64 binary that runs on any Linux with a Vulkan driver present, and states that mesa-vulkan-drivers is enough. The three commands below download it, mark it executable, and write a 512x512 RGBA image named hello.png.

```bash
curl -LO https://github.com/pg83/solo/releases/latest/download/vulkan-x86_64
chmod +x vulkan-x86_64
./vulkan-x86_64 hello.png
```

vulkan-aarch64 is the same demo for arm64. If discovery picks the wrong driver, the README shows a --driver flag taking an ICD manifest path. Manifest names vary between distributions, so the paths below are examples rather than universal ones.

```bash
./vulkan-x86_64 --driver /usr/share/vulkan/icd.d/radeon_icd.x86_64.json radeon.png
./vulkan-x86_64 --driver /usr/share/vulkan/icd.d/lvp_icd.json lavapipe.png
```

To confirm the executable is genuinely static, the README offers two readelf checks. The first should print nothing, the second should report that there is no dynamic section.

```bash
readelf -lW ./vulkan-x86_64 | grep INTERP
readelf -dW ./vulkan-x86_64
```

Building the same demo from source needs Python 3 and a C/C++ compiler in PATH. The ./build script takes the target name, and the resulting binary is run the same way.

```bash
git clone https://github.com/pg83/solo.git
cd solo
./build vulkan
./vulkan hello.png
```

To use SoLo as a library rather than a demo, ./build with no argument produces the standalone archive libdlfcn.a, and the published ./dlfcn symlink points at it. Note what the README does not cover: it documents no install prefix, no pkg-config file, and no CMake or Meson integration, so linking the archive into an existing build is left to the reader.

## The glibc surface is a shim, not glibc

The honest limitation is in the file names. lib/glibc_shim.cpp holds the implemented adapters; lib/glibc_stubs.cpp holds explicit fallbacks for the rest of the ABI. A driver that calls a glibc function outside the shim will hit a stub that fails with the symbol and version, and the load or the call does not proceed. That is a deliberate trade: a loud failure instead of a corrupted process, but it is still a failure, and the set of implemented functions is whatever the two files currently contain.

The second limitation is scope. The README badges the loader as x86-64 and aarch64, so other architectures are outside the stated support. The CI claim is about breadth of loading, not about every driver: it says the shared libraries of the 1,000 most-installed Debian packages, over 2,100 host objects, are loaded through SoLo on both architectures. That is strong evidence for the loader path, but it is not a statement that any particular proprietary driver works, and the README does not publish a list of drivers known to fail.

There is also a case where SoLo is simply the wrong tool. If your application can link its graphics stack statically, or if you are willing to ship a container with the matching libc, you avoid an ABI bridge entirely. SoLo earns its complexity only when the driver must come from the host and the executable must stay a single static file. The README also notes that LD_LIBRARY_PATH is ignored in secure-execution mode (AT_SECURE), matching how ld.so behaves, so setuid deployments cannot rely on it for library discovery.

## Compared with shipping a container or an AppImage

The obvious alternative is the one the README names first: bundle the runtime. A container image or an AppImage carries a known libc and known libraries, so the driver problem disappears because you control the whole userland. The cost is size and coupling. You now ship a userland that must match the host kernel and the host GPU stack closely enough to work, and the image grows with every library you add. SoLo inverts that: the executable stays one small static file, and the hardware-specific code stays where the vendor put it.

The second alternative is dynamic linking against the system libc, which is what most Linux desktop software does. It works, and it is what the ecosystem expects. The difference is that you inherit whatever glibc the machine has, which is exactly the dependency a static build is trying to remove. SoLo's position is that the driver is the only thing that genuinely has to be dynamic, so only that boundary gets a loader.

## Licence, releases, and what upgrades cost you

The repository is MIT licensed, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That is the whole of the licence implication here; whether MIT satisfies your organisation's policy on bundled ABI shims is a question for your own review, not something the README addresses.

On maintenance, the last push to main was on 2026-09-07, and the most recent release is 11, published the same day. Releases 10 and 9 landed on 2026-09-03 and 2026-08-22, so the cadence over that window is roughly weekly. The README documents no versioning policy, no deprecation process, and no rollback procedure, so an upgrade is a matter of tracking releases rather than following a stated compatibility contract. Because the glibc shim and stubs define which driver calls succeed, the practical upgrade cost is rechecking those two files whenever a driver you depend on starts using a symbol that previously had a stub. The README gives no compatibility matrix for that check.

## Conclusion

Adopt SoLo if you ship a fully static musl binary that needs the host GPU stack and you accept that the glibc ABI surface is only partly implemented, with unsupported calls failing loudly through generated stubs. Do not adopt it if you need glibc itself in the process, or if your application only ever touches libraries you can link statically. Before committing, check the two things the README leaves open: whether the glibc symbols your driver imports are among the adapters in lib/glibc_shim.cpp rather than the stubs in lib/glibc_stubs.cpp, and whether your distribution's Vulkan ICD manifest path matches what you pass to --driver. Build the vulkan target from source and run LD_TRACE_LOADED_OBJECTS=1 on your own target binary, because that output is the only listing of which objects SoLo served without mapping them.

## FAQ

### What is SoLo and what problem does it solve?

SoLo is a .so loader for static Linux binaries. It provides a dlfcn-style API backed by its own ELF loader and a glibc ABI bridge over musl, so a fully static musl executable can load the host's glibc-linked Vulkan or OpenGL driver without a second libc in the process.

### How do I install and run the SoLo Vulkan demo?

Download the prebuilt vulkan-x86_64 binary from the latest release, chmod +x it, and run it with an output filename such as hello.png. The README states any Linux with a Vulkan driver installed works, and mesa-vulkan-drivers is enough.

### Which architectures and libcs does SoLo support?

The README badges Linux on x86-64 and aarch64, with the ELF loader covering both. glibc is deliberately not loaded; imports are resolved to adapters over the process's existing musl runtime.

## Sources

- [Issues](https://github.com/pg83/solo/issues)
- [License: MIT](https://github.com/pg83/solo/blob/main/LICENSE)
- [pg83/solo on GitHub](https://github.com/pg83/solo)
- [README](https://github.com/pg83/solo/blob/main/README.md)
- [Releases](https://github.com/pg83/solo/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pg83-solo
