Se está debatiendo una solicitud para que el título de este artículo se cambie a DLL hell . No mueva este artículo hasta que se cierre la discusión. |
La introducción de este artículo puede ser demasiado breve para resumir adecuadamente los puntos clave . ( Noviembre de 2024 ) |
This article needs additional citations for verification. (November 2024) |
El infierno de las DLL es un término genérico para las complicaciones que surgen cuando se trabaja con bibliotecas de vínculos dinámicos (DLL) utilizadas con sistemas operativos Microsoft Windows más antiguos , [1] en particular las ediciones de 16 bits heredadas , que se ejecutan todas en un único espacio de memoria. El infierno de las DLL puede aparecer de muchas formas diferentes, en las que los programas afectados pueden no ejecutarse correctamente, si es que lo hacen. Es la forma específica del ecosistema de Windows del concepto general de infierno de dependencias .
Las DLL son la implementación de Microsoft de las bibliotecas compartidas . Las bibliotecas compartidas permiten que el código común se agrupe en un contenedor, la DLL, que es utilizada por cualquier software de aplicación en el sistema sin cargar múltiples copias en la memoria. Un ejemplo simple podría ser el editor de texto GUI , que es ampliamente utilizado por muchos programas. Al colocar este código en una DLL, todas las aplicaciones en el sistema pueden usarlo sin usar más memoria. Esto contrasta con las bibliotecas estáticas , que son funcionalmente similares pero copian el código directamente en la aplicación. En este caso, cada aplicación crece según el tamaño de todas las bibliotecas que utiliza, y esto puede ser bastante grande para los programas modernos.
El problema surge cuando la versión de la DLL en el equipo es diferente a la versión que se utilizó cuando se creó el programa. Las DLL no tienen un mecanismo integrado de compatibilidad con versiones anteriores, e incluso cambios menores en la DLL pueden hacer que su estructura interna sea tan diferente de las versiones anteriores que intentar utilizarlas generalmente hará que la aplicación se bloquee. Las bibliotecas estáticas evitan este problema porque la versión que se utilizó para crear la aplicación está incluida dentro de ella, por lo que incluso si existe una versión más nueva en otra parte del sistema, esto no afecta a la aplicación.
Una de las razones principales de la incompatibilidad de versiones es la estructura del archivo DLL. El archivo contiene un directorio de los métodos individuales (procedimientos, rutinas, etc.) incluidos en la DLL y los tipos de datos que toman y devuelven. Incluso cambios menores en el código de la DLL pueden provocar que este directorio se reorganice, en cuyo caso una aplicación que llama a un método en particular creyendo que es el cuarto elemento del directorio puede terminar llamando a una rutina completamente diferente e incompatible, lo que normalmente provocaría el bloqueo de la aplicación.
Existen varios problemas que suelen surgir con las DLL, especialmente después de haber instalado y desinstalado numerosas aplicaciones en un sistema. Las dificultades incluyen conflictos entre versiones de DLL, dificultad para obtener las DLL necesarias y tener muchas copias de DLL innecesarias.
Las soluciones a estos problemas ya se conocían cuando Microsoft estaba escribiendo el sistema DLL. [ cita requerida ] Éstas se han incorporado al reemplazo de .NET , "Ensamblajes".
Una versión particular de una biblioteca puede ser compatible con algunos programas que la utilizan e incompatible con otros. Windows ha sido particularmente vulnerable a esto debido a su énfasis en la vinculación dinámica de bibliotecas C++ y objetos OLE ( Object Linking and Embedding ). Las clases C++ exportan muchos métodos y un solo cambio en la clase, como un nuevo método virtual, puede hacerla incompatible con programas que se crearon con una versión anterior. Object Linking and Embedding tiene reglas muy estrictas para evitar esto: se requiere que las interfaces sean estables y los administradores de memoria no se comparten. Sin embargo, esto no es suficiente porque la semántica de una clase puede cambiar. Una corrección de error para una aplicación puede resultar en la eliminación de una característica de otra. Antes de Windows 2000 , Windows era vulnerable a esto porque la tabla de clases COM se compartía entre todos los usuarios y procesos. Solo un objeto COM en una DLL/EXE podía declararse como que tenía un ID de clase COM global específico en un sistema. Si algún programa necesitaba crear una instancia de esa clase, obtenía la implementación registrada centralmente actual. Como resultado, una instalación de un programa que instala una nueva versión de un objeto común podría dañar inadvertidamente otros programas que se instalaron previamente.
Un problema común y problemático ocurre cuando un programa recién instalado sobrescribe una DLL del sistema en funcionamiento con una versión anterior e incompatible. Los primeros ejemplos de esto fueron las bibliotecas ctl3d.dll
y ctl3dv2.dll
para Windows 3.1 : bibliotecas creadas por Microsoft que los editores de terceros distribuían con su software, pero cada uno distribuía la versión con la que la desarrolló en lugar de la versión más reciente. [2] La sobreescritura de DLL ocurre porque:
En COM y otras partes de Windows, antes de la introducción de los ensamblajes paralelos sin registro, [5] el Registro se utilizaba para determinar qué DLL subyacente utilizar. Si se registraba una versión diferente de un módulo, se cargaba esta DLL en lugar de la esperada. Este escenario podría deberse a instalaciones conflictivas que registran versiones diferentes de las mismas bibliotecas, en cuyo caso prevalecería la última instalación.
Las versiones de 16 bits de Windows (y Windows en Windows ) cargan solo una instancia de cualquier DLL dada; todas las aplicaciones hacen referencia a la misma copia en memoria, hasta que ninguna aplicación la esté usando y se descargue de la memoria. (Para las versiones de 32 y 64 bits de Windows, el uso compartido entre procesos ocurre solo cuando diferentes ejecutables cargan un módulo exactamente desde el mismo directorio; el código, pero no la pila, se comparte entre procesos a través de un proceso llamado "mapeo de memoria"). Por lo tanto, incluso cuando la DLL deseada se encuentra en un directorio donde se puede esperar que se encuentre, como en el directorio del sistema o el directorio de la aplicación, ninguna de estas instancias se utilizará si otra aplicación se ha iniciado con una versión incompatible de un tercer directorio. Este problema puede manifestarse como un error de aplicación de 16 bits que ocurre solo cuando las aplicaciones se inician en un orden específico.
En conflicto directo con el problema de la pisoteo de DLL: si las actualizaciones de una DLL no afectan a todas las aplicaciones que la utilizan, entonces se vuelve mucho más difícil "dar servicio" a la DLL, es decir, eliminar los problemas que existen en las versiones actuales de la DLL. (Las correcciones de seguridad son un caso particularmente convincente y doloroso). En lugar de reparar sólo la última versión de la DLL, el implementador idealmente debe hacer sus correcciones y probarlas para comprobar su compatibilidad en cada versión publicada de la DLL.
La incompatibilidad de DLL ha sido causada por:
%PATH%
la variable de entorno, que varían con el tiempo y de un sistema a otro, para encontrar DLL dependientes (en lugar de cargarlas desde un directorio configurado explícitamente);El infierno de las DLL era un fenómeno muy común en las versiones anteriores a Windows NT de los sistemas operativos de Microsoft. La causa principal era que los sistemas operativos de 16 bits no restringían los procesos a su propio espacio de memoria, por lo que no les permitían cargar su propia versión de un módulo compartido con el que fueran compatibles. Se esperaba que los instaladores de aplicaciones fueran buenos ciudadanos y verificaran la información de la versión de las DLL antes de sobrescribir las DLL del sistema existente. Microsoft y otros proveedores de herramientas de terceros proporcionaban herramientas estándar para simplificar la implementación de aplicaciones (que siempre implica el envío de las DLL dependientes del sistema operativo). Microsoft incluso exigía a los proveedores de aplicaciones que utilizaran un instalador estándar y que certificaran que su programa de instalación funcionaba correctamente antes de que se les concediera el uso del logotipo de Microsoft. El enfoque del instalador de buenos ciudadanos no mitigó el problema, ya que el aumento de la popularidad de Internet proporcionó más oportunidades para obtener aplicaciones no conformes.
La ambigüedad con la que se pueden cargar DLL que no están completamente calificadas en el sistema operativo Windows ha sido explotada por malware en los últimos años, [ ¿cuándo? ] abriendo una nueva clase de vulnerabilidad que afecta a aplicaciones de muchos proveedores de software diferentes, así como al propio Windows. [6]
Se han solucionado o mitigado varias formas de problemas de DLL a lo largo de los años.
Una solución sencilla para el infierno de las DLL en una aplicación es vincular estáticamente todas las bibliotecas, es decir, incluir la versión de la biblioteca requerida en el programa, en lugar de elegir una biblioteca del sistema con un nombre específico. [7] Esto es común en aplicaciones C/C++, donde, en lugar de tener que preocuparse por qué versión MFC42.DLL
está instalada, la aplicación se compila para que se vincule estáticamente con las mismas bibliotecas. Esto elimina las DLL por completo y es posible en aplicaciones independientes que utilizan solo bibliotecas que ofrecen una opción estática, como lo hace Microsoft Foundation Class Library . Sin embargo, se sacrifica el propósito principal de las DLL (compartir la biblioteca en tiempo de ejecución entre programas para reducir la sobrecarga de memoria); duplicar el código de la biblioteca en varios programas crea una sobrecarga de software y complica la implementación de parches de seguridad o versiones más nuevas del software dependiente.
El problema de sobrescritura de DLL (al que Microsoft denomina DLL Stomping ) se redujo un poco con Windows File Protection (WFP), [8] que se introdujo en Windows 2000. [ 9] Esto evita que aplicaciones no autorizadas sobrescriban las DLL del sistema, a menos que utilicen las API específicas de Windows que lo permiten. Todavía puede existir el riesgo de que las actualizaciones de Microsoft sean incompatibles con las aplicaciones existentes, pero este riesgo se reduce normalmente en las versiones actuales de Windows mediante el uso de ensamblajes en paralelo .
Las aplicaciones de terceros no pueden manipular los archivos del sistema operativo a menos que incluyan actualizaciones legítimas de Windows en su instalación o deshabiliten el servicio de protección de archivos de Windows durante la instalación. En Windows Vista o posterior, también toman posesión de los archivos del sistema y se otorgan acceso a ellos mismos. La utilidad SFC podría revertir estos cambios en cualquier momento.
Las soluciones aquí consisten en tener diferentes copias de las mismas DLL para cada aplicación, tanto en disco como en memoria.
Una solución manual sencilla para los conflictos era colocar las distintas versiones de la DLL problemática en las carpetas de las aplicaciones, en lugar de en una carpeta común para todo el sistema. Esto funciona en general siempre que la aplicación sea de 32 o 64 bits y que la DLL no utilice memoria compartida. En el caso de las aplicaciones de 16 bits, las dos aplicaciones no se pueden ejecutar simultáneamente en una plataforma de 16 bits o en la misma máquina virtual de 16 bits bajo un sistema operativo de 32 bits. OLE impedía esto antes de Windows 98 SE/2000, porque las versiones anteriores de Windows tenían un único registro de objetos COM para todas las aplicaciones.
Windows 98 SE/2000 introdujo una solución llamada ensamblaje lado a lado [10], que carga copias separadas de DLL para cada aplicación que las requiere (y, por lo tanto, permite que las aplicaciones que requieren DLL en conflicto se ejecuten simultáneamente). Este enfoque elimina los conflictos al permitir que las aplicaciones carguen versiones únicas de un módulo en su espacio de direcciones, al tiempo que preserva el beneficio principal de compartir DLL entre aplicaciones (es decir, reducir el uso de memoria) mediante el uso de técnicas de mapeo de memoria para compartir código común entre diferentes procesos que aún usan el mismo módulo. Sin embargo, las DLL que usan datos compartidos entre múltiples procesos no pueden adoptar este enfoque. [11] Un efecto secundario negativo es que las instancias huérfanas de DLL pueden no actualizarse durante los procesos automatizados.
Dependiendo de la arquitectura de la aplicación y el entorno de ejecución, las aplicaciones portables pueden ser una forma efectiva de reducir algunos problemas de DLL, ya que cada programa incluye sus propias copias privadas de cualquier DLL que necesite. [9] El mecanismo se basa en que las aplicaciones no califiquen completamente las rutas a las DLL dependientes al cargarlas, y el sistema operativo busque el directorio ejecutable antes de cualquier ubicación compartida. [12] Sin embargo, esta técnica también puede ser explotada por malware, [13] y la mayor flexibilidad también puede venir a expensas de la seguridad si las DLL privadas no se mantienen actualizadas con parches de seguridad de la misma manera que las compartidas.
La virtualización de aplicaciones también puede permitir que las aplicaciones se ejecuten en una "burbuja", lo que evita instalar archivos DLL directamente en el sistema operativo.
Existen otras contramedidas para evitar el problema de las DLL, algunas de las cuales pueden tener que usarse simultáneamente; algunas otras características que ayudan a mitigar el problema son: