linux - que - Carga dinámica y resolución de símbolos débiles
si ejecuto comando 1 & comando 2 lo que sucede es (1)
Al analizar esta pregunta , descubrí algunas cosas sobre el comportamiento de la resolución de símbolos débiles en el contexto de la carga dinámica ( dlopen
) en Linux. Ahora estoy buscando las especificaciones que rigen esto.
Tomemos un ejemplo . Supongamos que hay un programa a
que carga dinámicamente las bibliotecas b.so
y c.so
, en ese orden. Si c.so
depende de otras dos bibliotecas foo.so
(en realidad libgcc.so
en ese ejemplo) y bar.so
(en realidad libpthread.so
), entonces los símbolos exportados por bar.so
pueden usarse para satisfacer los vínculos de símbolos débiles en foo.so
. Pero si b.so
también depende de foo.so
pero no de bar.so
, estos símbolos débiles aparentemente no estarán vinculados contra bar.so
Parece como si las foo.so
foo.so solo buscaran símbolos de a
y b.so
y todas sus dependencias.
Esto tiene sentido, hasta cierto punto, ya que de lo contrario, cargar c.so
podría cambiar el comportamiento de foo.so
en algún punto donde b.so
ya ha estado usando la biblioteca. Por otro lado, en la pregunta que me hizo comenzar, esto causó algunos problemas, así que me pregunto si hay alguna forma de solucionar este problema. Y para encontrar formas de hacerlo, primero necesito una buena comprensión de los detalles exactos de cómo se especifica la resolución de los símbolos en estos casos.
¿Cuál es la especificación u otro documento técnico para definir el comportamiento correcto en estos escenarios?
Desafortunadamente, la documentación autorizada es el código fuente. La mayoría de las distribuciones de Linux utilizan glibc o su fork, eglibc. En el código fuente de ambos, el archivo que debe documentar dlopen () se lee de la siguiente manera:
manual / libdl.texi
@c FIXME these are undocumented:
@c dladdr
@c dladdr1
@c dlclose
@c dlerror
@c dlinfo
@c dlmopen
@c dlopen
@c dlsym
@c dlvsym
Las especificaciones técnicas existentes se pueden extraer de la especificación ELF y del estándar POSIX. La especificación ELF es lo que hace que un símbolo débil tenga sentido. POSIX es la especificación real para dlopen () .
Esto es lo que considero que es la parte más relevante de la especificación ELF.
Cuando el editor de enlaces busca bibliotecas de archivos, extrae miembros de archivos que contienen definiciones de símbolos globales no definidos. La definición del miembro puede ser un símbolo global o un símbolo débil.
La especificación ELF no hace referencia a la carga dinámica, por lo que el resto de este párrafo es mi propia interpretación. La razón por la que encuentro relevante lo anterior es que la resolución de los símbolos se produce en un solo "cuándo". En el ejemplo que da, cuando el programa a
carga dinámicamente b.so
, el cargador dinámico intenta resolver símbolos indefinidos. Puede terminar haciéndolo con símbolos globales o débiles. Cuando el programa carga dinámicamente c.so
, el cargador dinámico intenta nuevamente resolver símbolos indefinidos. En el escenario que describe, los símbolos en b.so
se resolvieron con símbolos débiles. Una vez resueltos, esos símbolos ya no están definidos. No importa si se usaron símbolos globales o débiles para definirlos. Ya no están indefinidos en el momento en que se carga c.so
La especificación ELF no proporciona una definición precisa de qué es un editor de enlaces o cuándo debe combinar los archivos de objetos. Presumiblemente no es un problema porque el documento tiene en mente la vinculación dinámica.
POSIX describe algunas de las funciones de dlopen () pero deja mucho para la implementación, incluida la sustancia de su pregunta. POSIX no hace referencia al formato ELF ni a los símbolos débiles en general. Para los sistemas que implementan dlopen (), no es necesario que existan nociones de símbolos débiles.
http://pubs.opengroup.org/onlinepubs/9699919799/functions/dlopen.html
El cumplimiento de POSIX es parte de otro estándar, la Base Estándar de Linux. Las distribuciones de Linux pueden o no elegir seguir estos estándares y pueden o no tomarse la molestia de estar certificadas. Por ejemplo, entiendo que una certificación formal de Unix por Open Group es bastante costosa, de ahí la abundancia de sistemas "similares a Unix".
Un punto interesante sobre el cumplimiento de los estándares de dlopen () se hace en el artículo de Wikipedia para la carga dinámica . dlopen (), como lo exige POSIX, devuelve un void *, pero C, como lo exige ISO, dice que un void * es un puntero a un objeto y dicho puntero no es necesariamente compatible con un puntero de función.
El hecho es que cualquier conversión entre los punteros de función y objeto debe considerarse como una extensión de implementación (inherentemente no portátil), y que no existe una forma "correcta" para una conversión directa, ya que a este respecto las normas POSIX e ISO contradicen cada una. otro.
Los estándares que existen contradicen y los documentos de estándares que existen pueden no ser especialmente significativos de todos modos. Aquí está Ulrich Drepper escribiendo sobre su desdén por Open Group y sus "especificaciones".
http://udrepper.livejournal.com/8511.html
Sentimiento similar se expresa en el post vinculado por rodrigo.
La razón por la que he hecho este cambio no es para ser más conforme (es bueno, pero no hay razón, ya que nadie se quejó del comportamiento anterior).
Después de analizarlo, creo que la respuesta correcta a la pregunta, tal como lo has formulado, es que no hay un comportamiento correcto o incorrecto para dlopen()
en este sentido. Podría decirse que una vez que la búsqueda ha resuelto un símbolo, deja de estar indefinido y en las búsquedas subsiguientes el cargador dinámico no intentará resolver el símbolo ya definido.
Finalmente, como indica en los comentarios, lo que describe en la publicación original no es correcto. Las bibliotecas compartidas cargadas dinámicamente se pueden usar para resolver símbolos no definidos en bibliotecas compartidas cargadas dinámicamente anteriormente. De hecho, esto no se limita a símbolos no definidos en el código cargado dinámicamente. Aquí hay un ejemplo en el que el ejecutable en sí tiene un símbolo indefinido que se resuelve a través de la carga dinámica.
C Principal
#include <dlfcn.h>
void say_hi(void);
int main(void) {
void* symbols_b = dlopen("./dyload.so", RTLD_NOW | RTLD_GLOBAL);
/* uh-oh, forgot to define this function */
/* better remember to define it in dyload.so */
say_hi();
return 0;
}
dyload.c
#include <stdio.h>
void say_hi(void) {
puts("dyload.so: hi");
}
Compilar y ejecutar.
gcc-4.8 main -fpic -ldl -Wl,--unresolved-symbols=ignore-all -o main
gcc-4.8 dyload.c -shared -fpic -o dyload.so
$ ./main
dyload.so: hi
Tenga en cuenta que el ejecutable principal fue compilado como PIC.