Blog de Abelardo Jara Berrocal: Ubuntu, electronica y software libre

Marzo 20, 2008

Ejecutando programas para versiones previas de Linux dentro de Fluxbuntu Gutsy mediante chroot o el uso de entornos de compatibilidad

Archivado en: Linux Ubuntu Administracion — Abelardo Jara @ 4:51 pm

Uno de los mejores programas para diseño de layouts para chips VLSI y tal vez el estándar a nivel industria es Cadence Virtuoso. En Internet (Emule) podemos conseguir un “backup” de las versiones IC5033 y de la IC5141.

Conseguí mi backup del Cadence IC5033. Lo recomiendo bastante para el curso de Microelectrónica de mi universidad, dado que es difícil conseguir un Cadence trabajando para dictado de las clases. Una alternativa libre muy buena es el Magic VLSI y el IRSIM las cuales también son muy buenas y se integran bien con el Hspice de Synopsys.

Sin embargo tuve problemas para instalaro dado que en Ubuntu Gutsy, la versión de libc es la 6 (libstdc++.so.6) que viene de Ubuntu Edgy para arriba. Así que tuve que hacer algunos pasos para instalarlo. En este artículo trataré de explicar los problemas que se presentan con las librerías libc en Linux y como resolverlos (con lo que pude finalmente ejecutar Cadence Virtuoso :) )

Las librerías en Linux

Un programa de Linux usualmente consiste de código máquina que ejecuta un conjunto de funciones específico. Normalmente, algunas de estas funciones son provistas por librerías. Tales librerías pueden existir como una versión estática (de extensión .a) que se enlazan (linkean) copiando la parte de su contenido que se necesita dentro de nuestro archivo ejecutable, o las llamadas librerías dinámicas (de extensión .so) que existen como un objeto dinámico y que se comparte entre varias aplicaciones.

Cuando el programa es creado, el desarrollador decide si usará las funciones o código definido dentro de una librería estática o una dinámica.

Una librería dinámica o compartida (.so en Linux y .dll en Windows) tiene la ventaja que su código máquina solo es cargado en la memoria de la computadora una vez y a su vez es compartida por todos los programas que la estén utilizando.

Otro beneficio grande es que el administrador del sistema puede fácilmente actualizar la librería compartida sin tener que tocar ninguno de los programas que la están utilizando. Esto es muy útil para actuaizacioes de seguridad.

Comento que lo que hemos visto arriba no es específico de Linux, sino de otros sistemas operativos modernos (como Winbugs)
.

Comienzan nuestros problemas

El problema principal se nos presenta cuando nos descargamos un programa propietario en la mayoría de los casos, del cual no tenemos el código fuente por lo que no lo podemos recompilar, y nos damos cuenta que quien lo compilo lo hizo usando librerías del sistema anteriores a las de nuestro moderno Ubuntu Gutsy. Lo más probable es que nuestro programa no funcione para nuestra desilusion.

Este problema ya se presentó antes con la transición de libc4 a libc5 o de libc5 a libc6 (libc6 es la librería actual que usa Ubuntu Gutsy). libc o también llamada la “librería C” es probablemente la librería más importante que se encuentra en sistemas UNIX o Linux, debido a que actúa como una interface entre nuestra aplicación y el kernel.

Cambios en esta librería pueden fácilmente dejar un programa sin funcionamiento, dado que en muchos casos los cambios que se han introducido en esta librería son incompatibles con las versiones anteriores.

También y aunque quizás no tan común para nosotros es que el programa que queremos ejecutar fue compilado en una máquina con versiones de libc más modernas a las que tiene nuestra máquina. Pero dado que siempre queremos tener nuestro Gutsy actualizado a la última versión, no creo que este sea el caso para nosotros.

Por sentido común, versiones de librerías nuevas que tienen funcionalidades no incluídas en versiones antiguas, llevan un número de versión indicando que el programa fue “linkeado” (o enlazado) con una versión más antigua de modo que no puede ser cargado y ejecutado. Este número de versión es llamado la revisión mayor de una librería compartida. El siguiente diagrama explica esta idea usando glibc como un ejemplo.

