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

时间:2019-06-02 14:36:13

标签: c linux gcc compilation static-linking

我想静态地选择性地链接libm.a,而动态地链接所有其他库(包括libc.so)。但是,如果我使用math.h中的数学函数,则几乎总是无法正确链接。为什么? 为何有时起作用? (例如,如果我仅使用sqrtfabs或奇怪的是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

prova.c:

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

如果我运行myscript.sh,则可以看到sqrttanh没问题。 sintan却无法链接:

$./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

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

注意::我对GCC使用了-fno-builtin标志,因此GCC不使用其任何内置函数。因此问题不存在。

1 个答案:

答案 0 :(得分:2)

(对我来说)不是很清楚为什么需要限制( libm.a + libc.so ),所以它闻起来像 XY问题

根据[RedHat.Bugzilla]: Bug 1433347 - glibc: Selective static linking of libm.a fails due to unresolved _dl_x86_cpu_features symbol(由@KamilCuk指出):

  

不支持此操作。

     

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

     

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

     

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

似乎是不受支持的配置。这是有道理的,但也有些令人困惑:即使 libc libm 是磁盘上的 2个独立文件(对于每种配置:< em> static , shared ),它们是同一库的一部分 g libc ) ,所以:

  • 使用静态构建的库的一半,而将另一半用作共享库,这不是确定(我想到的一些事情是: gcc -fPIC 标志,以及库的初始化)
  • 当然,在大多数情况下,库由单个文件组成,因此上述项目符号将不适用(存在混淆的地方)

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

  • 仅由某些(三角函数)使用(例如 sin cos ,但 tanh )-我只是在这里猜测:基于函数值的计算方式(例如 Taylor系列
  • 只有在 libc libm )同步时才会发生

我用了这个简单的程序。

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;
}

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

  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
    
  2. 两个库部分( libc libm )同步的情况:

    [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
    

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

  3. 切换到 libm.a (用于 sin 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
    

    如图所示:

    • 对于 sin ,链接器抱怨(甚至不含 -fno-builtin
    • 对于 tanh ,一切都很好

    从现在开始,我将重点讨论无法解决的情况

  4. 使用链接器标志(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)
    

    它已经通过了链接阶段,但是在运行时进行了 segfault (这是预料之中的)。

  5. 出现在 [Amper.Git]: open-source/glibc - Add _dl_x86_cpu_features to rtld_global 中(请注意,其中没有都在官方 glibc中[GNU]: Index of /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
    

    #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
    

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

尽管它对我有用(可能与您的情况相同),但我仍然认为它是一种解决方法( gainarie ),并且我不知道全部含义
所以,我的建议是使用推荐的选项之一(从步骤 #2。 )。
但是,看看其他编译器的行为会很有趣。