Administración de librerías estáticas y dinámicas en sistemas GNU/Linux

En Linux tenemos dos tipos de bibliotecas (o librerías, hacemos referencia a lo mismo): estáticas y dinámicas.

Bibliotecas estáticas

Con extensión .a:

find / -name '*.a'

....
/usr/local/lib/libintl.a
/usr/local/lib/libform.a
/usr/local/lib/libform_g.a
/usr/local/lib/libmenu.a
...

Se copia parte de la librería en los programas cuando se compilan. Dicho de otra manera, la biblioteca se “embebe” en el contenido del programa. Esto tiene varias consecuencias:

  • El programa ocupa más espacio en disco, ya que las librerías necesarias se encuentran en el propio programa. Como ventaja tenemos lo podemos llevar de un sistema a otro sin necesidad de migrar también las librerías (ya que están incluidas)
  • El programa en sí consume más RAM en ejecución, ya que las librerías están embebidas. Sin embargo, se ejecuta en principio más rápido ya que todo lo necesario está incluido en el propio programa.
  • Si una biblioteca que se utilizó durante la compilación se actualizara, sería necesario recompilar el programa para que incluyera los cambios que se han realizado en esa biblioteca.

Bibliotecas dinámicas

También llamadas compartidas. Extensión .so (shared object). Hoy en día son las que se utilizan por norma general:

find / -name '*.so'

...
/usr/lib/amd64/libc.so
/usr/lib/amd64/libc_db.so
/usr/lib/amd64/libcfgadm.so
/usr/lib/amd64/libcmd.so
...

En el caso de las bibliotecas dinámicas, el programa sólo incluye en su código referencias a las bibliotecas, que se localizan en otra parte. Las consecuencias de esto son:

  • Programas de tamaño reducido, ya que sólo incluyen referencias a las bibliotecas y nada embebido. Menor ocupación también en RAM.
  • El programa puede sacar partido de las actualizaciones de las bibliotecas sin necesidad de recompilar.

Pero por otro lado, surgen otra serie de requisitos y problemas:

  • En ocasiones las actualizaciones de bibliotecas no son compatibles con los programas que las utilizan. Por ello hay un sistema de versionado en las bibliotecas compartidas:

ej.: libsocket.so.1

Donde 1 es la versión de la biblioteca compartida libsocket

Para evitar conflicto, muchas veces se actualiza una biblioteca y también se deja como respaldo la versión anterior.

  • Para que los programas localicen las bibliotecas compartidas, será necesario definir una serie de variables de entorno y ficheros de configuración globales.
  • Un problema en una librería compartida afecta a todos los programas que hacen uso de ella.
  • Gran cantidad de dependencias entre librerías compartidas. Si hacemos uso de un programa de gestión de paquetes como yum o apt-get, puede ser menos doloroso por decirlo de alguna manera … si tenemos que resolver las dependencias manualmente, prefiero no hablar.

Con todo, la tendencia es a utilizar librerías compartidas. Las estáticas han quedado relegadas a funciones extrañas, programas antiguos y sistemas de emergencia que obviamente, llevan las bibliotecas embebidas.

Gestión de bibliotecas

Para que los programas puedan localizar las bibliotecas compartidas se puede proceder de dos maneras:

  • Variables de entorno
  • Fichero de configuración global

Fichero de configuración global: ld.so.conf

Localizado en /etc/ld.so.conf

Contendrá:

include ld.so.conf.d/*.conf

Es decir, incluye todos los ficheros (*) de configuración específicos del directorio /etc/ld.so.conf.d/

Cualquier cambio en estas rutas de bibliotecas, y especialmente si instalamos bibliotecas a partir de código fuente, tendremos que ejecutar el comando ldconfig.

¿Qué hace ldconfig?

Crea la caché de biblioteca: un fichero binario localizado en /etc/ld.so.cache y utilizado por los programas que hacen uso de bibliotecas compartidas.

Variables de entorno

Para ello se utilizará la variable LD_LIBRARY_PATH

export LD_LIBRARY_PATH=/usr/local/pruebalib:/usr/local/programa/lib

De esta manera se incluirá en los directorios de búsqueda de bibliotecas a /usr/local/pruebalib y /usr/local/programa/lib

Resolución de problemas

Dependencias: explicación del comando ldd

Para comprobar las librerías que utiliza un programa utilizaremos ldd:

ldd /usr/bin/ssh

...
linux-vdso.so.1 =>  (0x00007fff4d7c4000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f55cb434000)
libgssapi_krb5.so.2 => /usr/lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007f55ca7e2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f55ca423000)
/lib64/ld-linux-x86-64.so.2 (0x00007f55cb8d5000)
...

Donde:

  • linux-vdso.so.1 => : Muestra que no ha encontrado la biblioteca.
  • libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 : La biblioteca necesaria libselinux.so.1 se encuentra (referenciada con => ) en /lib/x86_64-linux-gnu/libselinux.so.1.
  • /lib64/ld-linux-x86-64.so.2 : ruta completa a la librería requerida.
  • Los números hexadecimales (ej .: 0x00007fff4d7c4000) son direcciones de carga de las librerías en memoria. Son aleatorias y no estables, por lo que si volvemos a ejecutar ldd /usr/bin/ssh observaremos que el número ha cambiado.

Localización de bibliotecas

Normalmente, problemas para localizar una biblioteca. Para solucionarlo, podremos proceder de distintas formas:

  • Comprobar que la biblioteca esté instalada con find. Si no lo está, la instalamos.
  • Si la biblioteca está disponible, quizá el programa afectado no pueda localizarla. Podríamos añadirla a la variable LD_LIBRARY_PATH.
  • Si la biblioteca está disponible pero en el sistema recibe un nombre distinto al que espera el programa, tendríamos que hacer un enlace simbólico al nombre de biblioteca al que hace referencia el programa

ej.: Siendo prueba.so.5.2 la biblioteca instalada y prueba.so.5 a la que hace referencia el programa (podemos verlo con ldd)

ln -s prueba.so.5.2 prueba.so.5
ldconfig

Y hasta aquí llegamos con el maravilloso mundo de las librerías de terror de Linux 😀