Перемещение во время загрузки и поддержка виртуальной памяти - это две разные концепции. Почти все процессоры и операционные системы в наши дни поддерживают виртуальную память. Единственный действительно важный момент для понимания виртуальной памяти: забыть физические адреса. Теперь это ответственность за аппаратное обеспечение и ОС, и, если вы не пишете систему подкачки, вы можете забыть о физических адресах. Все адреса, которые использует программа, являются виртуальными. Это огромное преимущество и значительно упрощает модель программирования. В 32-битных системах это просто означает, что каждый процесс получает свое собственное пространство памяти 4 ГБ, в диапазоне от 0x00000000
до 0xffffffff
.
.exe
представляет процесс. Линкер создает .exe
из .obj
файлов. Хотя оба являются двоичными файлами, .obj
файлы не являются исполняемыми, поскольку они не содержат адресов всех переменных и функций. Задача компоновщика состоит в том, чтобы предоставить эти адреса, которые он определяет, помещая эти .obj
файлы в конец и затем вычисляя точные адреса всех символов (функций и переменных). Таким образом, созданный .exe
содержит каждый адрес функций и переменных, «жестко запрограммированных» в нем. Но есть еще одна важная информация, необходимая для создания .exe
. Линкер должен иметь инсайдерские знания о том, где в памяти будет загружен .exe
. Это будет по адресу 0x00000000
, или по 0xffff0000
, или где-то еще? Например, в Windows все .exe
всегда загружаются по абсолютному начальному адресу 0x00400000
. Это называется базовым адресом. Когда компоновщик генерирует окончательные адреса символов (функций и переменных), он вычисляет их с этого адреса и далее.
Теперь .exe
редко нужно загружать по любому другому адресу. Но то же самое не верно для .dll
с. .ddl
s такие же, как .exe
s (оба отформатированы в формате файла переносимого исполняемого файла (PE), который описывает структуру памяти, например, куда идет текст, куда идут данные и как найти какой) , .dll
s также имеют предпочтительный адрес. Это просто означает, что компоновщик использует это значение, когда вычисляет адреса для символов внутри .dll
. Если по этому адресу загружен .dll
, то мы все настроены.
Но если .dll
не может быть загружен по этому адресу (скажем, это был 0x10000000
), потому что какой-то другой .dll
уже был загружен по этому адресу, тогда загрузчик найдет некоторое другое пространство в памяти и загрузит .dll
там. Однако глобальные адреса функций и символов в .dll
теперь неверны. Таким образом, загрузчик должен выполнить перемещение (также называемое «исправление»), в котором он корректирует адреса всех глобальных символов и функций, чтобы отразить их фактические адреса.
Чтобы выполнить эту настройку, загрузчик должен быть в состоянии найти все такие символы в .dll
. Файл PE имеет секцию .reloc
, которая содержит внутреннее смещение всех таких символов.
Конечно, есть и другие детали, например, относительно того, как можно использовать косвенное обращение, когда компилятор сгенерировал код, чтобы вместо прямых вызовов вызовы были косвенными и доступ к переменным осуществлялся через известные области памяти в заголовке. .exe
.
Наконец, суть заключается в следующем: вам нужно переместить (какого-то рода) для настройки адресов в вызове и перехода, а также инструкции по изменению доступа, когда код не загружается в позиции (в пределах адресного пространства 4 ГиБ) ожидалось загрузить. Когда ОС загружает .exe
, она должна выбрать подходящее место в этом адресном пространстве 4 ГБ, куда она скопирует код и фрагменты данных из этого .exe
на диск.