Converting a 32-bit x86 Gentoo Linux installation to 64-bit

Updated from the previous version.

On a few occasions, I have converted a 32-bit Gentoo Linux x86 installation to 64-bit using an in-place rebuild (emerge -e world), in spite of Gentoo's advice to perform a fresh install. This file documents the steps I followed to perform the conversion, as well as the problems I ran into along the way, and is current as of May 2024.

Note that most operations will need to be performed as root. As the sudo command may fail during the course of the rebuild, I recommend using a root shell with either su or sudo bash (or logging in directly as root), depending on your system and preference.

0. Back everything up

Before doing anything else, make sure to back up your entire system. If something goes wrong, you may become unable to even boot, much less perform any recovery operations like copying files around.

1. Install an x86-64 cross-compiler

In order to build an x86-64 kernel on a 32-bit system, you'll need to install an x86-64 cross compiler. This can be done by emerging the sys-devel/crossdev package and running crossdev -s1 -t x86_64 (optionally add -S to use the stable versions of the toolchain). To simplify later removal of those packages, particularly if you don't need to keep crossdev around, you can pass the -1 (--oneshot) option to emerge: emerge -1 sys-devel/crossdev crossdev -p -1 -s1 -t x86_64

2. Build an x86-64 kernel

The Linux kernel now treats 32-bit and 64-bit builds as two variations of the same architecture, so configuring a 64-bit kernel is mostly as easy as setting the "64-bit kernel" option. (If you don't have an existing .config file in your kernel source directory, copy it from the appropriately-versioned file in /boot or extract it from the running kernel with zcat /proc/config.gz >.config.) If you want to take a closer look at options specific to x86-64, you can instead change these two lines: # CONFIG_64BIT is not defined CONFIG_X86_32=y to: CONFIG_64BIT=y # CONFIG_X86_32 is not defined and then run make oldconfig.

Important: Make sure the CONFIG_IA32_EMULATION option ("IA32 Emulation" under "Executable file formats / emulations") is enabled, or you won't be able to boot the system with the new kernel! You don't need to enable CONFIG_X86_X32 ("x32 ABI for 64-bit mode"), however.

Once the kernel is configured, you'll need to build it with the x86-64 cross-compiler you installed in step 1. Run the command: make bzImage install modules modules_install CROSS_COMPILE=x86_64-pc-linux-gnu- (don't forget the hyphen at the end of the line). For a faster build, you can pass the option -jN (where N is the number of jobs to run at once; typically, one more than the number of logical CPU cores is a good choice) on the make command line, but you may need to run the targets one at a time (make -jN bzImage && make -jN install && ...).

3. Boot into the new kernel

Since you enabled CONFIG_IA32_EMULATION in the new kernel, it will be able to run your existing 32-bit system just fine (unless you have any out-of-tree modules, such as x11-drivers/nvidia-drivers). Reboot into the new kernel and verify that it works.

4. Set up a recovery shell

As insurance against something going wrong, set up some sort of recovery shell that doesn't depend on any live libraries in the system. This can either be a statically linked all-in-one shell binary like busybox (sys-apps/busybox) or a chrooted shell in your system backup. The latter is safer in that you'll also have all of your old binaries and libraries available, but make sure the backup is mounted readonly so you don't accidentally modify it. Also make sure to bind your root filesystem so that it's available inside the chroot: mount --bind / /chroot/mnt (assuming your chroot environment is located in /chroot and you want to make the root filesystem visible under /mnt inside the chroot).

5. Update linker search path

IMPORTANT: Ensure that your recovery shell is available from this point forward. If you use the chroot method, start up the chroot shell now – if you wait until something actually breaks, you may find that you can't run the chroot command to get into your shell!

