Перемещение — это процесс назначения адресов загрузки для позиционно-зависимого кода и данных программы и корректировка кода и данных для отражения назначенных адресов. [1] [2] До появления многопроцессорных систем и до сих пор во многих встроенных системах адреса для объектов были абсолютными, начиная с известного местоположения, часто с нуля. Поскольку многопроцессорные системы динамически связываются и переключаются между программами, возникла необходимость в возможности перемещать объекты с использованием позиционно-независимого кода . Компоновщик обычно выполняет перемещение совместно с разрешением символов , процессом поиска файлов и библиотек для замены символических ссылок или имен библиотек фактическими используемыми адресами в памяти перед запуском программы.
Перемещение обычно выполняется компоновщиком во время компоновки , но оно также может быть выполнено во время загрузки перемещающим загрузчиком или во время выполнения самой запущенной программой . Некоторые архитектуры полностью избегают перемещения, откладывая назначение адреса на время выполнения; например, в стековых машинах с нулевой адресной арифметикой или в некоторых сегментированных архитектурах, где каждый блок компиляции загружается в отдельный сегмент.
Объектные файлы сегментируются в различные типы сегментов памяти или секций. Примеры типов сегментов включают сегмент кода (.text) , инициализированный сегмент данных (.data) , неинициализированный сегмент данных (.bss) или другие, установленные программистом, такие как общие сегменты или именованные статические сегменты.
Таблица перемещений представляет собой список указателей, созданных транслятором (компилятором или ассемблером ) и сохраненных в объектном или исполняемом файле. Каждая запись в таблице, или «исправление», представляет собой указатель на абсолютный адрес в объектном коде, который должен быть изменен, когда загрузчик перемещает программу, чтобы она ссылалась на правильное местоположение. Исправления предназначены для поддержки перемещения программы как целостной единицы. В некоторых случаях каждое исправление в таблице само по себе относится к базовому адресу, равному нулю, поэтому сами исправления должны быть изменены по мере перемещения загрузчика по таблице. [2]
В некоторых архитектурах исправление, которое пересекает определенные границы (например, границу сегмента) или не выровнено по границе слова, является недопустимым и помечается компоновщиком как ошибка. [3]
Дальние указатели ( 32-битные указатели с сегментом :offset, используемые для адресации 20-битного пространства памяти размером 640 КБ , доступного программам DOS ), которые указывают на код или данные внутри исполняемого файла DOS ( EXE ), не имеют абсолютных сегментов, поскольку фактический адрес кода/данных зависит от того, где в памяти загружена программа, а это неизвестно, пока программа не загружена.
Вместо этого сегменты являются относительными значениями в файле DOS EXE. Эти сегменты необходимо исправить, когда исполняемый файл загружен в память. Загрузчик EXE использует таблицу перемещений для поиска сегментов, которые необходимо скорректировать.
В 32-разрядных операционных системах Windows не обязательно предоставлять таблицы перемещения для EXE-файлов, поскольку они являются первым образом, загружаемым в виртуальное адресное пространство, и, таким образом, будут загружены по своему предпочтительному базовому адресу.
Как для DLL , так и для EXE-файлов, которые используют рандомизацию адресного пространства (ASLR), технологию предотвращения эксплойтов , представленную в Windows Vista , таблицы перемещения снова становятся обязательными из-за возможности динамического перемещения двоичного файла перед выполнением, даже если они по-прежнему первыми загружаются в виртуальное адресное пространство.
При запуске собственных 64-разрядных двоичных файлов в Windows Vista и более поздних версиях ASLR является обязательным [ требуется ссылка ] , и поэтому разделы перемещения не могут быть пропущены компилятором.
Исполняемый формат Executable and Linkable Format (ELF) и формат разделяемой библиотеки, используемые большинством Unix-подобных систем, позволяют определять несколько типов перемещения. [4]
Компоновщик считывает информацию о сегментах и таблицы перемещения в объектных файлах и выполняет перемещение следующим образом:
Следующий пример использует архитектуру MIX Дональда Кнута и язык ассемблера MIXAL. Принципы одинаковы для любой архитектуры, хотя детали будут меняться.
[…] Абсолютный код и абсолютный объектный модуль — это код, который был обработан LOC86 для выполнения только в определенном месте памяти. Загрузчик загружает абсолютный объектный модуль только в определенное место, которое модуль должен занимать. Позиционно-независимый код (обычно называемый PIC) отличается от абсолютного кода тем, что PIC может быть загружен в любое место памяти. Преимущество PIC над абсолютным кодом заключается в том, что PIC не требует резервирования определенного блока памяти. Когда загрузчик загружает PIC, он получает сегменты памяти iRMX 86 из пула задания вызывающей задачи и загружает PIC в сегменты. Ограничение, касающееся PIC, заключается в том, что, как и в модели сегментации PL/M-86 COMPACT […], он может иметь только один сегмент кода и один сегмент данных, вместо того, чтобы позволять базовым адресам этих сегментов, а следовательно, и самим сегментам, динамически изменяться. Это означает, что программы PIC обязательно имеют длину менее 64 Кбайт. Код PIC может быть создан с помощью элемента управления BIND LINK86. Локализуемый во время загрузки код (обычно называемый кодом LTL) является третьей формой объектного кода. Код LTL похож на PIC тем, что код LTL может быть загружен в любое место памяти. Однако при загрузке кода LTL загрузчик изменяет базовую часть указателей, так что указатели не зависят от начального содержимого регистров в микропроцессоре. Из-за этой корректировки (корректировки базовых адресов) код LTL может использоваться задачами, имеющими более одного сегмента кода или более одного сегмента данных. Это означает, что программы LTL могут иметь длину более 64 Кбайт. FORTRAN 86 и Pascal 86 автоматически создают код LTL, даже для коротких программ. Код LTL может быть создан с помощью элемента управления BIND LINK86. […]
[…] Laws: […] "динамическое перемещение" ОС. Можете ли вы рассказать нам, что это такое и почему это было важно? […] Юбэнкс : […] то, что сделал Гэри […], было […] ошеломляющим. […] Я помню тот день в школе, когда он вбежал в лабораторию и сказал: «Я придумал, как переместить». Он воспользовался тем фактом, что единственным байтом всегда будет байт старшего порядка . И поэтому он создал битовую карту . […] неважно, сколько памяти у компьютера, операционную систему всегда можно переместить в старшую память. Поэтому вы могли бы коммерциализировать это […] на машинах с разным объемом памяти. […] вы не могли бы продавать 64K CP/M и 47K CP/M. Было бы просто смешно иметь жесткую компиляцию в адресах. Так что Гэри понял это однажды ночью, вероятно, среди ночи, думая о чем-то кодирующем, и это действительно сделало CP/M возможным для коммерциализации. Я действительно думаю, что без этого перемещения это была бы очень сложная проблема. Чтобы заставить людей купить его, это покажется им сложным, и если вы добавите больше памяти, вам придется пойти и получить другую операционную систему. […] Intel […] перевернула байты , верно, для адресов памяти. Но они всегда были в одном и том же месте, поэтому вы могли переместить его на границу 256 байт , если быть точным. Поэтому вы всегда могли переместить его с помощью всего лишь битовой карты того, где эти […] Законы: Безусловно, самое красноречивое объяснение, которое я когда-либо получал о динамическом перемещении […][9][10] (33 страницы)
[…] Вы когда-нибудь задумывались, как работает MOVCPM ? Поскольку BDOS и CCP находятся в верхней памяти, выше пользовательского приложения, адреса приходится менять каждый раз при изменении размера системной памяти. Теперь, когда требуется перемещение адресов в коде 8080 , поскольку относительная адресация не является частью оборудования. Как это сделать без реализации полнофункционального перемещающего ассемблера и загрузчика? На самом деле это довольно умно, и MP/M даже использует эту схему для создания своих перемещаемых постраничных файлов. Вы просто дважды собираете исходную программу со вторым началом сборки на 100H (256 байт) выше первого. Затем два двоичных образа сравниваются побайтно, и строится карта , в которой пары байтов отличаются по значению ровно на 100H. Результатом является список мест, в которых необходимо скорректировать значение перемещения, если необходимо переместить местоположение программы в памяти. MP/M называет такой тип файла PRL (page relocatable), но я не знаю, придумывал ли CP/M 2.2 когда-либо название для него. […]
[…] MOVCPM использует ранний тип формата PRL. По сути, CP/M собирается дважды; во второй раз — смещение 100H байт. Два двоичных файла сравниваются, и создается битовая карта . Установленный бит подразумевает, что старший байт адреса должен быть скорректирован. Младшие байты адреса не затрагиваются; отсюда «Страничный перемещаемый файл». Каждый байт в битовой карте соответствует 8 байтам в двоичных данных. […] Таким образом, все, что должно быть перемещено в MOVCPM, является частью изображения и его битовой карты перемещения. […]
[…] Я ссылался на файлы PRL и на то, как они изначально получили свое начало с MOVCPM , но стали неотъемлемой частью MP/M и CP/M 3.0 . Но файлы PRL используют битовую карту , в которой каждый бит соответствует ячейке памяти; один бит указывает, что смещение перемещения страницы должно быть добавлено к соответствующей ячейке памяти. Если у вас очень мало абсолютных ссылок на память (в отличие от относительных), вы можете использовать список указателей (2 байта на ссылку) вместо битовой карты. Это маловероятно в коде 8080 , в котором нет относительных переходов, но может быть рассмотрено для кода Z80 . Хитрость, чтобы быстро это выяснить, состоит в том, чтобы дважды собрать вашу программу; во второй раз со смещением 100H, а затем сравнить два двоичных файла. Преимущество перемещения во время выполнения в том, что вам не нужно нести штраф за код, который пытается обойти проблему перемещения — никаких «трюков»; просто пишите прямой код. […]
[…] Файл PRL — это перемещаемый двоичный файл, используемый MP/M и CP/M Plus для различных модулей, отличных от файлов .COM . Формат файла также используется для файлов FID на Amstrad PCW . Существует несколько форматов файлов, которые используют версии PRL: SPR (системный PRL), RSP (резидентный системный процесс). LINK-80 также может создавать файлы OVL ( оверлей ), которые имеют заголовок PRL, но не являются перемещаемыми. Драйверы GSX имеют формат PRL; как и резидентные системные расширения (.RSX). […][11]
[…] Формат REL генерируется Microsoft M80 и Digital Research RMAC. […]
[…] Из собранных файлов Microsoft .REL компоновщик должен сгенерировать исполняемый файл формата .PRL для MP/M . Формат .PRL по сути является файлом .COM с некоторой дополнительной информацией, позволяющей перемещать программу и ее данные на любую страницу. Как выглядит файл .PRL? Первые байты — это размер программы, за которым следует исходный адрес программы по адресу 0x0100. После программы добавляется побитовая маска, позволяющая системе MP/M знать, какие байты в программе необходимо изменить при перемещении программы. Как компоновщик делает это, не разбирая все приложение? Заранее программа скомпонована для двух разных источников 0x0100 и 0x0200 из объектов .REL. Трюк компоновщика заключается в простом распознавании того, какие байты в двух версиях исполняемого файла отличаются. Затем эти байты записываются в битовую маску, хранящуюся после исполняемого файла, и окончательная программа .PRL предназначена для запуска с 0x0100 плюс ее смещение страницы. Тот же трюк проделывается для исполняемых файлов .RSP и .SPR, за исключением того, что оба эти формата отказываются от смещения и запускаются с 0x0000 плюс их смещение страницы. […]