stringtranslate.com

Código independiente de la posición

En informática , el código independiente de la posición [1] ( PIC [1] ) o ejecutable independiente de la posición ( PIE ) [2] es un cuerpo de código de máquina que se ejecuta correctamente independientemente de su dirección de memoria . [a] 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 superponga con otra memoria utilizada, por ejemplo, por otras bibliotecas compartidas. PIC también se usó 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 modificaciones. 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 vinculador o cargador de programas modifica un programa antes de su ejecución. por lo que sólo se puede ejecutar desde una ubicación de memoria particular. [1] Generar código independiente de la posición es a menudo el comportamiento predeterminado de los compiladores , pero pueden imponer restricciones en el 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 usar 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 direcciones relativas 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 la IBM 701 [5] (29 de abril de 1952) o la UNIVAC I (31 de marzo de 1951), el código no era independiente de la posición: cada programa estaba construido para cargarse y ejecutarse 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 cargaban en el almacenamiento principal (o incluso se almacenaban en un tambor magnético para ejecutarlos directamente desde allí) y se ejecutaban uno a la vez. En tal contexto operativo, no era necesario un código independiente de la posición.

Incluso en sistemas básicos y con límites [b] como el CDC 6600 , el GE 625 y el UNIVAC 1107 , una vez que el sistema operativo cargaba el código en el almacenamiento de un trabajo, solo podía ejecutarse desde la dirección relativa en la que se cargó.

Burroughs introdujo un sistema segmentado , el B5000 (1961), en el que los programas abordaban segmentos indirectamente mediante palabras de control en la pila o en la tabla de referencia de programas (PRT); un segmento compartido podría abordarse a través de diferentes ubicaciones de PRT en diferentes procesos. De manera similar, en el B6500 posterior , todas las referencias de segmento se realizaban mediante posiciones en un marco de pila .

El IBM System/360 (7 de abril de 1964) fue diseñado con un direccionamiento truncado similar al del 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 pseudooperación USING . El programador puede cargar el registro base desde un registro que se sabe que contiene la dirección del punto de entrada, generalmente R15, o puede usar la instrucción BALR (forma de registro de rama y enlace) (con un valor R2 de 0) para almacenar la dirección de la siguiente instrucción secuencial. en el registro base, que luego se codificaba explícita o implícitamente en cada instrucción que hacía referencia a una ubicación de almacenamiento dentro del programa. Se podrían utilizar múltiples registros base, para código o para datos. Estas 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 sólo dos bytes.

Esta técnica de programación es estándar en los sistemas tipo IBM S/360. Ha estado en uso hasta el actual IBM System/z. Al codificar en lenguaje ensamblador, el programador debe establecer la direccionabilidad del programa como se describe 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 del Sistema 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 de FASE,* declaración JCL (Lenguaje de control de trabajo).

Por lo tanto, en 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 como para contener ese programa. A veces se producía fragmentación de la memoria 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 .

IBM introdujo por primera vez el almacenamiento virtual en IBM System/360 modelo 67 en (1965) para admitir el primer sistema operativo multitarea 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.

A modo de comparación, en los primeros sistemas segmentados como Burroughs MCP en Burroughs B5000 (1961) y Multics (1964), y en sistemas de paginación como IBM TSS/360 (1967) [c] , el código también era inherentemente independiente de la posición. , ya que las direcciones virtuales de subrutina en un programa estaban ubicadas en datos privados externos al código, por ejemplo, tabla de referencia del programa, segmento de enlace, sección de prototipo.

La invención de la traducción dinámica de direcciones (la función proporcionada por una MMU ) originalmente redujo la necesidad de 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 utilizaban 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 de 32 K de dos trabajos diferentes a los mismos bytes de memoria real, que contienen la única copia del programa.

Diferentes programas pueden compartir código común. Por ejemplo, el programa de nómina y el programa de cuentas por cobrar pueden contener ambos 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.

SunOS 4.x y ELF

Las llamadas a procedimientos dentro de una biblioteca compartida generalmente se realizan a través de pequeños apéndices de tabla de vinculación de procedimientos (PLT) , 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. [7]

Las referencias a datos de código independiente de la posición generalmente se realizan 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 los desplazamientos finales en el código. No es necesario ajustar las compensaciones al cargar la biblioteca compartida más tarde. [7]

