【问题标题】:ELF Shared Object in x86-64 Assembly languagex86-64 汇编语言中的 ELF 共享对象
【发布时间】:2012-03-09 14:49:49
【问题描述】:

我正在尝试在 ASM 中创建一个共享库 (*.so),但我不确定我是否正确...

我的代码是:

    .section .data
    .globl var1
var1:
    .quad     0x012345

    .section .text
    .globl func1
func1:
    xor %rax, %rax
  # mov var1, %rcx       # this is commented
    ret

要编译它,我运行

gcc ker.s -g -fPIC -m64 -o ker.o
gcc ker.o -shared -fPIC -m64 -o libker.so

我可以从 C 程序中访问变量 var1 并使用 dlopen() 和 dlsym() 调用 func1。

问题出在变量 var1 中。当我尝试从 func1 访问它时,即取消注释该行时,编译器会生成错误:

/usr/bin/ld: ker.o: relocation R_X86_64_32S against `var1' can not be used when making a shared object; recompile with -fPIC
ker.o: could not read symbols: Bad value
collect2: ld returned 1 exit status

我不明白。我已经用-fPIC编译过了,怎么回事?

【问题讨论】:

  • 添加一个全局函数,改为返回var1的地址。
  • @HansPassant:实际上已经有一个指针变量保存了 var1 的地址,它被称为var1@GOTCREL,并且可以通过 rip 相对位置无关代码访问。

标签: gcc assembly linker shared-libraries x86-64


【解决方案1】:

我已经用-fPIC编译过了,怎么回事?

这部分错误消息是针对链接编译器生成代码的人。

您正在手动编写 asm,正如 datenwolf 正确写的那样,在汇编中编写共享库时,您必须注意代码与位置无关。

这意味着文件不能包含任何 32 位绝对地址(因为不可能重定位到任意 64 位基址)。支持 64 位绝对重定位,但通常您应该只将其用于跳转表。


mov var1, %rcx 使用 32 位绝对寻址模式。您通常不应该这样做,即使在位置相关的 x86-64 代码中也是如此。 32 位绝对地址的正常用例是:将地址放入带有mov $var1, %edi 的 64 位寄存器中(零扩展到 RDI)
和索引静态数组:mov arr(,%rdx,4), %edx

mov var1(%rip), %rcx 使用相对于 RIP 的 32 位偏移量。这是处理静态数据的有效方法,即使没有 -fPIE-fPIC 用于静态/全局变量,编译器也总是使用它。

你基本上有两种可能:

  • 普通的库私有静态数据,就像 C 编译器将生成 __attribute__((visibility("hidden"))) long var1;-fno-PIC 相同

    .data
        .globl var1       # linkable from other .o files in the same shared object / library
        .hidden var1      # not visible for *dynamic* linking outside the library
    var1:
        .quad     0x012345
    
    .text
        .globl func1
    func1:
        xor  %eax, %eax             # return 0
        mov  var1(%rip), %rcx   
        ret
    
  • 编译器为-fPIC生成的完整的符号插入感知代码

    您必须使用全局偏移表。如果您告诉他为共享库生成代码,编译器就是这样做的。 请注意,由于额外的间接性,这会导致性能下降。

    如果您不小心限制符号可见性以允许内联,请参阅 Sorry state of dynamic libraries on Linux 了解更多关于符号插入以及它对共享库的代码生成造成的开销。

    var1@GOTPCREL 是指向您的var1 的指针的地址,该指针本身可以通过rip-relative 寻址访问,而内容(var1 的地址)在加载库期间由链接器填充.这支持使用您的库的程序定义var1 的情况,因此您的库中的var1 应该解析到该内存位置,而不是您的@ 的.data.bss(或.text)中的那个987654343@.

        .section .data
        .globl var1
        # without .hidden
    var1:
        .quad     0x012345
    
        .section .text
        .globl func1
    func1:
        xor %eax, %eax
        mov var1@GOTPCREL(%rip), %rcx
        mov (%rcx), %rcx
        ret
    

http://www.bottomupcs.com/global_offset_tables.html查看更多信息

-fPIC-fPIEAn example on the Godbolt compiler explorer 显示了符号插入对于获取非隐藏全局变量的地址的区别:

  • movl $x, %eax 5 个字节,-fno-pie
  • leaq x(%rip), %rax 7 个字节,-fPIE 和隐藏的全局变量或 static-fPIC
  • y@GOTPCREL(%rip), %rax 7 个字节和一个负载,而不仅仅是 ALU,-fPIC 具有非隐藏的全局变量。

实际上加载总是使用x(%rip),除了非隐藏/非static vars和-fPIC,它必须首先从GOT获取运行时地址,因为它不是链接时间常数偏移相对到代码。

相关:32-bit absolute addresses no longer allowed in x86-64 Linux?(PIE 可执行文件)。


此答案的先前版本指出,加载动态库时,DATA 和 BSS 段可以相对于 TEXT 移动。这是不正确的,只有库基地址是可重定位的。保证对同一库中其他段的 RIP 相对访问是正常的,并且编译器会发出执行此操作的代码。 ELF 标头指定需要如何将段(包含节)加载/映射到内存中。

【讨论】:

  • 好吧,首先给出相同的错误,除了重定位类型R_X86_64_PC32 而不是 R_X86_64_32S。第二个给出Error: junk 'CREL' after expressionError: non-pc-relative relocation for pc-relative field。删除 CREL 会删除第一个错误,但不会删除第二个错误。
  • 奇怪。我能够使用 binutils-2.22 组装我给出的两个示例。你可能有旧版本的 binutils 吗?特殊符号的名称可能已更改
  • 我有带有 binutils 2.20.1-16 和 gcc version 4.4.5 (Debian 4.4.5-8) 的 Debian Squeeze。
  • 抱歉打扰编辑,但我认为这对未来的读者来说比将其发布为我自己的答案更有用;否决/赞成投票需要很长时间才能将正确答案带到顶部。我 100% 确定代码可以在不通过 GOT 的情况下访问 .data / .bss 变量,因为 gcc 为-fPICstatic int foo; 执行此操作。因此,我们可以确定动态链接器不会相对于彼此移动段。
  • @PeterCordes 没问题,你大大改进了我的回答
【解决方案2】:

我不明白。我已经用-fPIC编译过了,怎么回事?

-fPIC 是关于从非机器代码创建机器代码的标志,即使用哪些操作。在编译阶段。 程序集未编译! 每个程序集助记符直接映射到机器指令,您的代码未编译。它只是转录成稍微不同的格式。

由于您是在汇编中编写的,因此您的汇编代码必须与位置无关,才能链接到共享库中。 -fPIC 对您的情况没有影响,因为它只影响代码生成。

【讨论】:

  • 是的,这是因为我的英语不好以及它和我的母语之间的差异。最后只有一个词常用来表示“制作程序”,我在这里机械地使用它......
【解决方案3】:

好吧,我想我找到了一些东西......

drhirsch 的第一个解决方案给出了几乎相同的错误,但重定位类型已更改。而且类型总是以 32 结尾。这是为什么呢?为什么 64 位程序使用 32 位重定位?

我是通过谷歌搜索找到的:http://www.technovelty.org/code/c/relocation-truncated.html

上面写着:

出于代码优化目的,mov的默认立即大小 指令是一个 32 位的值

原来如此。我使用 64 位程序,但重定位是 32 位的,我只需要使用 movabs 指令将其强制为 64 位。

此代码正在组装和工作(从内部函数func1 和通过dlsym() 从外部C 程序访问var1):

    .section .data 
    .globl var1 
var1: 
    .quad     0x012345

    .section .text 
    .globl func1 
func1: 
    movabs var1, %rax       # if one is symbol, other must be %rax
    inc %rax
    movabs %rax, var1
    ret

但我对全局偏移表存有疑问。我必须使用它,还是这种“直接”访问是绝对正确的?

【讨论】:

  • movabs 可以,但这就是所谓的大代码模型。它是有限的,因为您只能使用rax 而不能使用其他寻址模式。
  • @hirschhornsalz, movabs 适用于 %rsi%rdx pastebin
  • @IvanBlack:您正在使用movabs $imm64, %r64,它可用于任何寄存器。此代码使用从 64 位绝对地址 (moffs) 形式 (felixcloutier.com/x86/mov) 加载到 al/ax/eax/rax,它只有一个 RAX 操作码,而不是每个可能的寄存器(8 个操作码, 5 字节 mov $imm32, %r32 的 REX.W 形式,没有 ModRM 字节)。
猜你喜欢
  • 2023-03-25
  • 1970-01-01
  • 2015-07-05
  • 2013-08-04
  • 2015-04-05
  • 1970-01-01
  • 2017-01-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多