FreeBSD posee compatibilidad con Linux a nivel binario. Esto permite instalar y ejecutar la mayoría de los binarios de Linux en FreeBSD sin necesidad de tener que modificar o adaptarlos previamente. Se sabe incluso que en algunas situaciones los binarios para Linux se ejecutan de manera más eficiente en FreeBSD que en Linux. Sin embargo, algunos binarios que utilizan características específicas de Linux no son soportados bajo FreeBSD. Por ejemplo, binarios que utilizan llamas al sistema específicas de i386 como habilitar el modo 8086 virtual. Recientemente se ha incorporado el soporte para binarios Linux de 64 bit.

Cuando el kernel FreeBSD carga un archivo ELF, busca dentro del mismo si se trata de un binario FreeBSD o Linux. Basándose en ésto toma acciones apropiadas en una tabla para el binario. Esta tabla contiene algunos parámetros y punteros que permiten ejecutar el binario. Cuando el programa hace una llamada al sistema operativo (syscall), el kernel busca la función correcta dentro de esta tabla. Esto se hace para todo binario, ya sea FreeBSD o Linux, por ende idealmente no existe emulación alguna (sobrecarga) sino mas bien un simple mapeo de syscalls. Algún comportamiento específico es algo diferente entre algunas llamadas al sistema de Linux y FreeBSD, por lo que se puede llegar a necesitar cierta traducción adicional antes de invocar a la syscall FreeBSD subyacente.

Esta es la razón por la cual una gran cantidad de programas para Linux son ejecutados en FreeBSD a la misma velocidad que si fueran programas FreeBSD.

Por defecto, las librerías de Linux y el modo de compatibilidad se encuentran deshabilitados. Es posible instalar las librerías manualmente, o a través del port emulators/linux_base-c6. Este artículo explica cómo instalar y configurar dicho port, y cómo verificar su funcionamiento.

Antes de comenzar es necesario instalar los módulos del kernel FreeBSD que proveen soporte para la compatibilidad con Linux:

root@fbsd10:~ # kldload linux
root@fbsd10:~ # kldload linux64

Luego verificar que hayan sido cargados correctamente:

root@fbsd10:~ # kldstat | grep linux
 5    1 0xffffffff81e13000 31620    linux.ko
 6    2 0xffffffff81e45000 2781     linux_common.ko
 7    1 0xffffffff81e48000 2c910    linux64.ko

Ahora es posible compilar el port que provee las librerías Linux necesarias:

root@fbsd10:~ # portmaster emulators/linux_base-c6

O de manera alternativa:

# pkg install emulators/linux_base-c6

Este port contiene los paquetes de una instalación casi mínima de CentOS 6 de 32 bits. Estos paquetes, en conjunto con los módulos del kernel, proveen la base para el entorno de compatibilidad con Linux. Está diseñado para proveer un buena experiencia de uso al aprovechar la configuración de FreeBSD para la pieza de software Linux correspondiente, siempre que sea posible. Este port está sólo disponible para las arquitecturas i386/amd64.

En este caso, instalar con portmaster o pkg es lo mismo, pues no se compila nada sino que se descargan los paquetes RPM que contienen los binarios precompilados, desde el sitio de CentOS.

Al finalizar la instalación se debe habiltiar la conpatibilidad con Linux editando el archivo /etc/rc.conf:

root@fbsd10:~ # nano /etc/rc.conf
linux_enable="YES"

Dependiendo de la versión de FreeBSD será posible incrementar la versión de Linux emulada de acuerdo a la variable compat.linux.osrelease en /etc/sysctl.conf.

Si se utiliza la última versión estable de FreeBSD, no es necesario modificar dicha variable:

root@fbsd10:~ # sysctl compat.linux.osrelease
compat.linux.osrelease: 2.6.32

Ejecución de un binario Linux

La raíz de la instalación mínima de CentOS 6 se encuentra en en directorio /compat/linux/. Dentro de la misma es conveniente crear un directorio /usr/local/bin donde almacenar binarios Linux (en caso de que luego sea necesario hacer un chroot a la misma para algún binario que así lo requiera):

root@fbsd10:~ # cd /compat/linux/usr
root@fbsd10:/compat/linux/usr # mkdir -p local/bin
root@fbsd10:/compat/linux/usr # cd local/bin

A modo de ejemplo, traer un binario desde un sistema GNU/Linux utilizando scp (cp sobre SSH):

root@fbsd10:/compat/linux/usr/local/bin # scp -P 2222 root@debian7:/usr/local/bin/zpaq .

