linking library compile c gcc shared-libraries ld rpath

library - ¿Por qué ld necesita-rpath-link cuando vincula un ejecutable con un archivo así que necesita otro?



c dynamic linking (4)

Sólo tengo curiosidad aquí. He creado un objeto compartido:

gcc -o liba.so -fPIC -shared liba.c

Y un objeto más compartido, que enlaza con el anterior:

gcc -o libb.so -fPIC -shared libb.c liba.so

Ahora, al crear un archivo ejecutable que enlaza con libb.so , tendré que especificar -rpath-link a ld para que pueda encontrar liba.so cuando descubra que libb.so depende de ello:

gcc -o test -Wl,-rpath-link,./ test.c libb.so

De lo contrario ld se quejará.

¿Por qué es que ld DEBE poder localizar liba.so al vincular la test ? Porque para mí no parece que ld esté haciendo mucho más que confirmar la existencia de liba.so Por ejemplo, ejecutar readelf --dynamic ./test solo enumera libb.so según sea necesario, así que supongo que el enlazador dinámico debe descubrir la libb.so -> liba.so por sí misma, y ​​hacer su propia búsqueda de liba.so .

Estoy en una plataforma x86-64 GNU / Linux, y la rutina main () en test llama a una función en libb.so que a su vez llama a una función en liba.so


¿Por qué es que ld DEBE poder localizar liba.so al vincular la test ? Porque para mí no parece que ld esté haciendo mucho más que confirmar la existencia de liba.so Por ejemplo, ejecutar readelf --dynamic ./test solo enumera libb.so según sea necesario, así que supongo que el enlazador dinámico debe descubrir la libb.so -> liba.so por sí misma, y ​​hacer su propia búsqueda de liba.so .

Bueno, si entiendo el proceso de vinculación correctamente, en realidad ld no necesita ubicar incluso libb.so Simplemente podría ignorar todas las referencias no resueltas en la test con la esperanza de que el vinculador dinámico las resuelva al cargar libb.so en tiempo de ejecución. Pero si ld lo estuviera haciendo de esta manera, muchos errores de "referencia indefinida" no se detectarían en el momento del enlace, sino que se encontrarían al intentar cargar la test en tiempo de ejecución. Por lo tanto, ld hace una comprobación adicional de que todos los símbolos que no se encuentran en la test en sí pueden encontrarse en las bibliotecas compartidas de las que depende la test . Entonces, si el programa de test tiene un error de "referencia indefinida" (alguna variable o función no se encuentra en la test en sí y tampoco en libb.so ), esto se vuelve obvio en el momento del enlace, no solo en el tiempo de ejecución. Por lo tanto, tal comportamiento es solo un control de cordura adicional.

Pero ld va aún más lejos. Cuando vincula test , ld también verifica que todas las referencias no resueltas en libb.so se encuentren en las bibliotecas compartidas de las que depende libb.so (en nuestro caso, libb.so depende de liba.so , por lo que requiere que se encuentre liba.so en el momento del enlace). Bueno, en realidad ld ya ha hecho esta comprobación, cuando estaba vinculando libb.so ¿Por qué hace esto comprobando la segunda vez? Tal vez los desarrolladores de ld encontraron útil esta doble verificación para detectar dependencias rotas cuando intenta vincular su programa con una biblioteca obsoleta que podría cargarse en los momentos en que estaba vinculado, pero ahora puede no se puede cargar porque las bibliotecas de las que depende se actualizan (por ejemplo, liba.so se liba.so posteriormente y se eliminó parte de la función).

UPD

Acabo de hacer algunos experimentos. Parece que mi suposición "en realidad ld ya ha hecho esta comprobación, cuando estaba vinculando libb.so " está mal.

Supongamos que liba.c tiene el siguiente contenido:

int liba_func(int i) { return i + 1; }

y libb.c tiene el siguiente:

int liba_func(int i); int liba_nonexistent_func(int i); int libb_func(int i) { return liba_func(i + 1) + liba_nonexistent_func(i + 2); }

y test.c

#include <stdio.h> int libb_func(int i); int main(int argc, char *argv[]) { fprintf(stdout, "%d/n", libb_func(argc)); return 0; }

Al enlazar libb.so :

gcc -o libb.so -fPIC -shared libb.c liba.so

el enlazador no genera ningún mensaje de error que liba_nonexistent_func no pueda resolverse, en su lugar simplemente genera de forma silenciosa la biblioteca compartida libb.so El comportamiento es el mismo que haría con una biblioteca estática ( libb.a ) con ar que no resuelve los símbolos de la biblioteca generada también.

Pero cuando intentas enlazar test :

gcc -o test -Wl,-rpath-link=./ test.c libb.so

te sale el error:

