Linking error: selective static linking of libm.a in GCC

浪尽此生 提交于 2021-02-19 03:52:28

问题


I want to selectively link libm.a statically, all the other libraries (libc.so included) dinamically. But if I use the math functions from math.h, it almost always fails to link correctly. Why? And why does it work sometimes? (For example if I only use sqrt, fabs or, strangely, tanh, it seems to link correctly)

myscript.sh:

#!/bin/bash
for i in sqrt tanh sin tan  
do
     echo "-----$i----------"
     sed "s/ciao/$i/" prova.c >provat.c
     gcc provat.c -fno-builtin -l:libm.a
     [[ $? -eq 0 ]] && { echo -n "$i(2.0)="; ./a.out; echo " OK!"; }
         echo
done

prova.c:

#include <stdio.h>
#include <math.h>
int main()
{
    printf("%f", ciao(2.0));
    return 0;
}

If I run myscript.sh, I can see that sqrt and tanh give no problems. sin and tan, instead, fail to link:

$./myscript.sh
-----sqrt----------
sqrt(2.0)=1.414214 OK!

-----tanh----------
tanh(2.0)=0.964028 OK!

-----sin----------
/usr/lib/x86_64-linux-gnu/libm-2.27.a(s_sin.o): In function `__sin_ifunc':
(.text+0x4d42): undefined reference to `_dl_x86_cpu_features'
/usr/lib/x86_64-linux-gnu/libm-2.27.a(s_sin.o): In function `__cos_ifunc':
(.text+0x4da2): undefined reference to `_dl_x86_cpu_features'
collect2: error: ld returned 1 exit status

-----tan----------
/usr/lib/x86_64-linux-gnu/libm-2.27.a(s_tan.o): In function `__tan_ifunc':
(.text+0x5782): undefined reference to `_dl_x86_cpu_features'
collect2: error: ld returned 1 exit status

I don't understand these error messages. Can somebody explain what happens? Why can't I link libm.a statically (and the rest dinamically)? And why does it work sometimes?

Note: I use the -fno-builtin flag to GCC, so that GCC doesn't use any of its builtin functions. So the problem is not there.


回答1:


It's not very clear (to me) why the restriction (libm.a + libc.so) is necessary, so it smells like an XY Problem.

According to [RedHat.Bugzilla]: Bug 1433347 - glibc: Selective static linking of libm.a fails due to unresolved _dl_x86_cpu_features symbol (pointed out by @KamilCuk):

This is not supported.

You would be mixing a static libm.a with future libc.so.6 and ld.so and that breaks the interdependencies between the core libraries which form "the implemetnation of the C runtime."

Either the entire implementation of the runtime is statically linked or none of it is statically linked. You can't choose to link parts of it statically and not others because each part depends on the other to form a complete implementation. The math library is not a thin you can link against statically, it happens we have a libm.a, but that's an implementation detail.

Please use '-static' for the entire application.

it seems like it's an unsupported configuration. That makes sense, but it's also a little bit confusing: even if libc and libm are 2 separate files on disk (for each configuration: static, shared), they are part of the same library (glibc), so:

  • It's not OK to use half of a library statically built, and the other half as a shared object (some things that come into my mind are: gcc's -fPIC flag, and also library's initialization)
  • Of course, most of the times, a library consists of a single file, so the above bullet wouldn't apply (there's where the confusion comes from)

From the beginning, I suspected that it's some code that (detects and) uses (if present) some CPU capabilities (most likely to improve speed or accuracy), that:

  • Is only used by some of the (trigonometric) functions (like sin, cos, but not tanh) - I'm just guessing here: based on how the function values are computed (e.g. Taylor series)
  • It only happens when libc (libm) are in sync

I used this simple program.

main.c:

#include <stdio.h>
#include <math.h>

#if !defined(FUNC)
#  define FUNC sqrt
#endif


int main() {
    double val = 3.141592;
    printf("func(%.06lf): %.06lf\n", val, FUNC(val));
    return 0;
}

