News for hackers. Stuff that matters.
Re:C library system-call wrappers, or the lack thereof by cesarb on Wednesday November 14, 2018 @06:43AM (#18449148)
> What are the technical reasons that glibc cannot adhere to the Linux dogma, "Don't break userspace?"

I don't know if they have something like that officially, but in practice, they do follow it. Programs linked to an older version of glibc continue working with a newer version of glibc, in a large part thanks to symbol versioning, which allows them to keep the old versions of an interface available to old binaries, while new binaries get the new functionality.

> If they screw up and make a bad interface just modify it and bump the version number.

Bumping the glibc version number would mean recompiling everything (a program can't have two versions of glibc at the same time, so all libraries a program links to would have to be recompiled); we had that in the libc5 to libc6 transition last century. And since they won't bump the version number, it means they will have to keep the bad interface forever, even if it's just visible to binaries compiled against an older glibc.

For a recent example in which they actually went ahead and removed a bad interface: https://lwn.net/Articles/673724/ and https://sourceware.org/bugzilla/show_bug.cgi?id=19473 -- and according to the later, they did it in a way which still kept existing binaries working.

Re:C library system-call wrappers, or the lack thereof by jancsika on Wednesday November 14, 2018 @10:29AM (#18450547)
> I don't know if they have something like that officially, but in practice, they do follow it.

If that's true then I don't understand ldarby's comment on the article:

> The common problem that I suspect Cyberax is actually moaning about is if software uses other calls like memcpy() which on centos 7 gets a version of GLIBC_2.14:

> readelf -a foo | grep memcpy 000000601020 000300000007 R_X86_64_JUMP_SLO 0000000000000000 memcpy@GLIBC_2.14 + 0 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memcpy@GLIBC_2.14 (3) 55: 0000000000000000 0 FUNC GLOBAL DEFAULT UND memcpy@@GLIBC_2.14

> and this doesn't work on centos 6:

> ldd ./foo > ./foo: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by ./foo)

Just to be clear, my original question is why glibc technically cannot follow the same exact development model of the Linux kernel for retaining backward compatibility.

Re:C library system-call wrappers, or the lack thereof by cesarb on Wednesday November 14, 2018 @12:39PM (#18451805)
It's backwards compatible: software compiled on centos 6 will be using the older memcpy@GLIBC_2.2.5 symbol, which still exists on newer glibc together with the current memcpy@GLIBC_2.14 symbol, so it will work. What doesn't work is compiling with a newer glibc and expecting it to work on an older system.

The example above is actually a great example of bending over backwards to keep compatibility with broken userspace. Some programs incorrectly called memcpy with overlapping inputs, and an optimized version of memcpy started breaking these programs. Instead of just letting them break, the older symbol was kept with a slower implementation which accepts overlapping inputs, while new programs get the faster implementation at the memcpy@GLIBC_2.14 symbol.

Re:C library system-call wrappers, or the lack thereof by rkeene2 on Wednesday November 14, 2018 @12:33PM (#18451734)
glibc could follow the same model, but it would be wasteful for them to do so, without symbol versioning. With symbol versioning they DO. The memcpy(3) example is why. An old implementation of memcpy(3) supplied in older versions of glibc happened to not care if the memory regions overlapped. The interface for memcpy(3) said that they may not (or implementation defined behaviour occurred).

On some platforms, new instructions came out and allowed the glibc maintainers to write a faster version of memcpy(3) that still satisfied its documented interface, however it did not retain the undocumented behavior of allowing overlapping memory ranges.

So without symbol versioning we are given the options to either: 1. All be stuck with a slow memcpy(3) forever; or 2. break glibc user's code

Neither of those options were great. So the glibc maintainers decided to write a new function, let's call it "memcpy_fast()"[0]. But how do you get everyone to use it ? Symbol versioning is the answer here. At compile-time linking, there is a directive that tells the compiler that the current implementation of memcpy(3) is "memcpy_fast()", and that's the symbol that gets embedded into the executable's symbol table. If your code wasn't compiled against a glibc where this was available, you'd be using an older implementation. This gets you the best of both worlds: 1. Existing binaries continue to work (without the upgraded code path); 2. Newly produced binaries use the upgraded code path, and in theory are tested to ensure that they are working.

This does prevent executables from being compiled against a newer glibc than intended to be executed against... but, so what ? Linux ALSO doesn't guarantee that you that newer call semantics are available in older systems. The solution here is to either specifically indicate that you want the unversioned symbol, or compile against the lowest version of everything you wish to support. glibc is far from your biggest problem here given the ABI stability of many libraries.

Non-solutions: 1. Use macros: Side-effects 2. Just expose a new, unversioned symbol: Nobody will use it, you'll have to document it, it'll be a platform-specific call. If people do use it, then their binaries can't be used on older platforms (just like symbol versioning)

[0] The symbol is referred to as memcpy@@GLIBC_2.14