libb.so: undefined reference to `liba_nonexistent_func'' collect2: ld returned 1 exit status

Detectar dicho error no sería posible si ld no explorara recursivamente todas las bibliotecas compartidas. Así que parece que la respuesta a la pregunta es la misma que dije anteriormente: ld necesita -rpath-link para asegurarse de que el ejecutable vinculado pueda cargarse más tarde con la carga dinámica. Sólo un chequeo de cordura.

UPD2

Tendría sentido verificar las referencias no resueltas lo antes posible (al vincular libb.so ), pero por algunas razones, no hace esto. Probablemente es para permitir hacer dependencias cíclicas para bibliotecas compartidas.

liba.c puede tener la siguiente implementación:

int libb_func(int i); int liba_func(int i) { int (*func_ptr)(int) = libb_func; return i + (int)func_ptr; }

Entonces liba.so usa libb.so y libb.so usa liba.so (mejor nunca hagas tal cosa). Esto compila con éxito y funciona:

$ gcc -o liba.so -fPIC -shared liba.c $ gcc -o libb.so -fPIC -shared libb.c liba.so $ gcc -o test test.c -Wl,-rpath=./ libb.so $ ./test -1217026998

Aunque readelf dice que liba.so no necesita libb.so :

$ readelf -d liba.so | grep NEEDED 0x00000001 (NEEDED) Shared library: [libc.so.6] $ readelf -d libb.so | grep NEEDED 0x00000001 (NEEDED) Shared library: [liba.so] 0x00000001 (NEEDED) Shared library: [libc.so.6]

Si ld comprobó si había símbolos no resueltos durante el enlace de una biblioteca compartida, el enlace de liba.so no sería posible.

Tenga en cuenta que utilicé la tecla -rpath en lugar de -rpath-link . La diferencia es que -rpath-link se usa en el momento del enlace solo para verificar que todos los símbolos en el ejecutable final puedan resolverse, mientras que -rpath en realidad incrusta la ruta que usted especifica como parámetro en el ELF:

$ readelf -d test | grep RPATH 0x0000000f (RPATH) Library rpath: [./]

Así que ahora es posible ejecutar la test si las bibliotecas compartidas ( liba.so y libb.so ) están ubicadas en su directorio de trabajo actual ( ./ ). Si solo usara -rpath-link, no habría tal entrada en el ELF de test , y tendría que agregar la ruta a las bibliotecas compartidas al archivo /etc/ld.so.conf o a la LD_LIBRARY_PATH entorno LD_LIBRARY_PATH .

UPD3

En realidad, es posible buscar símbolos no resueltos durante la vinculación de una biblioteca compartida, la --no-undefined debe usar para hacer eso:

$ gcc -Wl,--no-undefined -o libb.so -fPIC -shared libb.c liba.so /tmp/cc1D6uiS.o: In function `libb_func'': libb.c:(.text+0x2d): undefined reference to `liba_nonexistent_func'' collect2: ld returned 1 exit status

También encontré un buen artículo que aclara muchos aspectos de la vinculación de bibliotecas compartidas que dependen de otras bibliotecas compartidas: Mejor comprensión de las dependencias secundarias de Linux para resolver con ejemplos .


En realidad, no le está diciendo a ld (al vincular libb con liba ) dónde está liba , solo que es una dependencia. Un rápido ldd libb.so te mostrará que no puede encontrar liba .

Como es de suponer que estas bibliotecas no están en su ruta de búsqueda de vinculador, obtendrá un error de vinculador cuando vincula el ejecutable. Tenga en cuenta que cuando vincula liba en sí, la función en libb aún no está resuelta, pero el comportamiento predeterminado de ld es no preocuparse por los símbolos no resueltos en los DSO hasta que vincule el ejecutable final.


Supongo que necesitas saber cuándo usar la opción -rpath-link y la opción -rpath-link . Primero cito lo que el man ld especificó:

  1. La diferencia entre -rpath y -rpath-link es que los directorios especificados por las opciones -rpath se incluyen en el ejecutable y se usan en tiempo de ejecución, mientras que la opción -rpath-link solo es efectiva en el momento del enlace. La búsqueda de -rpath de esta manera solo es compatible con enlazadores nativos y reticuladores que se han configurado con la opción --with-sysroot.

Debe distinguir entre tiempo de enlace y tiempo de ejecución. De acuerdo con la respuesta aceptada de anton_rh, la comprobación de símbolos no definidos no está habilitada al compilar y vincular bibliotecas compartidas o bibliotecas estáticas, sino HABILITADA al compilar y vincular ejecutables. (Sin embargo, tenga en cuenta que existen algunos archivos que son bibliotecas compartidas y ejecutables, por ejemplo, ld.so Escriba man ld.so para explorar esto, y no sé si la verificación de símbolos indefinidos está habilitada o no. Al compilar estos archivos de tipos "duales").

Entonces, -rpath-link se usa en la verificación del tiempo de enlace, y -rpath se usa para el tiempo de enlace y el tiempo de ejecución porque rpath está incrustado en los encabezados ELF. Pero debe tener cuidado de que la opción -rpath-link anule la opción -rpath durante el tiempo de enlace si se especifican ambas.

Pero aún así, ¿ -rpath-option qué -rpath-option y -rpath option? Creo que se utilizan para eliminar el "exceso de enlace". Ver esto Comprender mejor las dependencias secundarias de Linux resolver con ejemplos. , simplemente use ctrl + F para navegar a los contenidos relacionados con "sobreenlace". Debería centrarse en por qué "el enlace excesivo" es malo, y debido al método que adoptamos para evitar el "enlace excesivo", la existencia de las opciones de ld -rpath-link y -rpath es razonable: omitimos deliberadamente algunas bibliotecas en los comandos para compilar y enlazando para evitar el "solapamiento", y debido a la omisión, ld necesita -rpath-link o -rpath para localizar estas bibliotecas omitidas.


ld.so.conf sistema, a través de ld.so.conf , ld.so.conf.d , y el entorno del sistema, LD_LIBRARY_PATH , etc., proporciona las rutas de búsqueda de bibliotecas de todo el sistema que se complementan con las bibliotecas instaladas a través pkg-config información pkg-config y la Al igual que cuando se construye en contra de las bibliotecas estándar. Cuando una biblioteca reside en una ruta de búsqueda definida, las rutas de búsqueda de biblioteca estándar se siguen automáticamente permitiendo que se encuentren todas las bibliotecas requeridas.

No hay una ruta de búsqueda de biblioteca estándar en tiempo de ejecución para las bibliotecas compartidas personalizadas que crea usted mismo. Usted especifica la ruta de búsqueda a sus bibliotecas a través de la designación -L/path/to/lib durante la compilación y el enlace. Para bibliotecas en ubicaciones no estándar, la ruta de búsqueda de bibliotecas se puede colocar opcionalmente en el encabezado de su ejecutable (encabezado ELF) en tiempo de compilación para que su ejecutable pueda encontrar las bibliotecas necesarias.

rpath proporciona una forma de incrustar su ruta de búsqueda de biblioteca en tiempo de ejecución personalizada en el encabezado de ELF para que también puedan encontrarse sus bibliotecas personalizadas sin tener que especificar la ruta de búsqueda cada vez que se usa. Esto se aplica también a las bibliotecas que dependen de las bibliotecas. Como ha encontrado, no solo es importante el orden en que especifique las bibliotecas en la línea de comandos, sino que también debe proporcionar la ruta de búsqueda de la biblioteca en tiempo de ejecución, o rpath, para cada biblioteca dependiente con la que se está vinculando, de modo que el encabezado contiene la ubicación de todas las bibliotecas necesarias para ejecutar.

Addemdum de los comentarios

Mi pregunta es principalmente por qué ld debe "intentar automáticamente localizar la biblioteca compartida" (liba.so) e "incluirla en el enlace".

Esa es simplemente la forma en que ld funciona. Desde man ld "La opción -rpath también se usa al ubicar objetos compartidos que son necesarios para los objetos compartidos incluidos explícitamente en el enlace ... Si no se usa -rpath al vincular un ejecutable ELF, el contenido de la variable de entorno" LD_RUN_PATH " Se utilizará si está definido ". En su caso, liba no se encuentra en LD_RUN_PATH por lo que ld necesitará una manera de localizar liba durante la compilación de su ejecutable, ya sea con rpath (descrito anteriormente) o proporcionándole una ruta de búsqueda explícita.

En segundo lugar, lo que realmente significa "incluirlo en el enlace". Para mí, parece que simplemente significa: "confirmar su existencia" (de liba.so), ya que los encabezados ELF de libb.so no están modificados (ya tenían una etiqueta NECESITA contra liba.so), y los encabezados del ejecutivo solo declaran libb. así como NECESITA. ¿Por qué a ld le importa encontrar liba.so, no puede simplemente dejar la tarea al enlazador en tiempo de ejecución?

No, volviendo a la semántica de ld . Para producir un "buen enlace" , ld debe poder localizar todas las bibliotecas dependientes. ld lo contrario no se puede asegurar un buen enlace. El vinculador de tiempo de ejecución debe encontrar y cargar , no solo para encontrar las bibliotecas compartidas que necesita un programa . ld no puede garantizar que sucederá a menos que ld pueda ubicar todas las bibliotecas compartidas necesarias en el momento en que se vincule el programa.