В системах ELF, таких как Linux, адреса, по которым загружаются сегменты обычных исполняемых файлов (тип ELF ET_EXEC
), фиксируются во время компиляции.Общие объекты (тип ELF ET_DYN
), такие как библиотеки, построены так, чтобы быть независимыми от позиции, а их сегменты загружаются в любом месте адресного пространства (возможно, с некоторыми ограничениями для некоторых архитектур).Можно построить исполняемые файлы так, чтобы они на самом деле были 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;
}