在其中构建具有递归函数的 .so
Building .so with recursive function in it
在做一些项目时,我遇到了无法构建 so 库的问题。我收到如下错误:relocation R_X86_64_PC32 against symbol '' cannot be used when make a shared object;使用 -fPIC 重新编译
最终我设法找到了根本原因。它是库中的递归函数。例如,我有以下众所周知的例子:
.section .text
.globl factorial
.type factorial,STT_FUNC
factorial:
push %rbp
mov %rsp,%rbp
mov 16(%rbp),%rax
cmp ,%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 ,%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 库。
问题是:为什么在构建包含递归函数的共享库时出错?
P.S。在这种情况下,静态链接工作正常。
谢谢!
factorial
是一个全局标签,所以可以进行符号插入。参见 Sorry state of dynamic libraries on Linux. (Also, an example of interposing malloc
with LD_PRELOAD, and some docs)。
制作共享库时,call factorial
指令的目标不假定为同一文件中定义的factorial:
标签。那是 因为 你使用了 .globl factorial
.
正如 Jester 指出的那样,您应该为 call
目标定义一个单独的本地标签,这样您就可以保留全局 factorial
名称。
如果需要,您可以创建一个更简单的 "helper" 函数,该函数使用自己的自定义调用约定,并且不会为递归部分创建具有 %rbp
的堆栈帧。 (但是对于 x86-64,在堆栈上取一个 arg 已经是 non-standard)。
你可以通过PLT调用或memory-indirect通过GOT调用,但不要那样做;您不希望每个 call
都有额外的开销,并且您不希望符号插入将您的 non-standard-calling-convention 实现替换为传递 [=] 中第一个整数 arg 的普通实现19=].
说起来,在堆栈上传递 arg 很慢。你确实需要 save/restore 东西,除非你 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 的情况下, much 更慢)。
在做一些项目时,我遇到了无法构建 so 库的问题。我收到如下错误:relocation R_X86_64_PC32 against symbol '' cannot be used when make a shared object;使用 -fPIC 重新编译 最终我设法找到了根本原因。它是库中的递归函数。例如,我有以下众所周知的例子:
.section .text
.globl factorial
.type factorial,STT_FUNC
factorial:
push %rbp
mov %rsp,%rbp
mov 16(%rbp),%rax
cmp ,%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 ,%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 库。
问题是:为什么在构建包含递归函数的共享库时出错? P.S。在这种情况下,静态链接工作正常。 谢谢!
factorial
是一个全局标签,所以可以进行符号插入。参见 Sorry state of dynamic libraries on Linux. (Also, an example of interposing malloc
with LD_PRELOAD, and some docs)。
制作共享库时,call factorial
指令的目标不假定为同一文件中定义的factorial:
标签。那是 因为 你使用了 .globl factorial
.
正如 Jester 指出的那样,您应该为 call
目标定义一个单独的本地标签,这样您就可以保留全局 factorial
名称。
如果需要,您可以创建一个更简单的 "helper" 函数,该函数使用自己的自定义调用约定,并且不会为递归部分创建具有 %rbp
的堆栈帧。 (但是对于 x86-64,在堆栈上取一个 arg 已经是 non-standard)。
你可以通过PLT调用或memory-indirect通过GOT调用,但不要那样做;您不希望每个 call
都有额外的开销,并且您不希望符号插入将您的 non-standard-calling-convention 实现替换为传递 [=] 中第一个整数 arg 的普通实现19=].
说起来,在堆栈上传递 arg 很慢。你确实需要 save/restore 东西,除非你 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 的情况下, much 更慢)。