As you start building 64-bit programs, the dynamic linker will need to know how to find their libraries. Edit /etc/env.d/99local (or create it if it does not exist) and add a line reading: LDPATH="/lib64:/usr/lib64:${LDPATH}" and then run the command env-update. (The lib64 directories don't exist yet, but the added entries will be important in the next step.) Alternatively, you can edit ld.so.conf and insert those paths directly, but be aware that this file will be rewritten at least once before the proper control files are available, and you'll need to redo those edits each time.

6. Remove the x86-64 cross-compiler

In preparation for building a native x86-64 compiler, remove the cross-compiler you installed in step 1: crossdev -C x86_64 (If you passed the -1 flag to emerge, you can instead just run emerge -c to clean them out – but as usual, make sure that it doesn't also remove packages you intend to keep.)

Note that it's important to do this here and not later, because emerge has added basic sanity checks to verify that a compiler is available for the architecture configured in make.conf. If you advance past the following step before doing this removal, you may find that emerge breaks partway through.

(Previously, this step included renaming and symlinking /lib and /usr/lib directories for the 32-bit and 64-bit architectures. Since Gentoo on x86-64 now uses a configuration in which /lib (resp. /usr/lib) holds 32-bit libraries and 64-bit libraries are written to /lib64 (resp. /usr/lib64), you can safely leave your current 32-bit libraries in /lib and /usr/lib.)

7. Change /etc/portage/make.conf and /etc/portage/make.profile to use the x86-64 architecture

Since we're about to start installing x86-64 software, the Portage configuration data needs to be updated to use the x86-64 architecture. Edit make.conf to change any x86-32 references; for example, if you set CHOST=i686-pc-linux-gnu, change it to CHOST=x86_64-pc-linux-gnu. Then add the line: ABI_X86="32 64" which will cause Portage to build both 32-bit and 64-bit versions of libraries. (If you don't plan on running any prebuilt 32-bit binaries, it might be possible to omit this line or explicitly set ABI_X86="64", but you will probably need to leave it at "32 64" until the conversion is complete.)

You'll also need to update your make.profile link to point to an x86-64 profile instead of a 32-bit x86 profile. For example, if the link currently points to /usr/portage/profiles/default/linux/x86/23.0, update it with: rm /etc/portage/make.profile ln -s /usr/portage/profiles/default/linux/amd64/23.0 /etc/portage/make.profile Don't try to use ln -fs to overwrite the link, since that will instead put a link inside the existing profile – which is not what you want! Also ensure that you use a multilib-enabled profile; the versioned profiles (like 23.0) have multilib enabled, but the base (unversioned) profile does not.

8. Copy gcc, binutils, and glibc packages from an existing x86-64 Gentoo system

DANGER: Making an error in this step can leave your system unusable!

There seem to be circular dependencies in some of the basic system tools libraries: attempting to build glibc, for example, fails because it assumes the existence of system header files from a previous install of (64-bit) glibc, while the cross-compiler used earlier to compile the kernel seems to have trouble working with a native binutils install. The surest method seems to be to just copy the needed packages out of an existing x86-64 system.

Important: The packages copied from the 64-bit system (especially libraries) should be the same versions as those installed on the 32-bit system, to avoid confusion from having separate 32-bit and 64-bit files installed in the same location.

8.1. On an existing x86-64 system, create a tarball of the files contained in the sys-devel/gcc, sys-devel/binutils, and sys-libs/glibc packages. You'll also need the library dependencies for GCC: dev-libs/gmp, dev-libs/mpc, and dev-libs/mpfr, and sys-libs/zlib. (Current Gentoo profiles also enable the acl and zstd USE flags by default, requiring more dependencies; I recommend rebuilding gcc and binutils with USE="-acl -zstd" for this step.) You can do this with: equery -C files --filter=obj,sym \ sys-devel/gcc sys-devel/binutils sys-libs/glibc \ dev-libs/gmp dev-libs/mpc dev-libs/mpfr sys-libs/zlib \ | tar cTf - /tmp/install.tar

8.2. Copy that tarball to the system you're rebuilding, then extract it in a temporary directory: mkdir /tmp/install && tar Cxf /tmp/install /tmp/install.tar

8.3. Remove 32-bit libraries from the extracted files: rm -r /tmp/install/{lib,usr/lib,lib64/ld-linux.so.2} In theory, these files should be identical (or effectively so) to the ones already on your system, but since they're not needed for running 64-bit binaries, it's safer to just avoid installing them.

8.4. Install the files using mv: cd /tmp/install && find * -type f \ | while read f; do mkdir -pm755 "/$(dirname "$f")"; mv "$f" "/$f"; done Note that simply using cp or tar is risky: if you didn't remove 32-bit libraries as described above, the process will crash with a "Bus error" signal (how often do you see those anymore?) when it tries to overwrite /lib/libc-*.so, and you'll be left with a broken libc library that confuses even the 32-bit ld-linux.so.2 and prevents any programs from running at all.

9. Configure gcc and binutils

Gentoo's gcc and binutils wrappers need to be reconfigured to find the newly installed programs. Run gcc-config -l (resp. binutils-config -l) to find the option number for the x86_64 version, then run gcc-config NUMBER (resp. binutils-config NUMBER) to make it active.

It's possible the configuration scripts won't be able to locate the new programs; in that case, look in /etc/env.d/gcc (resp. /etc/env.d/binutils) and follow the format of the existing files to create an entry for the new x86-64 programs.

Even with the configuration entries installed, the scripts may report errors and not list the 64-bit packages, but regardless of what the list shows, you should (assuming you have no other architectures or toolchain versions installed) still be able to run: gcc-config 1; gcc-config 2 (resp. binutils-config) and get both the 32-bit and 64-bit toolchain configured correctly.

Once this is done, you should be able to compile and run a simple "hello world" program.

10. Emerge the sys-apps/coreutils package
Both as a check that the compiler toolchain is correctly installed and as a prerequisite for rebuilding glibc, emerge the sys-apps/coreutils package: emerge -1 sys-apps/coreutils

If you don't do this, attempting to emerge glibc will fail because, as a check of the validity of the newly-built glibc, the ebuild tries to execute /bin/date (which is currently a 32-bit executable) with the newly-built loader and libraries (which use the 64-bit architecture).

11. Emerge the packages you overwrote in step 8

Now you can emerge gcc, binutils, and glibc to bring Portage's state back in line with what's installed on the system: FEATURES=-collision-protect emerge -1 sys-devel/binutils dev-libs/gmp \ dev-libs/mpc dev-libs/mpfr sys-devel/gcc sys-libs/glibc FEATURES=-collision-protect is required to avoid merge errors because the new installs will (deliberately) overwrite some 64-bit-specific files you installed earlier.

As mentioned above, sys-libs/glibc attempts to run the /bin/date program with the newly built library to verify that it works, but it also tries several other programs, such as cal (from the util-linux package). Rather than finding and rebuilding all of these packages first, I recommend simply going into the ebuild (sys-libs/glibc/glibc-*.ebuild in the Gentoo Portage repository, /var/db/repos/gentoo or historically /usr/portage) and editing the relevant line, which should look something like: for x in cal date env free ls true uname uptime ; do to remove all programs except date: for x in date ; do These changes will be naturally reverted when you next run emerge --sync.

12. Emerge Xwindows and drivers (optional)

If you use Xwindows with a non-kernel driver (such as the x11-drivers/nvidia-drivers package), you'll need to rebuild it for the new kernel before you can start X. Since this will produce a 64-bit X module, you'll also need to rebuild the X server itself and any other drivers you use with it (such as xf86-input-keyboard or xf86-input-mouse). To find out what libraries you'll need to build a 64-bit binary of the X server, run ldd /usr/bin/Xorg and work out which package is responsible for each library (by, for example, using the equery program from the app-portage/gentoolkit package: equery b /usr/lib/FILENAME).

13. Emerge the world

At this point, your system is ready to rebuild all installed packages, so run: emerge -e world Optionally, you can first run emerge -pve world and check that there are no blockers or unexpected new packages.

As the rebuild proceeds, programs are likely to break; for example, if a library with no multi-ABI support is rebuilt, any programs which depend on it will stop working until they are also rebuilt. It's generally possible to continue using your system during the rebuild, but if you see any program behaving unexpectedly, it's probably best to stop using that program until it has been rebuilt. (In one case, the Seamonkey browser crashed early on in the build and subsequently refused to start up due to library architecture incompatibilities. I also got "error -2146697208" (0x800C0008) in a Wine-hosted Windows program trying to connect to an SSL website, which was resolved when Wine was rebuilt for the x86-64 architecture.)

Because Portage does not explicitly track architectures when resolving dependencies, you may encounter cases in which a package fails to build because a library on which it depends has not been rebuilt for x86-64. This typically manifests as either "package/library not found" at the configure stage or "library has incorrect architecture" at the link stage. In this case, find the package containing the missing library, rebuild it manually (with emerge -1), and then resume the original build process with emerge -r. In some cases, that package may have its own missing dependencies, requiring you to recurse through several levels of packages; for example, in order to get past dev-libs/libxml2 (with USE=python enabled) I had to rebuild dev-lang/python, which itself required sys-apps/util-linux for the uuid module, which in turn required sys-libs/pam to be rebuilt.

Be careful when doing such separate rebuilds to only rebuild one package at a time! Portage only tracks a single "resume" list, which is updated if you pass two or more packages (or a package set, like world) on the command line. If you try building two dependencies at once, you may find that when they complete, you can no longer resume your original emerge -e world and have to start over from the beginning again!

14. Remove old libraries

Once all packages have been successfully merged, you will probably have some libraries in /lib and /usr/lib which no longer belong to any packages. Such libraries may be obsolete, but especially if you have software on your system not managed by Portage (such as programs you've written or downloaded yourself), you should first check that each such library (and any corresponding symlink) is not referenced by anything on your system. You can generate a full list of library dependencies using something like: find / -xdev -type f -executable | while read f; do ldd "$f" 2>/dev/null | sed "s|^|$f:|"; done You can write that output to a file and then grep the file for each library or symlink to ensure that the library is unused. Note that a match doesn't necessarily mean the library cannot be deleted; if the reference is itself from an obsolete library, then both can be deleted.

Note that a file whose timestamp is older than when you started the rebuild might still belong to a package! This typically occurs when using a package which installs precompiled libraries, and was notably an issue with the emul-linux-x86-* packages which predated proper multi-ABI support in Gentoo. You can check whether each file belongs to a package with a command like "equery b PATHNAME".

15. Reboot

At this point, your system probably has a mixture of 32-bit and 64-bit code running. This isn't a problem in and of itself, but it may hide problems with your newly-installed 64-bit packages, so it's worth rebooting once to ensure that the 64-bit packages are all working as expected.


Andrew Church
Last modified: 2024/5/13