En informática , el código independiente de la posición [1] ( PIC [1] ) o el ejecutable independiente de la posición ( PIE ) [2] es un cuerpo de código de máquina que, al estar ubicado en algún lugar de la memoria primaria , se ejecuta correctamente independientemente de su dirección absoluta . PIC se usa comúnmente para bibliotecas compartidas , de modo que el mismo código de biblioteca se puede cargar en una ubicación en el espacio de direcciones de cada programa donde no se superpone con otra memoria en uso (por ejemplo, otras bibliotecas compartidas). PIC también se utilizó en sistemas informáticos más antiguos que carecían de MMU , [3]para que el sistema operativo pudiera mantener las aplicaciones alejadas entre sí incluso dentro del espacio de direcciones único de un sistema sin MMU.
El código independiente de la posición se puede ejecutar en cualquier dirección de memoria sin modificación. Esto difiere del código absoluto , [1] que debe cargarse en una ubicación específica para funcionar correctamente, [1] y del código localizable en tiempo de carga (LTL), [1] en el que un enlazador o cargador de programas modifica un programa antes de su ejecución, por lo que solo se puede ejecutar desde una ubicación de memoria en particular. [1] La generación de código independiente de la posición es a menudo el comportamiento predeterminado de los compiladores , pero pueden imponer restricciones al uso de algunas características del lenguaje, como no permitir el uso de direcciones absolutas (el código independiente de la posición tiene que utilizar direccionamiento relativo ). Las instrucciones que se refieren directamente a direcciones de memoria específicas a veces se ejecutan más rápido, y reemplazarlas con instrucciones equivalentes de direccionamiento relativo puede resultar en una ejecución ligeramente más lenta, aunque los procesadores modernos hacen que la diferencia sea prácticamente insignificante. [4]
Historia
En las primeras computadoras como IBM 701 [5] (29 de abril de 1952) o UNIVAC I (31 de marzo de 1951), el código dependía de la posición: cada programa se construía para cargar y ejecutar desde una dirección particular. Esas primeras computadoras no tenían un sistema operativo y no eran capaces de realizar múltiples tareas. Los programas se cargaron en el almacenamiento principal (o incluso se almacenaron en un tambor magnético para su ejecución directamente desde allí) y se ejecutaron uno a la vez. En tal contexto operativo, no era necesario un código independiente de la posición.
El IBM System / 360 (7 de abril de 1964) fue diseñado con direccionamiento truncado similar al de UNIVAC III , [6] teniendo en cuenta la independencia de la posición del código. En el direccionamiento truncado, las direcciones de memoria se calculan a partir de un registro base y un desplazamiento. Al comienzo de un programa, el programador debe establecer la direccionabilidad cargando un registro base; normalmente, el programador también informa al ensamblador con una pseudo-operación USANDO . El programador puede cargar el registro base desde un registro que se sabe que contiene la dirección del punto de entrada, normalmente R15, o puede usar la instrucción BALR (rama y enlace, formulario de registro) (con un valor R2 de 0) para almacenar la siguiente dirección secuencial de la instrucción. en el registro base, que luego se codificó explícita o implícitamente en cada instrucción que se refería a una ubicación de almacenamiento dentro del programa. Se pueden usar múltiples registros de base, para código o para datos. Dichas instrucciones requieren menos memoria porque no tienen que contener una dirección completa de 24, 31, 32 o 64 bits (4 u 8 bytes), sino un número de registro base (codificado en 4 bits) y un desplazamiento de dirección de 12 bits. (codificado en 12 bits), requiriendo solo dos bytes.
Esta técnica de programación es estándar en los sistemas de tipo IBM S / 360. Ha estado en uso hasta IBM System / z de hoy. Cuando se codifica en lenguaje ensamblador, el programador tiene que establecer la direccionabilidad del programa como se describió anteriormente y también usar otros registros base para el almacenamiento asignado dinámicamente. Los compiladores se encargan automáticamente de este tipo de direccionamiento.
El primer sistema operativo de IBM, DOS / 360 (1966), no utilizaba almacenamiento virtual (ya que los primeros modelos de System S / 360 no lo admitían), pero tenía la capacidad de colocar programas en una ubicación de almacenamiento arbitraria (o elegida automáticamente). durante la carga a través del nombre PHASE, declaración * JCL (Lenguaje de control de trabajos).
Por lo tanto, en los sistemas S / 360 sin almacenamiento virtual, se podía cargar un programa en cualquier ubicación de almacenamiento, pero esto requería un área de memoria contigua lo suficientemente grande para contener ese programa. A veces, la fragmentación de la memoria se produce al cargar y descargar módulos de diferentes tamaños. El almacenamiento virtual, por diseño, no tiene esa limitación.
Si bien DOS / 360 y OS / 360 no admitían PIC, las rutinas SVC transitorias en OS / 360 no podían contener constantes de direcciones reubicables y podían ejecutarse en cualquiera de las áreas transitorias sin reubicación.
El almacenamiento virtual se introdujo por primera vez en IBM System / 360 modelo 67 en (1965) para admitir el primer sistema operativo multitarea operativo y de tiempo compartido de IBM, TSS / 360. Las versiones posteriores de DOS / 360 (DOS / VS, etc.) y los sistemas operativos IBM posteriores utilizaron almacenamiento virtual. El direccionamiento truncado permaneció como parte de la arquitectura base y sigue siendo ventajoso cuando se deben cargar varios módulos en el mismo espacio de direcciones virtuales.
Otros primeros sistemas segmentados como Burroughs MCP en Burroughs B5000 (1961) y Multics (1964), sistemas de paginación como IBM TSS / 360 (1967) [a] o sistemas de base y límites [b] como GECOS en GE 625 y EXEC en UNIVAC 1107 , el código también era inherentemente independiente de la posición, ya que las direcciones en un programa eran relativas al segmento actual en lugar de absolutas.
La invención de la traducción dinámica de direcciones (la función proporcionada por una MMU ) redujo originalmente la necesidad de un código independiente de la posición porque cada proceso podría tener su propio espacio de direcciones independiente (rango de direcciones). Sin embargo, varios trabajos simultáneos que usaban el mismo código generaban un desperdicio de memoria física. Si dos trabajos ejecutan programas completamente idénticos, la traducción dinámica de direcciones proporciona una solución al permitir que el sistema simplemente asigne la dirección 32K de dos trabajos diferentes a los mismos bytes de memoria real, que contienen una única copia del programa.
Los diferentes programas pueden compartir un código común. Por ejemplo, el programa de nómina y el programa de cuentas por cobrar pueden contener una subrutina de clasificación idéntica. Un módulo compartido (una biblioteca compartida es una forma de módulo compartido) se carga una vez y se asigna a los dos espacios de direcciones.
Detalles técnicos
Llamadas de procedimiento dentro de una biblioteca compartida se hacen típicamente a través de una pequeña mesa procedimiento de vinculación talones , que luego llaman a la función definitiva. En particular, esto permite que una biblioteca compartida herede ciertas llamadas a funciones de bibliotecas cargadas previamente en lugar de usar sus propias versiones.
Las referencias de datos del código independiente de la posición generalmente se hacen indirectamente, a través de tablas de compensación global (GOT), que almacenan las direcciones de todas las variables globales a las que se accede . Hay un GOT por unidad de compilación o módulo de objeto, y está ubicado en un desplazamiento fijo del código (aunque este desplazamiento no se conoce hasta que se vincula la biblioteca ). Cuando un vinculador vincula módulos para crear una biblioteca compartida, fusiona los GOT y establece las compensaciones finales en el código. No es necesario ajustar las compensaciones al cargar la biblioteca compartida más tarde.
Las funciones independientes de posición que acceden a datos globales comienzan determinando la dirección absoluta del GOT dado su propio valor de contador de programa actual. Esto a menudo toma la forma de una llamada de función falsa para obtener el valor de retorno en la pila ( x86 ) o en un registro especial ( PowerPC , SPARC , MIPS , probablemente al menos algunos otros procesadores RISC [ palabras de comadreja ] , ESA / 390 ) , que luego se puede almacenar en un registro estándar predefinido. Algunas arquitecturas de procesador, como Motorola 68000 , Motorola 6809 , WDC 65C816 , Knuth's MMIX , ARM y x86-64 permiten referenciar datos por compensación del contador del programa . Esto tiene como objetivo específico hacer que el código independiente de la posición sea más pequeño, menos exigente en cuanto a registros y, por lo tanto, más eficiente.
DLL de Windows
Las bibliotecas de vínculos dinámicos (DLL) en Microsoft Windows utilizan la variante E8 de la instrucción CALL (llamada cercana, relativa, desplazamiento relativo a la siguiente instrucción). No es necesario corregir estas instrucciones cuando se carga una DLL.
Se espera que algunas variables globales (por ejemplo, matrices de cadenas literales, tablas de funciones virtuales) contengan una dirección de un objeto en la sección de datos respectivamente en la sección de código de la biblioteca dinámica; por lo tanto, la dirección almacenada en la variable global debe actualizarse para reflejar la dirección donde se cargó la DLL. El cargador dinámico calcula la dirección a la que hace referencia una variable global y almacena el valor en dicha variable global; esto activa la copia al escribir de una página de memoria que contiene dicha variable global. Las páginas con código y las páginas con variables globales que no contienen punteros al código o datos globales permanecen compartidas entre procesos. Esta operación debe realizarse en cualquier sistema operativo que pueda cargar una biblioteca dinámica en una dirección arbitraria.
En Windows Vista y versiones posteriores de Windows, la reubicación de archivos DLL y ejecutables la realiza el administrador de memoria del kernel, que comparte los binarios reubicados en varios procesos. Las imágenes siempre se reubican desde sus direcciones base preferidas, logrando la aleatorización del diseño del espacio de direcciones (ASLR). [7]
Las versiones de Windows anteriores a Vista requieren que los archivos DLL del sistema estén preenlazados en direcciones fijas no conflictivas en el momento del enlace para evitar la reubicación de imágenes en tiempo de ejecución. El cargador de DLL realiza la reubicación en tiempo de ejecución en estas versiones anteriores de Windows dentro del contexto de cada proceso, y las porciones reubicadas resultantes de cada imagen ya no se pueden compartir entre procesos.
El manejo de archivos DLL en Windows difiere del procedimiento anterior de OS / 2 del que deriva. OS / 2 presenta una tercera alternativa e intenta cargar archivos DLL que no son independientes de la posición en una "arena compartida" dedicada en la memoria y los asigna una vez que se cargan. Todos los usuarios de la DLL pueden utilizar la misma copia en memoria.
Multics
En Multics, cada procedimiento conceptualmente [c] tiene un segmento de código y un segmento de enlace. El segmento de código contiene solo código y la sección de vinculación sirve como plantilla para un nuevo segmento de vinculación. El registro de puntero 4 (PR4) apunta al segmento de enlace del procedimiento. Una llamada a un procedimiento guarda PR4 en la pila antes de cargarlo con un puntero al segmento de vinculación del destinatario. La llamada a procedimiento utiliza un par de punteros indirectos [8] con una bandera para provocar una trampa en la primera llamada para que el mecanismo de enlace dinámico pueda agregar el nuevo procedimiento y su segmento de enlace a la Tabla de segmentos conocidos (KST), construir un nuevo enlace segmento, coloque sus números de segmento en la sección de enlace de la persona que llama y restablezca la bandera en el par de punteros indirectos.
TSS
En IBM S / 360 Time Sharing System (TSS / 360 y TSS / 370) cada procedimiento puede tener un CSECT público de solo lectura y una Sección de Prototipo privada de escritura (PSECT). Un llamador carga una constante V para la rutina en el Registro general 15 (GR15) y copia una constante R para el PSECT de la rutina en la palabra 19 del área de guardado que apunta a GR13. [9]
Dynamic Loader [10] no carga páginas de programa ni resuelve constantes de dirección hasta que se produce el error de la primera página.
Ejecutables independientes de la posición
Los ejecutables independientes de la posición (PIE) son binarios ejecutables hechos completamente de código independiente de la posición. Si bien algunos sistemas solo ejecutan ejecutables PIC, existen otras razones por las que se utilizan. Los binarios PIE se utilizan en algunas distribuciones de Linux centradas en la seguridad para permitir que PaX o Exec Shield utilicen la aleatorización del diseño del espacio de direcciones para evitar que los atacantes sepan dónde está el código ejecutable existente durante un ataque de seguridad utilizando exploits que se basan en conocer el desplazamiento del código ejecutable en el binario, como los ataques de retorno a libc .
MacOS e iOS de Apple son totalmente compatibles con los ejecutables PIE a partir de las versiones 10.7 y 4.3, respectivamente; se emite una advertencia cuando los ejecutables de iOS que no son PIE se envían para su aprobación a la App Store de Apple, pero aún no hay un requisito estricto [ ¿cuándo? ] y las aplicaciones que no son PIE no se rechazan. [11] [12]
OpenBSD tiene PIE habilitado de forma predeterminada en la mayoría de las arquitecturas desde OpenBSD 5.3, lanzado el 1 de mayo de 2013. [13] Se agregó soporte para PIE en binarios vinculados estáticamente , como los ejecutables en /bin
y /sbin
directorios, cerca de finales de 2014. [14] openSUSE añadió PIE por defecto en 2015-02. Comenzando con Fedora 23, los mantenedores de Fedora decidieron construir paquetes con PIE habilitado como predeterminado. [15] Ubuntu 17.10 tiene PIE habilitado de forma predeterminada en todas las arquitecturas. [16] Los nuevos perfiles de Gentoo ahora soportan PIE por defecto. [17]
Android habilitó el soporte para PIE en Jelly Bean [18] y eliminó el soporte para enlazadores no PIE en Lollipop . [19]
Ver también
- Enlazador dinámico
- Archivo de objeto
- Segmento de código
Notas
- ^ Si bien TSS / 360 admitía PIC compartido, eso no era cierto para todos los sistemas de paginación
- ^ Pero se cargó una copia separada del código para cada trabajo.
- ^ Existen algunas desviaciones técnicas por motivos de rendimiento que están más allá del alcance de este artículo.
Referencias
- ^ a b c d e f "Tipos de código de objeto". Manual de referencia del cargador de aplicaciones iRMX 86 (PDF) . Intel . págs. 1-2, 1-3 . Consultado el 21 de agosto de 2017 .
[…] El código absoluto , y un módulo de objeto absoluto, es código que ha sido procesado por LOC86 para ejecutarse solo en una ubicación específica en la memoria. El cargador carga un módulo de objeto absoluto solo en la ubicación específica que debe ocupar el módulo. El código independiente de la posición (comúnmente conocido como PIC) se diferencia del código absoluto en que el PIC se puede cargar en cualquier ubicación de la memoria. La ventaja de PIC sobre el código absoluto es que PIC no requiere que reserve un bloque específico de memoria. Cuando el cargador carga el PIC, obtiene los segmentos de memoria del iRMX 86 del grupo del trabajo de la tarea que realiza la llamada y carga el PIC en los segmentos. Una restricción con respecto al PIC es que, como en el modelo PL / M-86 COMPACT de segmentación […], puede tener solo un segmento de código y un segmento de datos, en lugar de permitir que las direcciones base de estos segmentos y, por lo tanto, los segmentos mismos , varían dinámicamente. Esto significa que los programas PIC tienen necesariamente menos de 64K bytes de longitud. El código PIC se puede generar mediante el control BIND de LINK86. El código localizable en tiempo de carga (comúnmente conocido como código LTL) es la tercera forma de código objeto. El código LTL es similar al PIC en que el código LTL se puede cargar en cualquier lugar de la memoria. Sin embargo, al cargar código LTL, Loader cambia la parte base de los punteros para que los punteros sean independientes del contenido inicial de los registros en el microprocesador. Debido a esta corrección (ajuste de direcciones base), el código LTL puede ser utilizado por tareas que tienen más de un segmento de código o más de un segmento de datos. Esto significa que los programas LTL pueden tener más de 64K bytes de longitud. FORTRAN 86 y Pascal 86 producen automáticamente código LTL, incluso para programas cortos. El código LTL se puede generar mediante el control BIND de LINK86. […]
- ^ Ejecutables independientes de posición (PIE)
- ^ Levine, John R. (2000) [octubre de 1999]. "Capítulo 8: Carga y superposiciones" . Enlazadores y cargadores . La Serie Morgan Kaufmann en Ingeniería de Software y Programación (1 ed.). San Francisco, Estados Unidos: Morgan Kaufmann . págs. 170-171. ISBN 1-55860-496-0. OCLC 42413382 . ISBN 978-1-55860-496-4 . Archivado desde el original el 5 de diciembre de 2012 . Consultado el 12 de enero de 2020 .Código: [1] [2] Errata: [3]
- ^ Gabert, Alexander (enero de 2004). "Internos del Código Independiente de Posición" . Gentoo endurecido . Consultado el 3 de diciembre de 2009 .
[…] El direccionamiento directo sin PIC siempre es más barato (léase: más rápido) que el direccionamiento PIC. […]
- ^ "701 Anunciado" , IBM , 1952-04-29
- ^ Manual de referencia del sistema de procesamiento de datos UNIVAC III (PDF) . Corporación Sperry Rand. 1962. UT-2488.
- ^ "Avances en la gestión de memoria para Windows" . View.officeapps.live.com . Consultado el 23 de junio de 2017 .
- ^ "Sección 6 Formación de direcciones virtuales", DPS / LEVEL 68 & DPS 8M MULTICS PROCESSOR MANUAL (PDF) (Rev. 1 ed.), Honeywell Information Systems Inc. , 1982, págs. 6-21, AL39
- ^ "Sección 3: TSS para el programador: Svslcm". Conceptos e instalaciones del sistema de tiempo compartido de IBM (PDF) (Séptima ed.). Abril de 1978. p. 61. GC28-2003-6.
- ^ IBM System / 360 Time Sharing System Dynamic Loader (PDF) (Cuarta ed.). Septiembre de 1971. GY28-2031-3.
- ^ "iphone - Binario no PIE - El ejecutable 'nombre del proyecto' no es un ejecutable independiente de la posición. - Desbordamiento de pila" . stackoverflow.com .
- ^ "Biblioteca para desarrolladores de iOS" . apple.com .
- ^ "Versión 5.3 de OpenBSD" . 2013-05-01 . Consultado el 9 de mayo de 2020 .
- ^ "Heads Up: Actualizaciones de instantáneas para PIE estático" . 2014-12-24 . Consultado el 24 de diciembre de 2014 .
- ^ "Cambios / Endurecer todos los paquetes - FedoraProject" . fedoraproject.org .
- ^ "Equipo de Fundamentos de Ubuntu - Boletín semanal, 2017-06-15" . 2017-06-15 . Consultado el 17 de junio de 2017 .
- ^ "Nuevos perfiles 17.0 en el repositorio de Gentoo" . 2017-11-30 . Consultado el 10 de diciembre de 2017 .
- ^ "Mejoras de seguridad en Android 1.5 a 4.1 - Proyecto de código abierto de Android" . Proyecto de código abierto de Android .
- ^ "Mejoras de seguridad en Android 5.0 - Proyecto de código abierto de Android" . Proyecto de código abierto de Android .
enlaces externos
- Introducción al Código Independiente de Posición
- Internos del Código Independiente de Posición
- Programación en lenguaje ensamblador con PIC
- El curioso caso de los ejecutables independientes de posición