What it takes to run a 32-bit x86 program on a 64-bit x86 Linux system
Suppose that you have a modern 64-bit x86 Linux system (often called an x86_64 environment) and that you want to run an old 32-bit x86 program on it (a plain x86 program). What does this require from the overall system, both the kernel and the rest of the environment?
(I am restricting this to ELF programs, not very old a.out ones.)
At a minimum, this requires that the (64-bit) kernel support
programs running in 32-bit mode ('IA32') and making 32-bit kernel
calls. Supporting this is a configuration option in the kernel
(or actually a whole collection of them, but they mostly depend
on one called IA32_EMULATION). Supporting 32-bit calls on a
64-bit kernel is not entirely easy because many kernel calls involve
structures; those structures must be translated back and forth
between the kernel's native 64-bit version and the emulated 32-bit
version. This can raise questions of how to handle native values
that exceed what can fit in the fields of the 32-bit structures. The
kernel also has a barrel full of fun in the form of
ioctl(), which smuggles a
lot of structures in and out of the kernel in relatively opaque ways. A
64-bit kernel does want to support at least some 32-bit ioctls, such as
the ones that deal with (pseudo-)terminals.
(I suspect that there are people in the Linux kernel community who hope that all of this emulation and compatibility code can someday be removed.)
A modern kernel dealing with modern 32-bit programs also needs to provide a 32-bit vDSO, and the necessary information to let the program find it. This requires the kernel to carry around a 32-bit ELF image, which has to be generated somehow (at some point). The vDSO is mapped into the memory space of even statically compiled 32-bit programs, although they may or may not use it.
ldd output on dynamically linked 32-bit programs, I believe
this often shows up as a magic 'linux-gate.so.1'.)
This is enough for statically compiled programs, but of course very few programs are statically compiled. Instead, almost all 32-bit programs that you're likely to encounter are dynamically linked and so require a collection of additional compiled things. Running a dynamically linked program requires at least a 32-bit version of its dynamic linker (the 'ELF interpreter'), which is usually 'ld-linux.so.2'. Generally the 32-bit program will then go on to require additional 32-bit shared libraries, starting with the 32-bit C library ('libc.so.6' and 'libdl.so.2' for glibc) and expanding from there. The basic shared libraries usually come from glibc, but you can easily need additional ones from other packages for things like curses or the collection of X11 shared libraries. C++ programs will need libstdc++, which comes from GCC instead of glibc.
(The basic dynamic linker, ld-linux.so.2, is also from glibc.)
In order to do things like hostname lookups correctly, a 32-bit
program will also need 32-bit versions of any NSS modules that are used
/etc/nsswitch.conf, since all of these are shared libraries
that are loaded dynamically by glibc. Some of these modules come
from glibc itself, but others are provided by a variety of additional
software packages. I'm not certain what happens to your program's
name lookups if a relevant NSS module is not available, but at a
minimum you won't be able to correctly resolve names that come from
(You may not get any result for the name, or you might get an incorrect or incomplete result if another configured NSS module also has an answer for you. Multiple NSS modules are common for things like hostname resolution.)
I believe that generally all of these 32-bit shared libraries will have to be built with a 32-bit compiler toolchain in an environment that itself looks and behaves as 32-bit as possible. Building 32-bit binaries from a 64-bit environment is theoretically possible and nominally simple, but in practice there have been problems, and on top of that many build systems don't support this sort of cross-building.
(Of course, many people and distributions already have a collection of 32-bit shared libraries that have been built. But if they need to be rebuilt or updated for some reason, this might be an issue. And of course the relevant shared library needs to (still) support being built as 32-bit instead of 64-bit, as does the compiler toolchain.)