在其中构建具有递归功能的.so

时间:2018-03-19 21:31:36

标签: assembly shared-libraries x86-64 gnu dynamic-linking

在处理某个项目时,我遇到了无法构建库的问题。我收到的错误如下:重定位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库。

问题是:为什么在构建包含递归函数的共享库时会出错? 附:在这种情况下,静态链接工作正常。 谢谢!

1 个答案:

答案 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的情况下,很多更慢)。