在程序集中写入共享对象时,如何处理“针对受保护符号的重新定位R_X86_64_PC32”?

How to handle the "relocation R_X86_64_PC32 against protected symbol" when writing shared objects in assembly?

提问人:Marc 提问时间:11/16/2023 最后编辑:Peter CordesMarc 更新时间:11/16/2023 访问量:124

问:

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

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

关键部分是全局保护符号和对 中的引用。当我使用 组装源代码时,告诉我为本地引用插入了重定位(显然需要一些重定位条目):ggfgcc -c -o example.o --shared -fpic example.sobjdump -xgas

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 的博客(但我可能弄错了),这是因为链接器在发生符号插入时(在其他对象文件中)无法保证指针相等。

由于这永远不会成为我共享对象中的符号的实际问题,因此我想保持沉默。Linux ABI 0.1 似乎说我应该在我的源代码中添加一个包含设置 .在实践中如何做到这一点?gld.note.gnu.propertyGNU_PROPERTY_NO_COPY_ON_PROTECTED

如果可能的话,我不想在汇编程序和链接器的调用中添加额外的标志,因此我正在寻找一种解决方案,其中必要的修改只是源文件的一部分。

汇编 x86-64 elf gnu-assembler binutils

评论

1赞 Peter Cordes 11/16/2023
一种可能的解决方法是在同一地址定义第二个标签,但不定义 .(或跨共享库中的文件,因此它在此共享对象中可见,但从外部不可见,因此它根本不参与符号插入或动态链接。但这更笨拙,(sourceware.org/binutils/docs/as/Protected.html)的文档听起来像是它应该做的。.global.hidden.protected

答:

4赞 fuz 11/16/2023 #1

您不能像这样引用符号,因为虽然它现在受到保护,不会入,但它仍然受到与从共享库导出的任何对象类型符号相同的行为的约束。要解决此问题,请像使用任何其他导出的对象类型符号一样浏览 GOT。

背景

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

movq    g(%rip), %rax

并获取共享库使用的同一变量的值。其工作方式是,对于主程序引用但由共享库提供的所有对象类型符号,链接器在链接时查找符号的大小,并在可执行文件的 BSS 段中分配该空间。符号(在本例中为)被解析为指向该空间。gg

在加载时,动态加载器会查找共享库,该库定义数据并将其从共享对象的数据段复制到主可执行文件的 BSS 段中保留的空间中,并将 的 GOT 条目解析为该地址。这称为副本重定位。因此,共享库在访问时将访问主程序可以访问的相同变量。(如果主程序不访问 ,则不会发生副本重定位,并在共享库的数据/BSS 段中解析为其定义)gggggg

但是,仅当共享库通过 GOT 访问符号时,此方案才有效,因为符号不会与共享库一起重新定位。因此,您必须通过 GOT 才能访问该符号。

即做

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

在程序运行时,地址不会更改,因此在代码开头执行一次就足够了。开销应该很低。g

解决方法

解决方法包括:

  • 请考虑隐藏符号,并仅通过访问器函数将其公开给主可执行文件,并返回其地址。您可以使用映射文件(版本脚本)在一个位置设置库中所有函数的可见性,这可能比在源文件中注释符号更容易。

  • 如果主可执行文件和库是否看到相同的符号地址(例如,如果它是一个常量)并不重要,则可以为符号提供隐藏的别名,并将其用于内部引用

  • 您可以使用使共享库始终使用其自己的符号副本,即使它需要重新定位副本。请注意,这实际上禁用了在共享库和主可执行文件之间共享变量的功能。您也无法正确比较函数指针的相等性。因此,不应在生产中使用此选项。-Bsymbolic

  • 如果由于某种原因无法使用访问器函数,则可以通过指针绕行导出的符号,只允许在指针上进行复制重定位:

            .bss
            .globl local_g
            .hidden local_g
    local_g:
            .space 8
            .data
            .globl g
    g:      .quad local_g
    

    在主二进制文件中,声明为持有指向变量的指针,并取消引用它以访问变量。请考虑声明它,以便它不会被意外取消引用。请注意,此方法的性能比通过 GOT 从其他共享对象访问符号的性能更差。gconst

  • 您可以在编译所有程序部件和链接(共享库和主可执行文件)期间使用,以避免复制重新定位(您可能还需要将所有部件与 .请注意,此类编译的共享库与未使用此选项编译的主程序不兼容,并且不得链接到它们。反之亦然。-mno-direct-extern-access-Wz,nocopyreloc

然而,最好的选择是像使用任何其他全局符号一样通过 GOT。

评论

0赞 Peter Cordes 11/16/2023
因此,当 GAS 手册说“此指令覆盖命名符号的默认可见性(由其绑定设置:本地、全局或弱)时,GAS 手册是错误的。该指令将可见性设置为受保护,这意味着从定义符号的组件中对符号的任何引用都必须解析为该组件中的定义,即使另一个组件中的定义通常会抢占此定义。sourceware.org/binutils/docs/as/Protected.html)。或者,如果您通过 GOT,情况仍然如此,因此在工具链中不允许它是错过的选择?
1赞 fuz 11/16/2023
@PeterCordes 这种说法没有错。定义还是一样的;如果主程序要定义它自己的符号,它会得到一个单独的变量。但是,如果它只是引用 ,它将获得由共享库定义的。要做到这一点,必须保留副本重新定位的可能性。副本重定位与符号插入正交。ggg
2赞 fuz 11/16/2023
即复制重定位使得符号的实际位置可以在主二进制文件中,以满足 ABI 要求;这仍将是共享库定义的相同符号,只是它的重新定位方式与库的其余部分不同,因此无法通过R_X86_64_PC32访问。
1赞 Peter Cordes 11/16/2023
@Marc:是的,链接器可以修补主可执行文件,但前提是它使用 64 位寻址,而不是有效的 RIP 相对寻址。共享库通常加载距离主可执行文件超过 +- 2GiB,因此无法访问 32 位相对寻址。(x86-64 Linux 中不再允许 32 位绝对地址?也许 和 属性的某种组合(这可能是错误的名称)可以让编译器仅对这一个变量使用 64 位绝对值?-mcmodel=medium__attribute__((section(".largedata"))).h
1赞 fuz 11/16/2023
@PeterCordes 当然,但链接器仍将使用复制重定位,除非您明确禁用它们 ()。-z nocopyreloc