gcc - 从静态库创建共享库时保留所有导出的符号

标签 gcc shared-libraries

我正在从一个我没有源代码的静态库创建一个共享库。

许多 Stack Overflow 问题提供 answers关于如何做到这一点:

gcc -shared -o libxxx.so -Wl,--whole-archive libxxx.a -Wl,--no-whole-archive

但是,静态库的一些公共(public)函数作为隐藏函数包含在共享库中:
$ nm --defined-only libxxx.a | grep __intel_cpu_indicator_init
0000000000000000 T __intel_cpu_indicator_init
$ nm libxxx.so | grep __intel_cpu_indicator_init
00000000030bb160 t __intel_cpu_indicator_init

__intel_cpu_indicator_init 符号从导出变为隐藏。

它不是隐藏在过程中的唯一符号:
$ nm libxxx.a | grep ' T ' | wc -l
37969
$ nm libxxx.so | grep ' T ' | wc -l
37548
$ nm libxxx.a | grep ' t ' | wc -l
62298
$ nm libxxx.so | grep ' t ' | wc -l
62727

请注意,37969 + 62298 = 100267 和 37548 + 62727 = 100275。

我能做些什么让链接器生成一个共享库,其中所有公共(public)符号都来自静态库,并且在共享库中也是公共(public)的?

最佳答案

当某些全局符号定义中的某些全局符号定义时,您观察到的结果
归档在 libxxx.a 中的目标文件是用 function attribute 编译的
variable attribute visibility("hidden")

