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

zor*_*git 7 assembly linker gcc x86-64 shared-libraries

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

我的代码是:

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

    .section .text
    .globl func1
func1:
    xor %rax, %rax
  # mov var1, %rcx       # this is commented
    ret
Run Code Online (Sandbox Code Playgroud)

要编译它我运行

gcc ker.s -g -fPIC -m64 -o ker.o
gcc ker.o -shared -fPIC -m64 -o libker.so
Run Code Online (Sandbox Code Playgroud)

我可以访问变量var1并使用dlopen()和dlsym()从C中的程序调用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
Run Code Online (Sandbox Code Playgroud)

我不明白.我已经用-fPIC编译了,那有什么不对?

Gun*_*iez 11

我已经用-fPIC编译了,那有什么不对?

该部分错误消息适用于链接编译器生成的代码的人员.

你正在手工编写asm,因为datenwolf正确地写了,当在汇编中编写共享库时,你必须自己照顾代码与位置无关.

这意味着文件不能包含任何32位绝对地址(因为无法重定位到任意64位基址).64位绝对重定位支持,但通常你应该只使用了跳转表.


mov var1, %rcx使用32位绝对寻址模式.你通常不应该这样做,即使在依赖于位置的x86-64代码中也是如此.32位绝对地址的正常用例是:将地址放入64位寄存器mov $var1, %edi(零扩展到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
    
    Run Code Online (Sandbox Code Playgroud)
  • 像编译器生成的完整符号插入感知代码-fPIC.

    您必须使用全局偏移表.如果您告诉他为共享库生成代码,这就是编译器的工作方式.请注意,由于额外的间接性,这会带来性能损失.

    请参阅Linux上的抱歉动态库状态,了解有关符号插入的更多信息以及它对共享库的代码生成所产生的开销,如果您不小心限制符号可见性以允许内联.

    var1@GOTPCREL是指向您var1的指针的地址,指针本身可通过rip相对寻址访问,而内容(地址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
    
    Run Code Online (Sandbox Code Playgroud)

请参阅http://www.bottomupcs.com/global_offset_tables.html上的其他一些信息

在Godbolt编译器Explorer中的例子-fPIC对比-fPIE表明,符号插入使这种差异变得非隐藏的全局变量的地址:

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

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

相关:x86-64 Linux中不再允许32位绝对地址?(PIE可执行文件).


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

  • 很抱歉进行了侵入性编辑,但我认为这对未来的读者来说比将其发布为我自己的答案更有用;向下/向上投票需要很长时间才能将正确答案带到顶部。我 100% 肯定代码可以在不通过 GOT 的情况下访问 .data / .bss 变量,因为 gcc 为 `static int foo;` 的 `-fPIC` 做到了这一点。因此我们可以确定动态链接器不会相对于彼此移动段。 (2认同)
  • @PeterCordes 没问题,你极大地改进了我的答案 (2认同)

zor*_*git -1

好吧,我想我发现了一些东西......

drhirsch的第一个解决方案给出了几乎相同的错误,但重定位类型发生了变化。并且 type 总是以 32 结尾。为什么呢?为什么64位程序使用32位重定位?

我通过谷歌搜索发现了这一点:http ://www.technovelty.org/code/c/relocation-truncated.html

它说:

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

就是这样。我使用 64 位程序,但重定位是 32 位,我需要的只是用指令强制它为 64 位movabs

var1该代码正在汇编和运行(通过内部函数func1和外部 C 程序访问dlsym()):

    .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
Run Code Online (Sandbox Code Playgroud)

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