[在我从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存根特意使用了比必要的更长的立即数和跳转位移,因此即使您具有足够的PLT条目以使落入路径中的jmp ?_001
需要从后面的PLT条目到达的rel32
,它们的大小也是恒定的。
[当链接使用call printf wrt ..plt
的代码或链接仅使用call printf
的非PIE时,它们是由链接器自动生成的。
您可以通过编写call [rel printf wrt ..got]
来完全避免PLT,就像使用-fno-plt
进行编译时的GCC一样。这样做是尽早绑定(而不是惰性的),在启动_start
之前解决了所有GOT问题。 。参见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不会生成它,因此您不能对此做任何事情。
[我似乎回想起历史上将jmp rel32
作为32位代码中PLT存根的第一条指令,而不是jmp [mem]
,但这也许只是在我真正不了解之前对PLT存根如何工作的猜测。如果他们以这种方式工作,那么惰性动态链接would
会修改实际的PLT本身以固定相对的跳转目标,因此索引PLT条目的机器代码将很重要。 (因此,使每个条目的宽度都固定很重要)。但是现在32位代码都不再使用jmp rel32
,因此PLT存根是只读的。并且在64位代码中,jmp rel32
只能达到+ -2GiB,因此无法访问映射到随机地址的库。注意,那些比需要更长的指令对于每个PLT存根仅运行一次。第一次调用后,间接jmp
目标将成为库中的函数。 (On
jmp
目标将是jmp
之后的下一条指令。)填充可能是一件好事:在单个16字节代码块中的太多jmp
指令对某些CPU上的分支预测器不利。但是我认为限制是某些AMD或Core 2在16字节机器代码块中跳3或4次,因此无论如何都不会被6字节jmp [RIP+rel32]
+ 2字节push imm8
+ 2字节jmp rel8
。