Введение

Компьютерный сленг
В области вычислительной техники, "Ад DLL" (DLL Hell) – это термин, описывающий проблемы, возникающие при работе с динамически подключаемыми библиотеками (DLL), используемыми в операционных системах Microsoft Windows, особенно в устаревших 16-битных версиях, которые работают в едином адресном пространстве памяти. "Ад DLL" может проявляться различными способами, приводя к тому, что приложения не запускаются или работают некорректно. "Ад DLL" является специфической для экосистемы Windows формой общей концепции "Ад зависимостей".

Проблемы

DLL - это реализация Microsoft общих библиотек. Общие библиотеки позволяют объединять общий код в оболочку, DLL, которая используется любым прикладным программным обеспечением в системе без загрузки нескольких копий в память. Простым примером может служить графический текстовый редактор, широко используемый многими программами. Разместив этот код в DLL, все приложения в системе могут использовать его, не увеличивая потребление памяти. Это отличается от статических библиотек, которые функционально похожи, но копируют код непосредственно в приложение. В этом случае каждое приложение увеличивается в размере на объем всех используемых библиотек, что может быть значительно для современных программ. Проблема возникает, когда версия DLL на компьютере отличается от версии, использованной при создании программы. DLL не имеют встроенного механизма обратной совместимости, и даже незначительные изменения в DLL могут настолько изменить его внутреннюю структуру по сравнению с предыдущими версиями, что попытка использования приведет к сбою приложения. Статические библиотеки избегают этой проблемы, поскольку версия, использованная для сборки приложения, включена в него, поэтому даже если на системе существует более новая версия, это не повлияет на приложение. Ключевой причиной несовместимости версий является структура файла DLL. Файл содержит каталог отдельных методов (процедур, подпрограмм и т.д.), содержащихся в DLL, а также типы данных, которые они принимают и возвращают. Даже незначительные изменения в коде DLL могут привести к перестановке элементов в этом каталоге, в результате чего приложение, вызывающее определенный метод, полагая, что он является четвертым в каталоге, может вызвать совершенно другую и несовместимую подпрограмму, что обычно приводит к сбою приложения. Существует несколько распространенных проблем, связанных с DLL, особенно после многочисленных установок и удалений приложений на системе. Эти трудности включают конфликты между версиями DLL, сложности с получением необходимых DLL и наличие множества ненужных копий DLL. Решения этих проблем были известны еще во время разработки системы DLL в Microsoft. Они были реализованы в замене .NET, "Assemblies".

Несовместимые версии

Определенная версия библиотеки может быть совместима с одними программами, использующими ее, и несовместима с другими. Windows оказалась особенно уязвимой к этому из-за акцента на динамической линковке библиотек C++ и объектов Object Linking and Embedding (OLE). Классы C++ экспортируют множество методов, и даже одно изменение в классе, например, добавление нового виртуального метода, может привести к несовместимости с программами, скомпилированными для более ранней версии. Object Linking and Embedding имеет строгие правила для предотвращения подобных ситуаций: интерфейсы должны быть стабильными, а менеджеры памяти – не разделяемыми. Однако этого недостаточно, поскольку может измениться семантика класса. Исправление ошибки в одном приложении может привести к удалению функциональности в другом. До Windows 2000 Windows была уязвима, поскольку таблица классов COM была общей для всех пользователей и процессов. В системе только один COM-объект в одной DLL/EXE мог быть объявлен с конкретным глобальным идентификатором класса COM. Любая программа, нуждающаяся в создании экземпляра этого класса, получала текущую централизованно зарегистрированную реализацию. В результате установка программы, устанавливающей новую версию общего объекта, могла непреднамеренно нарушить работу других ранее установленных программ.

ДЛЛ стуча

