Cómo se usan y se hacen las Bibliotecas
Este pequeño documento lo escribo con la intención de ayudar a aclarar algunos puntos para el uso diario , y creación de bibliotecas (2) . He intentado redactarlo de forma sencilla, de manera que pueda servir al recien llegado al mundo UNIX que quiera tener una introducción al tema.
Intentaré describir de un modo sencillo cómo se usan las bibliotecas compartidas en los programas mediante un sencillísimo ejemplo que crea y usa bibliotecas. Este manual puede servir como primera aproximación a todo el que empiece a programar con UNIX y quiera ver de una forma rápida cómo se usan las bibliotecas dinámicas. Este documento hace referencia al formato ejecutable ELF, y solo se harán breves referencias sobre a.out.
Para todos aquellos que tengan más interés sobre el tema, pueden referirse a la bibliografía que apunto al final ( man pages, howtos y otras cosas facilmente encontrables en internet ).
Las bibliotecas son archivos de funciones precompiladas. Esto es, son archivos que contienen trozos de código que realizan funciones específicas. Las bibliotecas son colecciones de módulos u objetos en cada uno de los cuales se definen una o varias funciones.
Si atendemos a la forma en que la biblioteca es generada podemos distinguir entre bibliotecas estáticas y dinámicas. Atendiendo al formato en que los objetos de una biblioteca fueron compilados podemos distinguir entre bibliotecas ELF y a.out.
El nombre de una biblioteca generalmente empieza por `lib', va seguido de una parte que identifica a la biblioteca, y termina en `.a', `.sa' ó en `.so.xx.yy.yy', en donde xx yy zz son números.
Las bibliotecas terminadas en `.a', son bibliotecas estáticas, esto es, que se utilizan para crear programas compilados estáticamente. Las bibliotecas terminadas en `.sa', son bibliotecas que se usan en el momento de la compilación de programas en formato a.out, para crear programas enlazados dinámicamente que usarán la biblioteca `.so.xx.yy.zz' correspondiente al ejecutarse.
Las bibliotecas `.a' y `.sa' son las (mal) llamadas bibliotecas de desarrollo.
Las bibliotecas `.sa' se usan sólamente al crear programas `a.out' que vayan a usar la biblioteca dinámica. Estas bibliotecas no tienen ninguna función salvo que tengamos previsto compilar programas en este formato.
Las bibliotecas `.a', son usadas en la creación de ejecutables enlazados estáticamente, esto es, que no necesitan de la biblioteca dinámica para ser ejecutados. Existen bibliotecas `.a' en formato `ELF' y en formato `a.out', es decir, que contienen o bien funciones compiladas en formato ELF o en formato, a.out. Las bibliotecas en formato a.out, tanto estáticas como dinámicas se deben colocar en `/usr/i486-linuxaout/lib', y las que están en formato ELF irán en `/usr/lib, /lib, /usr/X11/lib'.
-- ejemplo -- Cómo reconocer una biblioteca estática ELF o a.out:
** saco un módulo de una biblioteca ELF quique-~/libs>$ ar xv /usr/X11/lib/libXtst.a XTest.o x - XTest.o ** compruebo su formato quique-~/libs>$ file XTest.o XTest.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 ** saco un módulo de una biblioteca a.out quique-~/libs>$ ar vx /usr/i486-linuxaout/lib/libXtst.a XTest.o x - XTest.o ** compruebo su formato quique-~/libs>$ file XTest.o XTest.o: Linux/i386 object file
Las bibliotecas terminadas en `.so.*', son las llamadas bibliotecas dinámicas o compartidas, y son las más importantes de todas desde que se ha extendido el formato ELF. Las bibliotecas dinámicas se usan cuando enlazamos un programa dinámicamente y tambien en el momento de la ejecución del programa. Al igual que ocurre con las bibliotecas estáticas `.a', existen versiónes ELF y una a.out de ellas.
Utilidad para crear y extraer miembros de una biblioteca ar
. Con
`ar'
(abreviación de 'archive'), podemos ver, extraer y añadir miembros a una
biblioteca. En el ejemplo de la sección primera, usamos ar
para extraer
el miembro `XTest.o' de una biblioteca,
Utilidad para examinar los miembros de una biblioteca nm
. Esta utilidad
nos
va a permitir examinar las funciones contenidas dentro de una biblioteca y
las funciones declaradas en su interior pero no definidas en ella. `nm'
sirve igualmente para examinar las funciones definidas en un fichero objeto,
esto es un fichero terminado en `.o' .
-- ejemplo -- `nm' , el examinador de bibliotecas
** Examinando un objeto compilado con PIC quique-~/libs>$ nm shared/mylib.o U _GLOBAL_OFFSET_TABLE_ <-- Es un objeto con PIC 00000000 t gcc2_compiled. 00000000 T itoa <-- Define la función itoa U malloc <-- Necesita la función malloc U sprintf <-- y sprintf ** Examinando un objeto normal quique-~/libs>$ nm ejemplo1.o 00000000 t gcc2_compiled. U itoa <-- Necesita de la funcion itoa 00000000 T main <-- Define la funcion main U printf <-- Necesita de la funcion printf ** Examinando una biblioteca estática quique-~/libs>$ nm static/libmia.a mylib.o: 00000000 t gcc2_compiled. 00000000 T itoa <-- Define la funcion itoa U malloc <-- Necesita de malloc U sprintf <-- Necesita sprintf
Utilidad para comprobar las dependencias dinámicas de un ejecutable, `ldd'. ldd es un programa que nos permite averiguar dos cosas: qué bibliotecas necesita un programa para ejecutarse, y si estas están o no disponibles.
-- ejemplo -- `ldd' , el comprobador de dependencias para ejecutables
quique-~/libs>$ ldd ejemplo2 libmia.so.1 => /home/quique/ta/shared/libmia.so.1 libc.so.5 => /lib/libc.so.5.3.12
Nota: Se deja al lector que pruebe a ejecutar ldd con llos otros dos ejecutables generados en nuestro ejemplo.
`ld.so' es un programa que nos facilita el trabajo con bibliotecas dinámicas. Se encarga de localizarlas y de extraer las funciones de ella necesitadas por nuestros ejecutables. `ld.so' busca las bibliotecas dinámicas siguiendo un fichero de configuracion (normalmente `etc/ld.so.conf' ) , y una variable de entorno `LD_LIBRARY_PATH'
-- ejemplo -- Mostrando a `ld.so' el camino a las bibliotecas
** ld.so no encuentra la biblioteca quique-~/libs>$ ldd ejemplo2 libmia.so.1 => not found libc.so.5 => /lib/libc.so.5.3.12 ** mostramos el camino a ld.so quique-~/libs>$ export LD_LIBRARY_PATH=/home/quique/libs/shared ** ahora sí que la encuentra quique-~/libs>$ ldd ejemplo2 libmia.so.1 => /home/quique/libs/shared/libmia.so.1 libc.so.5 => /lib/libc.so.5.3.12
Existe una versión de `ld.so' para ejecutables en formato ELF y otra para los que están en formato a.out.
El determinador de enlaces para bibliotecas dinámicas `ldconfig', crea los enlaces simbólicos necesarios desde las bibliotecas dinámicas para que `ld.so' pueda encontrarlas.
-- ejemplo -- El generador de enlaces simbólicos a bibliotecas compartidas, permite usar bibliotecas que son distintas a las que se usaron para compilar.
quique-~>$ ldd /bin/ls libc.so.4 (DLL Jump 4.6pl27) => /lib/libc.so.4.7.2
Significa que ls
necesita de `libc.so.4' .
Yo tengo la versión 7.2 de esa
biblioteca y se habrá creado un link desde ella a `libc.so.4' para que los
programas que la necesiten la encuentren.
En este caso ldd nos muestra tambien la biblioteca con la que fue compilado
`ls' ( la version 4.6 patch level 27 )
`ldconfig' creará los links a las bibliotecas más modernas. ldconfig buscará en los directorios indicados en `/etc/ld.so.conf', y creará en ellos links a las bibliotecas con número de versión mayor.
Ej: ldconfig -v | more
creará los links necesarios y nos informará de cuales
han cambiado.
Junto con este documento he incluido un ejemplo que nos permitirá ilustrar el resto de la explicación. El ejemplo construye una biblioteca dinámica, una estática y tres ejecutables enlazados de distinta forma:
Los tres ejecutables hacen lo mismo: convertir el entero 1 a una cadena de
texto usando la función char *itoa( int )
incluida en nuestra
biblioteca, y mostrar esa cadena usando printf()
. El ejemplo se compone
de los siguientes archivos:
-- tabla -- Archivos incluidos en el ejemplo
Makefile Instrucciones para make ejemplo1.c Nuestro programa ejemplo2.c Nuestro programa usando las funciones de libdl mylib.c Fuente para contruir las bibliotecas mylib.h Archivo de cabecera para usar las bibliotecas en nuestros programas
Y aquí el resultado después de la compilación , especificando los archivos a partir de los cuales se genera, y los archivos que necesitamos para ejecutar los tres programas que se generan:
-- ejemplo -- Dependencias al compilar y ejecutar los ejemplos
Archivo Objetivo Compila con Se ejcuta con static/libmia.a ______ mylib.c ** no ejecutable ** |____ mylib.h shared/libmia.so.1.0 ______ mylib.c ** no ejecutable ** |____ mylib.h ejemplo1 ______ ejemplo1.c libc.so |____ mylib.h |____ static/libmia.a ejemplo2 ______ ejemplo1.c libc.so |____ mylib.h shared/libmia.so.1.0 |____ shared/libmia.so.1.0 ejemplo3 ______ ejemplo3.c libc.so |____ libdl.so libdl.so shared/libmia.so.1.0
Las bibliotecas sirven al programador permitiéndole mantener en ellas partes de código separados del ejecutable. Mediante las bibliotecas el programador puede realizar las actualizaciones y ampliaciones a sus programas de una forma más sencilla a la vez que posibilita el reuso de funciones previamente escritas en nuevos programas.
Cuando construimos bibliotecas para nuestros programas, podemos hacerlo de diferentes formas. En los ejemplos que acompañan a este documento se muestran las tres formas más habituales de usar las funciones contenidas en una biblioteca en nuestros programas.
El gran problema de usar la primera forma, está en el tamaño de los ejecutables, ya que de la biblioteca ( `.a' ) se extraerá en tiempo de compilación etapa de enlace ) todas las funciones necesarias, y se incluirán en nuestro ejecutable.
El ejemplo numero 1 muestra cómo se compila un programa estáticamente. El compilador de GNU compilará automaticamente de forma estática si no es capaz de encontrar la biblioteca compartida. En este ejemplo , el ejecutable está compilado estáticamente contra nuestra biblioteca `libmia.a' , aunque dinámicamente contra `libc.so'
-- ejemplo -- Comprobando las dependencias un ejecutable estático
quique-~/libs>$ ldd ejemplo1 ejemplo1: libc.so.5 => /lib/libc.so.5.3.12
ldopen()
en tiempo de ejecuciónEstá indicada para casos en los que el usuario puede definir sus propias funciones que serán usadas por el programa. Un ejemplo típico de esto son las funciones definidas por el usuario de una base de datos. Presenta la ventaja de que el usuario puede definir sus propias bibliotecas y mantenerlas en su estructura de ficheros. Es quizá el caso más inusual.
El ejemplo 2, muestra este caso. Es la forma más complicada de usar
bibliotecas, ya que somos nosotros, con la ayuda de ldopen()
quienes
cargamos la función necesaria en tiempo de ejecución. ldopen()
está
definida en la biblioteca `libdl.so' y en nuestro ejemplo, el ejecutable
se enlazará dinámicante con ella para tomar la función ldopen()
en
tiempo de ejecución.
-- ejemplo -- Comprobando las dependencias de un ejecutable que usa ldopen()
quique-~/libs>$ ldd ejemplo2 ejemplo2: libdl.so.1 => /lib/libdl.so.1.7.14 libc.so.5 => /lib/libc.so.5.3.12
Es el método que se usa normalmente, aprovecha todas las ventajas de las bibliotecas compartidas. En nuestro ejemplo numero 3, la biblioteca `libmia.so.1' se enlazará dinámicamente con el programa.
-- ejemplo -- Comprobando las dependencias de un ejecutable dinámico puro
quique-~/libs>$ ldd ejemplo3 ejemplo3: libmia.so.1 => /home/quique/libs/shared/libmia.so.1 libc.so.5 => /lib/libc.so.5.3.12
Nuestro pequeño ejemplo crea dos bibliotecas, una estática ( `libmia.a' )
y otra
dinámica ( `libmia.so.1.0' ) que serán usadas por nuestros programas.
Estas bibliotecas contienen una sola función, char* itoa(), que convierte
un número entero a un array de caracteres.
La creación de la biblioteca estática es muy sencilla. Simplemenete compilar
el fichero mylib.c dónde se define la función itoa()
, y crear un archivo
que lo contenga con ar
:
-- ejemplo -- Generando una biblioteca estática
quique-~/libs>$ cc -c mylib.c -o static/mylib.o quique-~/libs>$ cd static quique-~/libs/static>$ ar rv libmia.a mylib.o a - mylib.o
La creación de una biblioteca dinámica es algo más complicada, aunque tampoco tanto. En comparación con la vieja forma de hacerlo, para biblioteca a.out, es realmente sencillo:
-- ejemplo -- Generando una biblioteca dinámica
quique-~/libs>$ gcc -fPIC -c mylib.c -o shared/mylib.o quique-~/libs>$ cd shared quique-~/libs/shared>$ gcc -shared -Wl,-soname,libmia.so.1 \ -o libmia.so.1.0 mylib.o quique-~/libs/shared>$ ln -f -s libmia.so.1.0 libmia.so.1
El último paso, la creación del enlace, se lo podíamos haber dejado a ldconfig que lo hiciese automáticamente:
quique-~/libs>$ ldconfig -n -v shared/ ldconfig: version 1.7.14 shared/: libmia.so.1 => libmia.so.1.0 (changed)
Si la biblioteca no está en uno de los PATHS definidos en `/etc/ld.so.conf', deberemos
añadir a la variable de entorno LD_LIBRARY_PATH
el directorio en el que
`ld.so' puede encontrar la biblioteca.
quique-~/libs>$ export LD_LIBRARY_PATH=/home/quique/libs/shared