在 Linux 等 ELF 系统上,正常可执行文件(ELF 类型ET_EXEC
)段加载的地址在编译时是固定的。ET_DYN
诸如库之类的共享对象(ELF 类型)被构建为与位置无关,它们的段可在地址空间中的任何位置加载(可能对某些体系结构有一些限制)。可以构建可执行文件,使其实际上是ET_DYN
- 这些被称为“与位置无关的可执行文件”(PIE),但不是一种常用技术。
您所看到的是您的main()
函数位于已编译可执行文件的固定地址文本段中。还可以尝试打印库函数的地址,例如printf()
在通过以下方式找到它之后dlsym()
——如果您的系统确实支持并启用了地址空间布局随机化 (ASLR),那么您应该看到该函数的地址从程序的运行变为运行. (如果您只是通过将引用直接放在代码中来打印库函数的地址,那么您实际上可能得到的是函数的过程查找表(PLT)蹦床的地址,该地址在您的可执行文件中的固定地址处静态编译.)
您看到的变量从运行到运行更改地址,因为它是在堆栈上创建的自动变量,而不是在静态分配的内存中。根据操作系统和版本,即使没有 ASLR,堆栈基址的地址也可能会从运行转移到运行。如果您将变量声明移动到函数之外的全局变量,您会看到它的行为方式与您的main()
函数相同。
这是一个完整的例子——用类似的东西编译gcc -o example example.c -dl
:
#include <stdio.h>
#include <dlfcn.h>
int a = 0;
int main(int argc, char **argv)
{
int b = 0;
void *handle = dlopen(NULL, RTLD_LAZY);
printf("&main: %p; &a: %p\n", &main, &a);
printf("&printf: %p; &b: %p\n", dlsym(handle, "printf"), &b);
return 0;
}