Войти
Уголок tool-программСтатьи

О старом добром rebase

Автор:

версия документа: 0.9

Введение
Слово о DLL
Как работать с утилитой
Как работает утилита
Ссылки
Код как он есть

Введение

В процессе написания продолжения предыдущей, не побоимся этого слова, статьи об экспорте возникла идея создания небольшой заметки о необходимости указания предпочтительного адреса загрузки исполняемых модулей. Несмотря на то, что в общеизвестном классическом труде Дж. Рихтера упоминались проблемы, возникающие с неудачным выбором базового адреса модуля - почему-то, как показывает анализ  запущенных на моём компьютере программ, разработчики решили забыть о противном свойстве загрузки динамических библиотек, видимо исходя из соображений "на глаз не тормозит вроде".

Данная статья проясняет, в чем собственно состоит проблема и предлагает нехитрую утилиту, которая немножко помогает в решении проблемы.

Заметка может быть полезна для общего развития широкому кругу программистов динамически подключаемых библиотек.

Слово о DLL


Уже в те далёкие времена, когда ещё были доступны широкой общественности процессоры отечественного производства, программисты начали лениться и писать неэффективные компиляторы, которые писали неэффективные программы, занимавшие всё больше и больше места в весьма дорогой оперативной памяти. Именно тогда появилась идея неиспользуемый в данный момент машинный код программы сбрасывать во внешнее хранилище данных и загружать только при вызове функций, код которых хранится в выгруженном участке программы (overlay-библиотеки).

Такой подход позволял более эффективно использовать память конкретной программе, однако не позволял совместно использовать какие-либо библиотеки несколькими одновременно загруженными программами. В результате каждая программа тянула за собой в память такой же кусок кода, что и остальные. CRT-библиотека самый яркий пример того, как программы нуждаются в совместном использовании кода.

С целью исправить ситуацию были придуманы динамически загружаемые библиотеки, которые могли совместно использоваться различными программами и при этом обладали рядом достоинств, но при  этом плодили и множество проблем.

Основная идея динамически загружаемых библиотек состоит в том, чтобы не помещать в исполняемый файл код вызываемых функций, а добавить раздел файла, в котором описываются необходимые для запуска программы библиотеки и оставлены, например, шлюзы для вызова функций из этих самых библиотек.

Впрочем, это известно читающим эту статью, и останавливаться на этом не стоит. Рассмотрим более подробно как загружается исполняемый модуль в память, в Win32.

У каждого исполняемого модуля существует предпочтительный базовый адрес (preferred base address), начиная с которого образ модуля (image) будет отображен в виртуальном адресном пространстве (причём вовсе не факт что файл будет отображён целиком и непрерывно, скорее лишь некоторые из разделов файла - секций).

При адресации внутри кода используются не относительные, а полные адреса (32 бит смещения) в силу того, что Win32 использует в рамках процесса плоскую модель памяти.

В результате при попытке поместить образ модуля не по предпочтительному адресу, все ссылки, используемые этим модулем при обращении к внутренним переменным будут неверны.

Чтобы реализовать возможность загружать образ по другому базовому адресу, для него сохраняются в секции .reloc так называемые fixup blocks для каждой страницы, в которых используется обращения к внутренним переменным. fixup блоки содержат смещение (более точно: Relevant Virtual Addresses, RVA) для страницы файла от базового адреса и список ячеек памяти, которые надо поправить в этой странице, если модуль был загружен не по предпочтительному базовому адресу.

Если системный загрузчик обнаруживает, что место необходимое для загрузки данного модуля уже занято, он пытается выполнить операцию загрузки по другому базовому адресу, что приводит к выполнению системой операций применения fixup-блоков.

Если секция .reloc отсутствует в файле, то он может быть загружен только по указанному базовому адресу и при возникновении коллизии - программа банально не запустится, что впрочем встречается довольно редко.

Применение fixup блоков в принципе не такая уж и ресурсоёмкая операция для одной библиотеки на современном аппаратном обеспечении (обычно менее чем в 2 раза увеличивается время загрузки библиотек), но когда десяток библиотек начинает заниматься данным непотребством плюс ещё и наша собственноручно сделанная, становится как-то не по себе от отсутствия культуры в массах.