该属性的作用是,当目标文件包含
全局符号定义链接到共享库中:

  • 在输出共享库的静态符号表 ( .symtab ) 中,符号的链接从全局更改为本地,
    这样当该共享库与其他任何东西链接时,链接器就看不到符号的定义。
  • 符号定义未添加到输出共享库的动态符号表 ( .dynsym )(默认情况下为)
    这样当共享库加载到进程中时,加载器同样无法找到符号的定义。

  • 简而言之,为了动态链接的目的,目标文件中的全局符号定义是隐藏的。

    看看这个:
    $ readelf -s libxxx.a | grep HIDDEN
    

    我希望您能获得未导出的全局符号的点击率。如果你不这样做,
    你不需要再读了,因为我对你所看到的没有其他解释
    并且不指望我建议不要用脚射击你的任何解决方法。

    这是一个插图:

    交流
    #include <stdio.h>
    
    void aa(void)
    {
        puts(__func__);
    }
    

    公元前
    #include <stdio.h>
    
    void __attribute__((visibility("hidden"))) bb(void)
    {
        puts(__func__);
    }
    

    de.c
    #include <stdio.h>
    
    void __attribute__((visibility("default"))) dd(void)
    {
        puts(__func__);
    }
    
    void ee(void)
    {
        puts(__func__);
    }
    

    我们将编译 a.cb.c像这样:
    $ gcc -Wall -c a.c b.c
    

    我们可以看到符号 aaab在它们各自的目标文件中定义和全局:
    $ nm --defined-only a.o b.o
    
    a.o:
    0000000000000000 T aa
    0000000000000000 r __func__.2361
    
    b.o:
    0000000000000000 T bb
    0000000000000000 r __func__.2361
    

    但我们也可以观察到这种差异:
    $ readelf -s a.o
    
    Symbol table '.symtab' contains 13 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
        ...
        10: 0000000000000000    19 FUNC    GLOBAL DEFAULT    1 aa
        ...
    

    与:
    $ readelf -s b.o
    
    Symbol table '.symtab' contains 13 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
        ...
        10: 0000000000000000    19 FUNC    GLOBAL HIDDEN     1 bb
        ...
    
    aaGLOBAL带有 DEFAULT 的符号可见性和 bbGLOBAL带有 HIDDEN 的符号能见度。

    我们将编译 de.c不同:
    $ gcc -Wall -fvisibility=hidden -c de.c
    

    在这里,我们指示编译器将任何符号隐藏
    可见性除非反补贴visibility属性被指定为
    它在源代码中。因此我们看到:
    $ readelf -s de.o
    
    Symbol table '.symtab' contains 15 entries:
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
        ...
        11: 0000000000000000    19 FUNC    GLOBAL DEFAULT    1 dd
        ...
        14: 0000000000000013    19 FUNC    GLOBAL HIDDEN     1 ee
    

    在静态库中归档这些目标文件不会改变它们:
    $ ar rcs libabde.a a.o b.o de.o
    

    然后,如果我们将所有这些链接到一个共享库中:
    $ gcc -o libabde.so -shared -Wl,--whole-archive libabde.a -Wl,--no-whole-archive
    

    我们发现:
    $ readelf -s libabde.so | egrep '(aa|bb|dd|ee|Symbol table)'
    Symbol table '.dynsym' contains 8 entries:
         6: 0000000000001105    19 FUNC    GLOBAL DEFAULT   12 aa
         7: 000000000000112b    19 FUNC    GLOBAL DEFAULT   12 dd
    Symbol table '.symtab' contains 59 entries:
        45: 0000000000001118    19 FUNC    LOCAL  DEFAULT   12 bb
        51: 000000000000113e    19 FUNC    LOCAL  DEFAULT   12 ee
        54: 0000000000001105    19 FUNC    GLOBAL DEFAULT   12 aa
        56: 000000000000112b    19 FUNC    GLOBAL DEFAULT   12 dd
    
    bbee , 分别是 GLOBALHIDDEN在目标文件中的可见性,
    LOCALlibabde.so 的静态符号中并且完全不在
    从其动态符号表。

    有鉴于此,您可能希望重新评估您的使命:

    libxxx.a 中的目标文件中被赋予隐藏可见性的符号有
    被隐藏是因为编译它们的人有理由
    希望从动态链接中隐藏它们。你有反补贴需求吗
    导出它们以进行动态链接?或者您可能只是想导出它们,因为
    你已经注意到它们没有导出,不知道为什么不导出?

    如果您仍然想取消隐藏隐藏符号,并且无法更改源代码
    归档在 libxxx.a 中的目标文件的数量,你最糟糕的手段是:
  • libxxx.a 中提取每个目标文件
  • 医生更换HIDDENDEFAULT其全局定义的可见性
  • 将其放入新存档 libyyy.a
  • 然后使用 libyyy.a而不是 libxxx.a .
  • binutils修改目标文件的工具是 objcopy .
    但是objcopy没有直接操作动态可见性的操作
    一个符号,你必须满足于一个“实现效果的迂回曲折”
    of” 取消隐藏隐藏符号:
  • objcopy --redefine-sym , 重命名每个隐藏的全局符号 S例如,__hidden__S .
  • objcopy --add-symbol , 添加一个新的全局符号 S__hidden_S 具有相同的值
    但得到 DEFAULT默认可见性。

  • 以两个具有相同定义的符号结尾:原始隐藏的符号
    以及一个新的未隐藏别名。

    更可取的是一种简单而单独地改变符号可见性的方法
    一个 ELF 目标文件,一种方法是提交 LIEF library (Library to Instrument Executable Formats) ——
    用于对象和可执行文件更改的瑞士军用链锯。

    这是一个调用 pylief 的 Python 脚本,LIEF Python 模块,取消隐藏
    ELF 对象文件中的隐藏全局变量:

    取消隐藏.py
    #!/usr/bin/python
    # unhide.py - Replace hidden with default visibility on global symbols defined
    #   in an ELF object file
    
    import argparse, sys, lief
    from lief.ELF import SYMBOL_BINDINGS, SYMBOL_VISIBILITY, SYMBOL_TYPES
    
    def warn(msg):
        sys.stderr.write("WARNING: " + msg + "\n")
    
    def unhide(objfile_in, objfile_out = None, namedsyms=None):
        if not objfile_out:
            objfile_out = objfile_in
        binary = lief.parse(objfile_in)
        allsyms = { sym.name for sym in binary.symbols }
        selectedsyms = set([])
        nasyms = { sym.name for sym in binary.symbols if \
                                sym.type == SYMBOL_TYPES.NOTYPE or \
                                sym.binding != SYMBOL_BINDINGS.GLOBAL or \
                                sym.visibility != SYMBOL_VISIBILITY.HIDDEN }
        if namedsyms:
            namedsyms = set(namedsyms)
            nosyms = namedsyms - allsyms
            for nosym in nosyms:
                warn("No symbol " + nosym + " in " + objfile_in + ": ignored")
            for sym in namedsyms & nasyms:
                warn("Input symbol " + sym + \
                    " is not a hidden global symbol defined in " + objfile_in + \
                    ": ignored")
            selectedsyms = namedsyms - nosyms
        else:
            selectedsyms = allsyms
    
        selectedsyms -= nasyms
        unhidden = 0;
        for sym in binary.symbols:
            if sym.name in selectedsyms:
                sym.visibility = SYMBOL_VISIBILITY.DEFAULT
                unhidden += 1
                print("Unhidden: " + sym.name)
        print("{} symbols were unhidden".format(unhidden))
        binary.write(objfile_out)
    
    def get_args():
        parser = argparse.ArgumentParser(
            description="Replace hidden with default visibility on " + \
                "global symbols defined in an ELF object file.")
        parser.add_argument("ELFIN",help="ELF object file to read")
        parser.add_argument("-s","--symbol",metavar="SYMBOL",action="append",
            help="Unhide SYMBOL. " + \
                "If unspecified, unhide all hidden global symbols defined in ELFIN")
        parser.add_argument("--symfile",
            help="File of whitespace-delimited symbols to unhide")
        parser.add_argument("-o","--out",metavar="ELFOUT",
            help="ELF object file to write. If unspecified, rewrite ELFIN")
        return parser.parse_args()
    
    
    def main():
        args = get_args()
        objfile_in = args.ELFIN
        objfile_out = args.out
        symlist = args.symbol
        if not symlist:
            symlist = []
        symfile = args.symfile
        if symfile:
            with open(symfile,"r") as fh:
                symlist += [word for line in fh for word in line.split()]
        unhide(objfile_in,objfile_out,symlist)
    
    main()
    

    用法:
    $ ./unhide.py -h
    usage: unhide.py [-h] [-s SYMBOL] [--symfile SYMFILE] [-o ELFOUT] ELFIN
    
    Replace hidden with default visibility on global symbols defined in an ELF
    object file.
    
    positional arguments:
      ELFIN                 ELF object file to read
    
    optional arguments:
      -h, --help            show this help message and exit
      -s SYMBOL, --symbol SYMBOL
                            Unhide SYMBOL. If unspecified, unhide all hidden
                            global symbols defined in ELFIN
      --symfile SYMFILE     File of whitespace-delimited symbols to unhide
      -o ELFOUT, --out ELFOUT
                            ELF object file to write. If unspecified, rewrite
                            ELFIN
    

    这是一个shell脚本:

    unhide.sh
    #!/bin/bash
    
    OLD_ARCHIVE=$1
    NEW_ARCHIVE=$2
    OBJS=$(ar t $OLD_ARCHIVE)
    for obj in $OBJS; do
        rm -f $obj
        ar xv $OLD_ARCHIVE $obj
        ./unhide.py $obj
    done
    rm -f $NEW_ARCHIVE
    ar rcs $NEW_ARCHIVE $OBJS
    echo "$NEW_ARCHIVE made"
    

    这需要:
  • $1 = 现有静态库的名称
  • $2 = 新静态库的名称

  • 并创建 $2包含来自 $1 的目标文件, 每个修改
    unhide.py取消隐藏所有隐藏的全局定义。

    回到我们的插图,我们可以运行:
    $ ./unhide.sh libabde.a libnew.a
    x - a.o
    0 symbols were unhidden
    x - b.o
    Unhidden: bb
    1 symbols were unhidden
    x - de.o
    Unhidden: ee
    1 symbols were unhidden
    libnew.a made
    

    并确认与:
    $ readelf -s libnew.a | grep HIDDEN; echo Done
    Done
    $ readelf -s libnew.a | egrep '(aa|bb|dd|ee)'
        10: 0000000000000000    19 FUNC    GLOBAL DEFAULT    1 aa
        10: 0000000000000000    19 FUNC    GLOBAL DEFAULT    1 bb
        11: 0000000000000000    19 FUNC    GLOBAL DEFAULT    1 dd
        14: 0000000000000013    19 FUNC    GLOBAL DEFAULT    1 ee
    

    最后,如果我们将共享库与新存档重新链接
    $  gcc -o libabde.so -shared -Wl,--whole-archive libnew.a -Wl,--no-whole-archive
    

    存档中的所有全局符号都被导出:
    $ readelf --dyn-syms libabde.so | egrep '(aa|bb|dd|ee)'
         6: 0000000000001105    19 FUNC    GLOBAL DEFAULT   12 aa
         7: 000000000000112b    19 FUNC    GLOBAL DEFAULT   12 dd
         8: 0000000000001118    19 FUNC    GLOBAL DEFAULT   12 bb
         9: 000000000000113e    19 FUNC    GLOBAL DEFAULT   12 ee
    

    [1]
    Download C/C++/Python libraries

    Debian/Ubuntu 提供 C/C++ 开发包 lief-dev .

    关于gcc - 从静态库创建共享库时保留所有导出的符号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54664759/

    相关文章:

    c - 向二维动态表添加元素

    c - 包含文件似乎被忽略

    c++ - 解析 GCC 打印的 C/C++ 编译错误

    linux - 抑制共享库的编译时链接

    c++ - 如何在 Linux 上延迟加载共享库

    c++ - 静态变量初始化顺序

    c++ - 为什么 GCC 调用 libc 的 sqrt() 而不使用它的结果?

    C++ 共享库到 main.cpp

    c++ - 链接到共享库时,Boost Log 无法正常工作

    linux - ld-linux.so 以什么顺序搜索共享库?