为何直接链接与dlopen加载同一库时函数内存地址不同?
sqrt Has Different Addresses When Linked Directly vs. dlopen-ed Great question! This behavior boils down to how ELF dynamic linking works on Linux, specifically the PLT (Procedure Linkage Table) and GOT (Global Offset Table) mechanism used for direct dynamic linking, versus direct symbol lookup via dlopen/dlsym.
First, let's recap your test setup to make sure we're on the same page:
Your Test Code & Output
You compiled a program that both links libm.so.6 directly (with -lm) and loads it dynamically via dlopen:
#include <stdio.h> #include <stdlib.h> #include <dlfcn.h> #include <math.h> #include <inttypes.h> int main() { void *dl, *dl_sqrt; dl = dlopen("/lib/x86_64-linux-gnu/libm.so.6", RTLD_LAZY); if (!dl) { fprintf(stderr, "%s\n", dlerror()); exit(1); } dl_sqrt = dlsym(dl,"sqrt"); if (!dl_sqrt) { fprintf(stderr, "%s\n", dlerror()); exit(1); } printf("Address of sqrt %p\n", (void*) sqrt); printf("Address of (dl)sqrt %p\n", (void*) dl_sqrt); return 0; }
Compilation command:
gcc dl-test.c -lm -ldl -o dl-test
Running it gives:
Address of sqrt 0x4006d0 Address of (dl)sqrt 0x7fa7ce6f7250
Why the Addresses Differ
1. The Directly Linked sqrt Address: A PLT Stub
When you link libm directly with -lm, the sqrt symbol in your code doesn't point directly to the actual sqrt function in libm.so.6. Instead, it points to a small stub function in your program's PLT (Procedure Linkage Table).
The PLT is a section in your executable that exists to handle lazy dynamic binding (the RTLD_LAZY behavior you're using). Here's how it works:
- On the first call to
sqrt(), the PLT stub checks the GOT (Global Offset Table) for the real address ofsqrt. - Initially, the GOT entry points back to the PLT stub, which triggers the dynamic linker (
ld-linux-x86-64.so.2) to look upsqrtinlibm.so.6, resolve its real address, and write that address to the GOT entry. - Subsequent calls to
sqrt()jump directly to the real address stored in the GOT, skipping the linker step.
The address 0x4006d0 you see is the address of this PLT stub, which lives in your executable's own memory space (low address range, typical for program code segments).
2. The dlopen-ed sqrt Address: The Real Function Entry
When you use dlopen to load libm.so.6 and dlsym to fetch sqrt, you're directly querying the dynamic library's symbol table for the actual address of the sqrt function code. This address (0x7fa7ce6f7250) lives in the memory region where libm.so.6 is loaded (high address range, typical for shared libraries).
There's no PLT/GOT indirection here—you're getting the raw entry point of the function in the shared library.
Is There an Indirect Mechanism?
Yes! The PLT/GOT system is the indirect mechanism at play for the directly linked sqrt. It's a core part of ELF dynamic linking that enables:
- Lazy binding: Symbols are only resolved when they're first called, speeding up program startup.
- Position-independent code (PIC): Shared libraries can be loaded at arbitrary memory addresses without needing to modify their code (the GOT holds the actual resolved addresses, so the library's code doesn't need hardcoded pointers).
Quick Verification
If you want to confirm the PLT stub is indeed a middleman, you can use objdump to look at your executable's PLT section:
objdump -R dl-test | grep sqrt
You'll see an entry pointing to the GOT, confirming that the sqrt symbol in your code is linked to the PLT/GOT system rather than the real function.
内容的提问来源于stack exchange,提问作者Aditi

