March 31, 2006

Setting up Linux compatibility on FreeBSD 6

Author: Gordon McEwen

As a FreeBSD desktop user I occasionally feel left out when it comes to the availability of applications, particularly desktop applications or binary-only browser plugins produced by commercial closed source vendors. Sometimes a good alternative lurks in the vast FreeBSD ports collection, but not always. The version available may lag a couple of revisions behind what I need, or the port might exclude my particular architecture. Fortunately, FreeBSD can run binaries and shared libraries that have been compiled for Linux and other Unix ABIs (such as SVR4 and SCO).

In this article I will cover the steps necessary to enable and configure Linux binary compatibility on FreeBSD 6. I'll also share a couple of my own experiences with getting some well-known desktop Linux applications to run on FreeBSD 6.

How does it work?

Although it is by no means essential to understand how Linux compatibility actually works, it sometimes helps to have an idea when it comes to problem solving.

Linux compatibility isn't instruction-level emulation or some kind of virtual machine. The Linux ABI is implemented in the FreeBSD kernel, so in most senses the Linux binaries could be considered to be running natively. Through what appears to be some sleight-of-hand the system selects at run time the Right Thing to do to run an executable. This is partly figured out based on branding information encoded in an ELF binary header. A command-line tool called brandelf can be used to report the brand:

# brandelf /usr/X11R6/lib/firefox/firefox-bin
File '/usr/X11R6/lib/firefox/firefox-bin' is of brand 'FreeBSD' (9).

# brandelf /usr/X11R6/lib/linux-firefox/firefox-bin
File '/usr/X11R6/lib/linux-firefox/firefox-bin' is of brand 'SVR4' (0).

This tool will also brand a binary if you use the -t option. Normally this isn't needed -- the brand is always set correctly for any recently built binaries.

After looking at the brand, the FreeBSD ELF loader will arrange to use the correct set of system calls -- Linux as opposed to FreeBSD. It will also link the correct shared libraries at run time. Shell scripts can be forced to run similarly by setting the first line to
#!/compat/linux/bin/sh and, although it isn't often needed, you can alter the environment by setting the UNAME_s environment variable (reported using uname -s) to Linux rather than FreeBSD.

For those who want to understand the real nuts and bolts, the FreeBSD Handbook has a more complete and accurate explanation.

Linux binary support is said to be less than 100% but I have rarely experienced any problems I could not trace to unresolved shared library dependencies. With this feature, FreeBSD users can run many Linux binaries easily and at performance levels that rival that of the native platform.


If you are running the GENERIC FreeBSD 6 kernel you're already set -- kernel Linux compatibility is compiled in by default. If you are not running the GENERIC kernel, or you are not sure what options are turned on in your kernel, you can perform a quick check by attempting to load the Linux kernel loadable object by hand. Use the kernel load utility as root:

# kldload linux

If it reports kldload: can't load Linux: File exists, you know you already have Linux compatibility loaded or compiled in. If it reports nothing at all, you have just succeeded in loading the Linux kernel loadable object. You can verify this by using the companion kernel status utility:

# kldstat

It returns a list of the currently loaded dynamically loadable kernel objects. If you don't have Linux compatibility compiled in, kldstat should now report that the object linux.ko has been loaded. (You can unload it manually by using the kernel unload utility, kldunload.) If you want to load the Linux object automatically every time you boot, add the following line to /boot/loader.conf:


