¿Por qué ld necesita-rpath-link cuando enlaza un ejecutable contra un so que necesita otro so?


Solo tengo curiosidad. He creado un objeto compartido:

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

Y otro objeto compartido, que enlaza con el anterior: {[17]]}

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

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

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

De lo contrario, ld se quejará.

¿Por qué es que ld DEBE ser capaz de localizar liba.so al enlazar test? Porque a mí no me parece ld está haciendo mucho más que confirmar liba.so's existencia. Por ejemplo, ejecutar readelf --dynamic ./test solo lista libb.so según sea necesario, así que supongo que el enlazador dinámico debe descubrir la dependencia libb.so -> liba.so por sí mismo, y hacer su propia búsqueda de liba.so.

Estoy en una plataforma GNU/Linux x86-64, 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.

Author: Troels Folke, 2014-07-06

4 answers

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

Bueno, si entiendo el proceso de enlace correctamente, ld en realidad no necesita localizar incluso libb.so. Podría simplemente ignore todas las referencias no resueltas en test esperando que dynamic linker las resuelva al cargar libb.so en tiempo de ejecución. Pero si ld estuviera haciendo esto, muchos errores de "referencia indefinida" no se detectarían en el momento del enlace, sino que se encontrarían al intentar cargar test en tiempo de ejecución. Así que ld simplemente hace una comprobación adicional de que todos los símbolos que no se encuentran en test pueden encontrarse realmente en bibliotecas compartidas de las que depende test. Así que si test programa tiene " indefinido reference " error (alguna variable o función que no se encuentra en test y tampoco en libb.so), esto se hace obvio en tiempo de enlace, no solo en tiempo de ejecución. Por lo tanto, tal comportamiento es solo una comprobación adicional de la cordura.

Pero ld va aún más lejos. Cuando se enlaza test, ld también comprueba que todas las referencias no resueltas en libb.so se encuentran en las bibliotecas compartidas de las que depende libb.so (en nuestro caso libb.so depende de liba.so, por lo que requiere que liba.so se encuentre en el momento del enlace). Bueno, en realidad ld ya ha hecho esta comprobación, cuando estaba enlazando libb.so. ¿Por qué hace esta comprobación por segunda vez... Tal vez los desarrolladores de ld encontraron esta doble comprobación útil para detectar dependencias rotas cuando intenta vincular su programa con una biblioteca obsoleta que podría cargarse en los momentos en que se vinculó, pero ahora no se puede cargar porque las bibliotecas de las que depende se actualizan (por ejemplo, liba.so se volvió a trabajar más tarde y de ella).

UPD

Solo hice algunos experimentos. Parece que mi suposición " en realidad ld ya ha hecho esta comprobación, cuando estaba enlazando libb.so" está mal.

Supongamos que el 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 se pueda resolver, en su lugar simplemente silenciosamente generar una biblioteca compartida rota libb.so. El comportamiento es el mismo que haría una biblioteca estática (libb.a) con ar que no resuelve los símbolos de la biblioteca generada también.

Pero cuando intentas vincular test:

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

Se obtiene el error:

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

Detectar este error no sería posible si ld no escaneara recursivamente todas las bibliotecas compartidas. Así que parece que la respuesta a la pregunta es la misma que dije anteriormente: ld necesidades -rpath-link para asegurarse de que el ejecutable vinculado se puede cargar más tarde mediante dynamic loaded. Sólo un chequeo de cordura.

UPD2

Tendría sentido verificar las referencias no resueltas lo antes posible (al enlazar libb.so), pero ld por algunas razones no hace esto. Es probablemente 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;
}

So liba.so uses libb.so y libb.so usa liba.so (mejor nunca hacer tal cosa). Esto compila y funciona con éxito:

$ 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 comprueba si hay símbolos sin resolver durante el enlace de una biblioteca compartida, el enlace de liba.so no sería posible.

Tenga en cuenta que he usado -rpath en lugar de -rpath-link. La diferencia es que - rpath-link se usa en el momento del enlace solo para comprobar que todos los símbolos en el ejecutable final se pueden resolver, mientras que - rpath en realidad incrusta la ruta que especifique como parámetro en el ELF:

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

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

UPD3

