He leído sobre las opciones de GCC para las convenciones de generación de código , pero no podía entender lo que hace "Generar código independiente de posición (PIC)". Por favor, dame un ejemplo para explicarme qué significa.
He leído sobre las opciones de GCC para las convenciones de generación de código , pero no podía entender lo que hace "Generar código independiente de posición (PIC)". Por favor, dame un ejemplo para explicarme qué significa.
Respuestas:
El código de posición independiente significa que el código de máquina generado no depende de estar ubicado en una dirección específica para funcionar.
Por ejemplo, los saltos se generarían como relativos en lugar de absolutos.
Pseudoensamblaje:
PIC: esto funcionaría si el código estaba en la dirección 100 o 1000
100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP
No PIC: esto solo funcionará si el código está en la dirección 100
100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP
EDITAR: en respuesta al comentario.
Si su código se compila con -fPIC, es adecuado para su inclusión en una biblioteca: la biblioteca debe poder reubicarse desde su ubicación preferida en la memoria a otra dirección, podría haber otra biblioteca ya cargada en la dirección que su biblioteca prefiere.
-fPIC
al compilar un programa o una biblioteca estática, porque solo existirá un programa principal en un proceso, por lo que no es necesario reubicar el tiempo de ejecución. En algunos sistemas, los programas aún se hacen independientes de la posición para una mayor seguridad.
Trataré de explicar lo que ya se ha dicho de una manera más simple.
Cada vez que se carga una biblioteca compartida, el cargador (el código en el sistema operativo que carga cualquier programa que ejecute) cambia algunas direcciones en el código dependiendo de dónde se cargó el objeto.
En el ejemplo anterior, el cargador escribe el "111" en el código que no es PIC la primera vez que se cargó.
Para los objetos no compartidos, es posible que desee que sea así porque el compilador puede hacer algunas optimizaciones en ese código.
Para el objeto compartido, si otro proceso desea "enlazar" a ese código, debe leerlo en las mismas direcciones virtuales o el "111" no tendrá sentido. pero ese espacio virtual ya puede estar en uso en el segundo proceso.
Whenever a shared lib is loaded, the loader changes some addresses in the code depending on where the object was loaded to.
Creo que esto no es correcto si se compila con -fpic y la razón por la cual existe -fpic, es decir, por razones de rendimiento o porque tiene un cargador que no puede reubicarse o porque necesita varias copias en diferentes ubicaciones o por muchas otras razones.
El código integrado en las bibliotecas compartidas normalmente debería ser un código independiente de la posición, de modo que la biblioteca compartida se pueda cargar fácilmente en (más o menos) cualquier dirección en la memoria. La -fPIC
opción asegura que GCC produzca dicho código.
-fPIC
indicador activado? ¿No está vinculado al programa? cuando el programa se está ejecutando, el sistema operativo lo carga en la memoria. ¿Me estoy perdiendo de algo?
-fPIC
utiliza la bandera para garantizar que esta lib se pueda cargar en cualquier dirección virtual en el proceso que la vincula? perdón por los comentarios dobles 5 minutos transcurridos no se puede editar el anterior.
libwotnot.so
) y vincularla ( -lwotnot
). Mientras se vincula, no necesita preocuparse -fPIC
. Solía ser el caso de que al compilar la biblioteca compartida, tenía que asegurarse de que -fPIC
se utilizara para todos los archivos de objetos que se construirían en la biblioteca compartida. Las reglas pueden haber cambiado porque los compiladores compilan con el código PIC de forma predeterminada en estos días. Entonces, lo que era crítico hace 20 años, y podría haber sido importante hace 7 años, es menos importante en estos días, creo. Las direcciones fuera del núcleo o / s son 'siempre' direcciones virtuales '.
-fPIC
. Sin pasar esta bandera, ¿el código generado al compilar el .so debe cargarse en direcciones virtuales específicas que podrían estar en uso?
Añadiendo más ...
Todos los procesos tienen el mismo espacio de direcciones virtuales (si la aleatorización de la dirección virtual se detiene mediante el uso de un indicador en el sistema operativo Linux) (para obtener más detalles, deshabilite y vuelva a habilitar la aleatorización del diseño del espacio de direcciones solo para mí )
Entonces, si es un exe sin enlace compartido (escenario hipotético), entonces siempre podemos dar la misma dirección virtual a la misma instrucción asm sin ningún daño.
Pero cuando queremos vincular un objeto compartido al exe, entonces no estamos seguros de la dirección de inicio asignada al objeto compartido, ya que dependerá del orden en que se vincularon los objetos compartidos. Dicho esto, la instrucción as dentro de .so siempre tendrá dirección virtual diferente según el proceso al que se vincula.
Por lo tanto, un proceso puede proporcionar una dirección de inicio a .so como 0x45678910 en su propio espacio virtual y otro proceso al mismo tiempo puede proporcionar una dirección de inicio de 0x12131415 y, si no utilizan el direccionamiento relativo, .so no funcionará en absoluto.
Por lo tanto, siempre tienen que usar el modo de direccionamiento relativo y, por lo tanto, la opción fpic.
El enlace a una función en una biblioteca dinámica se resuelve cuando la biblioteca se carga o en tiempo de ejecución. Por lo tanto, tanto el archivo ejecutable como la biblioteca dinámica se cargan en la memoria cuando se ejecuta el programa. La dirección de memoria en la que se carga una biblioteca dinámica no se puede determinar de antemano, porque una dirección fija puede chocar con otra biblioteca dinámica que requiere la misma dirección.
Existen dos métodos comúnmente utilizados para tratar este problema:
1. Reubicación. Todos los punteros y direcciones en el código se modifican, si es necesario, para ajustarse a la dirección de carga real. La reubicación se realiza mediante el vinculador y el cargador.
2. Código independiente de la posición. Todas las direcciones en el código son relativas a la posición actual. Los objetos compartidos en sistemas tipo Unix usan código independiente de la posición de forma predeterminada. Esto es menos eficiente que la reubicación si el programa se ejecuta durante mucho tiempo, especialmente en modo de 32 bits.
El nombre " código independiente de la posición " en realidad implica lo siguiente:
La sección de código no contiene direcciones absolutas que necesiten reubicación, sino solo direcciones relativas. Por lo tanto, la sección de código puede cargarse en una dirección de memoria arbitraria y compartirse entre múltiples procesos.
La sección de datos no se comparte entre múltiples procesos porque a menudo contiene datos que se pueden escribir. Por lo tanto, la sección de datos puede contener punteros o direcciones que necesitan reubicación.
Todas las funciones públicas y los datos públicos pueden anularse en Linux. Si una función en el ejecutable principal tiene el mismo nombre que una función en un objeto compartido, entonces la versión en main tendrá prioridad, no solo cuando se llame desde main, sino también cuando se llame desde el objeto compartido. Del mismo modo, cuando una variable global en main tiene el mismo nombre que una variable global en el objeto compartido, se utilizará la instancia en main, incluso cuando se accede desde el objeto compartido.
Esta llamada interposición de símbolos pretende imitar el comportamiento de las bibliotecas estáticas.
Un objeto compartido tiene una tabla de punteros a sus funciones, llamada tabla de vinculación de procedimientos (PLT) y una tabla de punteros a sus variables llamada tabla de desplazamiento global (GOT) para implementar esta función de "anulación". Todos los accesos a funciones y variables públicas pasan por estas tablas.
ps Cuando no se puede evitar el enlace dinámico, hay varias formas de evitar las características que consumen tiempo del código independiente de la posición.
Puede leer más de este artículo: http://www.agner.org/optimize/optimizing_cpp.pdf
Una pequeña adición a las respuestas ya publicadas: los archivos de objetos no compilados para ser independientes de la posición son reubicables; contienen entradas de tabla de reubicación.
Estas entradas permiten al cargador (ese bit de código que carga un programa en la memoria) reescribir las direcciones absolutas para ajustar la dirección de carga real en el espacio de direcciones virtuales.
Un sistema operativo intentará compartir una sola copia de una "biblioteca de objetos compartidos" cargada en la memoria con todos los programas que están vinculados a esa misma biblioteca de objetos compartidos.
Dado que el espacio de dirección de código (a diferencia de las secciones del espacio de datos) no necesita ser contiguo, y debido a que la mayoría de los programas que se vinculan a una biblioteca específica tienen un árbol de dependencia de biblioteca bastante fijo, esto tiene éxito la mayor parte del tiempo. En esos casos raros donde hay una discrepancia, sí, puede ser necesario tener dos o más copias de una biblioteca de objetos compartidos en la memoria.
Obviamente, cualquier intento de aleatorizar la dirección de carga de una biblioteca entre programas y / o instancias de programas (para reducir la posibilidad de crear un patrón explotable) hará que estos casos sean comunes, no raros, por lo que cuando un sistema ha habilitado esta capacidad, uno debe hacer todo lo posible para compilar todas las bibliotecas de objetos compartidos para que sean independientes de la posición.
Dado que las llamadas a estas bibliotecas desde el cuerpo del programa principal también se reubicarán, esto hace que sea mucho menos probable que se deba copiar una biblioteca compartida.