Version de GLIBC Nombre del archiv en Ubuntu Gutsy
libc5 /lib/libc.so.5
libc6 /lib/libc.so.6

Para mantener ciertos cambios de versión menores de una librería compartida, el nombre del archivo está aumentado con números adiciones; por ejemplo en mi máquina:

abelardo@fluxbuntu-abelardo:/lib$ ls -l libc.so.6
lrwxrwxrwx 1 root root 13 Mar 15 08:40 libc.so.6 -> libc-2.6.1.so

Como vemos, hay varias complicaciones causadas por incompatibilidad entre las librerías C++ compiladas con los compiladores GNU Classpath C++ anteriores al GCC 3.3 (la mayoría de las veces el GCC 2.95, como vimos en el artículo que publique de instalar Matlab 7 en Fluxbuntu :) )

Incluso actualmente con un Linux habiendo sido estandarizado incluyendo apropiados estándares para sus deficiniciones de API y ABI, no está completamente claro si las versiones futuras de los compiladores de C++ y sus librerías se mantendrán 100% compatibles.

Por estos problemas, la Linux Standards Base (LSB) recomienda que cada desarrollador de software propietario que desarrolla programas escritos en C++ debería “linkear” estáticamente las partes de su SW escritas en C++. Desafortunadamente, pocos desarrolladores de SW de código fuente cerrado siguen esta práctica.

En este artículo describiré 2 métodos para poder ejecutar programas que fueron enlazados usando librerías antiguas de C++. Espero que les sea de utilidad.

Diagnóstico del problema:

El problema se presenta generalmente cuando queremos ejecutar un programa escrito en C o C++ y nos da este mensaje:

Mensaje de error cuando ejecutamos un programa sin código fuente escrito en C o C++
./a.out: relocation error: ./a.out: symbol errno, version GLIBC_2.0 not defined in file libc.so.6 with link time reference

Si tu ves este mensaje de error acerca de un símbolo errno, entonces tu programa fue enlazado con una versión de glibc posterior a la 2.3. Las versiones posteriores a la 2.3 no tienen definido más errno como una variable global, para permitir el uso seguro de su “thread”. En este caso necesitarás utilizar un entorno de ejecución que permita compatibilidad con versiones de glibc anteriores (en particular glibc 2.2.5).

Solución del problema

1. La solución antigua: EXPORTANDO LA VARIABLE LD_ASSUME_KERNEL EN UBUNTU DAPPER

Hasta Ubuntu Dapper, se podía habilitar un modo de compatibilidad con kernels anteriores que utilizaran versiones de libc antiguas. Para esto, en línea de comando se podía exportar una variable LD_ASSUME_KERNEL de esta manera:

>>export a LD_ASSUME_KERNEL=2.4.1

Y luego ya se podía ejecutar esos programas antiguos que no teníamos su código fuente (versiones antiguas de Java por ejemplo, o programas propietarios como Matlab, Cadence, Modelsim, Oracle, etc).

Si sólo queríamos que un programa en particular usara compatibilidad con el kernel antiguo podíamos definir un alias en nuestro .bashrc

alias matlab=’LD_ASSUME_KERNEL=2.4.1 matlab”

Sin embargo esta compatibidad fue descartada en los kernels de Ubuntu Edgy en adelante. La razón fue que la implementación de los threads en Linux cambió radicalmente. POSIX (se llama así al conjunto de APIs para C/C++ en Linux) especifica un conjunto de interfaces (funciones y cabeceras .h) para aplicaciones que usan multi-hilos (o “multi-threaded”) que se llama POSIX-threads o “pthreads”. Un proceso simple puede contener múltiples “threads”, cada uno de los cuales puede estar ejecutando el mismo programa. Estos threads comparten la misma memoria global (segmentos de data y de “heap” o pila), pero cada thread tiene su propio stack (variables automáticas).

