Authors Top

If you have a few years of experience in the Linux ecosystem, and you’re interested in sharing that experience with the community, have a look at our Contribution Guidelines.

1. Introduction

These days, it’s handy and practical to execute 32-bit programs on a 64-bit system. That is to say, there are several situations in which we need to do this. For example, perhaps a 64-bit version of an application is not available or not yet ported. Likewise, we may need to use a Windows application or play an old game using the Wine compatibility layer. In other words, some applications require 32-bit OS support.

In this article, we’ll discuss how to compile a 32-bit program on a 64-bit Linux system using the gcc compiler. We’ll also learn the system requirements for building a 32-bit binary on a 64-bit system and how to use CMake to automate these builds.

2. Development Tools

Before starting, we’ll ensure that all development tools used in the compilation binary build processes are installed in our system. Firstly, we need GCCthe GNU compiler collection. Secondly, binutils is a set of tools for creating and managing binary programs, including a linker and assembler. In addition, we have to install Make – the build automation tool – and the C standard library glibc.

Most importantly, we have to install Multilib, which allows 32-bit programs to execute on 64-bit systems. Also, it will enable 32-bit and 64-bit libraries to coexist on the same system – we’ll need this because 32-bit programs require 32-bit libraries.

In Debian-like distributions, including Ubuntu, the gcc compiler, C library, and Make tool are present in the build-essential package. We’ll need to install the build-essential and binutils packages using apt:

$ sudo apt -y update
$ sudo apt -y install build-essential binutils

Similarly, we’ll install gcc-multilib package to add the Multilib mechanism to our system:

$ sudo apt -y install gcc-multilib

If we’re running Fedora, Redhat, or CentOS, we’ll use dnf to install the gcc compiler, Make, and binutils:

$ sudo dnf -y install gcc binutils make

In the same vein, to add 32-bit support for Redhat-like distributions, we run:

$ sudo dnf -y install glibc-devel.i686

3. Is the Compiler 32-bit or 64-bit?

Now, let’s check our hardware information using the uname command:

$ uname -m 
x86_64

By default, the compiler will produce binary files suitable for the architecture. So, if we have a 64-bit processor, it will produce a 64-bit binary.

Now, let’s check our gcc installation:

$ gcc -v

Using built-in specs.
COLLECT_GCC=/usr/bin/gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/10/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,objc,obj-c++,ada,go,d,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --with-isl --enable-offload-targets=nvptx-none --without-cuda-driver --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 10.3.1 20210422 (Red Hat 10.3.1-1) (GCC) 

As we can see, the target is x86_64 – this is the architecture name in the Linux system for the 64-bit processor (AMD or Intel).

4. Compiling 32-bit Programs on 64-bit Systems

As previously seen, the compiler will produce a binary for the target architecture, which in our case, is x86_64:

$ gcc prog.c -o prog

To verify the type of file produced by the compilation process, we use the file tool:

$ file prog
prog: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=07a13c851f21019b160a777a8ac047bed3a06489, for GNU/Linux 3.2.0, not stripped

If we try to compile a 32-bit program without making our system compatible with a 32-bit configuration, we’ll observe the following error:

$ $ gcc -m32 prog.c -o prog
In file included from /usr/include/features.h:489,
                 from /usr/include/bits/libc-header-start.h:33,
                 from /usr/include/stdio.h:27,
                 from menu.c:1:
/usr/include/gnu/stubs.h:7:11: fatal error: gnu/stubs-32.h: No such file or directory
    7 | # include <gnu/stubs-32.h>
      |           ^~~~~~~~~~~~~~~~
compilation terminated.

Now, let’s compile a 32-bit program using the -m32 option:

$ gcc -m32 prog.c -o prog

Here, we’ve successfully compiled a 32-bit program on a compatible system. We can check the binary using the file command:

$ file prog
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=a711fe476bf35d8e36ff6d5370ac384e3a84539d, for GNU/Linux 3.2.0, not stripped

5. Using CMake

Now, let’s use CMake to automate the compilation. CMake is software designed to automate compilation on various systems. We’ll need both the make tool and a C or C++ compiler to build a project.

Let’s see how to install CMake on Debian and Ubuntu distributions:

$ sudo apt -y install cmake

To install CMake on Fedora and Redhat like systems, we run:

$ sudo dnf install cmake

To build a 32-bit project using CMake, we’ll use a sample project composed of 3 files – main.c, prog.c and prog.h:

$ ls project/
main.c prog.c prog.h

First, we need to create a CmakeLists.txt file for passing in gcc options:

cmake_minimum_required(VERSION 3.19)
set(GCC_COVERAGE_COMPILE_FLAGS "-m32")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}")
project(prog)
add_executable(prog main.c prog.c)

Also, we’ll need to create a build directory to build our project:

$ cd project && mkdir build && cd build

After that, we run cmake from the build directory to generate a Makefile for our project:

$ cmake ..
-- The C compiler identification is GNU 10.3.1
-- The CXX compiler identification is GNU 10.3.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/baeldung/CMake/project/build

Finally, we run the Makefile using the make tool to generate the executable program:

$ make
scanning dependencies of target prog
[ 33%] Building C object CMakeFiles/hello.dir/main.c.o
[ 66%] Building C object CMakeFiles/hello.dir/prog.c.o
[100%] Linking C executable prog
[100%] Built target prog

After that, let’s check the binary using the file command:

$ file prog 
prog: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=21edad69c38b540f9d3dd508a78509bc2d8c61d8, for GNU/Linux 3.2.0, not stripped

As a result, we have a 32-bit executable program linked to a 32-bit C library.

6. Conclusion

It’s to our advantage if we’re able to execute 32-bit programs on 64-bit systems when needed – and without spending considerable time and effort on porting an old application to a 64-bit system.

In short, we’ve seen how to install development tools and make our system compatible with 32-bit programs. Finally, we covered how to use CMake to automate the compilation.

Authors Bottom

If you have a few years of experience in the Linux ecosystem, and you’re interested in sharing that experience with the community, have a look at our Contribution Guidelines.

Comments are closed on this article!