Более серьёзной проблемой является проблема замусоривания файла подкачки. Обычно образ модуля является сам себе своп файлом, т.е. когда необходимо освободить страницу физической памяти, система просто ставит на соответствующую страницу адресного пространства процесса флаг отсутствия в памяти, при обращении к ней происходит страничное прерывание и она загружается снова из того же файла. Однако если страница изменена (изменена с помощью fixup блоков в нашем случае), то при освобождении менеджером памяти физической памяти она будет сброшена в файл подкачки. А сброс данных из оперативной памяти на винчестер никогда не был особо быстрым. К тому же каждый такой финт уменьшает доступное виртуальное пространство за счёт хранения изменённых страниц.

Если библиотека используется совместно несколькими процессами, то она должна быть загружена по одному базовому адресу в различных адресных пространствах, иначе опять же получим копирование различающихся страниц.

По правде сказать, даже замусоривание своп файла не является такой уж супер страшной проблемой, но потери производительности начинаются с мелочей.

Отчего же десяток библиотек использует одинаковые базовые адреса? Оттого что по умолчанию при связывании объектных файлов указан одинаковый базовый адрес для результирующего модуля. И если для своих библиотек мы можем указать при линковке предпочтительный адрес такой, чтобы не возникало лишних операций во время выполнения, то для уже готовых библиотек это можно сделать с помощью дополнительных утилит. Одной из них является rebase.exe, которая по большому счёту занимается тем, что выполняет WinAPI функцию  ReBaseImage(), но с дополнительными удобствами.

Обучать работе с этой программой я не буду. А лишь опишу простой способ получения списка всех модулей запущенного на выполнения процесса с помощью Tool Help библиотеки, являющейся частью WinAPI. Она помогает перечислять потоки, кучи, модули процесса, а также читать память другого процесса, при наличии соответствующих прав у выполняемого процесса.

Как работать с утилитой

Приводимая утилита запускается из командной строки с параметром-именем образа, модули которого необходимо обследовать на наличие загрузки не по предпочтительному адресу. Процесс при этом должен быть запущен.

На выходе получается .bat-файл, запускающий по порядку собственно rebase для смены базового адреса для каждого файла.

Преимущество перечисления модулей активного процесса в том, что в адресном пространстве процесса могут присутствовать библиотеки, загруженные "вручную" с помощью LoadLibrary(), и поэтому раздела импорта исполняемого файла не содержит ссылок на данные библиотеки.

Как работает утилита

Классы th_processes и th_modules - это обёртка над функциями Tool Help библиотеки. Первый занимается перечислением процессов, второй модулей процесса.

При запуске если в командной строке не задан ни один параметр, то перечисляются все выполняющиеся процессы и запрашивается для какого из них необходимо провести анализ модулей, загруженных не по предпочтительному базовому адресу.

Если же в командной строке указано имя выполняемого файла процесса, то анализ проводится для указанного процесса, если он, конечно же, найден в списке выполняющихся. По имени получается идентификатор процесса (pid).

Для указанного идентификатора  получаются модули загруженные в адресное пространство, их базовые адреса, а также проверяется какой предпочтительный базовый адрес содержится в заголовке файла.

При несоответствии того что есть с тем что должно быть, генерируется .bat файл.
 

Ссылки


1.  Джеффри Рихтер, "Программирование в Windows для профессионалов"
2.  Matt Pietrek, "An In-Depth Look into the Win32 Portable Executable File Format",
  http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspx
3.  John Levin, "Linkers and Loaders",
  http://www.iecc.com/linker/linker03.html
4.  "Portable Executables Format",
  http://www.x86.org/ftp/manuals/tools/pe.pdf
5.  Ruediger R. Asche, "Rebasing Win32 DLLs: The whole story",
  http://msdn.microsoft.com/library/techart/msdn_pagetest.htm

Страницы: 1 2 3 Следующая »

25 января 2006

Комментарии [1]