вторник, 2 августа 2011 г.

Хак №12: создаём меньшие .exe файлы ($SetPEFlags)

Это перевод Hack#12: Create smaller .EXE files ($SetPEFlags). Автор: Hallvard Vassbotn.

Нет, этот пост - не про так называемые EXE-компрессоры: я не верю в их пользу. И это не чистый хак - мы не нарушаем никаких правил, это просто документирование слабо известной возможности Delphi 2006 (она не реализована в D7, и я ничего не знаю насчёт Delphi 2005, поскольку она давно у меня не стоит).

Вводная информация
Когда вы собираете DLL (или пакет, который является замаскированной DLL с Delphi-спецификой), линкёр (компоновщик) включает в неё то, что известно под именем "relocation table". Эта таблица содержит информацию об адресах, которые нуждаются в исправлении в том (вероятном) случае, когда DLL приходится загружаться по адресу, отличному от предполагаемого (т.н. базового адреса). Видите ли, все DLL по умолчанию компилируются с одним и тем же "идеальным" базовым адресом. Загрузчик ОС сначала попытается загрузить DLL именно по этому адресу, чтобы избежать накладных расходов по её перебазированию в памяти (исправление страниц памяти DLL приводит к их сохранению в файл подкачки и предотвращает совместное использование этих страниц несколькими процессами). Вот почему вам стоит вручную устанавливать опцию Image base на вкладке Linker опций проекта для ваших DLL и пакетов. Значение по умолчанию в Delphi для приложений, DLL и пакетов - $00400000. И поэтому DLL всегда по умолчанию будут перебазироваться - потому что в любом случае этот адрес уже будет занят (под .exe).

Следствием из этого является то, что .exe всегда загружается по фиксированному (виртуальному) адресу $00400000 и никогда не нуждается в перебазировании. Alas, приложению не нужна его relocation-таблица, так что мы можем удалить её, экономя немного на размере файла, без изменения поведения или производительности. Да это почти ланч на халяву!

До недавних пор пользователи Delphi вынуждены были использовать внешние утилиты для вырезания этой информации - вроде утилиты StripReloc от Jordan Russell. Она берёт имя .exe файла первым параметром и удаляет из него relocation-таблицу. Я обычно не заморачиваюсь этим вопросом в процессе разработки, но не против прогнать свои приложения через утилиту Jordan-а перед выкладыванием в дистрибутив. Она вырезает около 350 Кб из .exe в 7 Мб - приятное и бесплатное 5% уменьшение размера.

Насколько я знаю, большинство утилит Microsoft изначально производят .exe файлы без reloc-информации, а вот Delphi всегда добавляет её и для DLL и для EXE. До сегодняшнего дня. Delphi 2006 имеет недокументированную директиву компилятора, которая позволяет вам отключить создание relocation-таблицы в ваших проектах.

Чтобы протестировать эту возможность, я открыл проект ResXplor из папки Demos\DelphiWin32\VCLWin32\ResXplor (кстати, неплохое приложение). Сначала я скомпилировал его "как есть". Project | Information сказал мне, что размер .exe получился 614400 байт (и Windows Explorer с ним согласился). Затем я добавил в начало .dpr файла такую строку:
{$SetPEFlags 1}
(Мы посмотрим, что такое это загадочное значение 1, чуть позже).

Затем я перекомпилировал проект (нет надобности в полной пересборке, поскольку файлы модулей не изменены - изменялись только проект и настройки компоновщика). И размер .exe уменьшился до 577536 байт - это уменьшение на 36864 байт или 6%. Не плохо для одной строки, не так ли? ;)

Справка Delphi 2006 так говорит о директиве $SetPEFlags:
Тип: флаг
Синтаксис: {$SetPEFlags <integer expression>} {$SetPEOptFlags <integer expression>}
Область действия: локальная

Microsoft позволяет флагам заголовка PE (portable executable) указывать для приложения совместимость со службами ОС или запрашивать дополнительные услуги ОС. Эти директивы дают вам мощные возможности по адаптации приложение к высокоуровневым NT системам.