If you want to build Linux compatibility into the kernel so that there is no need to load the object every time, follow the instructions for building a new kernel and make sure that the following options are uncommented in your kernel configuration file (use COMPAT_LINUX32 instead of COMPAT_LINUX if you're building for amd64):

option COMPAT_43

The last of those kernel configuration options enables support for the Linux Process File System procfs which some Linux applications may require. The procfs normally allows applications to query process or device information from the kernel. You can add the following to /etc/fstab to ensure that the Linux procfs is mounted:

none /compat/linux/proc linprocfs rw 0

Note that it is mounted at /compat/linux/proc, not /proc as is usual on Linux. This is to distinguish it from the FreeBSD procfs which, if it is mounted at all, will be mounted at /proc.

Once you have updated /etc/fstab, type mount -a to mount the Linux procfs. It will happen automatically next time you boot.

Now that you have linux.ko loaded, or verified that it is compiled in,
you are ready to run Linux binaries. In fact, you will be able to run 100% Linux binaries without any more ado. The only problem is that the Linux binaries you want to run are unlikely to be statically linked. They may have anything from a handful to dozens of shared library dependencies (for example, the native Firefox on my system has 29 shared library dependencies, and a version built for Linux will have at least as many). You can see these dependencies for yourself by running ldd on any native application.

# ldd /usr/X11R6/lib/firefox/firefox-bin

For a Linux binary, these dependencies must be met by shared libraries compiled for Linux (the ones already installed on your FreeBSD system won't do) and they must be the same versions of the libraries that were used when the binary was compiled.

You should resist the temptation to copy Linux libraries off your nearest Linux system into /usr/lib or /usr/local/lib -- it's a recipe for disaster to mingle foreign libraries with native FreeBSD libraries. On FreeBSD, foreign binaries are cordoned off under /compat/linux, a directory that's created and populated when you install a base Linux compatibility environment.

There are two main approaches to putting together a Linux base under /compat/linux: install it from ports or build it by hand using Linux packages from a Linux distribution of your choosing. The first approach is usually the way to go -- it's generally simpler and is consistent with the FreeBSD practice of using ports and packages for installation. The alternative method may be harder work but gives you more control and allows you to select a package-based Linux distribution of your own liking. I'll describe only the ports approach; if you want to follow the second approach, you should should check out the consise instructions by FreeBSD's Tim Robbins. (I'll vouch that it works, as I initially followed that route using Slackware 10.2 packages, with good success.)

Installing from ports

Yes, there are Linux ports that download Linux binary packages onto your FreeBSD system! To get a rough idea how many such ports there are, go to /usr/ports and type:

# make search name=linux | grep Path

Most of ports on this list are individual Linux libraries, some are Linux applications, and those under /usr/ports/emulators are the base Linux library sets. This is only a rough list though -- not all such ports include 'linux' in the name. For example, the Adobe Acrobat Reader 7 for Linux port is 'print/acroread7.'

The base port upon which many of the Linux ports ultimately depend is linux_base-8. This is a foundational set of packages using Red Hat 8 i386 binary RPM packages. It isn't the only base Linux port though. There are others -- most more recent than Red Hat 8 -- but they conflict with each other, so you'll only be able to install one. As linux_base-8 is the port upon which all the other Linux ports depend, it's the one to go for if you want things simple and want to take advantage of the hard work the port maintainers put into figuring out all the binary dependencies for each application. If, on the other hand, you want a more recent Linux base or one that you're already familiar with, and you don't mind getting your hands dirty figuring out and downloading dependencies yourself, then you have a choice. More about that approach later.

For now, let's deal with linux_base-8. To install it:

# cd /usr/port/emulators/linux_base-8
# make install && make clean

If you prefer not to install right away, just type make. This downloads the RPMs from Fedora Legacy to /usr/ports/distfiles/rpm/i386/8.0. Once they're downloaded, make install will unpack them so that they end up rooted at /compat/linux. Under this directory you'll then find usr, lib, etc, and var, under which you'll find all the Linux binary shared libraries and configuration files. It's usually not necessary but you can explicitly chroot to the environment by typing chroot /compat/linux /bin/bash. At this point you'll only be able to run the Linux binaries under FreeBSD compatibility, in the chrooted environment. Press Ctrl-D or type exit to leave it.

Now, if you wish to install, say, Linux Opera (native Opera is only available for i386 FreeBSD) go to /usr/ports/www/linux-opera and type make install && make clean. This will download more RH8 RPMs that meet additional dependencies not met by linux_base-8, as well as the Linux Opera RPM. Again, library dependencies will end up under /compat/linux, but a startup script for the Linux Opera (version 8.51) will be installed under /usr/bin, and Opera itself under /usr/X11R6/share/linux.opera. If you want Flash installed for the Linux browser binary, go to /usr/ports/www/linux-flashplugin7 and type make install && make clean again. Now if you run linux-opera, you should find it has the Flash plugin already configured.

Using this Red Hat 8 install you should be able to install a load of applications, including Skype, Firefox 1.5 (which will also use the Flash plugin), and Adobe Acrobat Reader 5 or 7. In general you will meet with success, but sometimes binary dependencies will fail to download, and some dependencies may not be met even if the install appears to succeed.

If this happens, you may be able to fix up the runtime dependencies by hand. If you run the Linux application from the command line and it complains about missing shared libraries, you may be able to make an educated guess as to what RH8 RPM a missing library lives in and download it from the Red Hat 8 RPM archives at Fedora Legacy. (And herein lies the black art of setting up compatibility -- RPMs often contain multiple libraries and may have additional RPM dependencies. Finding the right ones may not be as difficult as it sounds, though; RPMs usually have meaningful names, so you can often put two and two together, given the full list of RPMs at Fedora Legacy. You can also use rpm2cpio rpm-name.rpm | cpio -itv (both tools found under /usr/ports/archivers) to list the RPM table of contents.) Once you've located and downloaded it, install the RPM to /compat/linux as root as follows:

# /compat/linux/bin/rpm -ihv --root=/compat/linux

If this command itself complains about missing RPM package dependencies, first check whether the library files are already present under /compat/linux (they may be, even if rpm is complaining). If they're not, as before, find, download, and install the RPM that supplies the missing library as identified by the rpm error message. Once you've installed the dependencies, go back and install the original RPM. You can force the RPM installion to go ahead even with missing RPM package dependencies by adding the --nodeps flag to the rpm command options. Do this if you figure the missing libraries are in place even if it is complaining that the RPM packages have not been installed.

If you decide you need to remove an RPM package, use the command:

/compat/linux/bin/rpm -e --root=/compat/linux

Note that in all these cases you are using the Linux rpm tool -- not the native FreeBSD rpm tool that you may have on your system. Any attempt to use the FreeBSD rpm command on a Linux RPM file will have it complaining about the RPM being for the wrong architecture. Conceivably, you could use FreeBSD's rpm2cpio and cpio commands (under /usr/ports/archivers), as follows:

# cd /compat/linux
# rpm2cpio rpm-name.rpm | cpio -ih

This unceremoniously unpacks an RPM and dumps it under /compat/linux. Using the Linux rpm is a better method, however.

Using a different Linux base

At this point you may feel happy that you are able to run the Linux binaries you are interested in and that they complement your native FreeBSD applications nicely. You can run linux-opera or linux-firefox or Skype without any bother. But this may not be enough. You may not have the version of the Linux application that you want, or you may have trouble getting recently compiled binaries, for which you have no source and which don't appear in ports, to run on this older Red Hat environment. This may be the reason why there are a variety of other base Linux ports under /ports/emulators.

Note that you will not be able to install any of these other ports at the same time as linux_base-8, and if you install an alternative to linux_base-8 you will not be able to install any other Linux application from ports -- they all depend on linux_base-8. So, if you use any of these, you will have to install Linux binaries by hand using the techniques described above for resolving unmet dependencies -- using packages appropriate to your chosen Linux base rather than RH8 RPMs, of course. You will also have to deinstall linux_base-8 and any other Linux applications and libraries before you can install an alternative.

Of the alternative Linux bases, two -- linux_base-debian and linux_base-gentoo-stage1 -- are marked suitable for the Alpha architecture (in addition to i386 and amd64) and, in the case of Gentoo, ia64. However, both were marked as broken at the time of my writing. I was able to install the SUSE 9.3 base, linux_base-suse-9.3, and run applications I had trouble running under Red Hat 8. (On the other hand, I was unable to get Adobe Acrobat Reader 7 running under linux_base-suse-9.3 even though it ran fine when installed from ports using linux_base-8.)

I could not get RealPlayer10 to install and work under linux_base-8. With SUSE 9.3 I downloaded and unpacked the binary by hand and got it running after I applied a couple of fix-ups to the SUSE base environment. The base SUSE 9.3 is missing a couple of auto-generated configuration files, namely pango.modules and gdk-pixbuf.loaders. The first prevented RealPlayer from running and, once I'd fixed it, the second caused the buttons to have no pixmaps. I created these configuration files by hand using the following commands:

# cd /compat/linux/opt/gnome/bin
# ./pango-querymodules | sed 's/opt\//compat\/linux\/opt\//g' > /compat/linux/etc/opt/gnome/pango/pango.modules
# ./gdk-pixbuf-query-loaders | sed 's/opt\//compat\/linux\/opt\//g' > /compat/linux/etc/opt/gnome/gtk-2.0/gdk-pixbuf.loaders

Now I'm running SUSE 9.3 as my preferred base Linux environment, in preference to my earlier Slackware 10.2 hand-rolled effort (fonts looked bad with Slackware, although I'm sure I could have solved the problem) and Red Hat 8, despite the availability of associated ports for many Linux applications.

Why not just run Linux?

After all this, you may wonder why anyone would bother jumping through these hoops rather than simply wipe the box clean and install Linux. Maybe for the same reason Linux users run essential Windows applications under Wine -- I run FreeBSD because I like it. It has a huge number of applications available for it. Being able to run a few applications that are available only for Linux is a bonus. Hopefully the trend will continue and a shrinking number of applications will fall into this category. (It is always worth double-checking that an application is not already in ports before selecting the Linux version.) Perhaps soon we'll see an amd64 Opera (or substitute your closed-source app of choice) and the availability of good alternative open source browser plugins built for the common *BSD architectures and versions.


  • BSD