Pull to refresh

Почему 0x00400000 является базовым адресом по умолчанию для EXE

Reading time 3 min
Views 46K
Original author: The Old New Thing
Базовым адресом по умолчанию для DLL является 0x10000000, но для исполняемых файлов это 0x00400000. Почему именно такое особое значение для EXE? Что такого особенного в 4 мегабайтах?

Это имеет отношение к размеру адресного пространства, отображаемого одной таблицей страниц в архитектуре x86, и такую конструкцию выбрали в 1987 году.

Единственным техническим требованием для базового адреса EXE является кратность 64 КБ. Но некоторые варианты базового адреса лучше, чем другие.

Цель выбора базового адреса состоит в минимизации вероятности, что модули будут перемещены. Это означает, что следует предотвратить столкновение 1) с другими объектами, которые уже в адресном пространстве (что и вызовет перемещение); 2) а также с объектами, которые могут появиться в адресном пространстве позже (форсируя их перемещение). Для исполняемых файлов избегать конфликта с объектами, которые могут появиться позже, означает уход из района адресного пространства, который может быть заполнен библиотеками DLL. Поскольку сама операционная система помещает файлы DLL в старшие адреса и базовым адресом по умолчанию для несистемных DLL является 0x10000000, то базовый адрес для EXE должен быть где-то младше 0x10000000, и чем младше, тем больше места останется до того, как вы начнёте конфликтовать с библиотеками. Но насколько низко нужно заходить?

Пункт 1 означает, что нужно также избегать объектов, которые уже в памяти. В Windows NT не так уж и много было на младших адресах. Единственное, что там было, это страница PAGE_NOACCESS, которая занимала нулевой адрес, чтобы выловить попытки доступа к нулевому указателю. Поэтому в Windows NT можно размещать исполняемые файлы по базовому адресу 0x00010000, и многие приложения делали именно так.

Но в Windows 95 на низких адресах было загружено гораздо больше всего. Менеджер виртуальных машин Windows 95 постоянно отображал первые 64 КБ физической памяти в первые 64 КБ виртуальной памяти, чтобы избежать ошибок CPU. (Windows 95 приходилось обходить множество багов CPU и багов прошивок). Более того, весь первый мегабайт виртуального адресного пространства отображался в логическом адресном пространстве активной виртуальной машины. (Для педантов: в реальности, чуть больше мегабайта). Такой способ отображения был требованием режима virtual-8086 процессоров x86.

Windows 95, как и её предшественница Windows 3.1, запускала Windows в специальной виртуальной машине (известной как System VM), и для совместимости по-прежнему пропускала самые разные вещи через 16-битный код, просто чтобы убедиться, что утка правильно крякает. Поэтому, даже когда CPU обрабатывал Windows-приложение (а не приложение MS-DOS), отображение адресного пространства виртуальной машины сохранялось, так что не приходилось заново делать его (и одновременно ресурсоёмкую процедуру преобразования адресов буфера) каждый раз, когда нужно запустить режим совместимости с MS-DOS.

Итак, первый мегабайт адресного пространства уходит со сцены. Что насчёт остальных трёх мегабайтов?

Теперь мы возвращаемся к маленькому намёку в начале статьи.

Чтобы быстро переключать контекст, менеджер виртуальных машин Windows 3.1 «округлял» контекст каждой виртуальной машины до 4 МБ. Он поступал так, чтобы контекст можно было переключить обновлением одного 32-битного значения в таблице страниц. (Для педантов: нужно обрабатывать и страницы атрибутов, но это всего десяток или около того бит). Из-за округления мы теряем три мегабайта адресного пространства, но, поскольку в наличии у нас есть 4 гигабайта адресного пространства, потеря менее 0,1% казалась небольшой жертвой ради значительного улучшения производительности. (Тем более, что в то время ни одно приложение и близко не приближалось к этому лимиту. Вообще, у компьютера было всего 2 МБ физической памяти).

Способ отображения памяти перенесли в Windows 95, с некоторыми поправками для работы с отдельными адресными пространствами 32-битных Windows-приложений. Вследствие этого, самым младшим адресом, по которому можно загрузить исполняемый файл в Windows 95, был 4 МБ, то есть 0x00400000.

Мелочи для гиков. Чтобы запретить приложениям Win32 доступ к области памяти, которая используется для режима совместимости MS-DOS, простой селектор данных на самом деле был расширяемым вниз селектором, который останавливался на границе 4 МБ. (Похожим образом, нулевой указатель в 16-битном приложении Windows приводил к нарушению прав доступа, потому что нулевой селектор являлся недействительным).

Компоновщик выбирает базовым адресом по умолчанию для исполняемых файлов 0x0400000, так что EXE может загружаться без перемещения как на Windows NT, так и на Windows 95. Никто в реальности больше не заботится об оптимизации под Windows 95, так что сейчас, в принципе, разработчики компоновщика могли бы выбрать другой базовый адрес по умолчанию. Но нет особого стимула делать это, кроме эстетического удовольствия от гармонии на диаграмме, особенно с тех пор, как ASLR всё равно ставит под вопрос эту гармонию. И к тому же, если они изменят базовый адрес, то люди начнут спрашивать: «Почему это у некоторых исполняемых файлов базовый адрес 0x04000000, а у других 0x00010000?».

TL;DR: Для быстрого переключения контекста.
Tags:
Hubs:
+81
Comments 11
Comments Comments 11

Articles