What does @plt mean here?
Asked Answered
C

2

104
0x00000000004004b6 <main+30>:   callq  0x400398 <printf@plt>

Anyone knows?

UPDATE

Why two disas printf give me different result?

(gdb) disas printf
Dump of assembler code for function printf@plt:
0x0000000000400398 <printf@plt+0>:  jmpq   *0x2004c2(%rip)        # 0x600860 <_GLOBAL_OFFSET_TABLE_+24>
0x000000000040039e <printf@plt+6>:  pushq  $0x0
0x00000000004003a3 <printf@plt+11>: jmpq   0x400388

(gdb) disas printf
Dump of assembler code for function printf:
0x00000037aa44d360 <printf+0>:  sub    $0xd8,%rsp
0x00000037aa44d367 <printf+7>:  mov    %rdx,0x30(%rsp)
0x00000037aa44d36c <printf+12>: movzbl %al,%edx
0x00000037aa44d36f <printf+15>: mov    %rsi,0x28(%rsp)
0x00000037aa44d374 <printf+20>: lea    0x0(,%rdx,4),%rax
0x00000037aa44d37c <printf+28>: lea    0x3f(%rip),%rdx        # 0x37aa44d3c2 <printf+98>
Chignon answered 29/3, 2011 at 7:34 Comment(1)
Where did that first output line come from? objdump I imagine?Oneman
P
160

It's a way to get code fix-ups (adjusting addresses based on where code sits in virtual memory, which may be different across different processes) without having to maintain a separate copy of the code for each process. The PLT, or procedure linkage table, is one of the structures which makes dynamic loading and linking easier to use (another is the GOT, or global offsets table).

Refer to the following diagram, which shows both your calling code and the library code (that you call) mapped to different virtual addresses in two different processes, A and B. There is only one copy of each piece of code in real memory, with the different virtual addresses within each process mapping to that real address):

Process A
    Addresses (virtual):
        0x1234                      0x8888
        +-------------+ +---------+ +---------+
        |             | | Private | |         |
        |             | | PLT/GOT | |         |
        | Shared      | +---------+ | Shared  |
    ===== application =============== library =====
        | code        | +---------+ | code    |
        |             | | Private | |         |
        |             | | PLT/GOT | |         |
        +-------------+ +---------+ +---------+
        0x2020                      0x6666
Process B

When the shared library is brought in to the address space, entries are constructed in the process-specific (private) PLT and/or GOT which will, on first use, perform some fix-up to make things faster. Subsequent usage will then bypass the fix-up as it will no longer be needed.

The process goes something like this.

printf@plt is actually a small stub which (eventually) calls the real printf function, modifying things on the way to make subsequent calls faster.

The real printf function is mapped into an arbitrary location in a given process (virtual address space), as is the code that is trying to call it.

So, in order to allow proper code sharing of calling code (left side above) and called code (right side), you cannot apply any fix-ups to the calling code directly since that will "damage" how it works in the other processes (that wouldn't matter if it mapped to the same location in every process but that's a bit of a restriction, especially if something else had already been mapped there).

So the PLT is a smaller process-specific area at a reliably-calculated-at-runtime address that isn't shared between processes, so any given process is free to change it however it wants to, without adverse effects on other processes.


Let's follow the process through in a bit more detail. The diagram above doesn't show the address of the PLT/GOT since it can be found using a location relative to the current program counter. This is evidenced by your PC-relative lookup:

<printf@plt+0>: jmpq  *0x2004c2(%rip)  ; 0x600860 <_GOT_+24>

By using position independent code in the called library, along with the PLT/GOT, the first call to the function printf@plt (so in the PLT) is a multi-stage operation, in which the following actions take place:

  • It calls the GOT version (via a pointer) which initially points back to some set-up code in the PLT.
  • That set-up code loads the relevant shared library if not yet done, then modifies the GOT pointer so that subsequent calls go directly to the real printf (at the process-specific virtual address) rather than the PLT set-up code.
  • It then calls the loaded printf code at that address.

On subsequent calls, because the GOT pointer has been modified, the multi-stage approach is simplified:

  • It calls the GOT version (via pointer), which now points to the real printf.

A good article can be found here, detailing how glibc is loaded at run time.

Pademelon answered 29/3, 2011 at 7:40 Comment(10)
Why two disas printf give me different result?Chignon
The stub is before I type r,and the other is after breakChignon
It can be reproduce with any executable with gdb.Chignon
I tried to read that article many times,but hardly understand it....What's the plt short for??Chignon
It's the procedure linkage table. I'll update with some more info.Pademelon
@Pademelon , thanks!But one doubt,The offset, $0×10, that was pushed onto the stack tells the linker code the offset of the symbol in the relocation table,however there's no $0x10 offset in the GOT...how does it actually matches?Chignon
@Pademelon Is there any way to force the GOT entry of a specific function call to be fixed when the image is loaded, so that, on the first call, the GOT version points to the real function?Consanguinity
@Luis, I'm not sure there is. I think the setup code actually loads the shared library as part of its process (and ASLR means it will load at some arbitrary address) so the address won't actually be known until after that point.Pademelon
@Pademelon reading this 11 years later and it is still helpful. thanks!Effortless
@Pademelon u can force the linker ld to perform all relocations by exporting an environment variable LD_BIND_NOW -> this way you can ensure real-time performancePeasecod
E
7

Not sure, but probably what you have seen makes sense. The first time you run the disas command the printf is not yet called so it's not resolved. Once your program calls the printf method the first time the GOT is updated and now the printf is resolved and the GOT points to the real function. Thus, the next call to the disas command shows the real printf assembly.

Evangelize answered 4/9, 2012 at 13:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.