Consider a simple C program:
#include <stdio.h>
int main()
{
puts("Hello");
return 0;
}
By running it with GDB, setting it LD_BIND_NOW=1for simplicity, I can notice the following:
$ 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)
Apparently, after binding the PLT record to the function, we still make a two-stage call:
call puts@pltjmp [ds:puts_address]
Comparing this with how it is implemented in Win32, all calls to imported functions, for example. MessageBoxAare executed as
call [ds:MessageBoxA_address]
i.e. in one step.
Even if you take into account a lazy commitment, you can still have, for example, [puts_address]contains a call _dl_runtime_resolveor something that is necessary at startup, so a one-way indirect call will still work.
So what is the cause of this complication? Is this some kind of branch prediction optimization or branch prediction optimization?
(v2)
, , call PLT; jump [GOT] . ( gcc):
#include <stdio.h>
int main()
{
for(int i=0;i<3;++i)
{
puts("Hello");
__asm__ __volatile__("nop");
}
return 0;
}
( LD_BIND_NOW unset) GDB:
$ 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, GOT puts:
(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]
.. , fixup ( , ).
: GCC?