What is the purpose of the procedure linkage table?
Categories:
Understanding the Procedure Linkage Table (PLT) in Linux Binaries

Explore the crucial role of the Procedure Linkage Table (PLT) and Global Offset Table (GOT) in dynamic linking for Linux executables, enabling shared libraries and efficient code reuse.
When a program on a Linux system uses shared libraries, it doesn't directly embed the code for every function it calls. Instead, it relies on dynamic linking, where the actual function addresses are resolved at runtime. This mechanism allows for efficient memory usage, easier updates to libraries, and reduced binary sizes. At the heart of this dynamic linking process are two critical data structures: the Procedure Linkage Table (PLT) and the Global Offset Table (GOT).
The Challenge of Dynamic Linking
Consider a program that calls a function like printf()
from the standard C library (libc.so
). At compile time, the exact memory address of printf()
within libc.so
is unknown because libc.so
might be loaded at a different base address each time the program runs. Hardcoding an address would break the program. The solution requires a flexible way to locate and jump to these external functions.
How PLT and GOT Work Together
The PLT and GOT work in tandem to resolve external function calls. The PLT is a section of executable code within your program, while the GOT is a section of data. When your program calls an external function for the first time, the PLT entry for that function acts as a trampoline, redirecting execution to the dynamic linker. The dynamic linker then finds the actual address of the function in the shared library, updates the corresponding GOT entry with this address, and transfers control to the function. Subsequent calls to the same function will directly jump to its resolved address via the updated GOT entry, bypassing the dynamic linker.
flowchart TD A[Program Calls External Function (e.g., printf)] --> B{PLT Entry for printf} B --> |First Call| C[Jump to Dynamic Linker Stub] C --> D[Dynamic Linker Resolves printf Address] D --> E[Update GOT Entry for printf] E --> F[Jump to Actual printf Function] F --> G[printf Executes] G --> H[Return to Program] B --> |Subsequent Calls| I[Jump to GOT Entry for printf] I --> F
Dynamic Linking Process via PLT and GOT
Anatomy of a PLT Entry
Each external function called by your program has a corresponding entry in the PLT. These entries are typically small code stubs. Let's look at a simplified x86 assembly representation of a PLT entry for a function foo
:
; PLT entry for 'foo'
foo@plt:
jmp qword ptr [rip + GOT_OFFSET_FOR_FOO] ; 1. Jump via GOT
push N ; 2. Push relocation index
jmp qword ptr [rip + PLT_RESOLVER_OFFSET] ; 3. Jump to dynamic linker resolver
Simplified x86-64 PLT entry structure
Here's a breakdown of the PLT entry's behavior:
- Initial Call (Unresolved): When
foo
is called for the first time, thejmp
instruction reads the address from the GOT entry. Initially, this GOT entry points back into the PLT itself, specifically to thepush N
instruction. - Push Relocation Index: The
push N
instruction pushes an index onto the stack. This index tells the dynamic linker which relocation entry (and thus which function) needs to be resolved. - Jump to Dynamic Linker: The second
jmp
instruction transfers control to the dynamic linker's resolver routine. This routine is responsible for finding the actual address offoo
in the shared library. - GOT Update and Execution: The dynamic linker updates the GOT entry for
foo
with the actual function address. It then jumps tofoo
. - Subsequent Calls (Resolved): On all subsequent calls to
foo
, the firstjmp
instruction in the PLT entry will directly jump to the actualfoo
function's address, as the GOT entry has been updated.
Security Implications: GOT Overwrites
While essential for dynamic linking, the GOT can also be a target for security exploits. If an attacker can overwrite an entry in the GOT, they can redirect a legitimate function call to arbitrary malicious code. This technique, known as a GOT overwrite, is a common method in exploit development. Modern systems employ various mitigations, such as Read-Only Relocations (RELRO), to protect the GOT from being written to after initialization.
#include <stdio.h>
void external_function() {
printf("Hello from external_function!\n");
}
int main() {
printf("Calling external_function...\n");
external_function();
printf("external_function returned.\n");
return 0;
}
A simple C program demonstrating an external function call that would use PLT/GOT
When compiling the above C code into an executable that links against a shared library containing external_function
, the compiler and linker will set up PLT and GOT entries to handle the dynamic resolution of external_function
at runtime.