Книги: [Классика] [Базы данных] [Internet/WWW] [Сети] [Программирование] [UNIX] [Windows] [Безопасность] [Графика] [Software Engineering] [ERP-системы] [Hardware]
Упаковка исполняемых файлов в среде Windows как она есть
(статья была опубликована в журнале "Программист")
В те далекие времена, когда объем жестких дисков (в просторечии - винчестеров) измерялся мегабайтами, - этих мегабайт никогда не хватало, и большинство файлов (особенно редко используемых) хранили в упакованном виде. Перед запуском файл распаковывали, а после завершения работы - упаковывали вновь, чтобы освободить место для распаковки других.
Когда же эти махинации всем окончательно надоели, программисты (вспомнив, что компьютер призван служить человеку, а не наоборот), додумались до автоматической распаковки исполняемых файлов "на лету". Идея заключается в дописывании к сжатому файлу крохотного распаковщика, которому передается управление при запуске файла, и который распаковывает исполняемый код не на диск, а непосредственно в оперативную память. Конечно, время загрузки ощутимо увеличивалось (особенно на машинах с медленными процессорами), но это с лихвой окупалось простотой запуска и экономией дискового пространства.
Вскоре появился целый "пантеон" упаковщиков (их тогда писали все кому не лень) - AINEXE, DIET, EXEPACK, LZEXE, PKLITE и масса других - всех не перечислись! И не удивительно: процессоры день ото дня становились все производительнее и производительнее - уже на "тройке" распаковка занимала столь незначительное время, что им было можно полностью пренебречь. К тому же приятным побочным эффектом оказалась защита от дизассемблирования.
Действительно, непосредственно дизассемблировать упакованный файл невозможно, - прежде его необходимо распаковать. Конечно, на каждый щит найдется свой меч - из под пера хакеров вышло немало замечательных универсальных распаковщиков (UNP, Intruder, UUP, а вершиной всему стал CPU386 со встроенным эмулятором реального режима 80386 процессора), но качество автоматической распаковки оставляло желать лучшего (порой распакованные файлы зависали при запуске или в процессе работы), а ручной трассировкой владели далеко не все.
Словом, при всех своих достоинствах, упаковка исполняемых файлов не имела никаких недостатков и не собиралась сдавать позиций даже с приходом емких (по тем временам!) одно - двух гигабайтных жестких дисков и лазерных накопителей на CD-ROM. Но с приходом Windows все изменилось
Сжатие файлов под Windows 9x\NT
Тысяча девятьсот девяносто пятый год мир медленно, но неотвратимо пересаживается на новую операционную систему - Windows 95. Пользователи осторожно осваивают мышь и графический интерфейс, а программисты тем временем лихорадочно переносят старое программное обеспечение на новую платформу. Объемы винчестеров к этому времени выросли настолько, что разработчики могли забыть слово "оптимизация", да они, судя по размеру современных приложений, его и забыли. Сто мегабайт - туда, триста сюда - эдак никаких гигабайт не хватит!
Вот тут-то и вспомнили о распаковке исполняемых файлов "на лету". На рынке появилось несколько программ - компрессоров, из которых наибольшую популярность завоевал ASPack, умеющий сжимать и разжимать не только "экзешники", но и динамические библиотеки. А в состав самой Windows 95 вошла динамическая библиотека "LZEXPAND.DLL", поддерживающая базовые операции упаковки-распаковки и "прозрачную" работу со сжатыми файлами. Пользователи и программисты не замедлили воспользоваться новыми средствами, но
в отличие от старушки MS-DOS, в Windows 9x\NT за автоматическую распаковку приходится платить больше, чем получать взамен. Ведь как в MS-DOS происходила загрузка исполняемых модулей? Файл целиком считывался с диска и копировался в оперативную память, причем наиболее узким местом была именно операция чтения с диска. Упаковка даже ускоряла загрузку, ибо физически считывался меньший объем данных, а их распаковка занимала пренебрежимо короткое время.
В Windows же загрузчик читает лишь заголовок и таблицу импорта файла, а затем проецирует его на адресное пространство процесса так, будто бы файл является частью виртуальной памяти, хранящейся на диске. (Вообще-то, все происходит намного сложнее, но не будем вдаваться в не относящиеся к делу подробности). Подкачка с диска происходит динамически - по мере обращения к соответствующим страницам памяти, причем загружаются только те их них, что действительно нужны.
Например, если в текстовом редакторе есть модуль работы с таблицами, он не будет загружен с диска до тех пор, пока пользователь не захочет создать (или отобразить) свою таблицу. Причем неважно - находится ли этот модуль в динамической библиотеке или в основном файле! (Вот и попробуйте после этого сказать, что Windows глупые люди писали!). Загрузка таких "монстров" как Microsoft Visual Studio и Word как бы "размазывается" во времени и к работе с приложением можно приступать практически сразу же после его запуска. А что произойдет, если файл упаковать? Правильно, - он будет должен считаться с диска целиком (!) и затем - опять-таки, целиком - распаковаться в оперативную память.
Стоп! Откуда у нас столько оперативной памяти? Ее явно не хватит и распакованные такой ценой страницы придется вновь скидывать на диск! Как говорится, за что боролись, на то и напоролись. Причем, если при проецировании неупакованного exe-файла оперативная память не выделяется, (ну, во всяком случае, до тех пор, пока в ней не возникнет необходимость), распаковщику без памяти никак не обойтись! А поскольку оперативная память никогда не бывает в избытке, она может быть выделена лишь за счет других приложений! Отметим также, что в силу конструктивных особенностей "железа" и архитектуры операционной системы, операция записи на диск заметно медленнее операции чтения.
Важно понять: Windows никогда не сбрасывает на диск не модифицированные страницы проецируемого файла. Зачем ей это? Ведь в любой момент их можно вновь считать из оригинального файла. Но ведь при распаковке модифицируются все страницы файла! Значит, система будет вынуждена "гонять" их между диском и памятью, что существенно снизит общую производительность всех приложений в целом.
Еще большие накладные расходы влечет за собой сжатие динамических библиотек. Для экономии памяти страницы, занятые динамической библиотекой, используются совместно всеми процессами, загрузившими эту DLL. Но как только один из процессов пытается что-то записать в память, занятую DLL, система автоматически создает копию модифицируемой страницы и предоставляет ее в "монопольное" распоряжение процесса-писателя. Поскольку, распаковка динамических библиотек происходит в контексте процесса, загрузившего эту DLL, система вынуждена многократно дублировать все страницы памяти, выделенные динамической библиотеке, фактически предоставляя каждому процессору свой собственный экземпляр DLL. Предположим, одна DLL размером в мегабайт, была загружена десятью процессами, - посчитайте: сколько памяти напрасно потеряется, если она сжата!
Таким образом, под Windows 9x\NT сжимать исполняемые файлы нецелесообразно, - вы платите гораздо больше, чем выручаете. Что же касается защиты от дизассемблирования Да, когда ASPack только появился, он отвадил от взлома очень многих неквалифицированных хакеров, но, увы, ненадолго! Сегодня только слепой не найдет руководства по ручному снятию ASPack'а. Существует и масса готового инструментария - от автоматических распаковщиков до плагинов к дизассемблеру IDA Pro, позволяющих ему дизассемблировать сжатые файлы. Поэтому, надеяться, что ASPack спасет вашу программу от взлома несколько наивно.
Измерения падения производительности от сжатия программ (DLL)
В заключение поговорим об измерении падения производительности от упаковки файлов. Казалось бы, что тут сложного - берем неупакованный файл, запускаем его, замеряв время загрузки, записываем результат на бумажке, упаковываем, запускаем еще раз, и Первый камень преткновения - что понимать под "временем загрузки"? Если проецирование, - так оно выполняется практически мгновенно, и им можно вообще пренебречь. Момент времени, начиная с которого с приложением можно полноценно работать? Так это от самого приложения зависит больше, чем от его упаковки. К тому же на время загрузки упакованных файлов очень сильно влияет количество свободной на момент запуска физической оперативной памяти (не путать с общим объемом памяти, установленной на машине). Если перед запуском упакованного файла завершить одно-два "монстроузных" приложения, то занятая ими память окажется свободной, и сможет беспрепятственно использоваться распаковщиком. Напротив, если свободной памяти нет, ее придется по крохам отрывать от остальных приложений
Даже если мы оценим изменение времени загрузки (что, кстати, сделать весьма проблематично - серия замеров на одной и той же машине, с одним и тем же набором приложений дает разброс результатов более чем на порядок!), как измерять падение производительности остальных приложений? Ведь, при нехватке памяти Windows в первую очередь избавляется от не модифицированных страниц, которые незачем сохранять на диске! В результате упаковка исполняемого файла может несколько повысить производительность самого этого файла, но значительного ухудшает положение неупакованных приложений, исполняющихся параллельно с ним!
Поэтому, никаких конкретных цифр здесь не приводится. Приблизительные оценки, выполненные "на глаз", показывают, что при наличии практически неограниченного количества оперативной памяти потери производительности составляют менее 10%, но при ее нехватке, скорость всех приложений падает от двух до десяти раз! (Для справки: в эксперименте участвовали исполняемые файлы Microsoft Word 2000, Visual Studio 6.0, Free Pascal 1.04, IDA Pro 4.17, Adobe Acrobat Reader 3.4, машина с процессором CLERION-300A, оснащенная 256 мегабайтами ОЗУ, для имитации нехватки памяти ее объем уменьшался до 64 мегабайт; использовались операционные системы - Windows 2000 и Windows 98).
Выводы:
- Исполняемые файлы под Windows лучше не паковать. В крайнем случае используйте для упаковки/распаковки функции операционной системы (LZInit, LZOpenFile, LZRead, LZSeek, LZClose, LZCopy) динамически распаковывая в специально выделенный буфер только те части файла, которые действительно нужны в данный момент для работы;
- Динамические библиотеки категорически не рекомендуется паковать вообще, ибо это ведет к чудовищному расходу и физической, и виртуальной памяти, и извращает саму концепцию DLL: один модуль - всем процессам.
- Кстати, о динамических библиотеках: не стремитесь кромсать свое приложение на множество DLL - страницы исполняемого файла не требуют физической памяти до тех пор, пока к ним не происходит обращений. Поэтому, смело помещайте весь код программы в один файл.
Аннотация
Статьи из книги
[Заказать книгу в магазине "Мистраль"]