assembly 在程序集中写入共享对象时,如何处理“relocation R_X86_64_PC32 against protected symbol”?

z9gpfhce  于 2023-11-19  发布在  其他
关注(0)|答案(1)|浏览(110)

我正在用汇编语言编写一个目标文件,并将其包含在共享对象中。我使用GNU工具链,目标是x86_64-pc-linux-gnu。请考虑以下(示例)源:

.text
        .globl  f
f:      leaq    g(%rip),%rax
        ret
        .data
        .globl  g
        .protected g
g:      .quad   8

字符串
关键部分是全局保护符号gf中对g的引用。当我使用gcc -c -o example.o --shared -fpic example.s组装源代码时,objdump -x告诉我,gas为本地引用插入了一个重定位(一些重定位条目显然是必要的):

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
0000000000000003 R_X86_64_PC32     g-0x0000000000000004


当我尝试链接文件时,问题出现了:

$ gcc -o example.so --shared -fpic example.s
/usr/bin/ld: /tmp/ccQ6BcLl.o: relocation R_X86_64_PC32 against protected symbol `g' can not be used when making a shared object
/usr/bin/ld: final link failed: bad value
collect2: error: ld returned 1 exit status


就我认为我已经通过阅读Ian Lance Taylor's blog理解(但我可能是错误的),这是由于这样一个事实,即链接器不能保证指针平等时,符号插入发生(在其他一些目标文件)。
由于这永远不会是我共享对象中的符号g的实际问题,我想静默ldLinux ABI 0.1似乎说我应该向包含GNU_PROPERTY_NO_COPY_ON_PROTECTED设置的源代码添加.note.gnu.property部分。实际上我如何做到这一点?
如果可能的话,我不想在汇编器和链接器的调用中添加额外的标志,所以我正在寻找一种解决方案,其中必要的修改只是源文件的一部分。

juzqafwq

juzqafwq1#

你不能像这样引用符号,因为虽然它现在被保护不受插入,但它仍然受到从共享库导出的任何对象类型符号的相同行为的影响。要解决这个问题,请像任何其他导出的对象类型符号一样通过GOT。

背景

ELF ABI的设计者希望共享对象对主程序是透明的。ELF ABI程序(但不是共享对象)完全不知道共享对象的存在,并且被写得好像程序使用的所有符号都被静态地链接到程序中。这包括对象类型符号,允许直接访问这些符号。例如,主程序可以做

movq    g(%rip), %rax

字符串
并获取共享库使用的同一变量g的值。其工作方式是,对于主程序引用但由共享库提供的所有对象类型符号,链接器在链接时查找符号的大小,并在可执行文件的BSS段中分配相应的空间。符号(在本例中为g)被解析为指向该空间。
在加载时,动态加载器找到定义g的共享库,并将g的数据从共享对象的数据段复制到主可执行文件的BSS段中保留的空间中,并将g的GOT条目解析到该地址。这称为复制重定位。因此,共享库,当访问g时,将访问主程序可以访问的相同变量。(如果主程序不访问g,则不会发生复制重定位,并且g将解析为其在共享库的数据/BSS段中的定义)
但是,此方案仅适用于共享库通过GOT访问符号的情况,因为符号不会随共享库重新定位。因此,您必须通过GOT访问符号。
即做

movq g@GOTPCREL(%rip), %rax  # find the address of g
        movq (%rax), %rax            # load the value of g


g的地址在程序运行时不会改变,所以在代码开始时只需要这样做一次就足够了。开销应该很低。

解决方法

解决方法包括:

  • 可以考虑隐藏符号,只通过访问器函数将其暴露给主可执行文件,并返回其地址。您可以使用Map文件(版本脚本)在一个位置设置库中所有函数的可见性,这可能比在源文件中注解符号更容易。
  • 如果主可执行文件和你的库看到相同的符号地址(例如,如果它是一个常量)并不重要,你可以为符号提供一个隐藏的别名,并将其用于内部引用
  • 您可以使用-Bsymbolic让共享库始终使用它自己的符号副本,即使它受到副本重定位。请注意,这实际上禁用了共享库和主可执行文件之间共享变量的能力。您也无法正确比较函数指针的相等性。因此,不应在生产环境中使用此选项。
  • 如果由于某种原因不能使用访问器函数,可以通过指针绕过导出的符号,只允许指针上的复制重定位:
.bss
        .globl local_g
        .hidden local_g
local_g:
        .space 8
        .data
        .globl g
g:      .quad local_g


在主二进制文件中,声明g为持有指向变量的指针,并解引用它来访问变量。考虑将它声明为const,这样它就不会被意外地解引用。注意,这种方法比通过GOT从其他共享对象访问符号的性能更差。

  • 你可以在编译所有程序部分和链接(共享库和主可执行文件)的过程中使用-mno-direct-extern-access来避免复制重定位(你可能还需要用-Wz,nocopyreloc链接所有部分。注意,这样编译的共享库与没有这个选项编译的主程序是ABI不兼容的,不能链接到它们。反过来也可以。

然而,最好的选择是只通过GOT与任何其他全球符号。

相关问题