La implementación antigua de pthreads se llamaba “Linuxthreads” y en libc6 se le descarta por haberse hecho obsoleta. A partir de libc6 la implementación de pthreads se llama NPTL (Native Posix Threads Library) que es una implementación moderna de pthreads. NPTL brinda una performance superior para aplicaciones con un número largo de threads o hilos y está de acuerdo a los requerimientos de los estándares definidos para POSIX. NPTL usa nuevas características que están disponibles en los kernel 2.6 de Linux. Debido a esto, el truco de exportar LD_ASSUME_KERNEL=2.4.1 no funciona más a partir de Ubuntu Edgy.

METODO 2: INSTALANDO UNA BOTELLA O JAULTA CHROOT DE DAPPER DE 32 BITS DENTRO DE GUTSY

La solución más directa sería instalar una botella o jaula “chroot” de Ubuntu Dapper dentro de mi Ubuntu Gutsy.

En este caso este método también es útil si tenemos una máquina con procesador AMD64 bits y queremos tener una botella con Ubuntu Dapper y que solo corra versiones de programas de 32 bits (para realizar compilación cruzada o porque un programa que necesito ejecutar no tiene versión para Ubuntu de 64 bits).

En mi caso haré la instalación de mi botella Dapper en /opt/dapperchroot

Primero debemos logueranos como usuario root:

#su root

(nos pide password)# mkdir /opt/dapperchroot
# apt-get install schroot debootstrap

Ahora necesitamos una imagen de Ubuntu Dapper

#wget http://archive.ubuntu.com/ubuntu/pool/main/d/debootstrap/debootstrap_1.0.7~dapper1_all.deb

Instalamos la imagen:

#dpk -i debootstrap_1.0.7~dapper1_all.deb

Instalamos un Ubuntu Dapper con configuración mínima de paquetes en /opt/dapperchroot

# sudo debootstrap –variant=buildd –arch i386 dapper /opt/dapperchroot http://archive.ubuntu.com/ubuntu/

Ahora necesitaremos enlazar nuestras carpetas de sistema de nuestro Ubuntu Gutsy dentro de nuestra botella, esto lo hacemos con el propósito de que nuestro Ubuntu Dapper embotellado reconozca a los mismos usuarios de Gutsy como sus usuarios y además les permita abrir conexiones X11. De este modo los programas que se ejecuten dentro de Ubuntu Dapper podrán usar el escritorio de nuestro Ubuntu Gutsy.

NOTA SUPER MEGA ARCHI IMPORTANTE: No borres tu directorio con tu botella o jaula chroot de Dapper desde dentro de Gutsy, ya que has enlazado dentro de esa botella directorios muy importantes del sistema como /dev /proc /etc y otros de modo que si borras el directorio donde esta tu botella, te llevaras también esos directorios de tu Ubuntu Gutsy (no me reclamen si esto les sucede jeje, pero es desagradable)

Editamos:

#gedit /etc/fstab file

Y añade las líneas siguientes:

/dev /opt/dapperchroot/dev none bind 0 0 #es bueno pero no muy seguro
/proc /opt/dapperchroot/proc none bind 0 0
/usr/share/fonts /opt/dapperchroot/usr/share/fonts none bind 0 0
/media /opt/dapperchroot/media none bind 0 0 #acceso a puertos, USBs, etc
/home /opt/dapperchroot/home none rbind 0 0
/tmp /opt/dapperchroot/tmp none bind 0 0 #esto abre puertas/sockets, para X11
/etc/passwd /opt/dapperchroot/etc/passwd none bind 0 0
/etc/shadow /opt/dapperchroot/etc/shadow none bind 0 0
/etc/group /opt/dapperchroot/etc/group none bind 0 0
/etc/hosts /opt/dapperchroot/etc/hosts none bind 0 0

Graba y sal, luego deberás crear algunas carpetas de sistema básicas dentro de tu botella para Dapper:

# mkdir -p /opt/dapperchroot/usr/share/fonts
# mkdir -p /opt/dapperchroot/media/cdrom0
# touch /opt/dapperchroot/etc/shadow
# touch /opt/dapperchroot/etc/hosts
# mount -a

Ahora ya esta listo tu Ubuntu Dapper, pero debemos poder iniciar en él, para eso usaremos el programa schroot, el cual lo instalamos antes junto con debootstrap. Para eso edita el archivo de configuración de schroot haciendo:

