我正在从一个我没有源代码的静态库创建一个共享库。
许多 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.c
和 b.c
像这样:$ gcc -Wall -c a.c b.c
我们可以看到符号
aa
和 ab
在它们各自的目标文件中定义和全局:$ 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
...
aa
是 GLOBAL
带有 DEFAULT
的符号可见性和 bb
是 GLOBAL
带有 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
bb
和 ee
, 分别是 GLOBAL
与 HIDDEN
在目标文件中的可见性,是
LOCAL
在 libabde.so
的静态符号中并且完全不在从其动态符号表。
有鉴于此,您可能希望重新评估您的使命:
在
libxxx.a
中的目标文件中被赋予隐藏可见性的符号有被隐藏是因为编译它们的人有理由
希望从动态链接中隐藏它们。你有反补贴需求吗
导出它们以进行动态链接?或者您可能只是想导出它们,因为
你已经注意到它们没有导出,不知道为什么不导出?
如果您仍然想取消隐藏隐藏符号,并且无法更改源代码
归档在
libxxx.a
中的目标文件的数量,你最糟糕的手段是:libxxx.a
中提取每个目标文件HIDDEN
与 DEFAULT
其全局定义的可见性 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/