¿Comprobación sencilla de símbolos no resueltos en bibliotecas compartidas?

82

Estoy escribiendo una biblioteca de objetos compartidos de C ++ bastante grande y me he encontrado con un pequeño problema que hace que la depuración sea un problema:

Si defino una función / método en un archivo de encabezado y me olvido de crear un código auxiliar para él (durante el desarrollo), ya que estoy construyendo como una biblioteca de objetos compartidos en lugar de un ejecutable, no aparecen errores en tiempo de compilación que me digan que tengo olvidado implementar esa función. La única forma en que descubro que algo está mal es en tiempo de ejecución, cuando finalmente una aplicación que se vincula con esta biblioteca se cae con un error de "símbolo indefinido".

Estoy buscando una manera fácil de verificar si tengo todos los símbolos que necesito en el momento de la compilación, quizás algo que pueda agregar a mi Makefile.

Una solución que se me ocurrió es ejecutar la biblioteca compilada nm -C -Upara obtener una lista exigida de todas las referencias no definidas. El problema es que esto también aparece con la lista de todas las referencias que se encuentran en otras bibliotecas, como GLibC, que por supuesto se vinculará junto con esta biblioteca cuando se prepare la aplicación final. Sería posible utilizar la salida de a nma greptravés de todos mis archivos de encabezado y ver si alguno de los nombres corresponde ... pero esto parece una locura. Seguramente este no es un problema poco común y existe una mejor manera de resolverlo.

David Claridge
fuente
3
nm -C -ume ha salvado varias veces! (tenga en cuenta las minúsculas -uen mi sistema). Dejo este comentario aquí para poder encontrarlo la próxima vez que lo necesite.
dpritch

Respuestas:

93

Consulte la opción del enlazador -z defs/ --no-undefined. Al crear un objeto compartido, el enlace fallará si hay símbolos sin resolver.

Si está utilizando gcc para invocar el enlazador, usará la -Wlopción del compilador para pasar la opción al enlazador:

gcc -shared ... -Wl,-z,defs

Como ejemplo, considere el siguiente archivo:

#include <stdio.h>

void forgot_to_define(FILE *fp);

void doit(const char *filename)
{
    FILE *fp = fopen(filename, "r");
    if (fp != NULL)
    {
        forgot_to_define(fp);
        fclose(fp);
    }
}

Ahora, si lo integra en un objeto compartido, tendrá éxito:

> gcc -shared -fPIC -o libsilly.so silly.c && echo succeeded || echo failed
succeeded

Pero si agrega -z defs, el enlace fallará y le informará sobre su símbolo faltante:

> gcc -shared -fPIC -o libsilly.so silly.c -Wl,-z,defs && echo succeeded || echo failed
/tmp/cccIwwbn.o: In function `doit':
silly.c:(.text+0x2c): undefined reference to `forgot_to_define'
collect2: ld returned 1 exit status
failed
R Samuel Klatchko
fuente
3
+1 esta respuesta podría ayudar a los chicos que provenían de Windows en el que los símbolos externos de DLL deben resolverse durante el tiempo de compilación. ¡Así se hace con el bonito fragmento de código!
Shmil The Cat
@ShmilTheCat: Hola, he intentado poner -Wl, -z, defs en mi archivo de creación, todavía obtengo un error de símbolo indefinido mientras se ejecuta, pero no durante la compilación. ¿Que puedo hacer?. En mi escenario, tengo dos carpetas una dentro de la otra (digamos, A / B, i, e B está dentro de A), y puedo ver algunos símbolos o B, en "libA.so". No estoy seguro de si todos los símbolos de B están disponibles en "libA.so". ¿Cómo me aseguro de eso?
Vinay
@ShmilTheCat Para que las cosas se parezcan aún más a una DLL de Windows, puede agregarla -Bsymbolica la línea de comandos del vinculador. Incluso si forgot_to_defineahora existe en la biblioteca gracias a la -zverificación, el ejecutable aún puede anularlo con su propia definición y las propias definiciones de la biblioteca irán a esa anulación; -Bsymbolicfuerza las cosas para que las propias definiciones de la biblioteca a sus funciones vayan a sus funciones.
Kaz
Funciona solo para funciones realmente utilizadas. Si lo hago // forgot_to_define(fp);, no informa un error
agentmith
17

En Linux (que parece estar usando) ldd -r a.outdebería darle exactamente la respuesta que está buscando.

ACTUALIZAR: una forma trivial de crear a.outcontra la cual verificar:

 echo "int main() { return 0; }" | g++ -xc++ - ./libMySharedLib.so
 ldd -r ./a.out
Ruso empleado
fuente
4
Eso hace casi exactamente lo mismo que nm -C -U, que sugerí en la publicación original. El problema es que no hay una aplicación 'a.out' de la que hablar, es una biblioteca compartida, por lo que ldd -r mylibrary.so ofrece una gran cantidad de resultados, porque usa símbolos de varias otras bibliotecas dinámicas ... Estoy particularmente interesado en los símbolos que faltan que están definidos en mis archivos de encabezado, en lugar de bibliotecas externas.
David Claridge
1
este debe ser aceptado, la pregunta es cuál es la manera más fácil de verificar los símbolos indefinidos, no cómo evitar los símbolos indefinidos
workplaylifecycle
8

¿Qué tal un testuite? Crea ejecutables simulados que se vinculan a los símbolos que necesita. Si el enlace falla, significa que la interfaz de su biblioteca está incompleta.

Stefano Borini
fuente
3

Tuve el mismo problema una vez. Estaba desarrollando un modelo de componentes en C ++ y, por supuesto, los componentes deberían cargarse en tiempo de ejecución de forma dinámica. Me vienen a la mente tres soluciones, que fueron las que apliqué:

  1. Tómese su tiempo para definir un sistema de compilación que pueda compilar estáticamente. Perderá algo de tiempo diseñándolo, pero le ahorrará mucho tiempo en detectar estos molestos errores de ejecución.
  2. Agrupe sus funciones en secciones conocidas y bien entendidas, de modo que pueda agrupar funciones / stubs para asegurarse de que cada función correspondiente tenga su stub. Si se toma el tiempo de documentarlo bien, puede escribir quizás un script que verifique las definiciones (a través de, por ejemplo, sus comentarios de doxygen) y verifique el archivo .cpp correspondiente.
  3. Realice varios ejecutables de prueba que carguen el mismo conjunto de bibliotecas y especifique el indicador RTLD_NOW para abrir (si está bajo * NIX). Señalarán los símbolos que faltan.

Espero que ayude.

Diego Sevilla
fuente
en las líneas de RTLD_NOW, ¿existe una var env para forzar la unión inmediata (no perezosa)?
Stefano Borini
1
Sip. aquí es LD_BIND_NOW (libc5; glibc desde 2.1.1) Si se establece en una cadena no vacía, hace que el enlazador dinámico resuelva todos los símbolos al inicio del programa en lugar de aplazar la resolución de la llamada de función hasta el punto en que se hace referencia a ellos por primera vez. Esto es útil cuando se usa un depurador.
Stefano Borini
Esto también puede ser útil para el propósito de OP: LD_WARN (solo ELF) (glibc desde 2.1.3) Si se establece en una cadena no vacía, advierte sobre los símbolos no resueltos.
Stefano Borini
Stefano: Sí, es cierto. Existen esas variables. Sin embargo, si pospone la carga dinámica para más tarde (por ejemplo, cuando el usuario carga un módulo), estas variables no son útiles a menos que realmente escriba el programa de prueba que carga las bibliotecas previstas (futuras).
Diego Sevilla