# gedit /etc/schroot/schroot.conf

Y añadí las siguientes líneas:

[dapper]
description=Ubuntu Dapper
location=/opt/dapperchroot
priority=3
users=abelardo
groups=abelardo
root-groups=root
aliases=default,dapper32,dapperchroot
personality=linux32
type=plain

Cambia en esas líneas por tu nombre de usuario y grupo, la línea personality=linux32 es muy importante dado que es la que indica que estás tratando de usar una botella de arquitectura de 32 bits (no de 64 bits) y lo otro que es importante es la línea que dice aliases=default, lo que hará que esta botella sea la que se ejecute por defecto. Esto es importante si quieres tener varias botellas con diferentes Linux (Redhat, Debian, otros, etc).

Ahora ya puedes iniciar tu Ubuntu Dapper en su botella o “chroot” . (A las botellas chroot también se les llama jaulas, que tal nombre, no?)

Inicialo con schroot -d dapper (dapper es el nombre de la botella en tu schroot.conf que tiepamos) o simplemente con el comando schroot:

root@fluxbuntu-abelardo:/etc/apt# schroot
I: [dapper chroot] Running login shell: ‘/bin/bash’
root@fluxbuntu-abelardo:/etc/apt#

Ahora necesitamos definir los repositorios en nuestra botella Dapper:

# echo “deb http://pe.archive.ubuntu.com/ubuntu/ dapper main restricted universe multiverse” > /etc/apt/sources.list
# echo “deb http://security.ubuntu.com/ubuntu dapper-security main restricted universe multiverse” >> /etc/apt/sources.list
# apt-get update

# apt-get upgrade
# dpkg-reconfigure locales

Listo nuestra botella de Dapper esta lista, mientras que estés logueado dentro de tu botella de Dapper puedes instalar cualquier aplicación que necesites con un apt-get (como sabemos :) )

Si estando dentro de Dapper recibimos un mensaje al ejecutar una aplicación que requiere X11 (graficos):

# schroot

#mi_aplicacion

“Xlib: connection to “:0.0″ refused by server”

Entonces ejecuta estas líneas para llamar a Dapper antes:

# xhost +localhost && schroot  –> con esto ya entrarás en tu botella Dapper

#export DISPLAY=:0.0
# application_name

Ahora ya puedes instalar dentro de tu Dapper cualquier aplicación que requiera el uso de LD_ASUMME_KERNEL=2.4.1

Si tu aplicación es propietaria y te dice que le falta algún archivo, entonces a medida que te diga que le falta tal o cual archivo instala el paquete que los contiene con un apt-get. El nombre del paquete .deb lo encuentras:

packages.ubuntu.com

Listo! :D Ahora puedes instalar Cadence, Oracle, una versión antigua de Java o lo que necesites. Espero que les sea util esta forma.

En esta ventana les muestro Dapper corriendo dentro de Fluxbuntu, e introduciendo un comando “ls” después de haber activado compatibilidad con kernel 2.4.1. Gutsy no acepta activar esta compatibilidad dado que NPTL, es el nuevo API para threads.

Una jaula chroot de Dapper corriendo en Fluxbuntu

MÉTODO 3: DEFINIENDO UN ENTORNO DE COMPATIBILIDAD DENTRO DE UBUNTU GUTSY

En este caso, lo que necesitamos es crear un directorio dentro de Gutsy donde coloquemos el programa de Linux ld-linux.so.2

Los binarios (o ejecutables) en Linux por lo general se les llama «ejecutables impuros». Se le llama ejecutable impuro al ejecutable que requiere de código externo para correr. Para ahorrar código y espacio en memoria, prácticamente todos los ELF (y los a.out) son impuros.
Ahora bien, ellos sólo poseen las librerías que necesitan que son
externas, pero más nada. El kernel de Linux posee una serie de módulos llamados cargadores, que se encargan de montar y enlazar dinámicamente (el proceso de buscar las librerías necesarias para que el ejecutable tenga lo que necesite) en emoria para iniciar la ejecución. El muy importante archivo ld-linux.so es el enlazador dinámico (runtime linker) para los binarios de la glibc. Cuando se carga un ejecutable cargador llama al ld-linux.so para preparar el enlace y la futura jecución. l-linux.so es un ELF pero puro, no requiere de librerías externas (si no tendrías el problema del huevo y la gallina jeje), y hace sa tarea.