Las funciones independientes de la 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 ), en un registro estándar específico ( SPARC , MIPS ) o en un registro especial ( POWER / PowerPC / Power ISA ), que luego puede ser movido a un registro estándar predefinido, o para obtenerlo en ese registro estándar ( PA-RISC , Alpha , ESA/390 y z/Architecture ). Algunas arquitecturas de procesador, como Motorola 68000 , ARM , x86-64 , versiones más nuevas de z/Architecture, Motorola 6809 , WDC 65C816 y MMIX de Knuth permiten hacer referencia a datos mediante desplazamiento del contador de programa . Esto tiene como objetivo específico hacer que el código independiente de la posición sea más pequeño, requiera menos registros y, por lo tanto, sea 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). Estas instrucciones no necesitan modificación cuando se carga la 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 desencadena la copia en escritura 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 a 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 archivos binarios reubicados entre múltiples procesos. Las imágenes siempre se reubican desde sus direcciones base preferidas, logrando la aleatorización del diseño del espacio de direcciones (ASLR). [8]

Las versiones de Windows anteriores a Vista requieren que las DLL del sistema estén previnculadas en direcciones fijas no conflictivas en el momento del enlace para evitar la reubicación de imágenes en tiempo de ejecución. La reubicación del tiempo de ejecución en estas versiones anteriores de Windows la realiza el cargador de DLL dentro del contexto de cada proceso, y las partes 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 un "área compartida" dedicada en la memoria y los asigna una vez cargados. Todos los usuarios de la DLL pueden utilizar la misma copia en memoria.

multics

En Multics cada procedimiento conceptualmente [d] tiene un segmento de código y un segmento de enlace. [9] [10] 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 vinculación del procedimiento. Una llamada a un procedimiento guarda PR4 en la pila antes de cargarlo con un puntero al segmento de enlace del destinatario. La llamada al procedimiento utiliza un par de punteros indirectos [11] con una bandera para provocar una trampa en la primera llamada, de modo que el mecanismo de enlace dinámico pueda agregar el nuevo procedimiento y su segmento de enlace a la tabla de segmentos conocidos (KST) y construir un nuevo enlace. segmento, coloque sus números de segmento en la sección de vinculación de la persona que llama y restablezca la bandera en el par de puntero indirecto.

SST

En IBM S/360 Time Sharing System (TSS/360 y TSS/370), cada procedimiento puede tener una CSECT pública de solo lectura y una sección de prototipo privada grabable (PSECT). Una persona que llama carga una constante V para la rutina en el Registro general 15 (GR15) y copia una constante R para la PSECT de la rutina en la palabra 19 del área de guardado que señala GR13. [12]

El cargador dinámico [13] 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 creados completamente a partir 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 (ASLR) 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 binario, como ataques de retorno a libc . (El kernel oficial de Linux desde 2.6.12 de 2005 tiene un ASLR más débil que también funciona con PIE. Es débil porque la aleatoriedad se aplica a unidades enteras de archivos ELF.) [14]

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 se envían ejecutables de iOS que no son PIE para su aprobación en la App Store de Apple, pero todavía no existe ningún requisito estricto [ ¿cuándo? ] y las solicitudes que no sean PIE no se rechazan. [15] [16]

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. [17] A finales de 2014 se agregó soporte para PIE en archivos binarios vinculados estáticamente , como los ejecutables en /biny directorios. [18] openSUSE agregó PIE como predeterminado en 2015-02. A partir de Fedora  23, los mantenedores de Fedora decidieron crear paquetes con PIE habilitado como predeterminado. [19] Ubuntu 17.10 tiene PIE habilitado de forma predeterminada en todas las arquitecturas. [20] Los nuevos perfiles de Gentoo ahora soportan PIE por defecto. [21] Alrededor de julio de 2017, Debian habilitó PIE de forma predeterminada. [22]/sbin

Android habilitó la compatibilidad con PIE en Jelly Bean [23] y eliminó la compatibilidad con enlazadores que no son PIE en Lollipop . [24]

Ver también

Notas

  1. ^ Esto permite que cada proceso que utiliza una copia compartida lo vea en una dirección virtual diferente.
  2. ^ Pero se cargó una copia separada del código para cada trabajo.
  3. ^ Si bien TSS/360 admitía PIC compartido, eso no era cierto para todos los sistemas de paginación.
  4. ^ Existen algunas desviaciones técnicas por motivos de rendimiento que están fuera del alcance de este artículo.