Below it's a series of steps that I followed when looking into the problem:

  1. Environment:

    [cfati@cfati-ubtu16x64-0:~/Work/Dev/StackOverflow/q056415996]> ~/sopr.sh
    *** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***
    
    [prompt]> uname -a
    Linux cfati-ubtu16x64-0 4.15.0-51-generic #55~16.04.1-Ubuntu SMP Thu May 16 09:24:37 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
    [prompt]> gcc --version
    gcc (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609
    Copyright (C) 2015 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    
    [prompt]> ldd --version
    ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23
    Copyright (C) 2016 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions.  There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    Written by Roland McGrath and Ulrich Drepper.
    [prompt]> ls
    main.c
    
  2. The case when the 2 library parts (libc and libm) are in sync:

    [prompt]> gcc -fPIC main.c -DFUNC=sin -o sin_static.out -static -lm
    [prompt]> ll sin_static.out
    -rwxrwxr-x 1 cfati cfati 1007744 Jun 13 20:08 sin_static.out
    [prompt]> ldd sin_static.out
            not a dynamic executable
    [prompt]> ./sin_static.out
    func(3.141592): 0.000001
    [prompt]>
    [prompt]> gcc -fPIC main.c -DFUNC=sin -o sin_mso.out -l:libm.so
    [prompt]> ll sin_mso.out
    -rwxrwxr-x 1 cfati cfati 8656 Jun 13 20:09 sin_mso.out
    [prompt]> ldd sin_mso.out
            linux-vdso.so.1 =>  (0x00007ffc80ddd000)
            libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f999636b000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9995fa1000)
            /lib64/ld-linux-x86-64.so.2 (0x00007f9996674000)
    [prompt]> ./sin_mso.out
    func(3.141592): 0.000001
    

    Everything works fine in both cases.

  3. Switch to libm.a (for sin and tanh):

    [prompt]> gcc -fPIC main.c -DFUNC=sin -o sin_ma.out -l:libm.a
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/libm.a(s_sin.o): In function `__cos':
    (.text+0x3542): undefined reference to `_dl_x86_cpu_features'
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/libm.a(s_sin.o): In function `__sin':
    (.text+0x3572): undefined reference to `_dl_x86_cpu_features'
    collect2: error: ld returned 1 exit status
    [prompt]> ll sin_ma.out
    ls: cannot access 'sin_ma.out': No such file or directory
    [prompt]>
    [prompt]> gcc -fPIC main.c -DFUNC=tanh -o tanh_ma.out -l:libm.a
    [prompt]> ll tanh_ma.out
    -rwxrwxr-x 1 cfati cfati 12856 Jun 13 20:10 tanh_ma.out
    [prompt]> ldd tanh_ma.out
            linux-vdso.so.1 =>  (0x00007ffcfa531000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f124625c000)
            /lib64/ld-linux-x86-64.so.2 (0x00007f1246626000)
    [prompt]> ./tanh_ma.out
    func(3.141592): 0.996272
    

    As seen:

    • For sin, the linker complained (even without -fno-builtin)
    • For tanh, things went fine

    From now on, I'm going to focus on the case that doesn't work.

  4. Play a bit with the linker flags (man ld ([die.linux]: ld(1) - Linux man page)):

    [prompt]> gcc -fPIC main.c -DFUNC=sin -o sin_ma_undefined.out -l:libm.a -Wl,--warn-unresolved-symbols
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/libm.a(s_sin.o): In function `__cos':
    (.text+0x3542): warning: undefined reference to `_dl_x86_cpu_features'
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/libm.a(s_sin.o): In function `__sin':
    (.text+0x3572): warning: undefined reference to `_dl_x86_cpu_features'
    [prompt]> ll sin_ma_undefined.out
    -rwxrwxr-x 1 cfati cfati 104088 Jun 13 20:10 sin_ma_undefined.out
    [prompt]> ldd sin_ma_undefined.out
            linux-vdso.so.1 =>  (0x00007fff984b0000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f274ad00000)
            /lib64/ld-linux-x86-64.so.2 (0x00007f274b0ca000)
    [prompt]> ./sin_ma_undefined.out
    Segmentation fault (core dumped)
    

    It passed the link phase, but segfaulted at runtime (which was kind of expected).

  5. Came across [Amper.Git]: open-source/glibc - Add _dl_x86_cpu_features to rtld_global (note that none of that is in the official glibc: [GNU]: Index of /gnu/libc). It's some pretty heavy stuff there. I "borrowed" some and saved it.

    _dl_x86_cpu_features.c:

    #if defined(_DL_X86_CPU_FEATURES__WORKAROUND)
    
    #  define FEATURE_INDEX_MAX 1
    
    
    enum {
        COMMON_CPUID_INDEX_1 = 0,
        COMMON_CPUID_INDEX_7,
        COMMON_CPUID_INDEX_80000001,        // for AMD
        // Keep the following line at the end.
        COMMON_CPUID_INDEX_MAX
    };
    
    
    struct cpu_features {
        enum cpu_features_kind  {
            arch_kind_unknown = 0,
            arch_kind_intel,
            arch_kind_amd,
            arch_kind_other
        } kind;
        int max_cpuid;
        struct cpuid_registers {
            unsigned int eax;
            unsigned int ebx;
            unsigned int ecx;
            unsigned int edx;
        } cpuid[COMMON_CPUID_INDEX_MAX];
        unsigned int family;
        unsigned int model;
        unsigned int feature[FEATURE_INDEX_MAX];
    };
    
    
    struct cpu_features _dl_x86_cpu_features;
    
    #endif
    

    #include "_dl_x86_cpu_features.c" also needs to be added in main.c:

    [prompt]> gcc -fPIC main.c -DFUNC=sin -D_DL_X86_CPU_FEATURES__WORKAROUND -o sin_ma_workaround.out -l:libm.a
    [prompt]> ll sin_ma_workaround.out
    -rwxrwxr-x 1 cfati cfati 104088 Jun 13 20:13 sin_ma_workaround.out
    [prompt]> ldd sin_ma_workaround.out
            linux-vdso.so.1 =>  (0x00007fff17b6c000)
            libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5a992e5000)
            /lib64/ld-linux-x86-64.so.2 (0x00007f5a996af000)
    [prompt]> ./sin_ma_workaround.out
    func(3.141592): 0.000001
    

    Apparently, it works (at least in my environment)!!!

Although it did the trick for me (and probably it will be the same in your case), I still consider it a workaround (gainarie), and I'm not aware of the full implications.
So, my advice is to go with (either one of) the recommended options (from step #2.).
But, it would be interesting to see how other compilers behave.



来源:https://stackoverflow.com/questions/56415996/linking-error-selective-static-linking-of-libm-a-in-gcc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!