Hacker News new | ask | show | jobs
by FreakLegion 1444 days ago
There's also UCRT, which ships with the OS since Windows 10. The logic of this rant was a real head-scratcher. If you must blame one side, it's LLVM. Fragmentation of C runtimes is annoying but inescapable. Glibc for example isn't any better.
5 comments

> Fragmentation of C runtimes is annoying but inescapable. Glibc for example isn't any better.

Glibc very much is better.

There cannot be more than a single version of a (tightly coupled) ld.so+libc.so pair in a given address space any more than there can be more than a single KERNEL32 version in a given address space, and given that some system services are exclusively accessible via dynamic linking (accelerated graphics, for one), this forces essentially everything on a conventional Linux desktop to use a single version of a single C runtime (whether Glibc or Musl). No need to guess whether you need CRTDLL or MSVCRT or MSVCR70 or MSVCR71 or ... or UCRT (plus which compiler-version-specific overlay?): you need a sufficiently recent libc.so.6.

I cannot say I like this design (in some respects I actually like the Windows one more), but it does force Glibc to have very strong back-compat mechanisms and guarantees; e.g. it includes a bug-compatible version of memcpy() for old binaries that depended on it working byte by byte. As far as I’m aware, this applies from the point GNU stopped pretending Linux did not exist and the “Linux libc” fork died, that is 1998 or thereabouts.

(There are a myriad reasons why old Linux binaries won’t run on a new machine, but Glibc isn’t one of them.)

This is not to say that Glibc is perfect. Actually building a backwards-compatible binary that references old symbol versions is a gigantic pain: you either have to use a whole old environment, apply symbol versioning hacks on top of a new one and pray the resulting chimera works, or patch antique Glibc sources for new compilers and build a cross toolchain. But if you already have an old binary, it is better.

Glibc is major source of "binary from X years ago doesn't work" in my experience, to the point that if I want to run stuff older than let's say 5 years I start by getting a docker container - or a specially taylored set of libs starting with dynamic linker itself. Somehow proprietary OpenGL drivers still worked, it was glibc related files that caused crashes.

Thanks to symbol versioning I never know if a binary will work unless I can dismiss it early by versioning causing it to not link at all.

And given that glibc backward compatibility for all practical purposes is shorter than 10 years, having to support centos can get you a lot of those issues

> Given that glibc backward compatibility for all practical purposes is shorter than 10 years

As far as I’m able to tell[1], Glibc has never removed a versioned symbol as long as symbol versioning has existed, so the intended backwards compatibility is “forever”. No binary built against a(n unpatched) Glibc should fail to load on a later Glibc due to an absent Glibc symbol version. (Of course, if you play non-version-aware dynamic linker and dlsym() not dlvsym() things in libc, you lose; if you bundle an “updated” part of libc with the application[2], you lose; etc., so this may be true but still not entirely foolproof. And given how many things there are in libc, if you’re going to lose it’s probably going to be there.)

[1] E.g. https://sourceware.org/git/?p=glibc.git;a=history;f=stdlib/V...

[2] https://news.ycombinator.com/item?id=29479903

Glibc 2.34 broke FreePascal. It calls some unversioned symbol for initialization that was removed
This is the opposite problem of what's being talked about. Binaries built against old versions of glibc should run just fine against newer versions. This is about a binary built against a newer version of glibc that doesn't run on an older one. This is common, and super annoying. There are ways to build to eliminate this problem, but they all feel like hacks, or involve a lot of extra work.