file reporta que se trata de un binario ELF de 64 bit linkeado estáticamente para GNU/Linux 2.6.32:

root@fbsd10:/compat/linux/usr/local/bin # file zpaq 
zpaq: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=030a674fbb576c85f5cd28c4af1016f36f995867, stripped

Veamos si funciona...

root@fbsd10:/compat/linux/usr/local/bin # ./zpaq
zpaq v7.05 journaling archiver, compiled May  4 2015
zpaq archiver for incremental backups with rollback capability.
http://mattmahoney.net/zpaq

Usage: zpaq {add|extract|list} archive[.zpaq] files... -options...
Files... may be directory trees. Default is the whole archive.
Archive may be "" to test compression or comparison.
* and ? in archive match numbers or digits in a multi-part archive.
Part 0 is the index. If present, no other parts are needed to add or list.
Commands (a,x,l) and options may be abbreviated if not ambiguous.
  -all [N]        Extract/list versions in N [4] digit directories.
  -key [password] AES-256 encrypted archive [prompt without echo].
  -noattributes   Ignore/don't save file attributes or permissions.
  -not files...   Exclude. * and ? match any string or char.
  -only files...  Include only matches (default: *).
  -summary        Be brief.
  -test           Do not write to disk.
  -threads N      Use N threads (default: 4).
  -to out...      Rename files... to out... or all to out/all.
  -until N        Roll back archive to N'th update or -N from end.
  -until 2016-04-28 18:19:51  Set date, roll back (UT, default time: 235959).
add options:
  -force          Add files even if the date is unchanged.
  -nodelete       Do not mark unmatched files as deleted.
  -method L       Compress level L (0..5 = faster..better, default 1).
          LB      Use 2^B MB blocks (0..11, default 04, 14, 26..56).
          i       Index (file metadata only).
  -fragment N     Set average dedupe fragment size = 2^N KiB (default: 6).
extract options:
  -force          Overwrite existing files (default: skip).
