我正在尝试在 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
与 -fPIE
的 An 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/