(Hacks such as https://github.com/wheybags/glibc_version_header -- which apparently does work very well, but still feels like an annoying hoop that should be unnecessary to jump through. I wish glibc's shipped headers actually could support this out of the box so you could set a preprocessor define like `-DGLIBC_TARGET_ABI=2.12` and it would just work.)

So a vscode thingie uploads a newer binary to an older host, tries to run it there, and fails? Because the people who built said binary did not care to make it backwards compatible (or better yet, statically linked)?

... Duh?

(I’m not an expert in VSCode DRM, to put it mildly, so I might be misinterpreting this discussion, but that’s what it looks like to me. Also, isn’t it referencing GLIBCXX i.e. libstdc++, which not even a part of Glibc?)

> referencing GLIBCXX i.e. libstdc++, which not even a part of Glibc

You're right, so not an example of a glibc failure, but rather another standard library. Thanks!

> There cannot be more than a single version of a (tightly coupled) ld.so+libc.so pair in a given address space

That's an odd restriction, and it is related to the fact that all symbols live in one global namespace in a process. It's annoying if you are trying to build something like a plugin system, or if you are using a dynamic language which by definition loads all libraries dynamically. This is also the reason that you cannot mix Glib (GTK+) versions in a process.

I think you should be able to dlopen some library, or heck just load some machine code, and be able to run it, just take care to only pass POD over the boundary and never `free` stuff you didn't `malloc`.

> Actually building a backwards-compatible binary that references old symbol versions is a gigantic pain: you either have to use a whole old environment, apply symbol versioning hacks on top of a new one and pray the resulting chimera works, or patch antique Glibc sources for new compilers and build a cross toolchain.

This is the example I gave downthread. It's an everyday problem for a lot of developers targeting Linux, whereas targeting decades-old Windows versions is a breeze.

Glibc isn't better, it just has different problems.

Targeting old GNU/Linux is a breeze too. Containers make compiling stuff for old distros super easy. I even use them to crosscompile stuff for Windows, macOS, Android etc. too, since I got tired of having to set up development environments on new machines.
The UCRT has even been present since Windows 7, if users keep up with updates. Or if applications bundle the UCRT installer with their own.
> Or if applications bundle the UCRT installer with their own.

But the UCRT is proprietary, so there are often legal issues with doing so.

There is an official Universal CRT Redistributable.
And java ships it, and installs it into its own java bindir, not the global system dir.

So you have two competing copies, which leads to race conditions (different "global" locks!) and nice cashes, until you can analyze it with DrMemory or the kernel debugger. Nobody does that.

Could you elaborate why Glibc isn't any better?

I remember some funny problems with Glibc, like, 20 years ago, but it's been invisible to me (as a user) since then. You get a new Glibc, old binaries still work, it's fine.

Just like with Windows the challenges affect developers rather than users.

> You get a new Glibc, old binaries still work, it's fine.

Indeed, but when you need to build for an older glibc it's not so simple. This is a common use case, since e.g. AWS's environments are on glibc 2.26.

Ideally you'd like to build for all targets, including older systems, from a single, modern environment (this is trivial in Windows) -- and you can do some gymnastics to make that happen[1] -- but in practice it's easier to just manage different build environments for different targets. This is partly why building Linux wheels is so convoluted for Python[2].

Hardly a world-ending problem, but my point is simply that C runtimes are a pain everywhere.

1. https://stackoverflow.com/questions/2856438/how-can-i-link-t...

2. https://github.com/pypa/manylinux

> Ideally you'd like to build for all targets, including older systems, from a single, modern environment (this is trivial in Windows)

https://github.com/sjmulder/netwake does what you're talking about, but it does a lot of gymnastics to make it work, and it also needs to use MinGW rather than MSVC for that to be the case.

I was thinking specifically of how easy it is to build for Windows 2000 even today. Going back to 3.11 is pretty cool!
I'm pretty sure I've run into binaries breaking on new versions of Glibc but maybe it's because the architecture or calling convention changed. I've never really gotten the sense that GNU cares much about binary compatibility (which makes sense, they argue that sharing binaries is mostly counter productive.)
Eh, no, this is strictly a Windows problem. On Windows every DLL can be statically linked each with its very private copy of the MSVCRT, which means -for example- that you'd better never ever pass one DLL's malloc()'ed memory pointers to another DLL's free().

On Unix systems (and Linux) this sort of thing can only ever happen if you have a statically-linked application that is linked with libdl and then it dlopen()s some ELF -- then you need the application's C library to be truly heroic. (Solaris stopped supporting that insanity in Solaris 10, though glibc apparently still supports it, I think?)

AIX, which happens to be a UNIX, has similar dynamic linking model.

Then we have the surviving mainframes and embedded RTOS, which aren't UNIXes either.

what has LLVM to do with software development on windows?

because developing for windows is cancer that people fall back to LLVM and others

it's all on microsoft for not cleaning their mess

even apple offers a better story than the platform for "developers, developers, developers, developers"

a shame, a well deserved shame, a hall of shame