Частая и проблемная ситуация возникает, когда недавно установленная программа перезаписывает работающую системную DLL более ранней, несовместимой версией. Ранними примерами этого были библиотеки ctl3d.dll и ctl3dv2.dll для Windows 3.1: Microsoft создавала библиотеки, которые сторонние издатели распространяли вместе со своим программным обеспечением, но каждый распространял версию, с которой он разрабатывал, а не самую последнюю. "Потоптение DLL" происходит из-за того, что: Microsoft в прошлом распространяла DLL-файлы времени выполнения как общие системные компоненты (изначально C:\WINDOWS и C:\WINDOWS\SYSTEM) для эффективного совместного использования кода в операционной системе с ограниченным объемом оперативной памяти и дискового пространства. Соответственно, сторонние разработчики также распространяли их таким образом. Установщики приложений обычно выполняются в привилегированном контексте безопасности, который имеет доступ к установке DLL в системные каталоги и к редактированию системного реестра для регистрации новых DLL как COM-объектов. Плохо написанный или неправильно настроенный установщик, таким образом, может понизить версию системной библиотеки в устаревших версиях Windows, где Защита файлов Windows или Защита ресурсов Windows не откатывает это изменение. Начиная с Windows Vista, изменения в основные библиотеки операционной системы может вносить только учетная запись "доверенного установщика". Приложениям Windows разрешалось включать обновления операционной системы в свои собственные программы установки. То есть многие DLL-файлы Microsoft являются перераспределяемыми, что означает, что приложения могут включать их, если им требуются службы этих библиотек. До появления Windows Installer установщики Windows исторически были коммерческими продуктами; многие пытались написать свои собственные установщики, игнорируя или неправильно обрабатывая проблемы с версиями. Некоторые среды разработки не автоматически добавляли ресурс версии в свои скомпилированные библиотеки, поэтому многие разработчики упускали этот аспект из виду. Проверка дат файлов, перезапись существующих файлов или пропуск операции копирования, если DLL уже была установлена, были единственными доступными альтернативами правильному управлению версиями. Иногда сама операционная система удаляла или заменяла DLL на более старые или устаревшие версии. Например, Windows 2000 могла установить DLL-файлы черно-белого принтера поверх DLL-файлов, поддерживающих цвет, если черно-белый принтер был установлен после цветного.

Неправильная регистрация ОКМ

В COM и других компонентах Windows, до появления механизма параллельного использования сборки без реестра, реестр использовался для определения, какую базовую DLL следует использовать. Если была зарегистрирована другая версия модуля, загружалась бы именно она, а не ожидаемая. Эта ситуация могла возникать из-за конфликтующих установок, регистрирующих различные версии одних и тех же библиотек, при этом последняя установленная версия имела приоритет.

Общие модули памяти

16-битные версии Windows (и Windows на Windows) загружают только один экземпляр любой DLL; все приложения обращаются к одной и той же копии в памяти, пока она используется хотя бы одним приложением, после чего она выгружается из памяти. (В 32-битных и 64-битных версиях Windows совместное использование между процессами происходит только в том случае, если разные исполняемые файлы загружают модуль из одного и того же каталога; код, но не стек, совместно используется между процессами посредством механизма, называемого "отображением в память".) Таким образом, даже если нужная DLL находится в каталоге, где её можно ожидать найти, например, в системном каталоге или каталоге приложения, ни один из этих экземпляров не будет использован, если другое приложение уже запущено с несовместимой версией из другого каталога. Эта проблема может проявляться как ошибка 16-битного приложения, возникающая только при запуске приложений в определенной последовательности.

Отсутствие эксплуатационной пригодности

В прямом конфликте с проблемой "раздавливания" DLL: если обновления DLL не затрагивают все приложения, использующие его, то становится значительно сложнее "обслуживать" DLL – то есть устранять проблемы, существующие в текущих версиях DLL. (Исправления безопасности – особенно наглядный и болезненный пример.) Вместо исправления только последней версии DLL разработчику в идеале необходимо вносить исправления и тестировать их на совместимость со всеми выпущенными версиями DLL.

Использование вредоносными программами

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

Решение

Различные проявления проблем, связанных с DLL, были решены или смягчены с течением времени.

Статическая связь

Простым решением проблемы "DLL Hell" в приложении является статическая линковка всех библиотек, то есть включение в программу необходимой версии библиотеки, а не использование системной библиотеки с заданным именем. Эта функция была внедрена в Windows 2000, которая загружает отдельные копии DLL для каждого приложения, которому они требуются (и таким образом позволяет приложениям, нуждающимся в конфликтующих DLL, работать одновременно). Такой подход устраняет конфликты, позволяя приложениям загружать уникальные версии модуля в свое адресное пространство, при этом сохраняя основное преимущество совместного использования DLL между приложениями (то есть снижение потребления памяти) за счет использования техник отображения памяти для совместного использования общего кода между различными процессами, которые по-прежнему используют один и тот же модуль. Однако DLL, использующие общие данные между несколькими процессами, не могут использовать этот подход. Одним из негативных последствий является то, что "осиротевшие" экземпляры DLL могут не обновляться в ходе автоматизированных процессов.

Портативные приложения

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