gpt4 book ai didi

linux - 为什么导入的函数在 Linux 中被间接调用?

转载 作者:IT王子 更新时间:2023-10-29 01:13:42 29 4
gpt4 key购买 nike

考虑一个简单的 C 程序:

#include <stdio.h>

int main()
{
puts("Hello");
return 0;
}

使用 GDB 运行它,为简单起见设置了 LD_BIND_NOW=1,我可以观察到以下内容:

$ gdb -q ./test -ex 'b main' -ex r
Reading symbols from ./test...done.
Breakpoint 1 at 0x8048420
Starting program: /tmp/test

Breakpoint 1, 0x08048420 in main ()
(gdb) disas
Dump of assembler code for function main:
0x0804841d <+0>: push ebp
0x0804841e <+1>: mov ebp,esp
=> 0x08048420 <+3>: and esp,0xfffffff0
0x08048423 <+6>: sub esp,0x10
0x08048426 <+9>: mov DWORD PTR [esp],0x8048500
0x0804842d <+16>: call 0x80482c0 <puts@plt>
0x08048432 <+21>: mov eax,0x0
0x08048437 <+26>: leave
0x08048438 <+27>: ret
End of assembler dump.
(gdb) si 4
0x080482c0 in puts@plt ()
(gdb) disas
Dump of assembler code for function puts@plt:
=> 0x080482c0 <+0>: jmp DWORD PTR ds:0x8049670
0x080482c6 <+6>: push 0x0
0x080482cb <+11>: jmp 0x80482b0
End of assembler dump.
(gdb) si
_IO_puts (str=0x8048500 "Hello") at ioputs.c:35
35 {
(gdb)

显然,将PLT入口绑定(bind)到函数上后,我们仍然进行两步调用:

  1. 调用 puts@plt
  2. jmp [ds:puts_address]

将其与它在 Win32 中的实现方式进行比较,所有导入函数的调用,例如MessageBoxA,都是这样

call [ds:MessageBoxA_address]

即一步到位。

即使考虑到惰性绑定(bind),仍然有可能有例如[puts_address] 包含对 _dl_runtime_resolve 的调用或启动时所需的任何内容,因此一步间接调用仍然有效。

那么出现这种并发症的原因是什么?这是某种分支预测或分支目标预测优化吗?

EDIT 回应 Employed Russian's answer (v2)

我的实际意思是 调用 PLT 的间接寻址; jump [GOT] 即使在惰性绑定(bind)的上下文中也是多余的。考虑以下示例(依赖于 gcc 未优化的编译):

#include <stdio.h>

int main()
{
for(int i=0;i<3;++i)
{
puts("Hello");
__asm__ __volatile__("nop");
}
return 0;
}

在 GDB 中运行它(LD_BIND_NOW 未设置):

$ gdb ./test -ex 'b main' -ex r -ex disas/r
Reading symbols from ./test...done.
Breakpoint 1 at 0x8048387
Starting program: /tmp/test

Breakpoint 1, 0x08048387 in main ()
Dump of assembler code for function main:
...
0x08048397 <+19>: c7 04 24 80 84 04 08 mov DWORD PTR [esp],0x8048480
0x0804839e <+26>: e8 11 ff ff ff call 0x80482b4 <puts@plt>
0x080483a3 <+31>: 90 nop
0x080483a4 <+32>: 83 44 24 1c 01 add DWORD PTR [esp+0x1c],0x1
...

反汇编puts@plt,可以看到puts的GOT入口地址:

(gdb) disas 'puts@plt'
Dump of assembler code for function puts@plt:
0x080482b4 <+0>: jmp DWORD PTR ds:0x8049580
0x080482ba <+6>: push 0x10
0x080482bf <+11>: jmp 0x8048284
End of assembler dump.

所以我们看到它是 0x8049580。我们可以修补 main() 的代码,将 e8 11 ff ff ff 90(地址 0x8048e9e)更改为间接调用 GOT 条目,即 call [ds: 0x8049580]:ff 15 80 95 04 08:

(gdb) set *(uint64_t*)0x804839e=0x44830804958015ff
(gdb) disas/r
Dump of assembler code for function main:
...
0x08048397 <+19>: c7 04 24 80 84 04 08 mov DWORD PTR [esp],0x8048480
0x0804839e <+26>: ff 15 80 95 04 08 call DWORD PTR ds:0x8049580
0x080483a4 <+32>: 83 44 24 1c 01 add DWORD PTR [esp+0x1c],0x1
...

在此之后运行程序仍然给出:

(gdb) c
Continuing.
Hello
Hello
Hello
[Inferior 1 (process 14678) exited normally]

即第一次调用做的是懒绑定(bind),后面两次只是用了fixup的结果(不信可以自己trace一下)。

所以问题仍然存在:为什么 GCC 不使用这种调用方式?

最佳答案

Apparently, after binding the PLT entry to the function, we still do a two-step call:

call puts@plt
jmp [ds:puts_address]

编译器和链接器无法知道你要设置LD_BIND_NOW=1在运行时,因此无法及时返回并重新编写生成的代码以直接使用 call [puts_address] .

另见 recent -fno-plt patches在 gcc-patches 邮件列表上。

Win32

Win32 不允许惰性函数解析(至少默认情况下不允许)。换句话说,他们编译/链接代码,只有LD_BIND_NOW=1一样工作。在编译/链接时被硬编码。一些历史here .

it's still possible to have e.g. [puts_address] contain the call to _dl_runtime_resolve or whatever is needed on startup, so the one-step indirect call would still work.

我觉得你很困惑。 [puts_address] 确实包含_dl_runtime_resolve在启动时(嗯,不完全是。Gory details)。您的问题是“为什么调用不能直接转到 [puts_address],为什么需要 puts@plt?”。

答案是_dl_runtime_resolve需要知道它正在解析哪个函数。它无法从 puts 的参数中推断出该信息. puts@plt 的全部存在理由 |正是为了向 _dl_runtime_resolve 提供该信息.

更新:

Why can't call <puts@plt> be replaced with call *[puts@GOT].

答案在第一个 -fno-plt 中提供patch我引用了:

“这是有警告的。这通常不能对所有人进行标记为 extern 的函数,因为编译器不可能判断是否函数是“真正外部的”(在共享库中定义)。如果一个函数不是真正的外部函数(最终在最终的可执行文件中定义),然后间接调用它是一种性能损失,因为它可能有是一个直接电话。”

然后您可能会问:为什么链接器(它知道 puts 是在同一个二进制文件中定义还是在单独的 DSO 中定义)为什么不能重写 call *[puts@GOT]call <puts@plt>

答案是这些是不同的指令(不同的操作码),链接器通常不会更改指令,只会更改指令内的地址(响应重定位条目)。

理论上链接器可以做到这一点,但还没有人为此烦恼。

关于linux - 为什么导入的函数在 Linux 中被间接调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32682318/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com