¿Por qué es importante el orden de la opción '-l' en gcc? [duplicar]
Estoy intentando compilar un programa que utiliza la biblioteca udis86 . En realidad, estoy usando un programa de ejemplo que figura en el manual de usuario de la biblioteca. Pero al compilar da error. Los errores que recibo son:
example.c:(.text+0x7): undefined reference to 'ud_init'
example.c:(.text+0x7): undefined reference to 'ud_set_input_file'
.
.
example.c:(.text+0x7): undefined reference to 'ud_insn_asm'
El comando que estoy usando es:
$ gcc -ludis86 example.c -o example
como se indica en el manual del usuario.
Claramente, el vinculador no puede vincular la biblioteca libudis. Pero si cambio mi comando a:
$ gcc example.c -ludis86 -o example
Empieza a funcionar. Entonces, ¿alguien puede explicar cuál es el problema con el primer comando?
Porque así es como funciona el algoritmo de vinculación utilizado por el vinculador GNU (al menos cuando se trata de vincular bibliotecas estáticas). El vinculador es de un solo paso y no vuelve a visitar las bibliotecas una vez que las ha visto.
Una biblioteca es una colección (un archivo) de archivos objeto. Cuando agrega una biblioteca usando la -l
opción, el vinculador no toma incondicionalmente todos los archivos objeto de la biblioteca. Solo toma aquellos archivos objeto que se necesitan actualmente , es decir, archivos que resuelven algunos símbolos actualmente no resueltos (pendientes). Después de eso, el vinculador se olvida por completo de esa biblioteca.
El vinculador mantiene continuamente la lista de símbolos pendientes a medida que procesa los archivos de objetos de entrada, uno tras otro de izquierda a derecha. A medida que procesa cada archivo objeto, algunos símbolos se resuelven y eliminan de la lista, y otros símbolos no resueltos recién descubiertos se agregan a la lista.
Entonces, si incluyó alguna biblioteca usando -l
, el vinculador usa esa biblioteca para resolver tantos símbolos actualmente pendientes como pueda y luego se olvida por completo de esa biblioteca. Si más tarde descubre repentinamente que ahora necesita algunos archivos objeto adicionales de esa biblioteca, el vinculador no "regresará" a esa biblioteca para recuperar esos archivos objeto adicionales. Ya es demasiado tarde.
Por esta razón, siempre es una buena idea usar -l
la opción al final de la línea de comando del vinculador, para que cuando el vinculador llegue a ella -l
pueda determinar de manera confiable qué archivos objeto necesita y cuáles no. Colocar la -l
opción como primer parámetro del enlazador generalmente no tiene ningún sentido: al principio la lista de símbolos pendientes está vacía (o, más precisamente, consta de un solo símbolo main
), lo que significa que el enlazador no tomará nada de la biblioteca en absoluto.
En su caso, su archivo objeto example.o
contiene referencias a símbolos ud_init
, ud_set_input_file
etc. El vinculador debe recibir ese archivo objeto primero. Agregará estos símbolos a la lista de símbolos pendientes. Después de eso, puede usar -l
la opción para agregar su biblioteca: -ludis86
. El vinculador buscará en su biblioteca y tomará todo lo que resuelva esos símbolos pendientes.
Si coloca la -ludis86
opción primero en la línea de comando, el vinculador efectivamente ignorará su biblioteca, ya que al principio no sabe que necesitará ud_init
, ud_set_input_file
etc. Posteriormente, al procesar example.o
descubrirá estos símbolos y los agregará al símbolo pendiente. lista. Pero estos símbolos permanecerán sin resolver hasta el final, ya que -ludis86
ya fueron procesados (y efectivamente ignorados).
A veces, cuando dos (o más) bibliotecas se refieren entre sí de forma circular, es posible que incluso sea necesario usar la -l
opción dos veces con la misma biblioteca, para darle al vinculador dos oportunidades de recuperar los archivos objeto necesarios de esa biblioteca.
Me encontré con este mismo problema hace un tiempo. La conclusión es que las herramientas gnu no siempre "buscarán hacia atrás" en la lista de bibliotecas para resolver los símbolos faltantes. Las soluciones fáciles son cualquiera de las siguientes:
Simplemente especifique las bibliotecas y los objetos en el orden de dependencia (como descubrió anteriormente)
O si tiene una dependencia circular (donde libA hace referencia a una función en libB, pero libB hace referencia a una función en libA), simplemente especifique las bibliotecas en la línea de comando dos veces. Esto es lo que también sugiere la página del manual. P.ej
gcc foo.c -lfoo -lbar -lfoo
Utilice los parámetros
-(
y-)
para especificar un grupo de archivos que tengan dependencias circulares. Consulte el manual del vinculador GNU para--start-group
y--end-group
. Consulte aquí para obtener más detalles.
Cuando utiliza la opción 2 o 3, probablemente esté introduciendo un costo de rendimiento por vincular. Si no tienes mucho que vincular, puede que no importe.
O usar volver a escanear
de la página 41 de la Guía de bibliotecas y enlazadores de Oracle Solaris 11.1 :
Pueden existir interdependencias entre archivos, de modo que la extracción de miembros de un archivo deba resolverse extrayendo miembros de otro archivo. Si estas dependencias son cíclicas, los archivos deben especificarse repetidamente en la línea de comando para satisfacer las referencias anteriores.
$ cc -o prog .... -lA -lB -lC -lA -lB -lC -lA
La determinación y el mantenimiento de especificaciones de archivo repetidas pueden resultar tediosos.
La opción -z rescan-now simplifica este proceso. El editor de enlaces procesa la opción -z rescan-now inmediatamente cuando se encuentra la opción en la línea de comando. Todos los archivos comprimidos que se hayan procesado desde la línea de comando antes de esta opción se reprocesan inmediatamente. Este procesamiento intenta localizar miembros de archivo adicionales que resuelvan referencias de símbolos. Esta nueva exploración del archivo continúa hasta que se pasa por la lista de archivos en la que no se extraen nuevos miembros. El ejemplo anterior se puede simplificar de la siguiente manera.
$ cc -o prog .... -lA -lB -lC -z rescan-now
Alternativamente, las opciones -z rescan-start y -z rescan-end se pueden usar para agrupar archivos mutuamente dependientes en un grupo de archivos. El editor de enlaces reprocesa estos grupos inmediatamente cuando se encuentra el delimitador de cierre en la línea de comando. Los archivos encontrados dentro del grupo se reprocesan en un intento de localizar miembros de archivo adicionales que resuelvan las referencias de símbolos. Esta nueva exploración del archivo continúa hasta que se pasa por el grupo de archivos en el que no se extraen nuevos miembros. Usando grupos de archivos, el ejemplo anterior se puede escribir de la siguiente manera.
$ cc -o prog .... -z rescan-start -lA -lB -lC -z rescan-end