将对象从 C++ 存档 (.a) 包含到共享库中

El *_*psa 7 c++ linker ffmpeg shared-libraries static-libraries

我正在尝试将一些目标文件包含到我正在构建的共享库中。执行以下命令(为简洁起见,已省略 [ETC] 中的内容):

/usr/bin/c++ -fPIC -std=c++14 -pthread -Iinclude/ext/liveMedia -Iinclude/ext/groupsock [ETC] -g -shared -Wl,-soname,libValkka.so -o lib/libValkka .so CMakeFiles/Valkka.dir/src/avthread.cpp.o CMakeFiles/Valkka.dir/src/opengl.cpp.o [ETC] CMakeFiles/Valkka.dir/src/decoders.cpp.o -lX11 -lGLEW -lGLU -lGL -Wl,--whole-archive lib/libavcodec.a -Wl,--no-whole-archive

所以基本上我只是创建一个共享库,其中大部分对象来自我自己的源代码(即 CMakeFiles/Valkka.dir/src/*.o),但其中一些来自外部静态库,位于“lib /libavcodec.a”。我收到以下错误:

/usr/bin/ld: lib/libavcodec.a(h264_cabac.o): relocation R_X86_64_PC32针对符号'ff_h264_cabac_tables'在制作共享对象时不能使用;使用 -fPIC /usr/bin/ld 重新编译:最终链接失败:错误值 collect2:错误:ld 返回 1 退出状态

但这太不真实了!我可以提取“libavcodec.a”

ar x libavcodec.a
Run Code Online (Sandbox Code Playgroud)

在那之后检查

readelf --relocs h264_cabac.o | egrep '(GOT|PLT|JU?MP_SLOT)' 
Run Code Online (Sandbox Code Playgroud)

确实给了一些**它:

00000000175d 003100000004 R_X86_64_PLT32 0000000000000000 __stack_chk_fail - 4 000000001926 0031000000004 R_000000006_4 000000000006_000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000心

...

就像

objdump -r h264_cabac.o | grep -i "relocation"
Run Code Online (Sandbox Code Playgroud)

因此,实际上,“libavcodec.a”中的目标文件已经被编译为获得 PIC(位置无关代码)。

为什么链接器不相信!?

相关链接:

如何在共享对象中包含存档的所有对象?

将档案 (.a) 链接到共享对象 (.so)

有没有办法确定 .a 或 .so 库是否已编译为位置无关代码?

我怎么能用像 objdump 这样的东西来判断一个目标文件是否是用 -fPIC 构建的?

Mik*_*han 7

TL; 博士

添加-Wl,-Bsymbolic到共享库的 gcc 链接选项。

为什么?

您正在测试与的 PICness h264_cabac.o

readelf --relocs h264_cabac.o | egrep '(GOT|PLT|JU?MP_SLOT)
Run Code Online (Sandbox Code Playgroud)

并得出结论,-fPIC如果您有任何命中,则编译目标文件。想必你得到这个测试从最喜欢的答案我怎样才能知道,有像objdump的,如果目标文件已建成-fPIC?

你得到了一些点击,我可以通过不止一种方式重现它:

从源代码

$ git clone https://github.com/FFmpeg/FFmpeg.git
$ cd FFmpeg
$ ./configure --enable-shared
$ make
Run Code Online (Sandbox Code Playgroud)

然后:

$ cd libavcodec
$ readelf --relocs h264_cabac.o | egrep '(GOT|PLT|JU?MP_SLOT)'
00000000175d  003100000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000001926  003100000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
00000000259f  003100000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000002f0d  003100000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000003216  003200000004 R_X86_64_PLT32    0000000000000000 av_log - 4
000000003460  00330000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_chroma422_dc_s - 4
000000003afc  003100000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000003fb6  00360000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_i_mb_type_info - 4
000000004031  00370000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_mb_sizes - 4
00000000409a  003800000004 R_X86_64_PLT32    0000000000000000 ff_init_cabac_decoder - 4
000000004248  00390000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_b_mb_type_info - 4
000000004299  003a00000004 R_X86_64_PLT32    0000000000000000 ff_h264_pred_direct_mo - 4
000000004a31  003b00000004 R_X86_64_PLT32    0000000000000000 ff_h264_check_intra4x4 - 4
000000004bd5  003200000004 R_X86_64_PLT32    0000000000000000 av_log - 4
000000004f85  003c0000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_p_mb_type_info - 4
0000000050fd  003d0000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_b_sub_mb_type_ - 4
000000005233  003a00000004 R_X86_64_PLT32    0000000000000000 ff_h264_pred_direct_mo - 4
00000000544a  003200000004 R_X86_64_PLT32    0000000000000000 av_log - 4
000000005bef  003a00000004 R_X86_64_PLT32    0000000000000000 ff_h264_pred_direct_mo - 4
000000006db5  003e00000004 R_X86_64_PLT32    0000000000000000 ff_h264_check_intra_pr - 4
000000006de9  003f0000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_p_sub_mb_type_ - 4
000000007171  003200000004 R_X86_64_PLT32    0000000000000000 av_log - 4
000000008b1b  003e00000004 R_X86_64_PLT32    0000000000000000 ff_h264_check_intra_pr - 4
00000000ad41  004000000009 R_X86_64_GOTPCREL 0000000000000000 ff_h264_chroma_dc_scan - 4
00000000ad84  004000000009 R_X86_64_GOTPCREL 0000000000000000 ff_h264_chroma_dc_scan - 4
00000000b758  003100000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
Run Code Online (Sandbox Code Playgroud)

来自 Ubuntu 16.04 开发包

$ sudo apt-get install libavcodec-dev
$ dpkg -S libavcodec.a
libavcodec-dev:amd64: /usr/lib/x86_64-linux-gnu/libavcodec.a
$ mkdir ~/deleteme
$ cd ~/deleteme
$ ar x /usr/lib/x86_64-linux-gnu/libavcodec.a h264_cabac.o
$ readelf --relocs h264_cabac.o | egrep '(GOT|PLT|JU?MP_SLOT)'
0000000000c7  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
0000000002fa  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
00000000179d  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000001966  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000001b09  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000001d4a  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000001ee5  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
00000000265f  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000002fcd  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
0000000032f6  002f00000004 R_X86_64_PLT32    0000000000000000 av_log - 4
000000003305  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000003bdc  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000003cb5  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
000000004121  00320000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_mb_sizes - 4
000000004187  003300000004 R_X86_64_PLT32    0000000000000000 ff_init_cabac_decoder - 4
000000004381  003400000004 R_X86_64_PLT32    0000000000000000 ff_h264_pred_direct_mo - 4
000000004afe  003500000004 R_X86_64_PLT32    0000000000000000 ff_h264_check_intra4x4 - 4
000000005556  003400000004 R_X86_64_PLT32    0000000000000000 ff_h264_pred_direct_mo - 4
00000000576a  002f00000004 R_X86_64_PLT32    0000000000000000 av_log - 4
000000005acf  003400000004 R_X86_64_PLT32    0000000000000000 ff_h264_pred_direct_mo - 4
000000006e31  002f00000004 R_X86_64_PLT32    0000000000000000 av_log - 4
000000006e58  003600000004 R_X86_64_PLT32    0000000000000000 ff_h264_check_intra_pr - 4
000000009c20  003600000004 R_X86_64_PLT32    0000000000000000 ff_h264_check_intra_pr - 4
00000000b425  002f00000004 R_X86_64_PLT32    0000000000000000 av_log - 4
00000000b5ab  002e00000004 R_X86_64_PLT32    0000000000000000 __stack_chk_fail - 4
Run Code Online (Sandbox Code Playgroud)

结果并不相同,我在第一种方式中获得了 26 次重定位,在第二种方式中获得了 25 次重定位。但是无论哪种方式都有很多 PIC 安全的重定位,我很高兴相信h264_cabac.ohad 的两个编译-fPIC,无论他们有什么其他选择。

我将说明显而易见的:ff_h264_cabac_tables您的链接抱怨的符号:

relocation R_X86_64_PC32 against symbol 'ff_h264_cabac_tables' can not be used when making a shared object
Run Code Online (Sandbox Code Playgroud)

不在这些列表中。这意味着该目标文件-从两个种源-包含两个PIC-安全和PIC-不安全的重定位。GCC如何能得到那个错误的,没有任何人察觉到现在?如果是这样,我是如何运行 FFmpeg 的共享库构建并libavcodec.so成功链接的?

让我们来看看 PIC不安全的重定位:

$ readelf --relocs h264_cabac.o | egrep -v '(GOT|PLT|JU?MP_SLOT)'
000000000017  002c00000002 R_X86_64_PC32     0000000000000000 ff_h264_cabac_tables - 4
...
...
Run Code Online (Sandbox Code Playgroud)

好吧,我将省略大约 160 行,但它们都描述了与 PC 相关的类型R_X86_64_PC32 重定位,并且提到的唯一符号,打折节名称和本地标签,是我们的朋友ff_h264_cabac_tables,符号表说:

$ readelf -s h264_cabac.o | grep ff_h264_cabac_tables
    44: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND ff_h264_cabac_tables
    
Run Code Online (Sandbox Code Playgroud)

它是一个全局变量,未在此目标文件中定义。

GCC-fPIC没有坏。但是,知道编译的目标文件并 -fPIC不能绝对保证它不包含R_X86_64_PC32引用未定义全局符号的PC 相关类型 重定位。relocation R_X86_64_PC32 against symbol 'ff_h264_cabac_tables'是这样的搬迁。该R_X86_64_PC32类型搬迁采用32位PC-相对寻址模式,这是有效的,但在64位联动的设定的严格的限制。链接器不能保证引用的符号不会被动态解析为在此寻址模式下无法表示的地址。它不会有,所以它说:

relocation R_X86_64_PC32 against symbol 'ff_h264_cabac_tables' can not be used when making a shared object
Run Code Online (Sandbox Code Playgroud)

及其建议:

recompile with -fPIC
Run Code Online (Sandbox Code Playgroud)

是基于假设,罪魁祸首目标文件未经编译-fPIC。这可能是正确的假设,但不一定是正确的,并且对你的罪魁祸首正确libavcodec.a(h264_cabac.o)

-fPIC如果 允许编译器进行所有汇编和代码生成,则使用 with 进行编译将保证您的 PIC 安全重定位。但是你的标本h264_cabac.o或我的标本不允许这样做。所有这些样本都是从FFmpeg/libavcodec/x86/h264_cabac.cFFmpeg 源代码树中编译而来的。查看该文件,您将看到它定义了引用extern 全局变量的函数,ff_h264_cabac_tables并在手工制作的内联汇编中实现。可以告诉 GCC 编译这些函数-fPIC,但它没有机会。这些函数的位置无关性由汇编代码的作者负责。

我们可以证明 GCC 能够编译一个h264_cabac.o只有 PIC 安全重定位的文件,如果允许的话。这将附带证明您的链接失败源于我们的文件样本的手工组装,并且还将向您显示链接失败的一种修复方法。FFmpeg 的./configure脚本有以下选项:

--disable-asm            disable all assembly optimizations
Run Code Online (Sandbox Code Playgroud)

这会导致h264_cabac.o从纯 C 源文件FFmpeg/libavcodec/h264_cabac.c而不是内联汇编源文件编译FFmpeg/libavcodec/x86/h264_cabac.c。那么让我们尝试一下:

$ cd FFmpeg
$ make clean
$ ./configure --enable-shared --disable-asm
$ make
$ cd libavcodec
$ readelf --relocs h264_cabac.o | grep ff_h264_cabac_tables
00000000000a  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
0000000000ca  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
000000001eb5  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
0000000021c6  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
0000000026fe  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
000000002a17  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
000000002f13  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
00000000324c  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
000000003509  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
00000000362a  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
0000000037d7  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
00000000592b  00300000002a R_X86_64_REX_GOTP 0000000000000000 ff_h264_cabac_tables - 4
Run Code Online (Sandbox Code Playgroud)

现在,所有重定位引用ff_h264_cabac_tables都是 PIC 安全的。我们不妨证明这h264_cabac.o可以在共享库中链接。我们知道,ff_h264_cabac_tables在不确定的h264_cabac.o,所以我们还需要链接它的目标文件定义。它恰好是./cabac.o

$ gcc -shared -o libfoo.so h264_cabac.o cabac.o
Run Code Online (Sandbox Code Playgroud)

瞧:

$ file libfoo.so
libfoo.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=ed63107b715b357853da94d4a031c0b06c30c5f2, not stripped
Run Code Online (Sandbox Code Playgroud)

但是,如果您必须将自己的共享库与这个未经优化的共享库相关联,h264_cabac.o并且对迫使您这样做的汇编编码缺陷感到有些失望,您可能仍然会感到有些委屈。这些感觉还为时过早。

请记住,我已经使用普通 vanilla 成功构建了 FFmpeg ./configure --enable-shared。我说过链接器在您失败的链接中的反对意见是R_X86_64_PC32 重定位引用ff_h264_cabac_tables ,如果ff_h264_cabac_tables 动态解决,在运行时可能不可行。它并不反对这样的类型R_X86_64_PC32 重定位。这是一种预防性反对,基于对ff_h264_cabac_tables 最终将如何解决的无知。

但我们知道ff_h264_cabac_tables实际上定义在cabac.o并且我们会将其包含在与 的链接中h264_cabac.o,就像它们都包含在 的链接中一样libavcodec.so。并且我们可以告诉链接器链接中的任何全局引用都将静态解析为被链接的共享库中的定义,如果有的话,通过向它传递参数:

-Bsymbolic
Run Code Online (Sandbox Code Playgroud)

这将消除其对任何 R_X86_64_PC32搬迁的。它知道它能够在链接时决定R_X86_64_PC32 重定位ff_h264_cabac_tables是否可行。如果没有,它会给出不同的错误:relocation truncated to fit:... 否则它会在没有评论的情况下成功。

难免就是这样 libavcodec.so就是在股票 FFmpeg 构建中成功链接的方式。再一次从顶部:

$ cd FFmpeg
$ make clean
$ ./configure --enable-shared
$ make
Run Code Online (Sandbox Code Playgroud)

然后强制重新链接 libavcodec.so

$ rm libavcodec/h264_cabac.o
$ $ make libavcodec/libavcodec.so V=1
gcc -I. -I./ -D_ISOC99_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE \
-D_POSIX_C_SOURCE=200112 -D_XOPEN_SOURCE=600 -DPIC -DZLIB_CONST -DHAVE_AV_CONFIG_H \
-std=c11 -fomit-frame-pointer -fPIC -pthread  -g -Wdeclaration-after-statement \
-Wall -Wdisabled-optimization -Wpointer-arith -Wredundant-decls -Wwrite-strings \
-Wtype-limits -Wundef -Wmissing-prototypes -Wno-pointer-to-int-cast -Wstrict-prototypes \
-Wempty-body -Wno-parentheses -Wno-switch -Wno-format-zero-length -Wno-pointer-sign \
-O3 -fno-math-errno -fno-signed-zeros -fno-tree-vectorize -Werror=format-security \
-Werror=implicit-function-declaration -Werror=missing-prototypes -Werror=return-type \
-Werror=vla -Wformat -fdiagnostics-color=auto -Wno-maybe-uninitialized  \
-MMD -MF libavcodec/h264_cabac.d -MT libavcodec/h264_cabac.o -c \
-o libavcodec/h264_cabac.o libavcodec/h264_cabac.c
sed 's/MAJOR/57/' libavcodec/libavcodec.v | cat > libavcodec/libavcodec.ver
gcc -shared -Wl,-soname,libavcodec.so.57 -Wl,-Bsymbolic ... etc. etc. ...
                                         ^^^^^^^^^^^^^^   
Run Code Online (Sandbox Code Playgroud)

所以不存在汇编编码缺陷。要h264_cabac.o 在共享库中链接手动优化,您只需要添加-Wl,-Bsymbolic到 gcc 链接选项。这是优化的要求。

让我们至少证明一下:

$ cd libavcodec/
$ gcc -shared -o libfoo.so h264_cabac.o cabac.o
/usr/bin/ld: h264_cabac.o: relocation R_X86_64_PC32 against symbol `ff_h264_cabac_tables' can not be used when making a shared object; recompile with -fPIC
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status
Run Code Online (Sandbox Code Playgroud)

又是你的失败。和:

$ gcc -shared -Wl,-Bsymbolic -o libfoo.so h264_cabac.o cabac.o
$ file libfoo.so 
libfoo.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=7dc86aeae353c4d92cdb5fa35d169bf019b47eb2, not stripped
Run Code Online (Sandbox Code Playgroud)

成功。