[Pre-RFC] target-glibc: Proposal to add native support for GLIBC versions for the -gnu targets
zackw:
Another very important reason for this proposal is that I expect your AI-assisted MVP implementation probably hits all the cases where "just call the old symbol" works by accident, and none of the ones where it'll link OK but then when you run the program it'll fail to load, crash, corrupt memory, or worse
Thing is, for better or worse, Rust is already in this situation.
In C, function signatures and struct definitions come from the headers present at compile time, while symbol versions come from the .so present at link time. These are expected to come from the same glibc version, so glibc can simultaneously update the headers to change a function signature and update the .so to point to a new version.
But in Rust, nearly everyone – including std – accesses glibc through the libc crate. And the libc trace ignores headers and hardcodes its own declarations, which are always the same regardless of the glibc version. Yet linking still works like usual, so you might end up linking against an older or newer symbol version depending on the version of the .so in your sysroot.
Therefore, for all symbols where glibc has made an incompatible change, libc must already be broken on either old sysroots or new sysroots!
There is one exception. For the cfsetspeed family of functions, glibc made a backwards-incompatible change in 2025, and the breakage was reported. libc addressed this, not by varying its declaration based on glibc version, but by explicitly choosing a symbol version at compile time (this is a mechanism that C generally doesn't use), so that it always links against the old symbol version regardless of the .so version.
It would actually be an improvement over the status quo if libc used explicit symbol versions for all glibc symbols. If glibc makes a backwards-incompatible change, libc is not going to pick up the new declaration by default, so logically speaking it shouldn't pick up the new symbol version by default either.
And if libc does that, then it's relatively straightforward to switch to raw-dylib and make target-glibc work.
Furthermore, target-glibc would provide a way for libc to know at compile time which versions are available. Right now it doesn't know, and so it's stuck offering only the old cfsetspeed interface, even if someone wanted to step up to add declarations for the new one. I suppose libc could instead infer the glibc version from the sysroot somehow, but an explicit target-glibc would be nicer.
Why do we care – why not just use the old interface forever? Two reasons. First, the change was made for a reason: in this case, supporting arbitrary speeds on serial ports. We don't want to be stuck not having access to newer functionality. Second, glibc only includes compatibility symbols on architectures that shipped the corresponding old glibc versions. If glibc adds a new architecture today, it will not include the old cfsetspeed interface, so when libc adds support for that architecture it will be forced to use the new interface.
There is no free lunch. For every incompatible change from glibc, libc really ought to do that reverse engineering you mentioned and declare old and new symbols with the correct versions. Otherwise, libc will at best get stuck on an old symbol version (with the previously mentioned consequences), or at worst get the versions mixed up and just be broken. The need to manually address ABI changes is a fundamental consequence of the choice to hardcode FFI declarations – which I personally believe is a bad approach. But that choice was made long ago, and is not changing anytime soon. Given that, if libc switches to explicitly tracking glibc symbol versions, at worst the situation will be equivalent to today, and at best it might be considerably better. In particular, ideally the hypothetical abilist-parsing tool should detect cases where a symbol has a version change after being initially introduced on a given architecture, and automatically flag those symbols for review.
The good news is that there aren't that many incompatible changes. Otherwise we would be seeing much more breakage in practice. But there are some, and we definitely get some symbols wrong today. As one example I found while researching this post, glibc prior to 2.33 had no mknodat symbol and instead made mknodat an inline function pointing to a __xmknodat symbol. However, libc always links directly to mknodat, so AFAICT, any program using mknodat will fail to link if the sysroot has an old glibc version. This would have been caught by the standard library build, which does use an old sysroot, except that the standard library never uses mknodat.
Discussion in the ATmosphere