[Перевод] Почему 0x00400000 является базовым адресом по умолчанию для EXE
Базовым адресом по умолчанию для DLL является 0×10000000, но для исполняемых файлов это 0×00400000. Почему именно такое особое значение для EXE? Что такого особенного в 4 мегабайтах? Это имеет отношение к размеру адресного пространства, отображаемого одной таблицей страниц в архитектуре x86, и такую конструкцию выбрали в 1987 году.
Единственным техническим требованием для базового адреса EXE является кратность 64 КБ. Но некоторые варианты базового адреса лучше, чем другие.
Цель выбора базового адреса состоит в минимизации вероятности, что модули будут перемещены. Это означает, что следует предотвратить столкновение 1) с другими объектами, которые уже в адресном пространстве (что и вызовет перемещение); 2), а также с объектами, которые могут появиться в адресном пространстве позже (форсируя их перемещение). Для исполняемых файлов избегать конфликта с объектами, которые могут появиться позже, означает уход из района адресного пространства, который может быть заполнен библиотеками DLL. Поскольку сама операционная система помещает файлы DLL в старшие адреса и базовым адресом по умолчанию для несистемных DLL является is 0×10000000, то базовый адрес для EXE должен быть где-то младше 0×10000000, и чем младше, тем больше места останется до того, как вы начнёте конфликтовать с библиотеками. Но насколько низко нужно заходить? Пункт 1 означает, что нужно также избегать объектов, которые уже в памяти. В Windows NT не так уж и много было на младших адресах. Единственное, что там было, это страница PAGE_NOACCESS, которая занимала нулевой адрес, чтобы выловить попытки доступа к нулевому указателю. Поэтому в Windows NT можно размещать исполняемые файлы по базовому адресу 0×00010000, и многие приложения делали именно так.
Но в 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 МБ, то есть 0×00400000.
Мелочи для гиков. Чтобы запретить приложениям Win32 доступ к области памяти, которая используется для режима совместимости MS-DOS, простой селектор данных на самом деле был расширяемым вниз селектором, который останавливался на границе 4 МБ. (Похожим образом, нулевой указатель в 16-битном приложении Windows приводил к нарушению прав доступа, потому что нулевой селектор являлся недействительным).
Компоновщик выбирает базовым адресом по умолчанию для исполняемых файлов 0×0400000, так что EXE может загружаться без перемещения как на Windows NT, так и на Windows 95. Никто в реальности больше не заботиться об оптимизации под Windows 95, так что сейчас, в принципе, разработчики компоновщика могли бы выбрать другой базовый адрес по умолчанию. Но нет особого стимула делать это, кроме эстетического удовольствия от гармонии на диаграмме, особенно с тех пор, как ASLR всё равно ставит под вопрос эту гармонию. И к тому же, если они изменят базовый адрес, то люди начнут спрашивать: «Почему это у некоторых исполняемых файлов базовый адрес 0×04000000, а у других 0×00010000?».
TL; DR: Для быстрого переключения контекста.