Rendered at 19:50:55 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
comex 13 hours ago [-]
This is a big forwards-compatibility risk. Suppose glibc adds a new symbol, and then a GPU driver adds a dependency on that symbol. The user wants to run an old executable with the updated GPU driver (maybe the old GPU driver doesn’t support their GPU). Normally, this would work fine: the user has to use a new copy of glibc, which will be compatible with both the new GPU driver and the old executable. But with your approach, the GPU driver is forced to use the glibc reimplementation which has been statically linked into the executable. Which, since the executable is old, can’t possibly implement the new symbol.
The same issue would occur if glibc adds a new version of an existing symbol and then the GPU driver is recompiled. (Or, for that matter, if a GPU driver adds a dependency on a symbol which glibc has always supported but which isn’t in the subset that you reimplemented, though in theory that could be solved if you reimplemented 100% of the symbols.)
account42 12 hours ago [-]
Yes this is just asking for trouble - and all it does is solve a problem that doesn't really exist. Just dynamically link against the oldest glibc you want to support. Its annoying that Linux toolchains don't have built in easy mode support for that but its much easier to deal with than this thing will be when it breaks.
It's also not just new symbols, the loader semantics also aren't static and new enough libraries may not support older semantics - e.g. the loader used to use DT_HASH entries for symbol resolution but now they are no longer present on all distributions.
egorfine 12 hours ago [-]
> Just dynamically link against the oldest glibc you want to support
I wish it would that simple for practical use cases.
I ship professional software for colorists for Hollywood studios and they absolutely love to never upgrade. We have to ship for RockyLinux 8. Sad.
RockyLinux doesn't sound so bad. It's not even EOL.
coredog64 7 hours ago [-]
We still have RHEL7 hosts in production. Every day it gets harder to find packages with a glibc 2.17 floor.
danudey 3 hours ago [-]
(I'm certain you know this already but I'm posting this for people who might want more context)
Part of the problem that people don't really realize is that it's not practical to just 'link against old glibc'.
In order to ensure compatibility with RHEL7 we need to link against RHEL7 libraries, which means we need to build on RHEL7. That means old, unpatched versions of glibc, libpcap, libcurl, openssl, or who knows what else, in case there's some backwards-incompatible change in newer RHEL versions.
Alternately, we can build/patch the libraries we use and compile them statically into the binary, which is vastly more maintenance work for us for very little benefit.
Meanwhile, compiling against older versions of libraries like glibc means we don't get the benefit of updates; not just new features in glibc, but things like more/better SIMD support in glibc algorithms, more/better optimizations in GCC, and so on.
Alternately, we can stop supporting RHEL7, like Redhat did, and build against RHEL8... Or we can build a separate version against each version of RHEL we want to support.
Funnily enough, we've been shipping RHEL7 RPMs of our product for years, and only recently realized that they won't actually run on RHEL7 because at some point the toolchain updated and now we're compiling against too new of a glibc version and then packaging it into a RHEL7 RPM. It wouldn't have worked on RHEL <8 for the past... few years? But no one uses RHEL7 so it went completely untested for ages and we didn't get any customer complaints.
Now we're re-labelling our RPMs as EL8, but it's just a cosmetic change so that we're claiming the version that we actually require.
egorfine 10 hours ago [-]
It's just this short of being EOL.
duskdozer 10 hours ago [-]
Almost three years?
>Rocky Linux 8 is supported by the Rocky Linux project until May 2029.
Yes but only the very last version and only paid support. Don't mind to pay, it's just that no amount of payment would be a proper solution to ancient environment with everything.
pas 7 hours ago [-]
static link everything? ship a docker image? ship your own userspace (like Oracle DB used (?) to)?
aidenn0 2 hours ago [-]
Neither of those fixes issues with system-calls not existing because the linux kernel was so old.
egorfine 4 hours ago [-]
Yeah, to make things spicy, what we ship is a plugin, so there's limit of how static we can link. glibc stays dynamic and that's a huge limiting factor.
I've got plans to try to build on RockyLinux9 and package its glibc along with the app. Simple helloworld works, so I have a glimpse of hope that it would be possible to ship like that.
yxhuvud 12 hours ago [-]
This is not a desirable solution on musl based systems. Whatever you do in that situation ends up horrible, so it is about finding the least bad solution. Which this seems like a workable variant of.
throwaway2046 11 hours ago [-]
> Just dynamically link against the oldest glibc you want to support.
Just the other day I tried running an older binary and it failed with a glibc error, despite it being linked to a glibc version that's barely 5 releases behind the one on my system. So maybe glibc isn't backwards compatible after all...
account42 10 hours ago [-]
Or maybe you misunderstood the error message.
pg83 10 hours ago [-]
Or may be not.
pg83 11 hours ago [-]
This means the user will update the binary with my program and continue using it happily.
I'm not offering a silver bullet, but the approach I've implemented is much better than what the industry currently offers.
cryptonector 4 hours ago [-]
Exactly. I think you're doing fine. It's weird that you're using C++ here, since that brings in a lot of crap, but then your programs are probably C++ anyway, so it's not a problem.
Honestly I'm impressed by this work. It's a thankless thing to have built, it's definitely feasible to build but more work than almost anyone would have been willing to do. What would be even neater is if Musl had this builtin.
pjmlp 12 hours ago [-]
So we are re-inventing patched a.out files, back when UNIX systems started to introduce dynamic loading, before ELF was invented?
Advocates of static linking keep forgetting once upon a time UNIX only had static linking, then we had overlays, and eventually dynamic linking came to be.
pg83 11 hours ago [-]
Of course, I remember those times very well.
And I also remember very well that dynamic linking appeared ONLY because we were catastrophically short on memory; everything else was added much later.
Now we have plenty of memory, and we can very well return to our blessed roots!
mohamedkoubaa 6 hours ago [-]
There is no RAM shortage in Ba-Sing-Se
pjmlp 11 hours ago [-]
Not at all, the pain of doing plugins with UNIX IPC was another one.
Ah, you have lots of memory, we're very wealthy. /s
nomel 19 hours ago [-]
I don't know much about musl.
> GPU: Vulkan and OpenGL drivers are supplied by the host as shared objects, usually built against glibc, and a fully static musl binary cannot normally dlopen() them.
Why? Have people managed to break the ancient concept of shared libraries, and this is a fix for that?
okanat 19 hours ago [-]
Because glibc and GNU set a terrible precedent. On GNU/Linux systems the shared binary interpreter / loader, GCC compiler, the C library and the system C/C++ ABI all depend into each other. You cannot change any of them independently. All shared libraries depend on the specific glibc version to load them into memory to be able to use that specific glibc version as their C library and make calls like dlopen.
Shared libraries have always been broken in Linux. Unfortunately many things like GPU drivers, graphics libraries and NSS need shared libraries to dynamically load certain runtimes (because you don't want to load all possible GPU drivers in existence to your RAM). So an ecosystem has been developed on top of terrible ABI and architecture GNU/glibc provided.
adev_ 17 hours ago [-]
> All shared libraries depend on the specific glibc version to load them into memory to be able to use that specific glibc version as their C library
That's currently the real core of the problem.
The loader (and libdl) need to be decoupled from the glibc itself under Linux.
Without that, any attempt to ship static binaries (or any binary with a different Libc) will be a source of perpetual pain.
nss plugins and its associated pain (sssd and avahi) are an other examples of that.
inigyou 7 hours ago [-]
libdl is the loader FWIW. It's all one big glibc codebase.
adev_ 7 hours ago [-]
What we name the loader is typically ld.so, more precisely /lib/ld-linux-x86-64.so.2 under x86_64.
But I do agree with you, the glibc is all one giant codebase shipped in one giant blob, dispatch under several names.
Even libdl.so is barely more than a wrapper and some glue around the libc.so itself.
matheusmoreira 13 hours ago [-]
Yeah, it's mind boggling how everything hard depends on glibc, even critical graphics systems.
I've become obsessed with getting rid of it, especially after I realized that contributing to GNU itself was a dead end. Freestanding Linux programming turned out to be much more fun anyway.
All libraries out there should adopt the SQLite design: programmers provide it with all the necessary functions. Instead of libraries hard depending on glibc, we get to inject the libc-ish subset it needs. Then we can use whatever we want under the hood. I'm working on porting SQLite to freestanding Linux system calls so it can run with zero dependencies. Wish I could say the same for software like mesa, I'd need a lot of help for this one...
stevefan1999 4 hours ago [-]
The concept you mentioned is just dependency injection in software engineering design, but instead of giving a class and protocol/interface you give a bunch of function pointers, which is, well, some kind of interface anyway
yxhuvud 12 hours ago [-]
Modularizing (and versioning each part independently) libc would go a long way. There is also a need to separate the stuff needed for system integration with what is necessary for users of the C language to actually do stuff.
matheusmoreira 12 hours ago [-]
> Modularizing
Can't be done. The libc is legacy, it can't be changed without breaking everything. It's also mandatory on every operating system other than Linux.
A change in paradigm is necessary. Freestanding C, not hosted C. This completely gets rid of the libc and is a surprisingly clean language. Linux only, because it's the only kernel with a stable binary interface. Every other OS forces a C runtime.
I once worked on a liblinux project that embodied this... Stopped because Linux itself has a nolibc thing in the kernel tree and I didn't want to compete with it. Now I'm working on the Rust version.
> what is necessary for users of the C language to actually do stuff
Surprisingly little. I wrote an entire lisp interpreter in freestanding C with Linux system calls. It managed to survive for a rather long time without any memory allocation at all.
The system layer is refreshingly tiny. It consists of a memory allocator and extremely basic functions like memmove and strlen. I successfully got rid of total nonsense like thread local errno, locales, implicit buffering, cached global state, possibly more. All that stuff is gone! Exactly one global survived: the stack canary generated by GCC and clang. Every other symbol in the ELF is controlled by me.
Wasn't able to get rid of the NUL terminator. Linux itself needs it. To get rid of that little billion dollar mistake requires an entirely new kernel with zero UNIX/POSIX influence. I had to make my peace with that one. All my buffers maintain an extra NUL byte at the end.
pg83 11 hours ago [-]
> A change in paradigm is necessary. Freestanding C, not hosted C. This completely gets rid of the libc and is a surprisingly clean language. Linux only, because it's the only kernel with a stable binary interface. Every other OS forces a C runtime.
Great choice for small programs, but what if I want hardware accelerated 3d?
matheusmoreira 11 hours ago [-]
Yeah, that's the annoying part. Been wondering about this for years, and graphics support was among the first issues raised on the lone lisp GitHub repository. At this point I've even started exploring the mesa codebase, made some patches but didn't submit them yet due to the AI stigma.
With Linux system calls alone it should be possible to set up kernel mode setting without depending on any toolkit at all. This should be enough to get a framebuffer for software rendering.
For hardware acceleration though, one must give this graphics context to an OpenGL ES implementation. That's where it gets ugly. There is no way to divorce that from the libc short of literally rewriting it.
Maybe Vulkan will enable it? I can't say for sure at my current knowledge level.
pjmlp 8 hours ago [-]
Vulkan is designed to only load core statically and the whole extension spaghetti dynamically, using driver entry points.
Modern 3D APIs are built around passing around a few buffers per frame and long command queues. Should be doable with IPC.
Splizard 11 hours ago [-]
exactly
dwattttt 11 hours ago [-]
> Can't be done. The libc is legacy, it can't be changed without breaking everything. It's also mandatory on every operating system other than Linux.
Windows explicitly does not want you to link the system libc. You are expected to bring your own, and doing so means your process has multiple libc's loaded into its address space.
And if you choose to build a binary that doesn't need a libc, you won't be bringing one.
delta_p_delta_x 8 hours ago [-]
> Windows explicitly does not want you to link the system libc
This is categorically false; UCRT[1] is a thing. The 'U' stands for universal. Unlike Linux, Windows allows developers to choose their ABI boundary and also ship that boundary if they desire, or use the 'system' one and ask older platforms to install redistributables or Windows update packages. There's the old and new C runtimes in MSVCRT.DLL and UCRTBASE.DLL, the C++ runtime in VCRUNTIME140.DLL, Win32 in KERNEL32.DLL, USER32.DLL and more, and then the stable-ish kernel interfaces in NTDLL.DLL, in order of 'closeness to the kernel'.
And also, 'libc' is a UNIXism; on Windows the term is CRT, for 'C runtime'.
> Unlike Linux, Windows allows developers to choose their ABI boundary
Linux has a stable system call ABI. I can trash the entire user space and boot Linux with init=/my/program if I want.
It's not that Linux doesn't allow developers to choose, it's that Linux doesn't actually control the userspace ABIs. Userspace is whatever we want it to be, and people settled on "GNU/Linux".
delta_p_delta_x 7 hours ago [-]
> Linux has a stable system call ABI
This is a noble but practically worthless ideal. Hardly anything is written against the kernel ABI except glibc itself. End-user application developers don't write against the kernel interface. Platform library and application developers like KDE and GNOME don't really write against the kernel interface either. Nowadays even driver developers don't entirely write against the kernel interface, instead also linking in glibc and lib{std}c++ for things like shader compilers (which is nowadays usually just a fork of Clang). Like it or not, glibc is the singular entry point for desktop Linux, which is the point of essentially every comment under this post.
That 'desktop Linux ≡ glibc' is something that I daresay the kernel developers also have to take responsibility for, rather than shrugging their shoulders and pawning it off to GNU. Linux's unstable user-mode is the entire reason for Docker's existence. It is bodge upon bodge and has led to much gnashing of teeth. The GNU/Linux community (I don't care for Stallman's meme rant here) have to grow a backbone and understand that an OS is much more than just a kernel, it is a platform, and currently the Linux platform is a disparate set of communities with highly divergent goals.
That's why I've always said the best Linux is Android, because Android is a platform.
matheusmoreira 6 hours ago [-]
> Hardly anything is written against the kernel ABI except glibc itself.
I'm working hard to change that.
Here's an entire lisp interpreter written in freestanding C using nothing but Linux system calls:
I'm going to add system calls to this thing every day until I have every single one. Then I'll use them to make all of my applications.
Already have my first project planned: a custom Rust network stack for my QEMU virtual machines so I can firewall them off in userspace without root access.
> the Linux platform
The linux platform is these system calls. Everything else is userspace. This is exactly what makes it special.
> The linux platform is these system calls. Everything else is userspace. This is exactly what makes it special.
This is exactly the attitude I meant by 'the kernel developers also have to take responsibility for, rather than shrugging their shoulders and pawning it off to GNU' and 'an OS is much more than just a kernel, it is a platform'.
By 'platform', I mean windowing toolkits, such as Wayland. Audio libraries like ALSA and PipeWire. Desktop idioms and libraries like DBus, and the entire freedesktop.org effort including Mesa and more. Init systems like systemd. Bootloaders like GRUB/systemd-boot. Networking, including systemd-networkd, iwd, and NetworkManager. Power and device management including PowerDevil, Bluez.
If all of this is considered 'user space' then my point is strongly vindicated. On most competitor OSs these comprise the 'platform'. You won't ever encounter a Windows or OS X developer arguing that USER32.DLL or Foundation is 'userspace' even if it is technically true and code from those libraries may only ever run in ring 3. From the average user's point of view, the only 'userspace' are end-user applications that they have installed, like Chrome, video games, etc. From that perspective, even purely user-mode applications that ship with the distro ought to considered 'platform'. This includes such trivia like calculator and camera applications.
We need to talk about the practical realities of a platform, not a theoretical, unattainable, and as I mentioned, useless ideal. These practical realities mean shipping a unified set of useful 'batteries included' user-mode libraries and applications. If one wants to develop the platform itself, then sure, the kernel has a nice stable interface, but on most Linux distributions the kernel is built from source anyway, so even that nicety is moot.
matheusmoreira 5 hours ago [-]
> By 'platform', I mean
You mean Linux distributions.
> From that perspective, even purely user-mode applications that ship with the distro ought to considered 'platform'.
Every Linux distribution is its own platform.
You decried Stallman's meme rant yet you just rewrote the entire rant in your own terms. Linux is the kernel, GNU's just the stuff on top. You can get rid of it if you want and make your own. Android did exactly that.
yjftsjthsd-h 6 hours ago [-]
It's more than a little funny that you disparage syscalls being Linux's stable interface, and then immediately praise Android, which only works because glibc isn't uniquely privileged the way you seem to want.
Also, I daily drive Alpine Linux (using musl) and I'm quite happy to assure you that glibc really is only one option, not at all mandatory for desktop Linux.
delta_p_delta_x 6 hours ago [-]
> It's more than a little funny that you disparage syscalls being Linux's stable interface, and then immediately praise Android, which only works because glibc isn't uniquely privileged the way you seem to want.
I never disparaged it; I said having it doesn't make it any more useful that OSs that don't, because the practical reality is that the kernel interface is narrow and highly technical, and a stable kernel binary interface has little to do with a user's day-to-day experience with an OS.
I daresay Android would still work pretty well even sans the kernel ABI stability, because the kernel is usually built from source, and if it isn't, then every Android source tree has kernel prebuilts for the kernel version shipping. I also never said nor implied glibc should be uniquely privileged; I think you have misrepresented my comment.
stevefan1999 4 hours ago [-]
> There's the old and new C runtimes in MSVCRT.DLL
VC-LTL Implements a good subset of modern CRT functions using the msvcrt.dll, which makes modern MSVC compiler to emit binaries that theoretically can run in Windows NT 4.1 or Windows 98 (until CFG broke them, but you can turn it off). Still can teach an old dog new tricks.
YY-Thunk is a little more funky, it tries to implement new Win32 userspace API for old system by emulating the behavior and expected values using userspace API in the old apartment, so you can try to implement a function in Windows 10 with functions in Windows Vista, and hopefully it will work out.
pjmlp 8 hours ago [-]
That was introduced in Windows 10, Windows history is a long one.
Usually Windows developers will use Win32 directly, ZeroMemory() instead of memset(), and so on.
delta_p_delta_x 8 hours ago [-]
Indeed. I was just about to point out the fact that the CRT only provides the C standard library; Windows applications can run perfectly fine without one by linking only to the Windows API DLLs.
dwattttt 8 hours ago [-]
By "system libc" I'm only referring to msvcrt.dll, found in the Windows directory. Quoting Raymond Chen:
> At some point, the decision was made to just give up and declare it an operating system DLL, to be used only by operating system components.
I think the grander point is that there is no real 'system CRT' on Windows; as I mentioned, there are multiple entry points each at an appropriate level of abstraction available to both platform and application developers (not that there is a real difference between the two, since platform developers may also write applications like Office). Many Windows platform libraries (WIL, for instance) themselves use UCRT instead of MSVCRT now. The latter exists, but it is by no means and has not ever been by any means the single entry point to the Windows platform, unlike glibc on most desktop Linux distributions.
matheusmoreira 10 hours ago [-]
> You are expected to bring your own, and doing so means your process has multiple libc's loaded into its address space.
That only massively compounds the problem.
> And if you choose to build a binary that doesn't need a libc, you won't be bringing one.
NT system calls are not stable. You still need to link against ntdll.dll at the very least, like a forced Linux vDSO.
dwattttt 10 hours ago [-]
> That only massively compounds the problem.
The Windows ecosystem, that manages to deliver built binaries easily & widely, regardless of whether the author has a 1 year old OS or a 15 year old OS, suggests that it's not as big a problem as you believe.
matheusmoreira 9 hours ago [-]
It's not a problem in the same way that things like snap or flatpak aren't problems. It works but it bloats things up considerably and makes you wonder where it all went so wrong. I mean, dozens of slightly incompatible runtimes inside a single process?
dwattttt 9 hours ago [-]
Those incompatible runtimes are separated by a linker that doesn't resolve all symbols globally, but rather scoped to the shared object they're expected from. They all coexist happily, and if you're so inclined you could resolve the same symbol from each, if you had reason to do so.
torginus 10 hours ago [-]
>A change in paradigm is necessary. Freestanding C, not hosted C. This completely gets rid of the libc and is a surprisingly clean language. Linux only, because it's the only kernel with a stable binary interface. Every other OS forces a C runtime.
I'm sure those OSes make efforts to make said runtime binary compatible between executables.
matheusmoreira 9 hours ago [-]
Yes, but the whole idea is to delete the runtime binary and talk to the kernel directly.
That's when you run into the Darth Vader of binary interfaces.
> I have altered the ABI. Pray I do not alter it further. -- De Raadt
torginus 5 hours ago [-]
I am not sure having a stable kernel ABI/API is really a goal in practice or intent in all cases.
GPU drivers are a perfect example - GPUs are hellishly complex, and their interfaces are super high-level - OpenGL/Vulkan is not something that should be implemented by the driver.
I think a general practice that is followed with GPU drivers is to move as much of the complex and non-privileged stuff out of the kernel, and only keep modesetting/power management/memory and control stuff in.
That reduces massively the amount of code that needs to run in Ring 0, but in exchange, the interface between the user and kernel blobs is non-stable. Programs talk to the userspace blob and there's no guarantee of compatibility between different kernel/userspace driver versions.
This is something even Linus accepts, with the only contention that the userspace blob needs to be open source as well. They're not maintaining something in tree, that they have no way of testing without proprietary external deps.
nextaccountic 7 hours ago [-]
> A change in paradigm is necessary. Freestanding C, not hosted C.
Or.. any other programming language. A lot of systems programming is done in Rust nowadays.
matheusmoreira 6 hours ago [-]
Yeah. I made an entire lisp for this, and I've recently revived my Rust liblinux crate too. I've been documenting the system calls as I add them. Hopefully one day it'll rival man7 as the Linux system call reference.
Root_Access 12 hours ago [-]
We have a similar no dependency philosophy
SQLite already does this with its VFS layer. You hand it the OS functions it needs instead of it grabbing them, etc.
You could look at going straight to syscalls, mesa would be a rewrite nightmare
I went zero-dependency on a production CMS. Rust, no framework, no external crates beyond argon2 for password hashing. Running live on client sites. The hard part wasn't building it, it was accepting that everything you reach for is pulling glibc or similar assumptions back in through the side door. SQLite's VFS model is the right pattern. More things should work that way.
asveikau 19 hours ago [-]
> system C/C++ ABI
C++ abi should not be included in this. It is independent from the other pieces and historically a source of incompatibility on its own.
Saying "C/C++ abi" as if they are the same is looney tunes, the former is very simple and stable and the latter is very complex.
How libstdc++ initializes global variables absolutely depends on glibc and ld-linux.so. That is part of C++ ABI.
account42 12 hours ago [-]
That's libstdc++ depending on a C ABI (or a GNU ABI to be precise) but that doesn't make it a C++ ABI. You can also have global constructors in C with GCC.
josefx 13 hours ago [-]
> the former is very simple
Somehow most of my portability issues seem to be caused by glibc, its symbol versioning and close ties to the dynamic loader. Minor versions aren't compatible, no two Linux distros ship the same version and you can't just provide your own without also patching in your own dynamic loader.
At least as far as the defaults on Linux go I consider C the root of all evil.
badsectoracula 13 hours ago [-]
AFAIK glibc is backwards compatible as long as a) your program is running on a newer version (i.e. you're not trying to dynamically link against an older version) and b) you're not using hidden/undocumented symbols.
In which case as long as you're using the documented public API and compile your program with the oldest version of glibc you want to support (some Ubuntu from 4-5 years ago should cover pretty much every current desktop) you should be fine. And with something like Docker this is trivial to do.
Sure it is annoying that you cannot use your current distro (especially if you use some rolling distro) to make binaries for everyone, but it takes very little effort to work around that. The only issue i can think of is if you absolutely want to compile using the latest version of your compiler and you cannot build the compiler from source to work in the Docker (or whatever) contain to work against the older glibc.
pg83 12 hours ago [-]
This approach only works for very simple cases.
In complex cases, it turns out that the old version of glibc also pulls in other libraries and the compiler, and you're stuck with a very ancient sysroot.
You may often find that you can't compile new library versions in such a sysroot and link them statically.
So, it looks good on paper, but forget about the ravines.
b5n 19 hours ago [-]
While I don't disagree with some of the pain you describe, you conveniently gloss over the fact that gnu developed a system that worked, and then made it free to everyone to consult and use.
okanat 19 hours ago [-]
BSD also did it. They did it better. Maybe more modern but AOSP also did it but at a different level of binary: instead of ELF, using compiled Java bytecode archives.
lmz 15 hours ago [-]
The root cause is alternative libc implementations. Are BSD syscalls considered stable? I remember Go moving to use libc on OpenBSD. Solaris also has the libc as the stable interface. Linux kernel is an outlier here guaranteeing stable syscalls but you wanting to use another libc is not Glibc's problem.
account42 12 hours ago [-]
Yeah it's not Linux specific but rather true for most Unix systems that libc == base system interface library.
pjmlp 11 hours ago [-]
Except the other UNIX systems, starting with the original one, evolved from only having static linking to various ways to connect libraries and applications.
uecker 19 hours ago [-]
In what sense do binary interpreter / loader, GCC compiler, C library and system C/C++ ABI dependent on each other? I have certainly mixed different versions of all these components without problems so far.
okanat 19 hours ago [-]
When you compile GCC you need to provide a full glibc installation as your target. It is also a dependency of libstdc++.
C++ global/static variable initialization depends on the specific version of glibc (they don't usually break compat, but they can and they did in the past) which also provides ld-linux.so that loads those global variable placeholders in the correct manner such that glibc and libstdc++ can initialize them correctly.
This is just one example. Thread local variables and behavior of things like pthreads with signal, fork etc all depend on glibc.
uecker 19 hours ago [-]
I can't comment on the C++, I can imagine there plenty of issues, but for C I don't see this. You need some libc if you compile with gcc, but this generally does not introduce a hard version dependency on the specific version (there may be a minimum requirement if you compile against a new version that a symbol with a different ABI).
mananaysiempre 19 hours ago [-]
I don’t imagine that you’re unaware of any of this, but: ld.so and libc.so are heavily interdependent in deliberately undocumented ways with Glibc and outright the same file with shared Musl. And while you might usually get away with using any old GCC with the right architecture and ABI (especially for C; cf the musl-gcc hack), technically it needs to be built to target a specific libc version (particularly via symbol versioning; I’ve long wanted to gather a set of patches to build an old Glibc and subsequently a cross-compiler using a new GCC so I could avoid PyPA’s manylinux monster or its moral equivalent for compatible dynamic binaries in simple cases). The C compiler of course is tied to the C ABI, and this wouldn’t be really worth mentioning except for the time where the GCC devs accidentally the whole SysV i386 ABI and pretended that the stack was always 16-byte aligned, why do you ask, except on RHEL. The C++ parts I can’t really comment on.
uecker 18 hours ago [-]
I am not really sure. For ld.so and libc.so I may believe this. The C ABI is very stable, and if you use a new symbol from a newer glibc, you certainly depend on it, but this can also be avoided. In any case, I do not see what is fundamentally misdesigned here. I can't quite image how it could work differently. If you upgrade something so that the e ABI changed you natually need to update other components. Static linking certainly seems a very poor replacement for this.
account42 12 hours ago [-]
> I’ve long wanted to gather a set of patches to build an old Glibc and subsequently a cross-compiler using a new GCC so I could avoid PyPA’s manylinux monster or its moral equivalent for compatible dynamic binaries in simple cases
And that's the correct approach and also one that many have taken. We just need someone willing to maintain that as an easy mode SDK for everyone.
pg83 12 hours ago [-]
> And that's the correct approach.
Who said that? This approach has many problems that have already been discussed here, not to mention the fact that it leaves Alpine and Bionic-based systems out in the cold.
Let me remind you that Bionic is the most widespread libc in the Linux world, and Alpine is the most popular Docker layer.
The world doesn't end with glibc. And it doesn't begin with it.
uecker 9 hours ago [-]
It could be fine if you care only about free-software desktop users, and not Google's propriety platform and hyperscalers.
pg83 19 hours ago [-]
For example, the itanium unwind ABI implementation lies between these three entities.
duped 19 hours ago [-]
The interpreter/loader is glibc and a key part of bootstrapping an executable built against glibc is loading libc itself before continuing on to load the program. Versioning is a problem when distributing binaries linked against a newer glibc to distros that ship an older one. The C compiler doesn't really care as much.
okanat 18 hours ago [-]
> The C compiler doesn't really care as much.
Until you define a thread local variable (C11) or use atomics (also C11) or define a global with an initial value. Then it happily generates code that depends on "whatever my target glibc + ld-linux.so needs".
uecker 18 hours ago [-]
It depends on functions defined in a standardized ABI.
okanat 18 hours ago [-]
You'd expect that but, no. That's why you cannot load glibc-linked binaries in a Musl distro. Edit: that's why the hacks like the original post is needed, as well.
The ABI is strongly dependent on explicit libc implementation in current Linux systems. There is no libc independent ABI on Linux.
uecker 18 hours ago [-]
Sorry, can you be more specific. I do not understand what the problem is. If Musl does not implement support for the ABI, this would be a musl problem?
okanat 18 hours ago [-]
There is no libc independent ABI. ABI doesn't purely mean just calling conventions.
When you compile libc, you also get a binary loader ld-linux.so with it. They are not two independent components of a system.
Basically all .so files compiled with glibc require the ld-linux.so that's also generated by that glibc (or a later version, if they didn't break the binary compatibility).
There are a lot of stuff that's executed by ld-linux.so and glibc that are not explicitly documented but they are absolutely necessary for your program to start and correctly initialize things like global variables or signal handling or loading other dynamic libraries. Some of that functionality sits in ld-linux.so and some of that in glibc. They have circular dependencies to each other. glibc expects ld-linux.so to put things in certain order but ld-linux.so also must load glibc first to have access to certain APIs. They are not part of System V ABI. They are not documented.
Musl maybe can implement this but it is simply reverse engineering what glibc did and then playing a game of cat and mouse. There is no independent ABI standard.
uecker 10 hours ago [-]
Sorry, again this too vague for me. What is the exact problem with atomics and thread_local in C that would make the ABI dependent on a specific version of glibc? I know the ABI is not just calling convention and e.g. for atomics may involve calling a function from libatomic. But from my understanding, this is all part of a standardized ABI that does not change and can be provided by different implementations.
Then, what is the exact reason a library compiled against glibc must be loaded by a specific ld-linux? I could see that this is true for C++ perhaps, or when you use very special features, but I do not see this for C.
I often compiled programs against one version of glibc and run it against a different version, so I know there is not a tight coupling. So please be specific in explaining in what scenarios this would break.
duped 5 hours ago [-]
Unless you use musl-gcc which is a proper GCC frontend with musl libc sysroot. The compiler is indeed (too) tightly coupled to its sysroot but linux is not special here. If anything interacts with the CRT it needs to know which CRT to use.
I'm not arguing that there aren't problems here, just that I think people have different expectations that are a little extreme. The libc implementation is a core part of the operating system, if you want to target that OS you should target that libc. Some distros (alpine) prefer musl libc, most prefer glibc. And distros really are different operating systems.
torginus 10 hours ago [-]
Btw, absolutely insane that it's 2026, and Linux cannot do the most defining OS thing - that is, provide a standardized environment to run binaries against.
All solutions to this problem are hacky, complex and controversal and highly fragmented, where this should be BASIC functionality
Root_Access 11 hours ago [-]
Most everyone else is talking about the problem while you mapped the room.
Solo is basically pg83's answer to the architecture you just described. If there is no libc independent ABI, build your own loader and shim the boundary.
I can understand Linus's obsession with taste and the areas it was overlooked or traded.
torginus 10 hours ago [-]
Wasn't Linux Standard Base supposed to fix this? I cannot imagine any reason why glibc would break at a rate that you can't keep the current version binary compatible for years.
Or do the MS thing, and ship multiple versions like msvcrt
duped 19 hours ago [-]
> all shared libraries depend on the specific glibc version to load them
Not really, though. glibc uses symbol versions that are forward but not backward compatible. If you got an error that said "this program was built for a newer version of <distro>" would you say the same thing?
Note this is the same (if not worse) on MacOS, and on windows you used to distribute the CRT with your application just to deal with the same problem.
Yes glibc has some backwards compat but you cannot load a binary compiled with a newer version of glibc using an older ld-linux.so. That's because the interdependency. Nor you can load binaries that depend on different libc.so files with glibc systems
I cannot comment on macOS, I have never used it. However this is not a problem with Windows. You can ship a newer CRT or you can install it as a system component using Microsoft's MSI. The dependency is one way on Windows. CRT purely depends on Win32. Moreover the loader is completely independent and DLLs are loaded into their own unique scoped namespace unlike Linux that loads them in global symbol namespace. That's why you can mix and match DLLs compiled for different CRT versions.
duped 18 hours ago [-]
That's what I'm saying though, glibc-linked binaries are forwards (but not backwards) compatible.
okanat 18 hours ago [-]
It is not just compatibility. You cannot load them into the memory with your system dynamic loader. You need to also ship ld-linux.so with the new version of glibc you have, if you were to distribute your program independently.
On Windows you don't need to ship a new binary loader. I can just ship Windows 10 UCRT DLL (which is the new libc of Windows) to Vista and my binaries will work. The binary loader isn't interlinked with the libc.
cylemons 14 hours ago [-]
Windows doesn't even have a concept of a loader binary right? I think its hardcoded into the kernel/win32 itself.
dwattttt 12 hours ago [-]
Windows loader is part of the system ABI, and programs' libc(s) are loaded by it.
One of the ways Windows manages to support multiple libc's is by being careful not to mix allocators; if a system API you call allocates on your behalf, your libc can't free it, the system API will offer a function to free it.
Windows loader is certainly available to user programs though; LoadLibrary has been around longer than many developers.
account42 12 hours ago [-]
LoadLibrary will also not be able to load arbitrary libraries compiled for newer versions of Windows though. Just like with gcc, Windows also does not guarantee forwards compatibility - because that would men freezing the feature set the system libraries provide.
pg83 11 hours ago [-]
Windows handles this much better than any other OS. The API passes versions (== structure sizes on the calling side), and WinAPI can handle that.
As for older Windows systems not being able to load new DLLs, they can; the format hasn't changed in a very long time. I've had experience with installing some DLLs on Windows NT 3.51 and running a modern Firefox, which is about 20 years behind the times.
dwattttt 11 hours ago [-]
Targeting an older version of Windows is "set a define so that the system headers don't expose functions that didn't exist on older Windows". You don't need an old toolchain to target old systems, you ask the newer toolchains to target it.
MS haven't made this work arbitrarily far back, I believe they deprecated targeting Windows XP in one of the more recent toolchains, but that was purely a "not worth supporting" situation.
EDIT: mixed up talking about older & newer
duped 6 hours ago [-]
Why? If you compile your program against an older glibc it will run on newer glibc. glibc is the loader.
krupan 16 hours ago [-]
In what way are they "broken" when Linux runs fine on millions of boxes? Sure, it might be a pain for proprietary software, but if your app is open source it's not that hard to build it on whichever distro you want. If your app is popular enough the distro maintainer will build it for you
pjmlp 12 hours ago [-]
Because many folks don't understand UNIX systems introduced dynamic linking for several reasons, and they actually only had static linking for almost 20 years, since UNIX was known outside Bell Labs.
Additionally many other OSes have had both approaches since their early days, Xerox PARC ones.
For some strange reason they assume to know better than all those researchers.
pg83 11 hours ago [-]
These decisions, and these studies, were made a VERY long time ago. It's completely unclear why the decisions made then are relevant now, and why they can't be challenged.
pjmlp 10 hours ago [-]
It is like advocating that we should drop cars and go back to chariots, because wooden wheels don't get flat, while forgetting why they are mostly used for tourists nowadays.
akerl_ 19 hours ago [-]
musl has no problem building and using shared libraries.
What you can't do is build something statically with musl and then reliably dlopen shared libraries built with glibc.
pg83 19 hours ago [-]
Well, now it's possible! Furthermore, SoLo binaries can run, without modification, on glibc-based distros, alpine, and soon on android/bionic (not committed yet).
account42 12 hours ago [-]
Only on current glibc distributions. Your SoLo-program WILL break on future distributions. This is not a sensible tradeoff.
pg83 11 hours ago [-]
Any software WILL break on future distributions, sooner or later.
yxhuvud 11 hours ago [-]
Who says it is not a sensible tradeoff? It may be a much better tradeoff than not being able to run it at all.
ranger_danger 19 hours ago [-]
musl does not perfectly emulate all aspects of glibc, so trying to use libraries that assume glibc can sometimes lead to problems.
pg83 19 hours ago [-]
On the one hand, this is technically true, but on the other, what serious issues do you know that will cause problems in practice? I run tests on 1,000 of the most popular Debian packages.
silisili 13 hours ago [-]
Not anymore, but for -years- it did DNS wrong because of the author being pedantic about an RFC wording.
skydhash 18 hours ago [-]
Mostly about precompiled libraries (proprietary software) and libraries and software that use GNU extensions.
shevy-java 12 hours ago [-]
> Why? Have people managed to break the ancient concept of shared libraries
If you break or remove a shared lib here, you may no longer be able to compile something from source. I had that happen in the past before I started to use more statically compiled programs (and busybox too).
Assuming everything works as-is via shared libraries at all times, makes little sense for ALL linux systems. For instance, some people upgrade glibc manually. Then you need a working base system to resume compilation. I do that for my customized gobolinux system, so I can use any program version as well as any glibc version (assuming I can still compile the program; many older programs no longer compile).
eqvinox 14 hours ago [-]
If you can figure out your own ELF loader, you can figure out how to build a partially static executable that doesn't need this. You can mix static and dynamic linking. Build tooling around that is just shit.
pg83 12 hours ago [-]
In this scenario, I'll have to choose which libc I want to run. These won't be portable Linux binaries in the true sense of the word; I'll have to leave Alpine out, and possibly Android, which I don't want.
eqvinox 8 hours ago [-]
Fair point - while it's certainly possible to make a decision per individual library elsewhere, libc is the one thing that really will need to be linked dynamically for glibc, and it'll freeze a minimum version into the binary (i.e. it should be built against an old version of glibc).
I don't think Android is a target in most cases of this, so this would boil down to shipping 2* binaries, one glibc and one musl.
I'd need to check how musl behaves for compatibility, I'm assuming it'll be a minimum like glibc.
All of this said - you'll need to ship multiple binaries anyway, these days: x86_64 + aarch64. Possibly more…
dwattttt 11 hours ago [-]
You don't strictly need to; you can write freestanding C, and use the Linux kernel directly.
shevy-java 12 hours ago [-]
Can you really mix it? When I break or remove a shared lib, some binaries no longer work. With static libs or even better, e. g. statically compiled busybox, I don't have that issue, so I disagree on the claim that mixing solves everything as such. I keep the basic toolchain I use as statically compiled variant. The whole system works better if I can break it less easily.
dwattttt 11 hours ago [-]
I'm not sure that's particularly relevant; if I null out bits of the statically linked binary to remove some code paths, they break too.
colinsane 2 hours ago [-]
i'm not sure how much people realize that the modern "graphics driver" is actually just "the kernel multiplexes userspace messages to/from the GPU and we've taught mesa to understand each family of GPU you'd ever care to support."
i helped somebody get Doom running on an old embedded system running some 4.x kernel. we just built everything, including mesa, statically and deployed that. imagine building everything static but loading the system's libgl.so instead (which was probably just a symlink or abstraction over mesa's own implementation). what's the benefit: we'd get older, less optimized graphics routines from 5 years ago?
if you're statically linking, then just bring your own graphics "driver". the kernel interfaces are stable enough. it's not conceptually different than embedding `syscall`s directly into your application the same way you do when statically linking libc.
pg83 2 hours ago [-]
> i'm not sure how much people realize that the modern "graphics driver" is actually just "the kernel multiplexes userspace messages to/from the GPU and we've taught mesa to understand each family of GPU you'd ever care to support."
In the case of OpenGL, it has a fairly sophisticated state tracker, which you can carry around with you. For example, I can statically compile ANGLE this way.
But even OpenGL/Vulkan have a shader compiler that's unique to the hardware (it's expensive to carry it around with every piece of hardware in the universe).
And I'm not even talking about libdrm, which is quite tightly tied to the kernel version.
I know exactly what I'm talking about, because I can also statically compile Mesa into my application.
sieve 11 hours ago [-]
Every couple of years, I revisit my PL dev hobby and this time I decided to create a language/runtime with pre-emptive scheduling using instruction fuel. While I always do freestanding builds, this time I decided that I also wanted to support native FFI.
That is when I realized the true horror of (g)libc. It wants to inject itself at the root of the library/program and everything from threading to dlopen/dlsym is impacted. I tried a lot of workarounds including trying to implement a loader myself, but the complexity (and fragility) grew so much that I felt it was not worth it.
Finally, I retreated into the safe world of a freestanding runtime + syscalls. FFI, if it has to happen, will occur via IPC of some kind. A second process linked against glibc that will manage calls on behalf of the clean first one.
rfgplk 11 hours ago [-]
I've implemented the same thing for micron (more or less). One advice I'd give you is to _really_ take care regarding SysV/ELF ABI conventions, there's tons of undocumented stuff in there and it's really easy to mess something up or cause a security defect (see AT_SECURE). That being said the way you're doing is also tricky(ish) because if I understood your implementation correctly you're hooking this into an already running musl which could cause backwards compatibility issues if musl changes under you. Doing this is safer if you control the entire runtime.
pg83 11 hours ago [-]
> One advice I'd give you is to _really_ take care regarding SysV/ELF ABI conventions, there's tons of undocumented stuff in there and it's really easy to mess something up or cause a security defect (see AT_SECURE)
Yes, it's not trivial, but I hope that over time everything will settle down.
> if I understood your implementation correctly you're hooking this into an already running musl which could cause backwards compatibility issues if musl changes under you
No, I hook this to musl, which is statically linked into my binary, and I have complete control over it.
Do you belive the machine is taking offence? It can not.
Telling me, a coder you never met or interacted with before, that I would introduce more bugs than The Product(tm) is pretty rude and prejudiced.
> The project author believes that, with capable human direction, modern LLMs write code faster than people and introduce fewer bugs.
pg83 16 hours ago [-]
> Do you belive the machine is taking offence? It can not.
Obviously, this is an offense to me.
> Telling me, a coder you never met or interacted with before, that I would introduce more bugs than The Product(tm) is pretty rude and prejudiced.
Don't exaggerate. I didn't say that you personally introduce more bugs (as you rightly pointed out, I don't know you and don't know how often you introduce bugs), I said that the average developer introduces more bugs than an SOTA LLM.
egoisticalgoat 13 hours ago [-]
> Obviously, this is an offense to me.
But why? You didn't write it. You said so yourself, you wrote the initial text but it was all reworded by claude.
matheusmoreira 13 hours ago [-]
It's still his project you're insulting.
funlang 11 hours ago [-]
Agree. The work itself looks solid. Good job to the author.
pg83 11 hours ago [-]
Thanks a lot, guys!
account42 12 hours ago [-]
Is it? In what sense?
matheusmoreira 12 hours ago [-]
In the sense that he had the idea and iterated on it until it became something real that actually works.
funlang 11 hours ago [-]
I agree. Even if the README was polished with AI, the technical work behind it is impressive. Let's focus on the code.
edoceo 15 hours ago [-]
No sense in arguing with LLM haters (or zealots). It's approaching religious levels of discourse. Let them have their opinion and move on.
Muromec 14 hours ago [-]
Of course it does. I once called the thing a /communist fuckface/ for speaking the wrong language and it responded in a way an offended person would.
I could choose to believe in a social fiction and you can't stop me.
Splizard 14 hours ago [-]
What are your plans around the maintenance of this project, how would you feel about solo being incorporated into the musl build for graphics.gd ?
mochaa 2 hours ago [-]
if you really want a single elf that could link to system libraries with a foreign libc (you shouldn't), the somehow correct-ish way to do it is map your preferred ldso manually , setup stack, call DT_ENTRY etc etc like how kernel does it, and just yank the libdl symbols.
see cosmo_dlopen for an example implementation.
pg83 2 hours ago [-]
The README.md link describes cosmopolitan's approach, among other things, and it's worse than mine.
torginus 10 hours ago [-]
I have faced a similar issue in the past, and I don't understand how static binaries from the host are supposed to solve this.
From what I remember, GPU access on Linux 'works' by accessing specific FDs under /dev, which are vendor specific - this is what these libs do under the hood.
The libraries don't have any magic powers - if the FD is inaccessible, you won't be able to do anything.
So there's some vendor specific access needed in containers anyway (or a blanket allow, which is a BAD idea).
Also not sure why dynamic linking isn't good enough for this - the issue lies with the permissions, not how you load/link libraries.
ok123456 4 hours ago [-]
Is the 'hello.jpg' reference intentional?
socceroos 15 hours ago [-]
Do people say "so", "ess-oh" or "dot-ess-oh"? The title "a .so" is clunky to the "ess-oh" gang.
notpushkin 15 hours ago [-]
I don’t think I’ve said it out loud more than a couple times in my life. But in general I think I spell out / pronounce the “dot” in file extensions unless it’s completely obvious from context.
account42 12 hours ago [-]
Well .so is short for shared object so I think "a .so" is correct.
valleyer 4 hours ago [-]
That's not generally how the English language works. We say "an SoC" but "a system-on-chip".
setheron 16 hours ago [-]
If you dynamically sold an SO are you still static even if you did it "custom" ?
At that point it's a dynamic loader in another name?
pg83 15 hours ago [-]
Technically, you're right, it's a dynamic loader. Technically, it's pure dynamic loading.
If we look at the issue at its core, we're still a statically linked program in a hostile environment, forced to dynamically load device drivers from the system.
It's similar to Golang; on MacOS, it has to use libSystem, even though otherwise, these are the statically linked Go binaries we're used to and love.
Let me add a little more detail: if I use vdso with gettimeofday in a statically linked program on Linux, am I still a statically linked program, or not? :)
snarfy 7 hours ago [-]
You normally do this sort of thing with plan old `dlopen` and `dlsym` and make your own "plugin" system.
simonask 19 hours ago [-]
It is a testament to the complete failure of the GNU/Linux userland that something like this seems at all attractive to spend time on (or, it seems, LLM tokens).
Actually, scratch that, because Windows and macOS have historically struggled with ABI compatibility as well (macOS less so, due to not caring about backward compatibility in the first place).
How did we get to the point where people feel they need to go to the length of embedding an ELF loader in their binary (!!) rather than just linking with glibc?
jeroenhd 13 hours ago [-]
> How did we get to the point where people feel they need to go to the length of embedding an ELF loader in their binary (!!) rather than just linking with glibc?
Most Linux distros have been built around the ability to compile their software together in a large repository, from source, so this was rarely ever an issue. Proprietary distribution or executing binaries from the internet like on Windows just wasn't really a common issue.
The problem arises when you start combining distros (glibc and MUSL for instance) or if you try to do the Windows model of sharing software. Historically, projects just compiled different versions for different distros.
When doing static compilation, just targetting an old version of glibc (which is generally forward compatible) also works.
You can hack your way into using software like this (or rather, have an LLM hack its way in) but I don't think any real distro actually cares. This issue exists in a quite small space where people are trying to use proprietary software built for glibc in MUSL environments for whatever reason, and the usual compatibility tricks don't work.
It's a niche use case for most Linux distros. It's not a "complete failure" of the GNU/Linux userland, it's the result of a couple of proprietary components not having MUSL builds available, or MUSL-based distributions not including libraries people want.
I'd like glibc to change so that these hacks aren't necessary for these use cases anymore, but it's not really a problem in practice for the vast majority of Linux use cases.
pg83 11 hours ago [-]
I disagree that this is a niche problem.
Yes, on the one hand, each specific distribution doesn't have this problem because it can pick up everything it needs.
But for us, independent developers of small programs, the problem is truly stark: we can't afford to build our programs for every distribution. And we can't afford to waste time navigating all the idiosyncrasies of various package repositories, both technical and political (not all repositories allow easy access).
And we can't count on someone else packaging our work until we become incredibly popular.
skydhash 8 hours ago [-]
> But for us, independent developers of small programs, the problem is truly stark: we can't afford to build our programs for every distribution.
Most people use the major distributions like Ubuntu, Debian, Fedora, Arch, alpine linux, void linux,… A build machine with VMs can easily take care of those.
> And we can't afford to waste time navigating all the idiosyncrasies of various package repositories, both technical and political (not all repositories allow easy access).
Most (if not all) package managers allow for custom repositories. No need to get access to the official ones.
diabllicseagull 18 hours ago [-]
I'm mostly taken aback all the solutions devised to go around the issue, especially the container-based ones. I really disliked it when I grabbed the flatpak version of Blender only to find out that it can't have HIP support. (they might have fixed it by now but you get the point)
wmf 18 hours ago [-]
Linux loves to leave papercuts unfixed or undocumented for decades. The solution is to build against an older version of glibc but no one tells you that or how to do it.
emidoots 17 hours ago [-]
Luckily Zig makes this quite easy to do. In Mach[0] we are able to just `zig build -Dtarget=x86_64-linux-gnu.2.28` to build GUI apps against an ~8 year old glibc version for maximum compatibility.
This is possible because Zig allows for targeting most glibc versions out of the box with its cross-compilation support.
1) Why should I limit myself to the available APIs?
2) Not just glibc. For example, if I build against the latest libstdc++, it will automatically support the more recent glibc. And pinning the old libstdc++ -well, that's just not a good idea.
krupan 16 hours ago [-]
Complete failure is strong words when lots and lots of Linux boxes are running just fine.
I think the disconnect is mostly people that can't decide if they want a stable distro or a rolling release distro. Most everyone uses a stable distro because it's stable, but then the want some up-to-date software that isn't ore-built for their (crusty old) stable distro and they get annoyed. My solution was to finally give in and embrace a rolling release distro (I use arch, btw). If there isn't a package for something I want, it's not hard to build something myself because all my build tools, kernel, and libs are up to date.
Other reasons to want static linking is to distribute proprietary software with no source code available. Linux certainly does not cater to that scenario and I suppose some might call that a complete failure ¯ \ _ ( ツ ) _ / ¯
pg83 19 hours ago [-]
Glibc has a terrible history of binary incompatibility. If that's so hard to believe, try running binaries built on one distribution on other distributions. Linux has two stable ABIs: the kernel ABI for static programs, and, ironically, WINE.
vlovich123 19 hours ago [-]
I haven’t heard of this and I don’t think you’re right. Glibc, for all its faults, as a general rule does backward compatibility well. The problem is if you compile against a newer glibc (common in CI by default) and try to run on a distro with an older (common in the wild). If your CI uses an older glibc you should be fine AFAIK.
There are also much less well-known "little things" that regularly pop up here and there.
> If your CI uses an older glibc you should be fine AFAIK.
In any case, my binaries work not only under glibc, but also under Alpine, and (work in progress) under android/bionic.
vlovich123 17 hours ago [-]
Not sure what you’re trying to show with that bug report but it’s not a case of cross distro glibc issues. If I read correctly it’s a vanilla behavioral change that exposed preexisting UB in flash.
Not sure how the comments about alpine or bionic relate either to my claim that cross distro glibc is fine.
pg83 17 hours ago [-]
It depends on how we define the ABI. I see it as a set of client-visible invariants that they rely on. In my world, glibc changed the client's visible invariants, breaking the client. The client works on one glibc-based host, but not on another. What is this if not "a case of cross-distro glibc issues?"
Overall, both of our points of view on compatibility were discussed well in that thread; we probably shouldn't repeat ourselves. :)
account42 11 hours ago [-]
ABI is not "whatever happens to work with this distro" but "what programs that comply with the API contract compile to". Overlapping memcpy arguments is an API contract violation and thus not something covered by the ABI either. This distinction is the entire reason why C has a separate memmove function. You can't just make up your own imaginary ABI contract and then blame the system when it doesn't fulfill it. That's going to result in self-inflicted plain on any OS.
pg83 11 hours ago [-]
It's so good that Linus thinks differently!
ninkendo 6 hours ago [-]
Even Linus’s comments in that bug hedge things a bit:
> and that if people depend on interfaces we exported having side effects that weren't intentional, we try to fix things so that they still work unless there is a major reason not to.
(Emphasis mine.)
In the Flash case, they changed the direction memcpy works in, in a way that didn’t actually improve any performance (Linus did benchmarks to show it) and Linus’s point was that there’s no upside to the change, and only downsides, so on the whole it makes no sense to keep the changed memcpy.
You could imagine plenty of scenarios where existing behavior truly is broken, and show that no popular software depends on the brokenness (which is why distros are in a great place to do these kinds of smoke tests, they have thousands of packages they can test), and make a judgement call.
A “zero regressions no matter what” policy is impossible in practice due to Hyrum’s law, at some point you have to use common sense and draw the line somewhere sensible.
ninkendo 8 hours ago [-]
> It depends on how we define the ABI. I see it as a set of client-visible invariants that they rely on
By that definition, any change of any kind will break ABI, Hyrum’s law and spacebar heating and all that.
__turbobrew__ 16 hours ago [-]
> Quite frankly, I find your attitude to be annoying and downright stupid.
- Linus Torvalds
That bug report was a good read.
jezek2 12 hours ago [-]
This is not true. Glibc supports symbol versioning. You can use it to select old versions of used symbols. The result is a binary that can work on 20 year old distros the same as on the latest, compiled with latest compiler and Glibc.
You can also compile using old distro and old Glibc to get similar effect. Though you would miss the advances of the newer compilers.
That irony is delicious. Build to WIN32 APIs to ship on Linux.
17 hours ago [-]
account42 12 hours ago [-]
> How did we get to the point where people feel they need to go to the length of embedding an ELF loader in their binary (!!) rather than just linking with glibc?
Due to FUD, mostly.
Sane people do just link with an old enough version of glibc.
catlifeonmars 19 hours ago [-]
So not completely static, since it must link against a libc :P
pg83 19 hours ago [-]
The binary itself is completely static; the link even provides commands on how to check this!
How are we supposed to take this stuff seriously if the author (sic) isn't even willing to write the readme? Claude exists! If I want some slop I can push the button myself.
pg83 19 hours ago [-]
Tell me, are there any substantive comments on the text, on what has been done, and on the technical implementation, and not on the form?
jeffbee 18 hours ago [-]
Why would I ask you about this work? The root of my question is why is this type of output shared on github, rather than the inputs?
pg83 18 hours ago [-]
Rough
analog_daddy 17 hours ago [-]
Disclaimer: This is a user’s perspective rather than a programmer’s perspective.
valid point. I am usually okay with LLM generated code since even if it might not be architecturally sound It is usually well commented and has tests and documentation for helping another agent/human debug any issues.
But, just the painful experience of debugging any dlopen related crashes and/or intermittent bugs; and the sheer amount of tokens burnt by an LLM chasing tangents when shown a stack trace; I wouldn’t touch this at least as a packager/consumer of certain apps for personal usage on older distros.
So far, AnyLinux-Appimages seem to be a mature solution with great support from the developers, in case anyone lands here for packaging applications to run on older distros.
pg83 17 hours ago [-]
Modern models, when properly managed with a human in the loop, write higher-quality code than humans and introduce significantly fewer bugs.
Therefore, it's quite the opposite - you should expect fewer "dlopen-related crashes and/or intermittent bugs."
amoss 13 hours ago [-]
> Modern models, when properly managed with a human in the loop, write higher-quality code than humans and introduce significantly fewer bugs.
I don't think anybody believes this, and interjecting it into every thread is not really convincing anyone.
someothherguyy 10 hours ago [-]
where is the study confirming this?
ChocolateGod 12 hours ago [-]
> So far, AnyLinux-Appimages seem to be a mature solution with great support from the developers, in case anyone lands here for packaging applications to run on older distros.
The Linux ecosystem already settled on containers to solve the problem. Namely Docker, Flatpak, Podman ec.
analog_daddy 1 hours ago [-]
They require admin privileges. A consumer on an enterprise servers might not have them, which is where I would guess glibc might not be latest one. If you are an admin then sure those solutions can be feasible.
Parents point is the readme was written by Claude. Signals low effort.
pg83 19 hours ago [-]
README.md was written by me, and I, of course, used claude/codex for it. In general, I do everything through claude/codex, the reasons are described in https://github.com/pg83/solo/blob/main/CONTRIBUTING.md . And no, it's not low effort, and no, I don't see the point in wasting time de-claude-ifying the text just to avoid it looking like I didn't spend enough time on it.
cryptonector 2 hours ago [-]
I almost always write my own prose, and when I have an LLM write me prose I edit it to make it my own, in my own style, and pithier than the LLM would make it.
I'm not saying everyone must do that. For me it's partly a point of pride, and partly empathy (editing for conciseness is showing empathy for others' time). But I am saying that people should consider doing it.
WD-42 18 hours ago [-]
You make it clear why you write all your code through a llm. But a README is not code. Presumably you would like people to read it. A machine authored readme reflects poorly on a project.
pg83 18 hours ago [-]
In any case, it's open source. If you don't like something, even if the project seems generally useful, go ahead and fix it. The PR came in. I'm an engineer and I can write good code, but that doesn't mean I can write good README.mds!
pg83 18 hours ago [-]
The author of the README is me, the machine just wrote it.
I am not a native speaker of English, my written English is simply terrible, no one wants to read the text that I wrote exactly :))
dummydummy1234 16 hours ago [-]
For context, I read Claude output every single day, I know what it is reliable with and what it is not. Or at least I have a feel for how much I can trust it.
It may not be clear to a non-english first language person, but when I read Claudes documentation, my brain immediately picks up claude-speak. Therefore, I expect the code to be generally correct, maybe, depending on how specific I was during my prompting. In no way shape or form do I really trust it, at least until i dig into the code and validate my mental model. And query the review for edge cases etc...
By writing your documentation with Claude, my brain immediately associates the quality of the project with the quality of unreviewed Claude output.
To a degree this is unfair, as it is like judging the quality of someone's work based on their accent.
But Claudes accent, has a high correlation with Claude, so unlike with people, where an accent has no bearing on technical ability, Claude being Claude does.
I would much rather read a typo ridden sentence than claudism, even just as a forward, explaining what you did vs the ai, and telling the users how much we should trust it.
Also, If you really insist on using AI to write, have another model rewrite docs/comments into regular English (opus 4.6 for example is much better than 4.7, 4.8, or 5.
It is open source so you can do whatever you like, but people (especially native English speakers) will discount your work, because Claude, especially opus 5 writes very very badly.
The people who say they would rather see a typo infested mess of a readme are serious. Or even just write in your native language and then have Claude translate.
Both of those are better indications of proof of effort than a Claude readme.
pg83 15 hours ago [-]
> Or even just write in your native language and then have Claude translate.
That's exactly what I did.
FooBarWidget 13 hours ago [-]
> By writing your documentation with Claude, my brain immediately associates the quality of the project with the quality of unreviewed Claude output.
I'm sorry, but I agree with the author: if a certain writing style makes you associate the work with low-quality, then that's your problem. The author shouldn't have to rewrite the readme just to avoid triggering your automatic unfounded associations. If you look at the substance of the work, including the test cases, then this is clearly not easy work that can be vibe coded in a single pass.
It's just like emdash. Everybody digs on how it's a signifier of LLM text, but I've used emdash for years because it's gramatically correct. I shouldn't have to stop using emdash just to avoid kneejerk reactions.
duskdozer 9 hours ago [-]
LLMs often use emdashes in a distinct incorrect way. It's not just the existence of any emdash, although considering you were in a very small minority of older users, it now warrants increased scrutiny, unfortunately for you.
nasso_dev 11 hours ago [-]
if 99 poor effort/quality projects have a readme that reads in a particular style, then you expect the 100th project with a readme in the same style to also be of poor effort/quality
statistically, it only makes sense for your expectations to immediately be low when you encounter this writing style because there are just so much slop out there
the author is free to keep that writing style but they should be aware that this will—at least on the surface level—make their project look exactly like the metric ton of slop we see posted everyday everywhere
cryptonector 47 minutes ago [-]
On the other hand I know how to tell if the underlying idea is good, and if it is I can decide to put up with reading through LLM outputs.
FooBarWidget 3 hours ago [-]
That's like saying a large percentage of X-colored people are criminals, therefore when we see an X-colored person it's reasonable to expect him to be criminal. As a society we have decided that heuristics like this, and prejudicing individuals based on statistics of the group, are not acceptable.
Also, what do we call it when people don't look beyond the writing style, such as at the code, or at the rigorousness of the tests, or at other substances beyond writing style? Slop assessment?
2 hours ago [-]
toast0 17 hours ago [-]
I think we're trying to tell you that we actually would prefer it.
xena 5 hours ago [-]
I would rather read "terrible" written english from a human than I would read "perfect" written english from Claude.
12 hours ago [-]
Barbing 18 hours ago [-]
I believe usually when someone complains about text written by a language model they are hoping to read human-written text instead of human-laundered LLM output.
jlebar 19 hours ago [-]
Implicit assumption of gp is that the README is llm authored. (Which, I agree is how it reads to me.)
j16sdiz 19 hours ago [-]
> backed by its own ELF loader (x86-64 and aarch64) and a glibc ABI bridge
Yacks
mananaysiempre 19 hours ago [-]
If you want to load the OpenGL/Vulkan vendor driver then unfortunately you don’t have much of a choice: those are linked against glibc, and I believe generally also against libwayland so screw off if you want a different protocol library (I might be wrong about the latter part). If you instead want to load plugins or whatnot into your statically linked executable, then personally I’d argue that you shouldn’t be emulating Linux dynamic linking semantics at all, because the whole late-bound global namespace thing is silly and wrong. (Solaris, which is where Glibc took this model from, moved away from it[1] as much as compatibility allowed, and so did Darwin[2], whereas Windows never made the mistake to begin with, but Glibc persisted and Musl copied it.)
Please elaborate and explain to people with less knowledge why this is bad.
arjvik 19 hours ago [-]
Mapping parts of files into executable memory, and then executing them, had better be bulletproof! Exploiting this seems like a direct path to RCE, and it's likely that this sort of library is used by privileged code.
Purely academically, this is a very cool piece of code! Just hoping that it gets a thorough vetting before used by privileged/security-critical software :)
pg83 18 hours ago [-]
Well, ld.so already does this, and it's no big deal. The Python interpreter also does this when executing a .py script (code is code, whether it's machine-readable or human-readable).
In any case, we take testing very seriously—every glibc shim we've written is covered with tests, and we run our loader against 1000 of the most popular Debian packages. The project has 100% code coverage. Perhaps, if I have the time, I'll also do some fuzzing on this thing.
The same issue would occur if glibc adds a new version of an existing symbol and then the GPU driver is recompiled. (Or, for that matter, if a GPU driver adds a dependency on a symbol which glibc has always supported but which isn’t in the subset that you reimplemented, though in theory that could be solved if you reimplemented 100% of the symbols.)
It's also not just new symbols, the loader semantics also aren't static and new enough libraries may not support older semantics - e.g. the loader used to use DT_HASH entries for symbol resolution but now they are no longer present on all distributions.
I wish it would that simple for practical use cases.
I ship professional software for colorists for Hollywood studios and they absolutely love to never upgrade. We have to ship for RockyLinux 8. Sad.
Part of the problem that people don't really realize is that it's not practical to just 'link against old glibc'.
In order to ensure compatibility with RHEL7 we need to link against RHEL7 libraries, which means we need to build on RHEL7. That means old, unpatched versions of glibc, libpcap, libcurl, openssl, or who knows what else, in case there's some backwards-incompatible change in newer RHEL versions.
Alternately, we can build/patch the libraries we use and compile them statically into the binary, which is vastly more maintenance work for us for very little benefit.
Meanwhile, compiling against older versions of libraries like glibc means we don't get the benefit of updates; not just new features in glibc, but things like more/better SIMD support in glibc algorithms, more/better optimizations in GCC, and so on.
Alternately, we can stop supporting RHEL7, like Redhat did, and build against RHEL8... Or we can build a separate version against each version of RHEL we want to support.
Funnily enough, we've been shipping RHEL7 RPMs of our product for years, and only recently realized that they won't actually run on RHEL7 because at some point the toolchain updated and now we're compiling against too new of a glibc version and then packaging it into a RHEL7 RPM. It wouldn't have worked on RHEL <8 for the past... few years? But no one uses RHEL7 so it went completely untested for ages and we didn't get any customer complaints.
Now we're re-labelling our RPMs as EL8, but it's just a cosmetic change so that we're claiming the version that we actually require.
>Rocky Linux 8 is supported by the Rocky Linux project until May 2029.
https://forums.rockylinux.org/t/what-is-eol-of-rl8/3316/3
I've got plans to try to build on RockyLinux9 and package its glibc along with the app. Simple helloworld works, so I have a glimpse of hope that it would be possible to ship like that.
Just the other day I tried running an older binary and it failed with a glibc error, despite it being linked to a glibc version that's barely 5 releases behind the one on my system. So maybe glibc isn't backwards compatible after all...
I'm not offering a silver bullet, but the approach I've implemented is much better than what the industry currently offers.
Honestly I'm impressed by this work. It's a thankless thing to have built, it's definitely feasible to build but more work than almost anyone would have been willing to do. What would be even neater is if Musl had this builtin.
Advocates of static linking keep forgetting once upon a time UNIX only had static linking, then we had overlays, and eventually dynamic linking came to be.
And I also remember very well that dynamic linking appeared ONLY because we were catastrophically short on memory; everything else was added much later.
Now we have plenty of memory, and we can very well return to our blessed roots!
Ah, you have lots of memory, we're very wealthy. /s
> GPU: Vulkan and OpenGL drivers are supplied by the host as shared objects, usually built against glibc, and a fully static musl binary cannot normally dlopen() them.
Why? Have people managed to break the ancient concept of shared libraries, and this is a fix for that?
Shared libraries have always been broken in Linux. Unfortunately many things like GPU drivers, graphics libraries and NSS need shared libraries to dynamically load certain runtimes (because you don't want to load all possible GPU drivers in existence to your RAM). So an ecosystem has been developed on top of terrible ABI and architecture GNU/glibc provided.
That's currently the real core of the problem.
The loader (and libdl) need to be decoupled from the glibc itself under Linux.
Without that, any attempt to ship static binaries (or any binary with a different Libc) will be a source of perpetual pain.
nss plugins and its associated pain (sssd and avahi) are an other examples of that.
But I do agree with you, the glibc is all one giant codebase shipped in one giant blob, dispatch under several names.
Even libdl.so is barely more than a wrapper and some glue around the libc.so itself.
I've become obsessed with getting rid of it, especially after I realized that contributing to GNU itself was a dead end. Freestanding Linux programming turned out to be much more fun anyway.
All libraries out there should adopt the SQLite design: programmers provide it with all the necessary functions. Instead of libraries hard depending on glibc, we get to inject the libc-ish subset it needs. Then we can use whatever we want under the hood. I'm working on porting SQLite to freestanding Linux system calls so it can run with zero dependencies. Wish I could say the same for software like mesa, I'd need a lot of help for this one...
Can't be done. The libc is legacy, it can't be changed without breaking everything. It's also mandatory on every operating system other than Linux.
A change in paradigm is necessary. Freestanding C, not hosted C. This completely gets rid of the libc and is a surprisingly clean language. Linux only, because it's the only kernel with a stable binary interface. Every other OS forces a C runtime.
I once worked on a liblinux project that embodied this... Stopped because Linux itself has a nolibc thing in the kernel tree and I didn't want to compete with it. Now I'm working on the Rust version.
> what is necessary for users of the C language to actually do stuff
Surprisingly little. I wrote an entire lisp interpreter in freestanding C with Linux system calls. It managed to survive for a rather long time without any memory allocation at all.
The system layer is refreshingly tiny. It consists of a memory allocator and extremely basic functions like memmove and strlen. I successfully got rid of total nonsense like thread local errno, locales, implicit buffering, cached global state, possibly more. All that stuff is gone! Exactly one global survived: the stack canary generated by GCC and clang. Every other symbol in the ELF is controlled by me.
Wasn't able to get rid of the NUL terminator. Linux itself needs it. To get rid of that little billion dollar mistake requires an entirely new kernel with zero UNIX/POSIX influence. I had to make my peace with that one. All my buffers maintain an extra NUL byte at the end.
Great choice for small programs, but what if I want hardware accelerated 3d?
With Linux system calls alone it should be possible to set up kernel mode setting without depending on any toolkit at all. This should be enough to get a framebuffer for software rendering.
For hardware acceleration though, one must give this graphics context to an OpenGL ES implementation. That's where it gets ugly. There is no way to divorce that from the libc short of literally rewriting it.
Maybe Vulkan will enable it? I can't say for sure at my current knowledge level.
https://vulkan.lunarg.com/doc/view/latest/mac/LoaderInterfac...
Windows explicitly does not want you to link the system libc. You are expected to bring your own, and doing so means your process has multiple libc's loaded into its address space.
And if you choose to build a binary that doesn't need a libc, you won't be bringing one.
This is categorically false; UCRT[1] is a thing. The 'U' stands for universal. Unlike Linux, Windows allows developers to choose their ABI boundary and also ship that boundary if they desire, or use the 'system' one and ask older platforms to install redistributables or Windows update packages. There's the old and new C runtimes in MSVCRT.DLL and UCRTBASE.DLL, the C++ runtime in VCRUNTIME140.DLL, Win32 in KERNEL32.DLL, USER32.DLL and more, and then the stable-ish kernel interfaces in NTDLL.DLL, in order of 'closeness to the kernel'.
And also, 'libc' is a UNIXism; on Windows the term is CRT, for 'C runtime'.
[1]: https://learn.microsoft.com/en-gb/cpp/porting/upgrade-your-c...
Linux has a stable system call ABI. I can trash the entire user space and boot Linux with init=/my/program if I want.
It's not that Linux doesn't allow developers to choose, it's that Linux doesn't actually control the userspace ABIs. Userspace is whatever we want it to be, and people settled on "GNU/Linux".
This is a noble but practically worthless ideal. Hardly anything is written against the kernel ABI except glibc itself. End-user application developers don't write against the kernel interface. Platform library and application developers like KDE and GNOME don't really write against the kernel interface either. Nowadays even driver developers don't entirely write against the kernel interface, instead also linking in glibc and lib{std}c++ for things like shader compilers (which is nowadays usually just a fork of Clang). Like it or not, glibc is the singular entry point for desktop Linux, which is the point of essentially every comment under this post.
That 'desktop Linux ≡ glibc' is something that I daresay the kernel developers also have to take responsibility for, rather than shrugging their shoulders and pawning it off to GNU. Linux's unstable user-mode is the entire reason for Docker's existence. It is bodge upon bodge and has led to much gnashing of teeth. The GNU/Linux community (I don't care for Stallman's meme rant here) have to grow a backbone and understand that an OS is much more than just a kernel, it is a platform, and currently the Linux platform is a disparate set of communities with highly divergent goals.
That's why I've always said the best Linux is Android, because Android is a platform.
I'm working hard to change that.
Here's an entire lisp interpreter written in freestanding C using nothing but Linux system calls:
https://github.com/lone-lang/lone
It has a system-call primitive too, which means it can do anything.
Here's a TCP echo server written in lone lisp:
https://github.com/lone-lang/lone/blob/master/examples/tcp-e...
It works, and you don't need libc.
> Like it or not, glibc is the singular entry point for desktop Linux
Well I don't like it, and I've made it my mission to fix it.
Maybe lisp isn't the answer, Rust is. I've revived my liblinux crate too.
https://github.com/matheusmoreira/liblinux
I'm going to add system calls to this thing every day until I have every single one. Then I'll use them to make all of my applications.
Already have my first project planned: a custom Rust network stack for my QEMU virtual machines so I can firewall them off in userspace without root access.
> the Linux platform
The linux platform is these system calls. Everything else is userspace. This is exactly what makes it special.
https://www.matheusmoreira.com/articles/linux-system-calls
This is exactly the attitude I meant by 'the kernel developers also have to take responsibility for, rather than shrugging their shoulders and pawning it off to GNU' and 'an OS is much more than just a kernel, it is a platform'.
By 'platform', I mean windowing toolkits, such as Wayland. Audio libraries like ALSA and PipeWire. Desktop idioms and libraries like DBus, and the entire freedesktop.org effort including Mesa and more. Init systems like systemd. Bootloaders like GRUB/systemd-boot. Networking, including systemd-networkd, iwd, and NetworkManager. Power and device management including PowerDevil, Bluez.
If all of this is considered 'user space' then my point is strongly vindicated. On most competitor OSs these comprise the 'platform'. You won't ever encounter a Windows or OS X developer arguing that USER32.DLL or Foundation is 'userspace' even if it is technically true and code from those libraries may only ever run in ring 3. From the average user's point of view, the only 'userspace' are end-user applications that they have installed, like Chrome, video games, etc. From that perspective, even purely user-mode applications that ship with the distro ought to considered 'platform'. This includes such trivia like calculator and camera applications.
We need to talk about the practical realities of a platform, not a theoretical, unattainable, and as I mentioned, useless ideal. These practical realities mean shipping a unified set of useful 'batteries included' user-mode libraries and applications. If one wants to develop the platform itself, then sure, the kernel has a nice stable interface, but on most Linux distributions the kernel is built from source anyway, so even that nicety is moot.
You mean Linux distributions.
> From that perspective, even purely user-mode applications that ship with the distro ought to considered 'platform'.
Every Linux distribution is its own platform.
You decried Stallman's meme rant yet you just rewrote the entire rant in your own terms. Linux is the kernel, GNU's just the stuff on top. You can get rid of it if you want and make your own. Android did exactly that.
Also, I daily drive Alpine Linux (using musl) and I'm quite happy to assure you that glibc really is only one option, not at all mandatory for desktop Linux.
I never disparaged it; I said having it doesn't make it any more useful that OSs that don't, because the practical reality is that the kernel interface is narrow and highly technical, and a stable kernel binary interface has little to do with a user's day-to-day experience with an OS.
I daresay Android would still work pretty well even sans the kernel ABI stability, because the kernel is usually built from source, and if it isn't, then every Android source tree has kernel prebuilts for the kernel version shipping. I also never said nor implied glibc should be uniquely privileged; I think you have misrepresented my comment.
Some advertisement for VC-LTL: https://github.com/Chuyu-Team/VC-LTL5 and https://github.com/Chuyu-Team/YY-Thunks
VC-LTL Implements a good subset of modern CRT functions using the msvcrt.dll, which makes modern MSVC compiler to emit binaries that theoretically can run in Windows NT 4.1 or Windows 98 (until CFG broke them, but you can turn it off). Still can teach an old dog new tricks.
YY-Thunk is a little more funky, it tries to implement new Win32 userspace API for old system by emulating the behavior and expected values using userspace API in the old apartment, so you can try to implement a function in Windows 10 with functions in Windows Vista, and hopefully it will work out.
Usually Windows developers will use Win32 directly, ZeroMemory() instead of memset(), and so on.
> At some point, the decision was made to just give up and declare it an operating system DLL, to be used only by operating system components.
https://devblogs.microsoft.com/oldnewthing/20140411-00/?p=12...
That only massively compounds the problem.
> And if you choose to build a binary that doesn't need a libc, you won't be bringing one.
NT system calls are not stable. You still need to link against ntdll.dll at the very least, like a forced Linux vDSO.
The Windows ecosystem, that manages to deliver built binaries easily & widely, regardless of whether the author has a 1 year old OS or a 15 year old OS, suggests that it's not as big a problem as you believe.
I'm sure those OSes make efforts to make said runtime binary compatible between executables.
That's when you run into the Darth Vader of binary interfaces.
> I have altered the ABI. Pray I do not alter it further. -- De Raadt
GPU drivers are a perfect example - GPUs are hellishly complex, and their interfaces are super high-level - OpenGL/Vulkan is not something that should be implemented by the driver.
I think a general practice that is followed with GPU drivers is to move as much of the complex and non-privileged stuff out of the kernel, and only keep modesetting/power management/memory and control stuff in.
That reduces massively the amount of code that needs to run in Ring 0, but in exchange, the interface between the user and kernel blobs is non-stable. Programs talk to the userspace blob and there's no guarantee of compatibility between different kernel/userspace driver versions.
This is something even Linus accepts, with the only contention that the userspace blob needs to be open source as well. They're not maintaining something in tree, that they have no way of testing without proprietary external deps.
Or.. any other programming language. A lot of systems programming is done in Rust nowadays.
SQLite already does this with its VFS layer. You hand it the OS functions it needs instead of it grabbing them, etc.
You could look at going straight to syscalls, mesa would be a rewrite nightmare
I went zero-dependency on a production CMS. Rust, no framework, no external crates beyond argon2 for password hashing. Running live on client sites. The hard part wasn't building it, it was accepting that everything you reach for is pulling glibc or similar assumptions back in through the side door. SQLite's VFS model is the right pattern. More things should work that way.
C++ abi should not be included in this. It is independent from the other pieces and historically a source of incompatibility on its own.
Saying "C/C++ abi" as if they are the same is looney tunes, the former is very simple and stable and the latter is very complex.
How libstdc++ initializes global variables absolutely depends on glibc and ld-linux.so. That is part of C++ ABI.
Somehow most of my portability issues seem to be caused by glibc, its symbol versioning and close ties to the dynamic loader. Minor versions aren't compatible, no two Linux distros ship the same version and you can't just provide your own without also patching in your own dynamic loader.
At least as far as the defaults on Linux go I consider C the root of all evil.
In which case as long as you're using the documented public API and compile your program with the oldest version of glibc you want to support (some Ubuntu from 4-5 years ago should cover pretty much every current desktop) you should be fine. And with something like Docker this is trivial to do.
Sure it is annoying that you cannot use your current distro (especially if you use some rolling distro) to make binaries for everyone, but it takes very little effort to work around that. The only issue i can think of is if you absolutely want to compile using the latest version of your compiler and you cannot build the compiler from source to work in the Docker (or whatever) contain to work against the older glibc.
In complex cases, it turns out that the old version of glibc also pulls in other libraries and the compiler, and you're stuck with a very ancient sysroot. You may often find that you can't compile new library versions in such a sysroot and link them statically.
So, it looks good on paper, but forget about the ravines.
C++ global/static variable initialization depends on the specific version of glibc (they don't usually break compat, but they can and they did in the past) which also provides ld-linux.so that loads those global variable placeholders in the correct manner such that glibc and libstdc++ can initialize them correctly.
This is just one example. Thread local variables and behavior of things like pthreads with signal, fork etc all depend on glibc.
And that's the correct approach and also one that many have taken. We just need someone willing to maintain that as an easy mode SDK for everyone.
Who said that? This approach has many problems that have already been discussed here, not to mention the fact that it leaves Alpine and Bionic-based systems out in the cold.
Let me remind you that Bionic is the most widespread libc in the Linux world, and Alpine is the most popular Docker layer.
The world doesn't end with glibc. And it doesn't begin with it.
Until you define a thread local variable (C11) or use atomics (also C11) or define a global with an initial value. Then it happily generates code that depends on "whatever my target glibc + ld-linux.so needs".
The ABI is strongly dependent on explicit libc implementation in current Linux systems. There is no libc independent ABI on Linux.
When you compile libc, you also get a binary loader ld-linux.so with it. They are not two independent components of a system.
Basically all .so files compiled with glibc require the ld-linux.so that's also generated by that glibc (or a later version, if they didn't break the binary compatibility).
There are a lot of stuff that's executed by ld-linux.so and glibc that are not explicitly documented but they are absolutely necessary for your program to start and correctly initialize things like global variables or signal handling or loading other dynamic libraries. Some of that functionality sits in ld-linux.so and some of that in glibc. They have circular dependencies to each other. glibc expects ld-linux.so to put things in certain order but ld-linux.so also must load glibc first to have access to certain APIs. They are not part of System V ABI. They are not documented.
Musl maybe can implement this but it is simply reverse engineering what glibc did and then playing a game of cat and mouse. There is no independent ABI standard.
Then, what is the exact reason a library compiled against glibc must be loaded by a specific ld-linux? I could see that this is true for C++ perhaps, or when you use very special features, but I do not see this for C.
I often compiled programs against one version of glibc and run it against a different version, so I know there is not a tight coupling. So please be specific in explaining in what scenarios this would break.
I'm not arguing that there aren't problems here, just that I think people have different expectations that are a little extreme. The libc implementation is a core part of the operating system, if you want to target that OS you should target that libc. Some distros (alpine) prefer musl libc, most prefer glibc. And distros really are different operating systems.
All solutions to this problem are hacky, complex and controversal and highly fragmented, where this should be BASIC functionality
Solo is basically pg83's answer to the architecture you just described. If there is no libc independent ABI, build your own loader and shim the boundary.
I can understand Linus's obsession with taste and the areas it was overlooked or traded.
Or do the MS thing, and ship multiple versions like msvcrt
Not really, though. glibc uses symbol versions that are forward but not backward compatible. If you got an error that said "this program was built for a newer version of <distro>" would you say the same thing?
Note this is the same (if not worse) on MacOS, and on windows you used to distribute the CRT with your application just to deal with the same problem.
Yes glibc has some backwards compat but you cannot load a binary compiled with a newer version of glibc using an older ld-linux.so. That's because the interdependency. Nor you can load binaries that depend on different libc.so files with glibc systems
I cannot comment on macOS, I have never used it. However this is not a problem with Windows. You can ship a newer CRT or you can install it as a system component using Microsoft's MSI. The dependency is one way on Windows. CRT purely depends on Win32. Moreover the loader is completely independent and DLLs are loaded into their own unique scoped namespace unlike Linux that loads them in global symbol namespace. That's why you can mix and match DLLs compiled for different CRT versions.
On Windows you don't need to ship a new binary loader. I can just ship Windows 10 UCRT DLL (which is the new libc of Windows) to Vista and my binaries will work. The binary loader isn't interlinked with the libc.
One of the ways Windows manages to support multiple libc's is by being careful not to mix allocators; if a system API you call allocates on your behalf, your libc can't free it, the system API will offer a function to free it.
Windows loader is certainly available to user programs though; LoadLibrary has been around longer than many developers.
As for older Windows systems not being able to load new DLLs, they can; the format hasn't changed in a very long time. I've had experience with installing some DLLs on Windows NT 3.51 and running a modern Firefox, which is about 20 years behind the times.
MS haven't made this work arbitrarily far back, I believe they deprecated targeting Windows XP in one of the more recent toolchains, but that was purely a "not worth supporting" situation.
EDIT: mixed up talking about older & newer
Additionally many other OSes have had both approaches since their early days, Xerox PARC ones.
For some strange reason they assume to know better than all those researchers.
What you can't do is build something statically with musl and then reliably dlopen shared libraries built with glibc.
If you break or remove a shared lib here, you may no longer be able to compile something from source. I had that happen in the past before I started to use more statically compiled programs (and busybox too).
Assuming everything works as-is via shared libraries at all times, makes little sense for ALL linux systems. For instance, some people upgrade glibc manually. Then you need a working base system to resume compilation. I do that for my customized gobolinux system, so I can use any program version as well as any glibc version (assuming I can still compile the program; many older programs no longer compile).
I don't think Android is a target in most cases of this, so this would boil down to shipping 2* binaries, one glibc and one musl.
I'd need to check how musl behaves for compatibility, I'm assuming it'll be a minimum like glibc.All of this said - you'll need to ship multiple binaries anyway, these days: x86_64 + aarch64. Possibly more…
i helped somebody get Doom running on an old embedded system running some 4.x kernel. we just built everything, including mesa, statically and deployed that. imagine building everything static but loading the system's libgl.so instead (which was probably just a symlink or abstraction over mesa's own implementation). what's the benefit: we'd get older, less optimized graphics routines from 5 years ago?
if you're statically linking, then just bring your own graphics "driver". the kernel interfaces are stable enough. it's not conceptually different than embedding `syscall`s directly into your application the same way you do when statically linking libc.
In the case of OpenGL, it has a fairly sophisticated state tracker, which you can carry around with you. For example, I can statically compile ANGLE this way.
But even OpenGL/Vulkan have a shader compiler that's unique to the hardware (it's expensive to carry it around with every piece of hardware in the universe).
And I'm not even talking about libdrm, which is quite tightly tied to the kernel version.
I know exactly what I'm talking about, because I can also statically compile Mesa into my application.
That is when I realized the true horror of (g)libc. It wants to inject itself at the root of the library/program and everything from threading to dlopen/dlsym is impacted. I tried a lot of workarounds including trying to implement a loader myself, but the complexity (and fragility) grew so much that I felt it was not worth it.
Finally, I retreated into the safe world of a freestanding runtime + syscalls. FFI, if it has to happen, will occur via IPC of some kind. A second process linked against glibc that will manage calls on behalf of the clean first one.
Yes, it's not trivial, but I hope that over time everything will settle down.
> if I understood your implementation correctly you're hooking this into an already running musl which could cause backwards compatibility issues if musl changes under you
No, I hook this to musl, which is statically linked into my binary, and I have complete control over it.
https://github.com/pg83/solo/commits/main/README.md
https://github.com/pg83/solo/commits/main/
Rude.
It only gets better from there - https://github.com/pg83/solo/blob/main/CONTRIBUTING.md!
Telling me, a coder you never met or interacted with before, that I would introduce more bugs than The Product(tm) is pretty rude and prejudiced.
> The project author believes that, with capable human direction, modern LLMs write code faster than people and introduce fewer bugs.
Obviously, this is an offense to me.
> Telling me, a coder you never met or interacted with before, that I would introduce more bugs than The Product(tm) is pretty rude and prejudiced.
Don't exaggerate. I didn't say that you personally introduce more bugs (as you rightly pointed out, I don't know you and don't know how often you introduce bugs), I said that the average developer introduces more bugs than an SOTA LLM.
But why? You didn't write it. You said so yourself, you wrote the initial text but it was all reworded by claude.
I could choose to believe in a social fiction and you can't stop me.
see cosmo_dlopen for an example implementation.
From what I remember, GPU access on Linux 'works' by accessing specific FDs under /dev, which are vendor specific - this is what these libs do under the hood.
The libraries don't have any magic powers - if the FD is inaccessible, you won't be able to do anything.
So there's some vendor specific access needed in containers anyway (or a blanket allow, which is a BAD idea).
Also not sure why dynamic linking isn't good enough for this - the issue lies with the permissions, not how you load/link libraries.
If we look at the issue at its core, we're still a statically linked program in a hostile environment, forced to dynamically load device drivers from the system.
It's similar to Golang; on MacOS, it has to use libSystem, even though otherwise, these are the statically linked Go binaries we're used to and love.
Let me add a little more detail: if I use vdso with gettimeofday in a statically linked program on Linux, am I still a statically linked program, or not? :)
Actually, scratch that, because Windows and macOS have historically struggled with ABI compatibility as well (macOS less so, due to not caring about backward compatibility in the first place).
How did we get to the point where people feel they need to go to the length of embedding an ELF loader in their binary (!!) rather than just linking with glibc?
Most Linux distros have been built around the ability to compile their software together in a large repository, from source, so this was rarely ever an issue. Proprietary distribution or executing binaries from the internet like on Windows just wasn't really a common issue.
The problem arises when you start combining distros (glibc and MUSL for instance) or if you try to do the Windows model of sharing software. Historically, projects just compiled different versions for different distros.
When doing static compilation, just targetting an old version of glibc (which is generally forward compatible) also works.
You can hack your way into using software like this (or rather, have an LLM hack its way in) but I don't think any real distro actually cares. This issue exists in a quite small space where people are trying to use proprietary software built for glibc in MUSL environments for whatever reason, and the usual compatibility tricks don't work.
It's a niche use case for most Linux distros. It's not a "complete failure" of the GNU/Linux userland, it's the result of a couple of proprietary components not having MUSL builds available, or MUSL-based distributions not including libraries people want.
I'd like glibc to change so that these hacks aren't necessary for these use cases anymore, but it's not really a problem in practice for the vast majority of Linux use cases.
Yes, on the one hand, each specific distribution doesn't have this problem because it can pick up everything it needs.
But for us, independent developers of small programs, the problem is truly stark: we can't afford to build our programs for every distribution. And we can't afford to waste time navigating all the idiosyncrasies of various package repositories, both technical and political (not all repositories allow easy access).
And we can't count on someone else packaging our work until we become incredibly popular.
Most people use the major distributions like Ubuntu, Debian, Fedora, Arch, alpine linux, void linux,… A build machine with VMs can easily take care of those.
> And we can't afford to waste time navigating all the idiosyncrasies of various package repositories, both technical and political (not all repositories allow easy access).
Most (if not all) package managers allow for custom repositories. No need to get access to the official ones.
This is possible because Zig allows for targeting most glibc versions out of the box with its cross-compilation support.
[0] https://machengine.org
1) Why should I limit myself to the available APIs?
2) Not just glibc. For example, if I build against the latest libstdc++, it will automatically support the more recent glibc. And pinning the old libstdc++ -well, that's just not a good idea.
I think the disconnect is mostly people that can't decide if they want a stable distro or a rolling release distro. Most everyone uses a stable distro because it's stable, but then the want some up-to-date software that isn't ore-built for their (crusty old) stable distro and they get annoyed. My solution was to finally give in and embrace a rolling release distro (I use arch, btw). If there isn't a package for something I want, it's not hard to build something myself because all my build tools, kernel, and libs are up to date.
Other reasons to want static linking is to distribute proprietary software with no source code available. Linux certainly does not cater to that scenario and I suppose some might call that a complete failure ¯ \ _ ( ツ ) _ / ¯
There are also much less well-known "little things" that regularly pop up here and there.
> If your CI uses an older glibc you should be fine AFAIK.
In any case, my binaries work not only under glibc, but also under Alpine, and (work in progress) under android/bionic.
Not sure how the comments about alpine or bionic relate either to my claim that cross distro glibc is fine.
Overall, both of our points of view on compatibility were discussed well in that thread; we probably shouldn't repeat ourselves. :)
> and that if people depend on interfaces we exported having side effects that weren't intentional, we try to fix things so that they still work unless there is a major reason not to.
(Emphasis mine.)
In the Flash case, they changed the direction memcpy works in, in a way that didn’t actually improve any performance (Linus did benchmarks to show it) and Linus’s point was that there’s no upside to the change, and only downsides, so on the whole it makes no sense to keep the changed memcpy.
You could imagine plenty of scenarios where existing behavior truly is broken, and show that no popular software depends on the brokenness (which is why distros are in a great place to do these kinds of smoke tests, they have thousands of packages they can test), and make a judgement call.
A “zero regressions no matter what” policy is impossible in practice due to Hyrum’s law, at some point you have to use common sense and draw the line somewhere sensible.
By that definition, any change of any kind will break ABI, Hyrum’s law and spacebar heating and all that.
- Linus Torvalds
That bug report was a good read.
You can also compile using old distro and old Glibc to get similar effect. Though you would miss the advances of the newer compilers.
https://docs.appimage.org/reference/best-practices.html
I hear you about WINE though.
Due to FUD, mostly.
Sane people do just link with an old enough version of glibc.
valid point. I am usually okay with LLM generated code since even if it might not be architecturally sound It is usually well commented and has tests and documentation for helping another agent/human debug any issues.
But, just the painful experience of debugging any dlopen related crashes and/or intermittent bugs; and the sheer amount of tokens burnt by an LLM chasing tangents when shown a stack trace; I wouldn’t touch this at least as a packager/consumer of certain apps for personal usage on older distros. So far, AnyLinux-Appimages seem to be a mature solution with great support from the developers, in case anyone lands here for packaging applications to run on older distros.
I don't think anybody believes this, and interjecting it into every thread is not really convincing anyone.
The Linux ecosystem already settled on containers to solve the problem. Namely Docker, Flatpak, Podman ec.
Wanna see those containers running in those old systems. Good luck getting flatpak to run on a kernel without PR_SET_NO_NEW_PRIVS.
I'm not saying everyone must do that. For me it's partly a point of pride, and partly empathy (editing for conciseness is showing empathy for others' time). But I am saying that people should consider doing it.
It may not be clear to a non-english first language person, but when I read Claudes documentation, my brain immediately picks up claude-speak. Therefore, I expect the code to be generally correct, maybe, depending on how specific I was during my prompting. In no way shape or form do I really trust it, at least until i dig into the code and validate my mental model. And query the review for edge cases etc...
By writing your documentation with Claude, my brain immediately associates the quality of the project with the quality of unreviewed Claude output.
To a degree this is unfair, as it is like judging the quality of someone's work based on their accent.
But Claudes accent, has a high correlation with Claude, so unlike with people, where an accent has no bearing on technical ability, Claude being Claude does.
I would much rather read a typo ridden sentence than claudism, even just as a forward, explaining what you did vs the ai, and telling the users how much we should trust it.
Also, If you really insist on using AI to write, have another model rewrite docs/comments into regular English (opus 4.6 for example is much better than 4.7, 4.8, or 5.
It is open source so you can do whatever you like, but people (especially native English speakers) will discount your work, because Claude, especially opus 5 writes very very badly.
The people who say they would rather see a typo infested mess of a readme are serious. Or even just write in your native language and then have Claude translate.
Both of those are better indications of proof of effort than a Claude readme.
That's exactly what I did.
I'm sorry, but I agree with the author: if a certain writing style makes you associate the work with low-quality, then that's your problem. The author shouldn't have to rewrite the readme just to avoid triggering your automatic unfounded associations. If you look at the substance of the work, including the test cases, then this is clearly not easy work that can be vibe coded in a single pass.
It's just like emdash. Everybody digs on how it's a signifier of LLM text, but I've used emdash for years because it's gramatically correct. I shouldn't have to stop using emdash just to avoid kneejerk reactions.
statistically, it only makes sense for your expectations to immediately be low when you encounter this writing style because there are just so much slop out there
the author is free to keep that writing style but they should be aware that this will—at least on the surface level—make their project look exactly like the metric ton of slop we see posted everyday everywhere
Also, what do we call it when people don't look beyond the writing style, such as at the code, or at the rigorousness of the tests, or at other substances beyond writing style? Slop assessment?
Yacks
[1] https://www.linker-aliens.org/blogs/rie/entry/direct_binding...
[2] https://web.archive.org/web/20011004090044/http://developer....
Purely academically, this is a very cool piece of code! Just hoping that it gets a thorough vetting before used by privileged/security-critical software :)
In any case, we take testing very seriously—every glibc shim we've written is covered with tests, and we run our loader against 1000 of the most popular Debian packages. The project has 100% code coverage. Perhaps, if I have the time, I'll also do some fuzzing on this thing.