【问题标题】:Is it possible to access the procedure linkage table in NASM?是否可以访问 NASM 中的过程链接表?
【发布时间】:2020-04-17 18:56:46
【问题描述】:

在我从 Agner Fog 的 objconv 收到的报告中,我在 .plt(过程链接表)部分看到了要修复的建议错误,例如:

SECTION .plt    align=16 execute                        ; section number 9, code

?_001:  push    qword [rel ?_086]                       ; 10F0 _ FF. 35, 00201F12(rel)
    jmp     near [rel ?_087]                        ; 10F6 _ FF. 25, 00201F14(rel)

; Filling space: 4H
; Filler type: Multi-byte NOP
;       db 0FH, 1FH, 40H, 00H

ALIGN   8
?_002:  jmp     near [rel ?_088]                        ; 1100 _    FF. 25, 00201F12(rel)

; Note: Immediate operand could be made smaller by sign extension
        push    0                                       ; 1106 _ 68, 00000000
; Note: Immediate operand could be made smaller by sign extension
        jmp     ?_001                                   ; 110B _ E9, FFFFFFE0

在两种情况下(以及代码中未显示的其他情况),它建议“立即操作数可以通过符号扩展变得更小”。如何访问过程链接表以进行这些更改?是否可以?

【问题讨论】:

  • PLT 由链接器自动生成。我不认为你可以改变这样做的方式。另请注意,这对性能的影响很小。

标签: matplotlib assembly optimization x86-64 nasm


【解决方案1】:

PLT 存根故意使用比必要更长的立即数和跳转位移,因此即使您有足够的 PLT 条目,直通路径中的 jmp ?_001 需要 rel32 才能从后面的 PLT 条目到达,它们的大小也是恒定的.

链接器在链接使用call printf wrt ..plt 的代码或链接仅使用call printf 的非PIE 时自动生成它们。

您可以通过编写 call [rel printf wrt ..got] 来完全避免 PLT,就像使用 -fno-plt 编译时 GCC 所做的那样。 这会进行早期绑定(而不是惰性),在启动之前解决所有 GOT你的_start。 见Can't call C standard library function on 64-bit Linux from assembly (yasm) code。使用default rel 可以让您省略寻址模式的显式rel 部分。等效的 AT&T 语法是 call *printf@GOTPCREL(%rip)


我不知道这个固定宽度的 PLT 存根数组是否对于运行时的任何东西都是绝对必要的。 例如惰性动态链接只修改 GOT,而不是 PLT 本身,因为现代 PLT 使用间接跳转。 push 0 正在推送 PLT 条目的索引,但我认为没有任何东西使用它来实际查找该 PLT 存根的机器代码的地址,仅索引 GOT 条目。

此时它可能只是链接器中错过的优化。 NASM 不会生成它,因此您无法真正做任何事情。 p>

我似乎记得在历史上看到 jmp rel32 作为 32 位代码中 PLT 存根的第一条指令,而不是 jmp [mem],但也许这只是在我真正了解很多之前对 PLT 存根如何工作的猜测。如果他们曾经那样工作,惰性动态链接修改实际的 PLT 本身以修复相对跳转目标,因此索引 PLT 条目的机器代码将很重要。 (因此让每个条目的宽度都固定很重要)。

但如今,即使是 32 位代码也不使用 jmp rel32,因此 PLT 存根是只读的。而在 64 位代码中,jmp rel32 只能达到 +-2GiB,因此无法访问映射到随机地址的库。


请注意,对于每个 PLT 存根,那些比需要更长的指令只运行一次。在第一次调用之后,间接的 jmp 目标将成为库中的函数。 (第一次调用时,jmp 目标将是jmp 之后的下一条指令。)

填充可能是件好事:单个 16 字节代码块中有太多 jmp 指令对某些 CPU 上的分支预测器不利。但我认为对于某些 AMD 或 Core 2,限制就像 16 字节机器代码块中的 3 或 4 次跳转,因此无论如何 6 字节 jmp [RIP+rel32] + 2 字节 push imm8 + 都不会受到影响2字节jmp rel8

【讨论】:

  • 感谢您对 plt 表如何工作的详细解释。我认为最好的方法是使用 call [rel printf wrt ..got] 并取消 .plt。我怀疑这会提高性能,因为我们不必跳到 plt.我会尝试并重新 objconv 看看。
  • @RTC222:是的,越来越多的发行版开始使用-fno-plt 作为标准构建选项,以消除每次调用的间接级别。它可能会稍微减慢运行big_program --help 的速度,因为必须为未调用的库函数完成大量动态链接,但除此之外的缺点较低。请参阅 32-bit absolute addresses no longer allowed in x86-64 Linux? - 我在其中包含了一个关于 -fno-plt 的部分,其中包含一个关于性能的链接。
猜你喜欢
  • 1970-01-01
  • 2018-04-08
  • 2020-11-29
  • 2023-01-21
  • 2021-11-17
  • 2010-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多