NOTA IMPORTANTE: Si un binario fue compilado bajo una versión antigua de GLIBC entonces debe ser enlazado dinámicamente con esa versión (ld-linux.so).

Algunos programas muy antiguos por ejemplo fueron compilados con GLIBC2.0, (ejemplo primeras versiones de Netscape) y esa versión de GLIBC esta en particular plagada de errores que fueron por supuesto resueltos en las siguientes versiones.

Para que podamos definir un entorno de ejecución usando un ld-linux.so de un Linux antiguo, necesitaremos también de las librerías .so antiguas que usaba ese programa antiguo que queremos ejecutar.

Para saber que librerías requiere nuestro programa que queremos ejecutar podemos usar el comando ldd:

#ldd /bin/bash
linux-gate.so.1 => (0xffffe000)
libreadline.so.4 => /lib/libreadline.so.4 (0×40036000)
libhistory.so.4 => /lib/libhistory.so.4 (0×40062000)
libncurses.so.5 => /lib/libncurses.so.5 (0×40069000)
libdl.so.2 => /lib/libdl.so.2 (0×400af000)
libc.so.6 => /lib/tls/libc.so.6 (0×400b2000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0×40000000)
If a shared library cannot be found, an error message will be shown:
libfoo.so.1 => not found

Entonces así sabemos de que librerías dinámicas require nuestro programa y estas .so (versiones antiguas por supuesto) son las que debemos colocar en nuestro directorio donde esté nuestro ld-linux.so antiguo y que estamos usando como entorno de ejecución. Generalmente las librerías .so básicas que requerimos son las que vienen en estos paquetes (los números de versión son de ejemplo pero deben concordar con tu ld-linux.so antiguo):

glibc-2.3.2-95.27
libgcc-3.2.3-42
libstdc++-3.2.3-42

Para obtener un ld-linux.so y estas librerías antiguas requerimos tener acceso a una máquina con estos archivos antiguos. Una forma fácil es conseguirse los .rpm de Redhat con los números de versión que les listo arriba. Otra mejor es que los copiemos de un Ubuntu Dapper instalado o de la botella que creamos en el método 2. Veamos los pasos:

Programa que queremos migrar (el nombre es cualquiera, digamos zoo) zoo
Prefijo ($prefix) ubicación o directorio donde ubicaremos estos archivos con ld-linux.so y sus librerías .so /opt/compat-env/zoo
Lista de archivos requeridos glibc-2.3.2-95.27
libgcc-3.2.3-42
libstdc++-3.2.3-42
($srcdir) Directorio origen donde los archivos requeridos fueron descomprimidos /root/zoo-src

Paso 1

Crea directorios bin y lib dentro del directorio que definiste como $prefix

% prefix=/opt/compat-env/zoo
% mkdir -p $prefix/bin $prefix/lib

Paso 2

Coloca copias de los archivos requeridos en los directorios adecuados:

% srcdir=/root/zoo-src
% cd $prefix/bin
% cp -p $srcdir/zoo .
% cd $prefix/lib
% rpm2cpio $srcdir/glibc-2.3.2-95.27*.rpm | cpio -idvm ‘*libc*.so*’
% find . -name ‘*libc*.so*’ -exec mv -v ‘{}’ . \;
% rpm2cpio $srcdir/glibc-2.3.2-95.27*.rpm | cpio -idvm ‘*ld*.so*’
% find . -name ‘*ld*.so*’ -exec mv -v ‘{}’ . \;
% rpm2cpio $srcdir/libgcc-3.2.3-42*.rpm | cpio -idvm ‘*libgcc_so*.so*’
% find . -name ‘*libgcc_so*.so*’ -exec mv -v ‘{}’ . \;
% rpm2cpio $srcdir/libstdc++-3.2.3-42*.rpm | cpio -idvm ‘*libstdc*.so*’
% find . -name ‘*libstdc*.so*’ -exec mv -v ‘{}’ . \;
% rm -rf lib usr

