【问题标题】:Linking two .o files together将两个 .o 文件链接在一起
【发布时间】:2023-03-19 08:33:01
【问题描述】:

我有两个 .asm 文件,一个在另一个内部调用函数。我的文件如下所示:

mainProg.asm:

 global main
 extern factorial

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

阶乘.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

更改目标文件的顺序不会更改错误。 我尝试寻找链接两个 .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    $0x0,%rdi
   4:   74 0e                   je     14 <l2>
   6:   b8 01 00 00 00          mov    $0x1,%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    $0x1,%eax
  19:   c3                      retq   

这与源文件几乎相同。它明明包含factorial 函数,那为什么ld 没有检测到呢?是否有不同的方法来链接两个 .o 文件?

【问题讨论】:

  • 您需要将factorial 设为全局符号。

标签: gcc assembly linker 64-bit nasm


【解决方案1】:

您需要factorial.asm 中的global factorial 汇编程序指令。没有它,它仍然在符号表中,但链接器不会考虑将它用于对象之间的链接。

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

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


另外,请注意imul rax, rdiruns 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 推送/弹出rcx?如果您正在编写遵循标准 ABI 的函数(绝对是一个好主意,除非有很大的性能提升),并且您希望某些东西能够在 call 中存活下来,请将其保存在像 rbx 这样的调用保留寄存器中。

【讨论】:

  • 啊,我没有意识到标签开头的. 使它成为本地的;我认为这只是一些程序员使用的命名约定。感谢您的提示!
  • @luolimao:我更新了一些技巧,包括收紧你的阶乘代码。你没有说mainrdx:rax 作为返回值。您可以通过在循环中执行 cmp rdi, 1 / jne 来保存迭代,以避免乘以 1。或者甚至展开最后两次迭代以通过移位乘以 2。不过,可能不值得额外的说明。无论如何,是的,intel-syntax asm 似乎并没有记录在任何地方的一个地方。指令集的东西在一个地方,汇编指令/标签的东西在 YASM 和 NASM 手册中。
  • 啊,我认为mulimul 快,因为它更旧,但显然不是;你假设我不使用来自factorialrdx 也是正确的。就push rcx 而言,我开始用完寄存器(rbx 保存一些其他数据),我推送了rcx,因为它是循环计数器(而不是sub esp, 8call factorial、@987654350 @,并将计数器存储在,例如,r12)
  • 我假设您强调使用sub rsp, 8 而不是esp,因为它会将rsp 的高32 位清零?老实说,我几乎在所有命令中都使用了 64 位寄存器(除非我需要移动单个字节或其他东西),因为我不太确定何时使用 32 位寄存器更好。
  • 我在 Agner Fog 的 insn 表中发现英特尔 Haswell 的 32 与 64b 操作数大小的唯一性能差异是 div/idiv,其中 64b 慢得多。单操作数 imulmul 在 64b 中实际上更快(少一个 uop)。也许接线“自然地”将 128b 结果发送到 rdx:rax,而将 64b 结果拆分为 edx:eax 需要额外的 uop。所以一个好的经验法则可能仍然是总是使用 32b,除非你知道你需要 64b。通常避免使用 16b 和 8b,除非您希望与 prev 值行为合并。 (狭窄的内存存储很好,并且 movsx 加载。)
猜你喜欢
  • 1970-01-01
  • 2015-10-18
  • 2011-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多