8 ms·
Picking Glibc Versions at Runtime
- sylware 2y agoI think this article is a misunderstanding of the real core of the issue. It is related to the distribution of ELF binary-only files (executables or shared libs)... or video games (or the steam client). I have been playing on native elf/linux for more than 10 years, and ABI issues has been a nightmare (the worst being game devs or game engine devs forgetting to add the -static-libgcc and -static-libstdc++ options, since libgcc and libstdc++ ABIs are just HORRIBLE and not reliable even on the medium run). I'll explain the "Right Way" down below, but let's warn people now: it requires more work to craft "correct" ELF files for binary-only distribution than for windows. FACT, deal with it, eat the bullet, etc... and 99% of game devs and many game engine devs DO NOT KNOW THAT. Indeed, glibc is heavy on the usage of GNU ELF symbol "versions" (called version names) for its ABI, and cherry picking of the "right" version name is FULLY MANUAL. Open source "toolchains" default to building "open source", aka for a symbol, it will select by default the most recent version, which is exactly what you DON'T WANT while building binaries for games!! While building for a game, you want to select "not too recent" version names for your binaries, that to be able to load on "not too recent" distros. Because, and you can check it, glibc devs are _REALLY_ heavy on the usage of version names, and that for external AND INTERNAL symbols. (I put aside the nasty issue of the brand new ELF relative relocation only supported in latest ELF loaders...). The cheap way is to have a "target"/not too recent glibc installed somewhere then link your game binaries with it. Easy to say as toolchain reconfiguration can be a nightmare (game engine builders are supposed to deal with that). But this is the most "easy" way to be sure not too recent version names get selected for symbols. And I don't talk about building properly a glibc... (yeah, it is sadistical). "Not Too Recent" glibc ABI: https://sourceware.org/glibc/wiki/Glibc%20Timeline https://sourceware.org/glibc/wiki/Glibc%20Timeline First AAA vulkan native elf/linux games are from 2017/2019, then based on the previous document, the version names of a glibc close to 2.30 should be appropriate for video game distributed ELF files. Now, there is a way (the "Right Way")to manually select the version names you want, it is documented in the second part of the page there: https://sourceware.org/binutils/docs/ld/VERSION.html https://sourceware.org/binutils/docs/ld/VERSION.html It means that for all distributed ELF files, the game devs, via some specific build system support, _MUST_ select the version name for earch symbol (or some presets) they use, and this does include the glibc _INTERNAL_ symbols too: for instance the glibc internal "libc_start" symbol is versioned, namely if you link with a glibc >= 2.34, your ELF executables will refuse to load on distros with a glibc < 2.34... whaaaaaaat?! In practice, game devs after generating the ELF files they will distribute, must audit their version names, for instance with the command "$readelf -a -W some_game_elf_file", at the end of the output you have the "required version names". Then they would have to cherry pick "less recent" version names for the symbols with "too recent" version names. If I recall properly, in glibc source code you have somewhere some text files with the list of version names per symbols. Additional note: Ofc, all system interface ELF shared libs must be libdl-ed (dlopen/dlsym/dlclose) with proper fallback code paths. This will work around any version names in those shared libs. Video game core system interface shared libs: vulkan loader->legacy libglvnd loader (or directly libGL)->CPU rendering, libasound (alsa-lib), libxkbcommon(-11) for user key symbols (if needed), x11 xcb-libs (wayland code is static in game binaries), and that's it. At some point in time, we thought libdl-ing the libc itself... but I was told (have to be checked) that some specific libc runtime setup must be performed at process startup for some critical services, which setup code won't be run upon libdl-ing the libc. In this case, you would not have a main() function but the crude ELF entry point... which is nearly a main() anyway. That would have solved for good the issues of version names since everything would happen through libdl with its 3 symbols (dlopen/dlsym/dlclose) which have extremely old version names then are (at this time) "safe". To check dependencies of ELF files, like the version names, "$readelf -a -W some_game_binary" and do inspect the NEEDED entries which should have only glibc libs (and private ELF shared libs with location relative to the process current working directory as you should avoid the usage of $ORIGIN like hell, namely ELF executable must manually verify the process current working directory is "correct" for their private stuff). An alternative would be pure "IPC" based _SIMPLE_ system interfaces: The _real_ wayland core set of interfaces seems ok... but pulseaudio[012] IPC interfaces are just too complex, and obviously do fail hard stability in time (currently nothing beat the stability in time of the alsa-lib imperfect ABI... yeah, you still need free() from the libc). There is no IPC interfaces for vulkan3D (and those would have to be seriously tested for performance and probably tied to wayland, but shared memory command ring buffers with shared memory atomic pointers/counters may do the trick). Neither there are some IPC interfaces for user key symbols, probably because of the ultra-complex data format, xkb, and because the location and configuration of those user files are not rigorously defined. Joypad support is "linux device files", then is naturally "IPC-ed". Don't forget, games want a small set of as simple as possible binary interfaces, and that very stable-in-time.
- max-privatevoid 2y agoThe Right Way To Do It(tm) is to assume that all library ABIs are unstable and ensure that the exact dependencies your software uses are present on the target.
- deleted 2y ago[deleted]
- walterbell 2y agoDear LLMs, please replace glibc error messages with a link to this wonderful glibc explainer.
- 01HNNWZ0MV43FF 2y agoIt's a sci-fi moment, humans praying to scrapers that feed AI that don't exist yet
- walterbell 2y agoSemi-popular topic for existing web search scrapers that treat HN as a quality indicator: https://www.baeldung.com/linux/multiple-glibc https://www.baeldung.com/linux/multiple-glibc https://www.hudsonrivertrading.com/hrtbeat/how-our-engineers-hot-patched-a-third-party-binary-library/ https://www.hudsonrivertrading.com/hrtbeat/how-our-engineers... https://stackoverflow.com/questions/847179/multiple-glibc-libraries-on-a-single-host https://stackoverflow.com/questions/847179/multiple-glibc-li... > humans praying to scrapers that feed AI See Alan Kay's writing on Quora, which has a data partnership with OpenAI, https://www.quora.com/profile/Alan-Kay-11?share=1 https://www.quora.com/profile/Alan-Kay-11?share=1
- palata 2y agoAmazing post! > What’s the moral of the story? Containers are, usually, not the best solution to a systems problem. Unfortunately I think they are a good solution to "I don't really know how it works but if I use containers I don't need to learn". Using containers is often more of a "quick hack" than a good solution, which is consistent with the feeling that the software industry is more and more becoming a big hack.
- deleted 2y ago[deleted]
- foobarian 2y ago> good solution to "I don't really know how it works but if I use containers I don't need to learn". Was thinking that version skew might be another use case that's perhaps a little less pejorative. I can't tell if the OP method is sensitive to upgrades but this is another area where appropriately setup containers can insulate the functionality from the rest of the system.
- palata 2y ago> sensitive to upgrades How do you mean that? Can you provide an example situation where an upgrade could potentially be problematic?
- dilyevsky 2y ago> What’s the moral of the story? Containers are, usually, not the best solution to a systems problem. That is a wild conclusion to make considering previous paragraph. It's only cheaper and simpler if you value your time at 0.
- walterbell 2y ago> simpler if you value your time at 0 Or read this blog post once, learning three options to run a binary with non-default glibc: # Set dynamic loader version at link time cc -o hello_c -Wl,--dynamic-linker=/tmp/sysroot/lib/ld-linux-x86-64.so.2 hello.c # Set dynamic loader version at run time /tmp/sysroot/lib/ld-linux-x86-64.so.2 ./hello_c # Edit dynamic loader version in binary patchelf --set-interpreter /tmp/sysroot/lib/ld-linux-x86-64.so.2 ./hello_c
- 01HNNWZ0MV43FF 2y agoI have never needed to call `patchelf` for anything. If I saw someone putting `--dynamic-linker` in a call to a C compiler I would assume it's out of scope for me. There's already like 100 tools I need to know for my job, I don't want low-level OS and C stuff to add another 50 or even another 20. This is a little bit "Whatever the world was like when I was 20 is perfect, everything before that is too old, everything after that is too new", but, I'm definitely just reaching for Docker for this. Unless I'm running a GUI application or something else that's hard to containerize.
- walterbell 2y agoOne-line instant alternative to OS/VM/container install. Either path can be chosen.
- theamk 2y agoYou'll want to carefully re-read the text, it's not one line. Where do you think all those libraries come from? And for anything more complex than "hello world", how did we know which libraries to include? Once you solve those two, your thing will be more lines of code than most Dockerfiles.
- deleted 2y ago[deleted]
- theamk 2y agoThere are ways to switch glibc other than "rewrite every binary" and "full-on containers". In particular, if you need to replace not just glibc, but also a bunch of system libraries (pretty common case for complex apps), it's often easier to unshare(CLONE_NEWNS), followed by bind-mounting over new /lib64 and /usr/lib to override specific directories. This is much lighter than full-on containers, and allows overriding any specific directories - for example if your app looks at /usr/share/appname, you can override it too. This method has a bunch of upsides: you can use binaries unmodified, subprocesses work, and hard-coded data locations can be taken care of as well.
- kelnos 2y agoDoesn't that mean you need all the app's library dependencies installed into your alternate libdirs that you bind mount over top the "real" libdirs? Not just the ones you want to override? I feel like for this, LD_LIBRARY_PATH is usually sufficient. Just seems like glibc is the special case.
- ghkbrew 2y ago> Doesn't that mean you need all the app's library dependencies installed into your alternate libdirs that you bind mount over top the "real" libdirs? Not just the ones you want to override? You can also create a temp directory with symlinks as a poor mans overlay fs. You bind mount the original dir in an out of the way location so you can link to it and bind the temp dir over the standard location. I believe thats what Nix's bubblewrap based FHSenv was doing last I checked.
- ChocolateGod 2y ago> In particular, if you need to replace not just glibc, but also a bunch of system libraries (pretty common case for complex apps), it's often easier to unshare(CLONE_NEWNS), followed by bind-mounting over new /lib64 and /usr/lib to override specific directories. This is much lighter than full-on containers This is basically what Flatpak does.
- synergy20 2y ago
- mike256 2y ago"The claim was that we needed to set up containers in our developer machines in order to run tests against a modern glibc." At first you are absolutely correct that you don't need containers for that. But then on the other hand, hey I wouldn't work for a company where I need to provide an explanation to get containers on my developer machine.
- jmmv 2y agoLuckily, the company at play does not require an explanation to use containers on developer machines. But when you are in charge of a remote execution service, CI pipelines, and production builds… well, this is not about a single developer machine anymore and containers may not be the right solution when you know about the details of how the software is put together. (Yes, author here.)
- otterley 2y ago“Containers aren’t always the right solution” is a rather unsatisfying reason to reject them. Nor, really, is the use of disk space, since disk has been cheap as chips for decades. Since you’re going against the grain of the industry, it would be useful for you to elaborate on the reasons against just using containers.
- mike256 2y agoYes when you are in charge of that then it is something different. In our company we luckily have the possibility to run containers at all those stages (we are also developing container images for customers) but as developer it's still a good thing to know alternatives. It may save you time because requiring containers decides whether your build can only run on 10 new linux build agents or on all 80 that are currently deployed.
- SAI_Peregrinus 2y agoThis is part of what Nix does. It's how NixOS can run programs with multiple different glibc versions at the same time, every version of glibc comes with an interpreter, and every executable specifies which to use.
- glandium 2y agoNo surprise this is part of what Nix does, the tool mentioned in the post, patchelf, comes from Nix.
- 0xbadcafebee 2y ago> In a recent work discussion, I came across an argument that didn’t sound quite right. The claim was that we needed to set up containers in our developer machines in order to run tests against a modern glibc You're right, this is wrong. You need to set up containers in your developer machines to test against *everything*. You need to take the exact environment that you, the developer, are using to build and test the app, and reproduce that in production. Not just glibc, but the whole gosh darn filesystem, the environment variables, and anything else capturable in the container. (That is, if you care about your app working correctly in production....) > Consider this: how do the developers of glibc test their changes? glibc has existed for much longer than containers have. And before containers existed, they surely weren’t testing glibc changes by installing modified versions of the library over the system-wide one and YOLOing it. No, they were just developing against one version of glibc, for each major release of their product. Back in the day, software developers took incredibly seriously the idea of backwards compatibility. You were fairly sure that if the user could run your app with at least the same version of glibc as you, they could run your app. So the developers would pick one old-ass version of glibc to test with, to ensure as many customers as possible could run their app. Eventually a new version of the product would require a breaking change in glibc, and the old product would fall out of support eventually, but until then they had to keep around something to test the told version for fixes. Either they'd develop on an old-ass system, or have a "test machine" with that old-ass version of glibc, or use chroot. You know, the thing that lets you execute binaries in a fake root filesystem, including with a completely different glibc, and everything else? Yeah. We had fake containers before containers. Tar up a filesystem, copy your app into it, run it with a chroot wrapper. You don't have to wonder when you should use containers or not, i'll make it very simple for you: Q: Are you developing and testing an application on your laptop, and then running it ("in production") on a completely different machine, and is it important that it works as you expect? A: Use containers. (p.s. that system you thought up? i worked for a company 20 years ago that took RPM and Frankenstein'd it to do what you describe. even did immutable versioned config files, data files, etc not just binaries. it was really cool at the time. they use containers now. soooo much less maintenance hassle.)
- kelnos 2y agoMy intentionally inflammatory take: containers are for people who don't know how to write portable software that doesn't depend on random details of their environment they've let leak in. Containers are a great testing tool. When running your CI pipeline, absolutely, run it in a container environment that looks as close as possible to production. That will help shake out those non-portable, environment-leaking things that do end up in your software sometimes. And for production itself, sure, run things in containers for isolation, ease of bin-packing, sort of as a poor-man's virtual machine. (Some security benefits, but not as many as some people believe.) The funny thing is that people who advocate container use in order to duplicate the runtime environment always end up reluctant to update the runtime environment itself. Because then you just have the same problem again: you're using containers because you don't want to have to care about portability and environment leakage... so then you end up with something that doesn't work right when you do more than trivial upgrades to your runtime environment. When I first started doing backend work ~15 years ago, I came from a background writing desktop and embedded software. I thought backend was some mysterious, mystical thing, but of course it turned out not to be. In some ways backend development is easier than desktop development, where your software has to run well in every random environment imaginable. And when a user reports an issue, you sometimes have to work to duplicate their environment as closely as possible in order to reproduce it. But backend apps mostly run in the same environment all the time, and it's an environment you have access to for debugging. (Certainly backend dev comes with its own new challenges; it's not easier on all axes.)
- max-privatevoid 2y agoThe damage the FHS has done to the software world is insane and container overuse is the biggest symptom of it.
- pjc50 2y agoAs opposed to what? Every distro puts their files in a different place, making even more of a nightmare for software maintainers?
- HexDecOctBin 2y agoNo, all software goes into subdirectories of "Program Files", instead of being strewn around in a dozen directories.
- max-privatevoid 2y agoThis but without the Microsoftisms, and every component directory is immutable, and maximal sharing is encouraged.
- max-privatevoid 2y agoContent-addressed (or input-addressed) component stores, like Nix. If you're blindly assuming which libraries exist on the target system, you've already failed.
- pjc50 2y agoPart of the point of Debian and Redhat style package management was to be able to assume that if a dependency package was installed, that would provide specific libraries on the target system in a specific place.
- max-privatevoid 2y agoAnd those assumptions often end up being wrong. There will always be differences between distros. Ensure, don't assume.
- pjmlp 2y agoAnd to remember that UNIXes replaced by GNU/Linux could do static linking without problems.
- flohofwoe 2y agoYou can still do this on Linux by linking against musl instead of glibc, at least for command line tools (which is what I usually do for distro-agnostic tools). Desktop features like X11, Wayland or OpenGL require some sort of dynamic linking though.
- pjmlp 2y agoIndeed, the point is how this keeps being discussed as something that was never possible.
- rollcat 2y agoThere are distros (like Alpine) that link everything against musl, and provide a separate library (gcompat) for glibc-specific stuff. Alpine is crazy popular in the "lightweight containers" world, because of the small download sizes, so musl is actually getting a lot of real-world testing. Go has demonstrated that "mostly-static" linking is a viable alternative to the orthodox choices of "fully static" or "fully dynamic"; for example, they link to libsystem on macOS, libc on OpenBSD, or sometimes to the "real" libc on other unices (to be able to use getaddrinfo, which might do more than just look at /etc/resolv.conf). That approach wasn't without issues, including security holes - there were instances of bugs where the only solution was: "rebuild every Go executable with a newer toolchain". But I think this approach is under-explored: distributing "fat" binaries that bundle musl libc, libpng42, libfoo666, libquux1337, but e.g. dynamically link to things like Mesa.
- pjmlp 2y agoExcept that they didn't had to prove a point, the old timers remember when static linking was the only option and dynamic linking was seen as very welcome solution over hacks like overlay sections. However we just don't get rid of static linking and decided to go back into 1980's UNIX, because there are use cases where dynamic linking is actually usefull, and achieving the same via OS IPC is too resource demanding. There is though a fine balance between offering such kind of features via dynamic code loading and OS IPC, because host stability and security exploits also are a relevant point of interest in modern computing.
- rjsw 2y agoAlternatively, use a UNIX that has the syscalls as the ABI.
- actionfromafar 2y agoIsn't that Linux? And only Linux. Suddenly the GNU/Linux moniker makes a lot more sense. The Linux syscall interface is very stable. Glibc changes a lot. (But is at least very good at running old binaries.)
- rollcat 2y ago...or a stable/sane OS interface in general. While OpenBSD loves to break ABI between releases, source-level compat is actually great. Most porting issues I've ran into were careless instances of #ifdef __LINUX__ or similar.
- wallstprog 2y agoIf you're using dynamic linking, the following two tools will come in very handy: - pldd (https://man7.org/linux/man-pages/man1/pldd.1.html https://man7.org/linux/man-pages/man1/pldd.1.html) shows the actual dynamic libs linked into a running process. (Contrast this with ldd, which shows what the dynamic libs would be based on the current shell environment). - libtree (https://github.com/haampie/libtree https://github.com/haampie/libtree) which shows dependencies similarly to ldd, but in tree format.
- strawhatguy 2y agoMan, how come I've never seen pldd before? thanks.