链接错误:GCC 中 libm.a 的选择性静态链接

S E*_*S E 5 c linux gcc compilation static-linking

我想有选择libm.a地静态链接所有其他库(libc.so包括)动态链接。但如果我使用 中的数学函数math.h,它几乎总是无法正确链接。为什么?为什么有时会起作用?(例如,如果我只使用sqrt,fabs或者,奇怪的是,tanh,它似乎链接正确)

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
Run Code Online (Sandbox Code Playgroud)

普罗瓦.c:

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

如果我运行myscript.sh,我可以看到这一点sqrt并且tanh不会出现任何问题。sin相反tan,无法链接:

$./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
Run Code Online (Sandbox Code Playgroud)

我不明白这些错误消息。有人可以解释发生了什么吗?为什么我不能libm.a静态链接(其余部分动态链接)?为什么有时会起作用?

注意:我使用-fno-builtinGCC 标志,这样 GCC 就不会使用它的任何内置函数。所以问题不存在。

Cri*_*ati 5

(对我来说)不太清楚为什么限制(libm.a + libc.so)是必要的,所以它闻起来像XY Problem

根据[RedHat.Bugzilla]:Bug 1433347 - glibc:由于未解析的 _dl_x86_cpu_features 符号,libm.a 的选择性静态链接失败(由 @KamilCuk 指出):

不支持此操作。

您会将静态 libm.a 与未来的 libc.so.6 和 ld.so 混合在一起,这会破坏形成“C 运行时的实现”的核心库之间的相互依赖性。

运行时的整个实现要么是静态链接的,要么都不是静态链接的。您不能选择静态链接其中的一部分而不链接其他部分,因为每个部分都依赖于另一个部分来形成完整的实现。数学库不是一个可以静态链接的库,碰巧我们有一个 libm.a,但这是一个实现细节。

请在整个应用程序中使用“-static”。

看来这是一个不受支持的配置。这是有道理的,但它也有点令人困惑:即使libclibm是磁盘上的2 个独立文件(对于每个配置: static共享),它们也是同一个库(g libc )的一部分,因此:

  • 使用静态构建的库的一半,而另一半作为共享对象是不行(我想到的一些事情是:gcc-fPIC标志,以及库的初始化)
  • 当然,大多数时候,库由单个文件组成,因此上面的项目符号不适用(这就是混乱的来源)

从一开始,我就怀疑这是一些代码(检测并)使用(如果存在)某些CPU功能(最有可能提高速度或准确性),即:

  • 仅由某些(三角)函数使用(例如sincos,但不是 tanh) - 我只是在这里猜测:基于函数值的计算方式(例如泰勒级数
  • 仅当libc ( libm ) 同步时才会发生

我使用了这个简单的程序。

主.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;
}
Run Code Online (Sandbox Code Playgroud)

下面是我在调查问题时遵循的一系列步骤:

  1. 环境:

    [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
    
    Run Code Online (Sandbox Code Playgroud)
  2. 2 个库部分( libclibm)同步时的情况:

    [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
    
    Run Code Online (Sandbox Code Playgroud)

    在这两种情况下一切都工作正常。

  3. 切换到libm.a(对于sintanh):

    [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
    
    Run Code Online (Sandbox Code Playgroud)

    正如所见:

    • 对于sin,链接器抱怨(即使没有-fno-builtin
    • 对于tanh来说,一切都很顺利

    从现在开始,我将专注于不起作用的情况

  4. 稍微玩一下链接器标志(man ld[die.linux]: ld(1) - Linux 手册页)):

    [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)
    
    Run Code Online (Sandbox Code Playgroud)

    它通过了链接阶段,但在运行时出现了段错误(这是预期的)。

  5. 遇到了[Amper.Git]: open-source/glibc - 将 _dl_x86_cpu_features 添加到 rtld_global(请注意,这些都不在官方glibc [GNU]: /gnu/libc 的索引)。那里有一些相当重的东西。我“借”了一些并保存起来。

    _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
    
    Run Code Online (Sandbox Code Playgroud)

    #include "_dl_x86_cpu_features.c"还需要在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
    
    Run Code Online (Sandbox Code Playgroud)

    显然,它有效(至少在我的环境中)!

尽管它对我有用(并且可能在您的情况下也是如此),但我仍然认为它是一种解决方法(gainarie),并且我不知道其全部含义
因此,我的建议是采用推荐选项(从步骤#2 开始)(其中之一)。
但是,看看其他编译器的行为方式会很有趣。