Эти директивы позволяют вам установить флаговые биты в заголовке PE файла, а именно - в поле Characteristics основного заголовка и поле DLLCharacteristics дополнительного заголовка, соответственно. Большинство флагов поля Characteristics, устанавливаемых $SetPEFlags, специфичны для объектных файлов и библиотек. Флаги DLLCharacteristics, устанавливаемые $SetPEOptFlags, описывают, когда вызывать точку входа в DLL.

<integer expression> в этих директивах может включать в себя идентификатор целочисленной константы Delphi - вроде констант IMAGE_FILE_xxxx, объявленных в Windows.pas. Множественные флаги следует соединять через OR.
Так что она (и связанная с ней директива SetOptPEFlags) являются способом уведомить ОС об определённых аспектах вашего приложения. К примеру, она может использоваться, чтобы сказать ОС, что ваше приложение умеет работать с памятью более 2 Гб. Давайте посмотрим на константы IMAGE_FILE, объявленные в модуле Windows:
const
  { Relocation info stripped from file. }
  IMAGE_FILE_RELOCS_STRIPPED               = $0001;
  { File is executable  (i.e. no unresolved externel references)}
  IMAGE_FILE_EXECUTABLE_IMAGE              = $0002;
  { Line nunbers stripped from file. }
  IMAGE_FILE_LINE_NUMS_STRIPPED            = $0004;
  { Local symbols stripped from file. }
  IMAGE_FILE_LOCAL_SYMS_STRIPPED           = $0008;
  { Agressively trim working set }
  IMAGE_FILE_AGGRESIVE_WS_TRIM             = $0010;
  { App can handle >2gb addresses }
  IMAGE_FILE_LARGE_ADDRESS_AWARE           = $0020;
  { Bytes of machine word are reversed. }
  IMAGE_FILE_BYTES_REVERSED_LO             = $0080;
  { 32 bit word machine. }
  IMAGE_FILE_32BIT_MACHINE                 = $0100;
  { Debugging info stripped from file in .DBG file }
  IMAGE_FILE_DEBUG_STRIPPED                = $0200;
  { If Image is on removable media, copy and run from the swap file}
  IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP       = $0400;
  { If Image is on Net, copy and run from the swap file. }
  IMAGE_FILE_NET_RUN_FROM_SWAP             = $0800;
  { System File. }
  IMAGE_FILE_SYSTEM                        = $1000;
  { File is a DLL. }
  IMAGE_FILE_DLL                           = $2000;
  { File should only be run on a UP machine }
  IMAGE_FILE_UP_SYSTEM_ONLY                = $4000;
  { Bytes of machine word are reversed. }
  IMAGE_FILE_BYTES_REVERSED_HI             = $8000;
Тут много всего, но для наших целей нам интересно только одно первое значение:
const
  { Relocation info stripped from file. }
  IMAGE_FILE_RELOCS_STRIPPED               = $0001;
Вот откуда взялось волшебное число 1. Если мы добавим модуль Windows в uses .dpr файла и напишем директиву SetPEFlags под этим uses, то мы можем переписать директиву в само-документирующемся виде:
{$SetPEFlags IMAGE_FILE_RELOCS_STRIPPED}
Это скажет ОС, что файл не содержит информации для перебазирования, так что он не может быть перебазирован в run-time. В дополнение к этому компоновщик Delphi теперь умеет распознавать эту ситуацию и использует эту директиву как инструкцию "не вставлять в исполняемый модуль relocation-таблицу"!

Примечание: компилятор позволяет использовать ту же директиву для удаления reloc-информации из пакетов и DLL, но это крайне не рекомендуется. Хотя это и создаст меньшую по размерам DLL, эта DLL не сможет загрузиться, если по её предпочитаемому базовому адресу уже что-то будет загружено.

Счёт: Delphi 7 против Delphi 2006: 0 - 1 ;)

6 комментариев:

  1. В Delphi XE работает.

    ОтветитьУдалить
  2. А у меня в Delphi XE ошибка вываливается
    DLL подключается статический

    https://picasaweb.google.com/lh/photo/qxXIAgxkHgtjaetleR3nhQ?feat=directlink

    ОтветитьУдалить
  3. В Delphi XE8 сработало.
    С 20 метров уменьшилось до 13,9 метров.
    DLL не подключал.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.

Примечание. Отправлять комментарии могут только участники этого блога.