upvote
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.

reply
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...

reply
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.
reply
> 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.

reply
> 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?

reply
Modern 3D APIs are built around passing around a few buffers per frame and long command queues. Should be doable with IPC.
reply
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.

reply
Vulkan is designed to only load core statically and the whole extension spaghetti dynamically, using driver entry points.

https://vulkan.lunarg.com/doc/view/latest/mac/LoaderInterfac...

reply
exactly
reply
>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.

reply
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

reply
> 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.

reply
> 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'.

[1]: https://learn.microsoft.com/en-gb/cpp/porting/upgrade-your-c...

reply
> 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".

reply
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.

reply
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.
reply
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.

https://devblogs.microsoft.com/oldnewthing/20140411-00/?p=12...

reply
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.
reply
> 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.

reply
> 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.

reply
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?
reply
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.
reply
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.

reply
> 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.

reply
libdl is the loader FWIW. It's all one big glibc codebase.
reply
> 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.

reply
See: https://news.ycombinator.com/item?id=49355262

How libstdc++ initializes global variables absolutely depends on glibc and ld-linux.so. That is part of C++ ABI.

reply
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.
reply
> 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.

reply
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.

reply
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.

reply
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.
reply
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.
reply
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.
reply
Yeah it's not Linux specific but rather true for most Unix systems that libc == base system interface library.
reply
Except the other UNIX systems, starting with the original one, evolved from only having static linking to various ways to connect libraries and applications.
reply
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.
reply
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.

reply
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).
reply
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.
reply
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.
reply
> 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.

reply
> 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.

reply
It could be fine if you care only about free-software desktop users, and not Google's propriety platform and hyperscalers.
reply
For example, the itanium unwind ABI implementation lies between these three entities.
reply
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.
reply
> 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".

reply
It depends on functions defined in a standardized ABI.
reply
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.

reply
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?
reply
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.

reply
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.

reply
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

reply
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

reply
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.

reply
> 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.

reply
See https://news.ycombinator.com/item?id=49355262 .

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.

reply
That's what I'm saying though, glibc-linked binaries are forwards (but not backwards) compatible.
reply
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.

reply
Windows doesn't even have a concept of a loader binary right? I think its hardcoded into the kernel/win32 itself.
reply
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.

reply
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.
reply
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.

reply
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

reply
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
reply
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.

reply
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.
reply
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.
reply
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.

reply
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).
reply
Only on current glibc distributions. Your SoLo-program WILL break on future distributions. This is not a sensible tradeoff.
reply
Who says it is not a sensible tradeoff? It may be a much better tradeoff than not being able to run it at all.
reply
Any software WILL break on future distributions, sooner or later.
reply
musl does not perfectly emulate all aspects of glibc, so trying to use libraries that assume glibc can sometimes lead to problems.
reply
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.
reply
Not anymore, but for -years- it did DNS wrong because of the author being pedantic about an RFC wording.
reply
Mostly about precompiled libraries (proprietary software) and libraries and software that use GNU extensions.
reply
> 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).

reply