bibliotecas-HOWT

Cómo se usan y se hacen las Bibliotecas

(1)

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 ).

Qué son, cómo son y dónde están las bibliotecas

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.

Algunas utlidades del sistema relacionadas con bibliotecas.

ar

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,

nm

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

ldd

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

`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.

ldconfig

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.

Un ejemplo de ayuda

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   

Utilización de bibliotecas en nuestros programas

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.

Compilando estáticamente

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

Usando ldopen() en tiempo de ejecución

Está 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

Usando ld.so en tiempo de ejecución

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

Creando bibliotecas

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

(3)

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

Bibliografía

Agradecimientos