list (compare files) options:
  -force          Compare file contents instead of dates (slower).
  -not =[+-#?^]   Exclude by comparison result.
  -summary [N]    Show N largest files/dirs only (default: 20).

Funciona perfectamente.

Binarios linkeados de forma dinámica

Pero ¿qué pasa con los binarios linkeados de forma dinámica?

Tal como indicaba la salida de file, este binario está linkeado de forma estática (no depende de librerías compartidas):

root@fbsd10:/compat/linux/usr/local/bin # ldd zpaq 
ldd: zpaq: not a dynamic ELF executable

Veamos qué sucede al intentar ejecutar un binario linkeado de forma dinámica, por ejemplo la herramienta pwd en un sistema GNU/Linux de 64 bits:

root@linux64:~# which pwd
/bin/pwd
root@linux64:~# ldd /bin/pwd 
        linux-vdso.so.1 =>  (0x00007ffea9904000)
        libc.so.6 => /lib/libc.so.6 (0x00007f673a307000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f673a67a000)
root@linux64:~# ldd /lib64/ld-linux-x86-64.so.2 
        statically linked
root@linux64:~# ldd /lib/libc.so.6
        /lib64/ld-linux-x86-64.so.2 (0x00007fc8c8f67000)
        linux-vdso.so.1 =>  (0x00007fff306dc000)

Se observa que el binario pwd linkea dinámicamente a las librerías linux-vdso.so.1 (virtual ELF dynamic shared object), libc.so.6 (standard C library) y ld-linux-x86-64.so.2 (Linux dynamic linker/loader). A su vez, el loader está linkeado estáticamente (obviamente, sino quién carga al loader) y la libc está linkeada dinámicamente al loader y al vdso (como todo binario linkeado de forma dinámica). Para más información consultar las siguientes manpages desde un sistema GNU/Linux:

man vdso
man ld
man ld-linux
man libc

De aquí se desprende que, para que pwd funcione, se requieren dichas librerías dinámicas.

Desde el sistema FreeBSD que cuenta con el modo de compatibilidad habilitado, traer el binario de pwd del host Linux de 64 bits:

root@fbsd10:/compat/linux/usr/local/bin # scp -P 2222 root@linux64.linuxito.com:/bin/pwd .
root@fbsd10:/compat/linux/usr/local/bin # ll
total 942
-rwxr-xr-x  1 root  wheel    31968 Apr 28 15:28 pwd*
-rwxr-x---  1 root  wheel  1648736 Apr 12 15:19 zpaq*

Se observa que se trata efectivamente de un binario ELF de 64 bits:

root@fbsd10:/compat/linux/usr/local/bin # file pwd 
pwd: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.18, BuildID[sha1]=f50215e5dc10c9e4ebbedcb8b5ce42875b1a8b93, stripped

Al listar las dependencias con librerías compartidas utilizando ldd, éste reporta error al no poder localizar el linker/loader:

root@fbsd10:/compat/linux/usr/local/bin # ldd pwd 
pwd:
ELF interpreter /lib64/ld-linux-x86-64.so.2 not found, error 2
pwd: signal 6

Este error ocurre porque la instalación de CentOS 6 no cuenta con el directorio /lib64:

root@fbsd10:/compat/linux/usr/local/bin # ll /compat/linux
total 20
drwxr-xr-x   2 root  wheel   60 Apr 12 15:05 bin/
drwxr-xr-x  24 root  wheel   61 Apr 12 15:05 etc/
drwxr-xr-x   8 root  wheel  115 Apr 12 15:05 lib/
drwxr-xr-x   2 root  wheel    2 Apr 12 15:03 mnt/
drwxr-xr-x   2 root  wheel    2 Apr 12 15:03 opt/
drwxr-xr-x   2 root  wheel    2 Apr 12 15:03 proc/
drwxr-xr-x   2 root  wheel   57 Apr 12 15:05 sbin/
drwxr-xr-x   2 root  wheel    2 Apr 12 15:03 selinux/
drwxr-xr-x   2 root  wheel    2 Apr 12 15:03 srv/
drwxr-xr-x   2 root  wheel    2 Apr 12 15:03 sys/
drwxr-xr-x  13 root  wheel   14 Apr 12 15:18 usr/
drwxr-xr-x  14 root  wheel   15 Apr 12 15:05 var/

Entonces, crear dicho directorio y traer el linker/loader de 64 bits desde el host Linux:

root@fbsd10:/compat/linux/usr/local/bin # cd /compat/linux
root@fbsd10:/compat/linux # mkdir lib64
root@fbsd10:/compat/linux # cd lib64/
root@fbsd10:/compat/linux/lib64 # scp -P 2222 root@linux64.linuxito.com:/lib64/ld-linux-x86-64.so.2 .

Ahora ldd funciona inmediatamente, aunque no se encuentra la libc:

root@fbsd10:/compat/linux/usr/local/bin # ldd pwd 
pwd:
        linux_vdso.so.1 =>  (0x00007ffffffff000)
        libc.so.6 => not found

Notar que no fue necesario ejecutar un chroot dentro de /compat/linux para que ldd encuentre a la (recién traída) librería ld-linux-x86-64.so.2, la cual no se encuentra dentro del directorio /lib64 en el sistema de archivos raíz del host FreeBSD, sino que se encuentra dentro de /compat/linux/lib64.

Esto se debe a que, cuando el kernel FreeBSD detecta un binario Linux, tiene en cuenta algunas consideraciones adicionales respecto a archivos y directorios. Si el programa busca un archivo o directorio /A, el kernel primero busca a /​compat/​linux/​A, y si no lo encuentra, busca luego por /A. Este comportamiento es el que permite ejecutar de manera transparente los binarios Linux sin necesidad de utilizar chroot.

Ahora bien, si buscamos la libc faltante, ésta se encuentra dentro de /compat/linux/lib/:

root@fbsd10:/compat/linux/usr/local/bin # find /compat/linux/ -name libc.so.6
/compat/linux/lib/i686/nosegneg/libc.so.6
/compat/linux/lib/libc.so.6

El problema es que se trata de una versión de 32 bits:

root@fbsd10:/compat/linux/usr/local/bin # file /compat/linux/lib/libc.so.6
/compat/linux/lib/libc.so.6: symbolic link to libc-2.12.so
root@fbsd10:/compat/linux/usr/local/bin # file /compat/linux/lib/libc-2.12.so 
/compat/linux/lib/libc-2.12.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=76ea73f60dc47ac24b37eb54b41354c5419e8cce, for GNU/Linux 2.6.18, not stripped

Y, claramente, el binario Linux pwd de 64 bits requiere una libc de 64 bits.

Traer la libc de 64 bits desde el host Linux de 64 bits:

root@fbsd10:/compat/linux/usr/local/bin # cd /compat/linux/lib64
root@fbsd10:/compat/linux/lib64 # scp -P 2222 root@linux64.linuxito.com:/lib/libc.so.6 .
root@fbsd10:/compat/linux/lib64 # ll
total 92
-rwxr-xr-x  1 root  wheel   128744 Apr 28 15:30 ld-linux-x86-64.so.2*
-rwxr-xr-x  1 root  wheel  1478056 May  2 10:40 libc.so.6*
root@fbsd10:/compat/linux/lib64 # file libc.so.6 
libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ba804939a2d595d44953e0a655bb5339c47ff9e4, for GNU/Linux 2.6.18, stripped

Pero al verificar nuevamente, la libc sigue sin encontrarse:

root@fbsd10:/compat/linux/lib64 # cd /compat/linux/usr/local/bin/
root@fbsd10:/compat/linux/usr/local/bin # ldd pwd 
pwd:
        linux_vdso.so.1 =>  (0x00007ffffffff000)
        libc.so.6 => not found

Si recuerdan la salida de ldd (en el host Linux de 64 bits desde donde proviene este binario), pwd busca la libc dentro del directorio /bin. Por ende, no queda otra alternativa que pisar el link simbólico dentro de /compat/linux/lib para que libc.so.6 apunte a la versión de 64 bits que se acaba de traer desde el host Linux de 64 bits:

root@fbsd10:/compat/linux/usr/local/bin # cd /compat/linux/lib
root@fbsd10:/compat/linux/lib # rm libc.so.6
root@fbsd10:/compat/linux/lib # ln -s ../lib64/libc.so.6 libc.so.6
root@fbsd10:/compat/linux/lib # ll libc.so.6 
lrwxr-xr-x  1 root  wheel  18 May  2 10:44 libc.so.6@ -> ../lib64/libc.so.6

Ahora ldd encuentra la libc correcta:

root@fbsd10:/compat/linux/lib # cd /compat/linux/usr/local/bin/
root@fbsd10:/compat/linux/usr/local/bin # ldd pwd 
pwd:
        linux_vdso.so.1 =>  (0x00007ffffffff000)
        libc.so.6 => /lib/libc.so.6 (0x0000000800827000)
        /lib64/ld-linux-x86-64.so.2 (0x0000000800607000)

Y es posible ejecutar el programa sin inconvenientes:

root@fbsd10:/compat/linux/usr/local/bin # ./pwd 
/compat/linux/usr/local/bin

¡Éxito! Gracias a este modo de compatibilidad se logra correr un binario proveniente de un sistema GNU/Linux de 64 bits sin necesidad de recompilar o siquiera hacer un rebranding del ELF. El efecto secundario es que ya no se podrá correr binarios de 32 bits porque la libc apunta a una versión de 64 bits. Para solucionar esto es necesario implementar el soporte multilib.

Conclusiones

Cuando se necesitan ejecutar binarios Linux de 64 bits en un sistema FreeBSD, el port emulators/linux_base-c6 no es de utilidad, pues es una instalación mínima de CentOS 6 de 32 bits (al menos hasta el momento no existe un port para emular un sistema GNU/Linux de 64 bits), por ende todas las librerías corresponden a la versión compilada para 32 bits.

En tal caso, no queda otra alternativa que traer uno manualmente todas las librerías necesarias desde un sistema de 64 bits, respetando la jerarquía de directorios dentro de /compat/linux.

Para un cierto binario en un sistema Linux (php5-fpm por poner un ejemplo), es posible obtener todas las librerías dinámicas de las cuales depende utilizando el siguiente comando:

# tar czvf libs_php5-fpm.tar.gz $(ldd /usr/sbin/php5-fpm | cut -d'>' -f2 | sed -e 's/[\ \t]*//g' | cut -d'(' -f1)

El archivo libs_php5-fpm.tar.gz contendrá todas las librerías dinámicas de las cuales depende php5-fpm. El problema es que a su vez se debe realizar esta tarea recursivamente para cada una de las librerías.

Por otro lado, en sistemas Debian y derivados es posible recopilar todos los binarios que incluye un determinado paquete ejecutando:

# tar czvf php-fpm.tar.gz $(file $(dpkg -L php5-fpm) | grep -vi directory | cut -d':' -f1)

Alternativamente uno podría traer absolutamente todas las librerías de una instalación mínima de la distribución de su preferencia, y así armar una especie de Linux base, como es lo que intenta lograr el port emulators/linux_base-c6.

Lo más interesante de este modo de compatibilidad Linux en FreeBSD, es que habilita a crear jails Linux, logrando así paravirtualización entre sistemas operativos heterogéneos, algo impensado en otras soluciones de contenedores.

Referencias

Configuring Linux® Binary Compatibility

The FreeBSD-​linuxulator ex­plained (for users)


Tal vez pueda interesarte


Compartí este artículo