在处理某个项目时,我遇到了无法构建库的问题。我收到的错误如下:重定位R_X86_64_PC32对符号''在制作共享对象时不能使用;使用-fPIC 重新编译 最终我找到了根本原因。它是图书馆中的递归函数。例如,我有以下众所周知的例子:
.section .text
.globl factorial
.type factorial,STT_FUNC
factorial:
push %rbp
mov %rsp,%rbp
mov 16(%rbp),%rax
cmp $1,%rax
je end_factorial
dec %rax
push %rax #this is how we pass the argument to function
call factorial
pop %rbx
inc %rbx
imul %rbx,%rax
end_factorial:
mov %rbp, %rsp
pop %rbp
ret
现在,让我们尝试构建共享库:
as -g -o fact.o fact.s
ld -shared fact.o -o libfact.so
ld: fact.o: relocation R_X86_64_PC32 against symbol `factorial' can not be used when making a shared object; recompile with -fPIC
如果我包装了阶乘函数,就像这样:
.section .text
.globl fact
.type fact,STT_FUNC
fact:
factorial:
push %rbp
mov %rsp,%rbp
mov 16(%rbp),%rax
cmp $1,%rax
je end_factorial
dec %rax
push %rax #this is how we pass the argument to function
call factorial
pop %rbx
inc %rbx
imul %rbx,%rax
end_factorial:
mov %rbp, %rsp
pop %rbp
ret
我可以构建没有错误的so库。
问题是:为什么在构建包含递归函数的共享库时会出错? 附:在这种情况下,静态链接工作正常。 谢谢!
答案 0 :(得分:3)
factorial
是一个全局标签,因此可以使用符号插入。见Sorry state of dynamic libraries on Linux。 (另外,an example of interposing malloc
with LD_PRELOAD和一些docs)。
创建共享库时,call factorial
指令的目标不会被认为是同一文件中定义的factorial:
标签。那是,因为您使用了.globl factorial
。
正如Jester所指出的那样,您应该为call
目标定义一个单独的本地标签,以便保留全局factorial
名称。
你可以做一个更简单的帮助"如果需要,使用自己的自定义调用约定并且不为递归部分创建%rbp
的堆栈帧的函数。 (但是对于x86-64来说,在堆栈上取一个arg已经不合标准了。)
你可以通过GOT通过PLT或内存间接呼叫,但不要这样做;你不希望每个call
有额外的开销,而你不希望用符号插入来替换你的非标准调用约定实现,用普通的实现传递第一个整数arg %rdi
。
说到这一点,在堆栈上传递arg很慢。除非rewrite the recursion to be tail-recursive, like factorial_helper(accumulator*n, n-1)
,否则您需要保存/恢复某些内容。但是,您每次都不需要使用%rbp
制作堆栈框架。
您在call
之前没有保持16字节堆栈对齐,但在调用您自己不关心它的私有函数时,您不需要这样做。
当然,如果您完全关心性能,那么您首先不会使用递归实现,因为factorial
执行此操作的唯一原因是学习练习。重写为tail-recursive允许您(or the compiler if writing in C)将call
/ ret
转换为jmp
,这当然会成为循环。
相关:What are good examples that actually motivate the study of recursion?。二元树遍历或Ackermann函数比迭代更容易递归实现,但factorial
或Fibonacci更难(在Fibonacci的情况下,很多更慢)。