Why are imported functions called so indirectly in Linux?

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@plt
  • jmp [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?

+4
1

-, PLT :

call puts@plt
jmp [ds:puts_address]

, LD_BIND_NOW=1 , , call [puts_address].

. -fno-plt patches gcc-patches.

Win32

Win32 ( , ). , / , , LD_BIND_NOW=1 /. .

, , [puts_address] _dl_runtime_resolve , , .

, . [puts_address] _dl_runtime_resolve (, . Gory details). : " [puts_address], puts@plt?".

: _dl_runtime_resolve , . puts. raison d'Γͺtre puts@plt _dl_runtime_resolve.

Update:

call <puts@plt> call *[puts@GOT].

-fno-plt patch :

" . , extern, , " extern" ( ). ( ), - , ".

: ( , puts DSO) call *[puts@GOT] call <puts@plt>?

, ( ), , ( ).

, .

+5

Source: https://habr.com/ru/post/1608226/


All Articles