为何共享库采用GOT(全局偏移表)实现?无法直接修改.text段mov指令的原因
Great question—this is one of those core dynamic linking concepts that feels counterintuitive at first, but makes perfect sense once you dig into the constraints of modern OSes and shared library design. Let’s break down the key reasons:
1. .text is Read-Only & Shared Across Processes
Modern operating systems map the .text segment of a shared library as read-only and memory-shared across all processes that use the library. This is a huge memory-saver—instead of every process having its own copy of the library’s code, they all share the same physical pages.
If the dynamic linker tried to modify the address in a mov instruction directly in .text, that would trigger a copy-on-write (CoW) event: the OS would create a private copy of that .text page for the process. Do this for enough references, and you lose all the memory efficiency that shared libraries were designed for.
The Global Offset Table (GOT) lives in the .data segment (which is writable and private per process), so modifying GOT entries doesn’t affect the shared .text pages at all.
2. Position-Independent Code (PIC) Requirements
Shared libraries need to be position-independent—meaning they can be loaded at any arbitrary memory address (thanks to ASLR, Address Space Layout Randomization, a critical security feature).
If .text had hardcoded absolute addresses (like mov rax, 0x12345678), the library would break if loaded at a different base address. With GOT, the .text instructions only reference the GOT using relative offsets (e.g., mov rax, [rip + 0x123] on x86-64). These offsets stay the same no matter where the library is loaded, so .text never needs modification.
3. Efficient Symbol Relocation
Imagine a shared library symbol (like printf) is referenced 50 times across .text. If we had to modify each mov instruction individually, the dynamic linker would have to perform 50 separate write operations during startup.
With GOT, we only need to update one GOT entry for that symbol. Every reference in .text points to that single GOT entry, cutting down on relocation work significantly and speeding up program startup.
4. Lazy Binding (Delayed Symbol Resolution)
Most programs don’t use every symbol in a shared library right away. Lazy binding lets the dynamic linker resolve symbols only when they’re first called, instead of during startup—this reduces initial load time.
GOT works hand-in-hand with the Procedure Linkage Table (PLT) to make this possible:
- The first time you call a function like
printf, you jump to the PLT entry. - The PLT triggers the dynamic linker to look up
printf’s address and write it into the corresponding GOT entry. - All subsequent calls skip the PLT and read the resolved address directly from the GOT.
This kind of runtime dynamic update would be impossible if we were modifying .text directly—you can’t easily patch instructions on the fly without breaking the read-only constraint and shared memory model.
A Quick Note on Non-PIC Libraries
Older systems allowed non-PIC shared libraries, where the dynamic linker did modify .text directly. But this approach has major downsides:
- No memory sharing of
.textpages (each process gets its own copy). - Incompatibility with ASLR, which is standard now for security.
- Slower startup due to more relocation work.
So GOT is the modern, efficient, and secure solution to all these problems.
内容的提问来源于stack exchange,提问作者Jason Yu

