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

Éti*_*nne 6 gcc shared-libraries

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

许多堆栈溢出问题提供了如何做到这一点的答案

gcc -shared -o libxxx.so -Wl,--whole-archive libxxx.a -Wl,--no-whole-archive
Run Code Online (Sandbox Code Playgroud)

但是,静态库的一些公共函数作为隐藏函数包含在共享库中:

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

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

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

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

Mik*_*han 7

当存档的某些目标文件中的某些全局符号定义libxxx.a使用函数属性变量属性编译时,您观察到的结果 visibility("hidden")

当包含全局符号定义的目标文件链接到共享库时,此属性具有以下效果:

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

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

看看这个:

$ readelf -s libxxx.a | grep HIDDEN
Run Code Online (Sandbox Code Playgroud)

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

这是一个插图:

交流电

#include <stdio.h>

void aa(void)
{
    puts(__func__);
}
Run Code Online (Sandbox Code Playgroud)

公元前

#include <stdio.h>

void __attribute__((visibility("hidden"))) bb(void)
{
    puts(__func__);
}
Run Code Online (Sandbox Code Playgroud)

十二月

#include <stdio.h>

void __attribute__((visibility("default"))) dd(void)
{
    puts(__func__);
}

void ee(void)
{
    puts(__func__);
}
Run Code Online (Sandbox Code Playgroud)

我们将编译a.cb.c像这样:

$ gcc -Wall -c a.c b.c
Run Code Online (Sandbox Code Playgroud)

我们可以看到符号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
Run Code Online (Sandbox Code Playgroud)

但我们也可以观察到这种差异:

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

与:

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

aaGLOBAL具有DEFAULT可见性的符号并且bbGLOBAL 具有HIDDEN可见性的符号。

我们将以de.c不同的方式编译:

$ gcc -Wall -fvisibility=hidden -c de.c
Run Code Online (Sandbox Code Playgroud)

在这里,我们指示编译器任何符号都将被赋予隐藏可见性,除非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
Run Code Online (Sandbox Code Playgroud)

在静态库中归档这些目标文件不会改变它们:

$ ar rcs libabde.a a.o b.o de.o
Run Code Online (Sandbox Code Playgroud)

然后,如果我们将所有这些链接到一个共享库中:

$ gcc -o libabde.so -shared -Wl,--whole-archive libabde.a -Wl,--no-whole-archive
Run Code Online (Sandbox Code Playgroud)

我们发现:

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

bbee,它们在目标文件中GLOBAL具有HIDDEN可见性,LOCAL位于 的静态符号中,libabde.so并且完全不存在于其动态符号表中。

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

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

如果您仍然想取消隐藏隐藏的符号,并且无法更改存档在 中的目标文件的源代码,那么libxxx.a最糟糕的方法是:

  • 从中提取每个目标文件 libxxx.a
  • 大夫它来代替HIDDENDEFAULT知名度在全球的定义
  • 将其放入新存档 libyyy.a
  • 然后使用libyyy.a代替libxxx.a

修改binutils目标文件的工具是objcopy. 但是objcopy没有直接操作符号的动态可见性的操作,您必须满足于“实现”取消隐藏隐藏符号的效果的迂回曲折:

  • 使用objcopy --redefine-sym,将每个隐藏的全局符号重命名S__hidden__S
  • 使用objcopy --add-symbol,添加一个新的全局符号S,该符号具有相同的值,__hidden_SDEFAULT默认情况下可见。

最终得到两个具有相同定义的符号:原始隐藏的符号和一个新的未隐藏的别名。

更可取的是一种简单且单独更改 ELF 对象文件中符号可见性的方法,一种方法是提交LIEF 库(库到仪器可执行格式) - Swiss Army Chainsaw 用于对象和可执行文件更改1 .

这是一个 Python 脚本,它调用pyliefLIEF 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()
Run Code Online (Sandbox Code Playgroud)

用法:

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

这是一个shell脚本:

取消隐藏.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"
Run Code Online (Sandbox Code Playgroud)

这需要:

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

并确认与:

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

最后,如果我们将共享库与新存档重新链接

$  gcc -o libabde.so -shared -Wl,--whole-archive libnew.a -Wl,--no-whole-archive
Run Code Online (Sandbox Code Playgroud)

存档中的所有全局符号都被导出:

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

[1] 下载 C/C++/Python 库

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