Paso 3

Creamos un script “wrapper” para iniciar las variables de entorno apropiadas de nuestro entorno y empezar el programa que queremos:

% cd $prefix/bin
% mv zoo zoo.exec
% cat > zoo << EOF
#! /bin/bash
prefix=/opt/compat-env/zoo
LD_LIBRARY_PATH=?$prefix/lib${LD_LIBRARY_PATH+:${LD_LIBRARY_PATH}}?
export LD_LIBRARY_PATH
$prefix/lib/ld-linux.so.2 ${0}.exec ?$@?
EOF
% chmod 755 zoo

Ahora el programa “zoo” puede ser ejecutado y las versiones de glibc, libgcc y libstdc++ que usemos serán las requeridas y usadas en versiones antiguas.

Y si ańn tengo problemas?

Sin embargo, aún podemos tener problemas, a veces podemos tener un problema con este mensaje de error:

symbol __libc_wait, version GLIBC_2.0 not defined in file libc.so.6 with link time reference

Las versiones nuevas de las librerías GNU C asignan números de versión no solo a la librería como un conjunto, sino también a los símbolos individuales también. Esto se hace para adecuarse a estándares de ciertos lenguajes, en particular el símbolo global __libc_wait con número de versión GLIBC_2.0 no está definido más. Esto puede ser resuelto de nuevo usando una versión antigua de la librería C GNU o uno puede usar una característica medio truco de glibc y de su enlazador en tiempo de ejecución (runtime linker). Es posible de sobreescribir símbolos definidos en objetos compartidos los cuales pueden ser cargados en el curso de la resolución de dependencias del “runtime linker” (ld-linux.so).

Esto puede ser usado para definir símbolos que de otro manera no existen como en particular la función __libc_wait que necesitamos.

El arreglo adecuado para este problema es crear un pequeño objeto compartido conteniendo la implementación requerida y cargarlo en el script de inicio de carga.

% cd $prefix/lib
% cat > libcwait.c << EOF

#include <errno.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
pid_t __libc_wait (int *status)
{
int res;
asm volatile (“pushl %%ebx\n\t”
“movl %2, %%ebx\n\t”
“movl %1, %%eax\n\t”
“int $0×80\n\t”
“popl %%ebx”
: “=a” (res)
: “i” (__NR_wait4), “0″ (WAIT_ANY), “c” (status), “d” (0),
“S” (0));
return res;
}
EOF
#gcc -fpic -O2 -shared libcwait.c -o libcwait.so

Ahora debemos hacer unas modificaciones al script de cargado:

% cd $prefix/bin
% cat > zoo << EOF
#! /bin/bash
prefix=/opt/compat-env/zoo
LD_LIBRARY_PATH=?$prefix/lib${LD_LIBRARY_PATH+:${LD_LIBRARY_PATH}}?
export LD_LIBRARY_PATH
LD_PRELOAD=$prefix/lib/libcwait.so
export LD_PRELOAD
$prefix/lib/ld-linux.so.2 ${0}.exec ?$@?
EOF
% chmod 755 zoo

Listo, ahora ya debería funcionar. Este método es el usado por algunos programadores que hacen parches para correr algunos juegos antiguos de Linux como Civilization 3 o los juegos de la empresa Loki que anteriormente publicó juegos para Linux.

Espero que les sea de utilidad. Muchos saludos, viva el Perú y la Universidad Nacional de Ingeniería de Lima.

Si están en Lima les invito con mucho gusto a visitar la empresa de mis padres, Pinturas y Conexos EIRL, Jr.Grau 240 Bellavista Callao, Telefono: 4296426. Contactar con Abelardo Jara Abarca. Somos una pequeña empresa de venta de pinturas y productos de ferreteria, sera un gusto y honor poder atenderlos. Muchas gracias :)

 

Blog de WordPress.com.