### Bug#966173: libc6: __atan2_finite reference in dlopened module no longer found in executable linked to libm

On Fri, Jul 24, 2020 at 10:11:04AM +0100, Simon McVittie wrote: > The bug (#966150) is that a version of uix86_64.so compiled with a slightly > older (2020-02-18) toolchain fails to load on an up-to-date sid system, with: > undefined symbol: __atan2_finite Because the binary was not linked with -lm, the linker never saw the real symbol __atan2_finite@GLIBC2_16, so the linke only emitted a reference to __atan2_finite. At least dpkg-shlibdeps or so should warn about that. > I've been trying to put together a standalone reproducer that only uses > libdl and libm, but so far I have not been successful. Something like that? | % cat test.c | void __atan2_finite(void); | void test(void) { | __atan2_finite(); | } | % gcc --shared -o test.so test.c -Wall -W | % objdump -x test.so | grep atan | *UND* __atan2_finite Regards, Bastian -- Men will always be men -- no matter where they are. -- Harry Mudd, "Mudd's Women", stardate 1329.8

### Bug#966173: libc6: __atan2_finite reference in dlopened module no longer found in executable linked to libm

Package: libc6 Version: 2.31-1 Severity: normal I've encountered an odd bug in openarena (#966150) which I'm concerned might be a glibc regression affecting other packages. Some background: openarena is a game running on the ioquake3 engine (main executable: /usr/lib/ioquake3/ioquake3). During startup, the engine dlopens some modules, which implement the actual openarena game and UI. One of those modules is uix86_64.so. uix86_64.so uses mathematical functions from libm, but is not itself linked to libm. At runtime (at least on older systems) it works as intended, because the ioquake3 executable *is* linked to libm. I'm aware that this is not the most robust setup, and uix86_64.so would ideally be linked with -lm to make it self-contained; but it's documented as being expected to work, and has always worked in the past: Symbol references in the shared object are resolved using (in order): symbols in the link map of objects loaded for the main program and its dependencies; [... and some more places ...] — dlopen(3) The bug (#966150) is that a version of uix86_64.so compiled with a slightly older (2020-02-18) toolchain fails to load on an up-to-date sid system, with: undefined symbol: __atan2_finite If I recompile openarena in a sid chroot, *with no source code changes* (in particular uix86_64.so is still not linked to -lm!), then it starts to work again. The recompiled uix86_64.so has an undefined reference to atan2, but no reference to __atan2_finite any more. I'm going to address this in bullseye by making openarena more robust (explicitly linking to -lm). After I've done that, the updated version of openarena will not be suitable as a reproducer for this bug report, but the buster version of openarena will still be suitable. If you believe this is not a significant regression in glibc and should only be fixed by changes in openarena, I have no problem with doing that and just closing this bug report. However, I wanted to raise this in case it affects other previously-built binaries. This can be reproduced somewhat conveniently as follows: * Have a buster virtual machine * Install openarena and enough of a desktop to get a terminal in an X11 environment * Run openarena * It succeeds * To exit quickly: Shift+Escape, type "/quit", Enter * Add a bullseye apt source and "apt update", but do not upgrade everything * Upgrade libc6 from 2.28-10 to 2.31-1, while upgrading as few other packages as possible * I used aptitude, which made me also upgrade gcc-9 and related packages, removing gcc-8 * Run openarena * It fails as described in #966150 * Downgrade libc6 and closely-related packages from 2.31-1 to 2.28-10 * In my case this meant downgrading libc-dev-bin, libc6-dev, libc6 and libc-bin, and removing libcrypt-dev and libcrypt1 * Run openarena * It succeeds again, confirming that this was a glibc behaviour change I've been trying to put together a standalone reproducer that only uses libdl and libm, but so far I have not been successful. I believe this is related to a change in the representation of the __atan2_finite symbol, which is used (at least by versions of openarena compiled against older glibc) because openarena is compiled with -ffast-math. In 2.28-10, that symbol was not hidden: $ objdump -Tx /lib/x86_64-linux-gnu/libm.so.6 ... 00028280 g iD .text 0046 GLIBC_2.15 __atan2_finite In 2.31-1, it is hidden, and there is no non-hidden definition (default symbol-version): $ objdump -Tx /lib/x86_64-linux-gnu/libm.so.6 ... 0002a1e0 g iD .text 0049 (GLIBC_2.15) __atan2_finite Because uix86_64.so is not directly linked to -lm, it has an undefined reference to __atan2_finite with no particular version: $ objdump -Tx /usr/lib/openarena/baseoa/pak6-patch088/uix86_64.so ... D *UND* __atan2_finite As far as I can work out, this unversioned undefined reference can be satisfied by __atan2_finite@@GLIBC_2.15 in the global namespace from the old libm, but not by the hidden version __atan2_finite@GLIBC_2.15 in the new libm. Thanks, smcv

### Bug#966173: libc6: __atan2_finite reference in dlopened module no longer found in executable linked to libm

On Fri, 24 Jul 2020 at 14:36:54 +0200, Bastian Blank wrote: > On Fri, Jul 24, 2020 at 10:11:04AM +0100, Simon McVittie wrote: > > The bug (#966150) is that a version of uix86_64.so compiled with a slightly > > older (2020-02-18) toolchain fails to load on an up-to-date sid system, > > with: > > undefined symbol: __atan2_finite > > Because the binary was not linked with -lm, the linker never saw the > real symbol __atan2_finite@GLIBC2_16, so the linke only emitted a reference > to __atan2_finite. Right. However, note that there's no mention of __atan2_finite() in the source code - it's only used because older glibc would replace atan2() with a reference to __atan2_finite() when building with -ffast-math. > At least dpkg-shlibdeps or so should warn about that. For at least openarena, it doesn't seem to. I'm not sure why not. For the next update to openarena I'm going to build it with -Wl,-z,defs so that missing dependencies are always fatal. However, that isn't always applicable: some plugin architectures (like Python extensions) rely on being able to pick up symbols exported by the executable, which are not necessarily programmatically distinguishable from symbols that are defined by libraries used by the executable. > > I've been trying to put together a standalone reproducer that only uses > > libdl and libm, but so far I have not been successful. > > Something like that? > > | % cat test.c > | void __atan2_finite(void); > | void test(void) { > | __atan2_finite(); > | } I was aiming for something a bit closer to openarena's situation, where there is no explicit reference to __atan2_finite() in the source code: it calls atan2(), and cc -ffast-math rewrites that into a call to __atan2_finite(). I've now managed to make this work: see attached. Compile them and run ./prog in a buster environment (or an outdated bullseye/sid environment with glibc < 2.31), then run ./prog in an up-to-date bullseye/sid environment without recompiling. libmymodule.so will get a dynamic reference to __atan2_finite. The historical result is that prog outputs 0.463648, twice. The result in up-to-date bullseye/sid is that prog outputs 0.463648, once, and then fails with "undefined symbol: __atan2_finite". Using __FINITE_MATH_ONLY__ (which is defined by -ffast-math) is necessary to be able to reproduce the bug this way. If you consider this sort of thing to be too niche to be supportable, please feel free to close the bug. smcv all = prog libmymodule.so CFLAGS = -ffast-math check: $(all) objdump -Tx libmymodule.so ./prog all: $(all) prog: prog.c Makefile $(CC) $(CFLAGS) -Wl,--no-as-needed -o $@ $< -ldl -lm # Note that this cannot be compiled with -Wl,-z,defs: it deliberately has # undefined references to symbols from libm libmymodule.so: module.c Makefile $(CC) $(CFLAGS) -shared -o $@ $< clean: rm -f $(all) #include #include #include #include #if !defined(__FINITE_MATH_ONLY__) || !__FINITE_MATH_ONLY__ #warning Not using finite-only mathematics #endif int main (void) { void *module; double (*my_atan2) (double, double); printf ("%f\n", atan2 (1, 2)); module = dlopen ("${ORIGIN}/libmymodule.so", RTLD_NOW); if (module == NULL) errx(1, "%s", dlerror ()); my_atan2 = (double (*) (double, double)) dlsym (module, "my_atan2"); if (my_atan2 == NULL) errx(1, "%s", dlerror ()); printf ("%f\n", my_atan2 (1, 2)); return 0; } #if !defined(__FINITE_MATH_ONLY__) || !__FINITE_MATH_ONLY__ #warning Not using finite-only mathematics #endif #include double my_atan2 (double x, double y) { return atan2 (x, y); }