将两个 .o 文件链接在一起

Linking two .o files together

我有两个 .asm 文件,一个在另一个内部调用函数。我的文件看起来像:

mainProg.asm:

 global main
 extern factorial

    section .text
main:
;---snip---
    push rcx
    call factorial
    pop  rcx
;---snip---
    ret

factorial.asm:

    section .text
factorial:
    cmp rdi, 0
    je  l2

    mov rax, 1
l1: 
    mul rdi
    dec rdi
    jnz l1
    ret

l2:
    mov rax, 1
    ret

(是的,我可以通过实施改进一些地方。)

我尝试按照How to link two nasm source files:

中的步骤编译它们
$ nasm -felf64 -o factorial.o factorial.asm
$ nasm -felf64 -o mainProg.o mainProg.asm
$ gcc -o mainProg mainProg.o factorial.o

前两个命令没有问题,但最后一个失败

mainProg.o: In function `main':
mainProg.asm:(.text+0x22): undefined reference to `factorial'
collect2: error: ld returned 1 exit status

更改目标文件的顺序不会更改错误。 我尝试搜索 link 两个 .o 文件的解决方案,并找到了问题 C Makefile given two .o files。正如那里提到的,我 运行 objdump -S factorial.o 得到了

factorial.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <factorial>:
   0:   48 83 ff 00             cmp    [=14=]x0,%rdi
   4:   74 0e                   je     14 <l2>
   6:   b8 01 00 00 00          mov    [=14=]x1,%eax

000000000000000b <l1>:
   b:   48 f7 e7                mul    %rdi
   e:   48 ff cf                dec    %rdi
  11:   75 f8                   jne    b <l1>
  13:   c3                      retq   

0000000000000014 <l2>:
  14:   b8 01 00 00 00          mov    [=14=]x1,%eax
  19:   c3                      retq   

这与源文件几乎相同。明明有factorial功能,为什么ld检测不到呢? link 两个 .o 文件是否有不同的方法?

您需要 factorial.asm 中的 global factorial 汇编指令。没有它,它仍然在符号 table 中,但链接器不会将其视为对象之间的链接。

factorial: 这样的标签介于 global/external 符号和像 .loop1: 这样的本地标签之间(根本不存在于目标文件中)。局部标签是减少反汇编混乱的好方法,每个函数一个块而不是每个分支目标之后开始一个单独的块。

非全局符号只对反汇编和类似的东西有用,AFAIK。我认为它们会连同调试信息一起被 strip.

剥离

另外,注意 imul rax, rdi runs faster,因为它不必将结果的高半部分存储在 %rdx 中,甚至不需要计算它。

另请注意,您可以 objdump -Mintel -d 进行英特尔语法反汇编。 Agner Fog 的 objconv 也非常好,但它需要更多的输入,因为默认情况下输出不会转到标准输出。 (虽然 shell 包装函数或脚本可以解决这个问题。)

无论如何,这样会更好:

global factorial
factorial:
    mov eax, 1   ; depending on the assembler, might save a REX prefix

    ; early-out branch after setting rax, instead of duplicating the constant
    test   rdi, rdi   ; test is shorter than compare-against-zero
    jz .early_out

.loop:                 ; local label won't appear in the object file
    imul   rax, rdi
    dec    rdi
    jnz .loop
.early_out:
    ret

为什么 main push/pop rcx?如果您正在编写遵循标准 ABI 的函数(除非有很大的性能提升,否则绝对是个好主意),并且您希望某些东西在 call 中存活下来,请将其保存在调用保留寄存器中,例如 rbx.