En realidad es posible verificar si hay símbolos sin resolver durante la vinculación de la biblioteca compartida, --no-undefined Se debe usar la opción 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 vincular bibliotecas compartidas que dependen de otras bibliotecas compartidas: Mejor comprensión de las dependencias secundarias de Linux resolviendo con ejemplos.

 20
Author: anton_rh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-24 12:37:22

Usted 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 a nivel de todo el sistema que se complementan con bibliotecas instaladas a través de pkg-config información y similares cuando se construye contra bibliotecas estándar. Cuando una biblioteca reside en una ruta de búsqueda definida, las rutas de búsqueda estándar de la biblioteca se siguen automáticamente, lo que permite encontrar todas las bibliotecas necesarias.

No existe un estándar búsqueda de bibliotecas en tiempo de ejecución path para las bibliotecas compartidas personalizadas que cree usted mismo. Usted especifica la ruta de búsqueda a sus bibliotecas a través de la designación -L/path/to/lib durante compile y link. 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 ELF para que su las bibliotecas también se pueden encontrar sin tener que especificar la ruta de búsqueda cada vez que se utiliza. Esto también se aplica a las bibliotecas que dependen de bibliotecas. Como ha encontrado, no solo es importante el orden en el que especifica las bibliotecas en la línea de comandos, sino que también debe proporcionar la ruta de búsqueda de bibliotecas en tiempo de ejecución, o rpath, información para cada biblioteca dependiente con la que está enlazando, de modo que el encabezado contenga la ubicación de todas las bibliotecas necesarias para ejecutar.

Addemdum from Comments

Mi pregunta es principalmente por qué ld debe " tratar automáticamente de localizar biblioteca compartida" (liba.so) y "inclúyelo en el enlace".

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

Secundariamente lo que realmente significa "incluirlo en el enlace". A mí me parece que solo significa: "confirmar su existencia "(liba.so ' s), desde libb.so ' s cabeceras ELF no se modifican (ya tenían un Etiqueta NECESARIA votos en contra liba.so), y las cabeceras del exec solo declaran libb.so as NECESARIO. ¿Por qué ld se preocupa por encontrar liba.so, no se puede simplemente dejar ¿la tarea al enlazador de tiempo de ejecución?

No, volvamos a la semántica de ld. Para producir un "buen enlace", ld debe ser capaz de localizar todas las bibliotecas dependientes de. ld no puede asegurar un buen vínculo de otra manera. El enlazador de tiempo de ejecución debe buscar y cargar , no solo para encontrar las bibliotecas compartidas necesario por un programa. ld no puede garantizar que eso suceda a menos que ld pueda localizar todas las bibliotecas compartidas necesarias en el momento en que el programa está vinculado.

 6
Author: David C. Rankin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-29 05:00:56

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

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

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

Así que -rpath-link se usa en la comprobación de tiempo de enlace, y -rpath se usa para tiempo de enlace y tiempo de ejecución porque rpath está incrustado en encabezados ELF. Pero debe tener cuidado de que la opción -rpath-link anulará la opción -rpath durante el tiempo de enlace si se especifican ambas.

Pero aún así, ¿por qué -rpath-option y -rpath opción? Creo que se utilizan para eliminar el "overlinking". Vea esto Mejor comprensión de Linux resolución de dependencias secundarias con ejemplos., simplemente use ctrl + F para navegar a contenidos relacionados con "overlinking". Debe centrarse en por qué " overlinking "es malo, y debido al método que adoptamos para evitar" overlinking", la existencia de ld opciones -rpath-link y -rpath es razonable: omitimos deliberadamente algunas bibliotecas en los comandos para compilar y enlazar para evitar" overlinking", y debido a la omisión, ld necesita -rpath-link o -rpath para localizar estas bibliotecas omitidas.

 1
Author: Han XIAO,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-04 11:31:12

En realidad no le estás diciendo a ld (al enlazar libb contra liba) donde liba es sólo que es una dependencia. Un rápido ldd libb.so le mostrará que no puede encontrar liba.

Dado que presumiblemente estas bibliotecas no están en su ruta de búsqueda de enlazador, obtendrá un error de enlazador cuando vincule el ejecutable. Tenga en cuenta que cuando vincula liba en sí, la función en libb es todavía sin resolver, pero el comportamiento predeterminado de ld es no preocuparse por los símbolos sin resolver en DSOs hasta que vincule el ejecutable final.

 0
Author: Mark Nunberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-06 23:58:29