gcc - x86-64 汇编语言中的 ELF 共享对象

标签 gcc assembly linker shared-libraries x86-64

我正在尝试在 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 进行了编译,所以有什么问题吗?

最佳答案

I've already compiled with -fPIC, so what's wrong?

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

您正在手工编写 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

基本上有两种可能性:

  • 正常的库私有(private)静态数据,就像 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)中的那个 .so

        .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 的非隐藏/非 -fPIC 变量,它必须首先从 GOT 获取运行时地址,因为它不是相对于代码的链接时间常量偏移量。

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


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

关于gcc - x86-64 汇编语言中的 ELF 共享对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54329030/

相关文章:

c++ - gcc - 如何使用地址 sanitizer

c++ - Xcode 默认标志从何而来?

gcc - Macports 不安装手册页

c - 为什么不能将 asm 中调用 c 库 printf 的 stdout 传送到其他程序?

c - 调试 C 程序(int 声明)

assembly - 如何防止 GNU ld 对目标文件重新排序?

ios - 仅链接设备上的第三方框架

ios - C编译器无法创建可执行文件(安装Cocoapods)

c - Sparc V8比较和交换函数的内联汇编实现

c++ - Unresolved external