Referencias

  1. ^ abcdef "Tipos de código 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 de 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 usted reserve un bloque de memoria específico. Cuando el cargador carga PIC, obtiene segmentos de memoria iRMX 86 del grupo del trabajo de la tarea que realiza la llamada y carga el PIC en los segmentos. Una restricción relativa al PIC es que, al igual que en el modelo de segmentación COMPACT PL/M-86 […], sólo puede tener un segmento de código y un segmento de datos, en lugar de dejar que las direcciones base de estos segmentos y, por tanto, los segmentos mismos , varían dinámicamente. Esto significa que los programas PIC tienen necesariamente menos de 64 Kbytes de longitud. El código PIC se puede producir 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, el cargador cambia la parte base de los punteros para que 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 usarse en tareas que tengan 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 64 KB de longitud. FORTRAN 86 y Pascal 86 producen automáticamente código LTL, incluso para programas cortos. El código LTL se puede producir mediante el control BIND de LINK86. […]
  2. ^ "Posición de ejecutables independientes (PIE)".
  3. ^ Levine, John R. (2000) [octubre de 1999]. "Capítulo 8: Carga y superposiciones". Enlazadores y cargadores. La serie Morgan Kaufmann sobre programación e ingeniería de software (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 2020Código: [1][2] Fe de erratas: [3]
  4. ^ Gabert, Alexander (enero de 2004). "Posición interna del Código Independiente". 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. […]
  5. ^ "701 anunciado", IBM , 29 de abril de 1952
  6. ^ Manual de referencia del sistema de procesamiento de datos UNIVAC III (PDF) . Corporación Sperry Rand . 1962.UT-2488.
  7. ^ ab Gingell, Robert A.; Lee, Meng; Dang, Xuong T.; Weeks, Mary S. Bibliotecas compartidas en SunOS (PDF) . 1987 Conferencia y exposición técnica de USENIX de verano. págs. 131-146.
  8. ^ "Avances en la gestión de memoria para Windows". Ver.officeapps.live.com . Consultado el 23 de junio de 2017 .
  9. ^ Orgánico, Elliott Irving (1972). El sistema Multics; un examen de su estructura . Prensa del MIT . ISBN 9780262150125. LCCN  78157477.
  10. ^ Daley, Robert C.; Dennis, Jack B (mayo de 1968). "Memoria virtual, procesos y uso compartido en multics". Comunicaciones de la ACM . 11 (5). Asociación de Maquinaria de Computación : 306–312 . Consultado el 21 de julio de 2024 .
  11. ^ "Sección 6 Formación de direcciones virtuales", MANUAL DEL PROCESADOR MULTICS DPS/LEVEL 68 & DPS 8M (PDF) (Rev. 1 ed.), Honeywell Information Systems Inc. , 1982, págs. 6–21, AL39 , consultado en 2023-03 -25
  12. ^ "Sección 3: TSS para: Programador Svslcm". Conceptos e instalaciones del sistema de tiempo compartido de IBM (PDF) (Séptima ed.). Abril de 1978. p. 61. GC28-2003-6.
  13. ^ Cargador dinámico del sistema de tiempo compartido IBM System / 360 (PDF) (Cuarta ed.). Septiembre de 1971. GY28-2031-3.
  14. ^ Lettieri, G. "Aleatorización del diseño del espacio de direcciones" (PDF) .
  15. ^ "iphone - Binario no PIE - El ejecutable 'nombre del proyecto' no es un ejecutable independiente de la posición. - Desbordamiento de pila". stackoverflow.com .
  16. ^ "Biblioteca para desarrolladores de iOS". apple.com .
  17. ^ "Lanzamiento de OpenBSD 5.3". 2013-05-01 . Consultado el 9 de mayo de 2020 .
  18. ^ "Aviso: actualizaciones de instantáneas para PIE estático". 2014-12-24 . Consultado el 24 de diciembre de 2014 .
  19. ^ "Cambios/Fortalecer todos los paquetes - FedoraProject". fedoraproject.org .
  20. ^ "Equipo de Ubuntu Foundations - Boletín semanal, 15 de junio de 2017". 2017-06-15 . Consultado el 17 de junio de 2017 .
  21. ^ "Nuevos perfiles 17.0 en el repositorio de Gentoo". 2017-11-30 . Consultado el 10 de diciembre de 2017 .
  22. ^ Liang, Mudong (8 de agosto de 2017). "¿Cuándo decidió Debian habilitar PIE de forma predeterminada?". debian.org . Consultado el 6 de julio de 2021 .
  23. ^ "Mejoras de seguridad en Android 1.5 a 4.1: proyecto de código abierto de Android". Proyecto de código abierto de Android .
  24. ^ "Mejoras de seguridad en Android 5.0: proyecto de código abierto de Android". Proyecto de código abierto de Android .

Enlaces externos