Cross Compiling With GCC

Important note - Although this document was originally written in 2004, and refers to version of gcc, binutils, glibc, and mingw runtimes that are now obsolete, the basic procedures for setting up a cross-compiling environment from scratch are still the same today. For Linux users, most modern distros including Fedora and Ubuntu actually have official packages for installing a cross-compiling environment that targets MS Windows. Rather than building your own compiler, you are advised to use the officially provided ones if you happen to need to build MS Windows executables on Linux.

The GNU Compiler Collection lends itself extremely well to cross compiling, due to its modular architecture. This page is a brief overview of setting up GCC to compile executables to run on foreign linux platforms, and also the Mingw Win32 platform What is Cross Compiling Cross compiling is the process of building libraries and binaries on one platform that are intended for use on another platform. The other platform may be a different operating system entirely than the host the exe is being built on, or simply another architecture running the same operating system as the host. We will examine the following scenarios:

  • Targetting the i386-redhat-linux platform from MacOS X
  • Targetting Win32/Mingw from Mac OS X

Because we this document is from the perspective of a Mac OS X host, all of our objects are compiled with a prefix of /sw, to be compatible with fink. On a standard linux box, the prefix should probably be /usr/local. Keep this in mind if you try this on a Linux or Unix box.

GCC Cross Compiler Build Steps

  1. Build and install binutils (includes linker, assembler) for target
  2. Install runtime libraries and header files, especially C standard library
  3. Build and install gcc

Dealing with Autoconf

In building a cross compiler or cross compiling a program, the configure script flags must be set properly. Below is a brief explaination of the three main switches that control cross compiling:

Switch Explaination
--build The architecture of the host OS (usually autodetected)
--host The architecture of the OS that the resulting program will run on
--target The architecture of the OS that this program is intended to work with

Note the subtle difference between the --host and --target flags. When building a cross compiler, both bintuils and gcc are configured with the --target set to the target operating system, but since they are cross compilers, they will run on the local host OS. Thus we don't specify anything for the --host option. For building libraries and binaries with our cross compiler, we would specify both the --target and --host flags to be the target operating system because the libraries or binaries are intended to actually run on that platform.

The architecture designations used by autoconf configure scripts usually are in the following format:

    architecture-vendor-os

Sometimes the vender tag is left out. The following are several examples of common architecture tags used:

    i386-redhat-linux
    ppc-yellowdog-linux
    ppc-apple-darwin
    i686-pc-cygwin
    i386-mingw32
    i386-pc-mingw32
    i686-pc-cygwin
    i386-linux
    i686-linux
    sparc-linux
    sparc64-linux
    arm-linux

When configuring, if you don't know the full vender tag, you can often just use the platform-os name.

Building An i386-redhat-linux cross-compiler

Although we are using OS X as the host OS, these steps work exactly the same on any linux host, targetting any other linux architecture.

Obtain the source

You can either get the source code for binutils and gcc directly from the pristine sources at ftp.gnu.org, or you can extract them from the redhat (or whatever distro you want to cross-compile for) sources, in this case SRPMS. By extracting them from the SRPM, you can guarranty that your cross compiler will be exactly the same as the redhat official compiler, thus allowing it to be used in a distributed compile environment with greater success.

In addition, you will also need to extract the glibc libraries from the redhat binary rpms (you need the -devel and -headers rpms for the header files). Although you can compile glibc with your cross compiler, you will need to bootstrap the compiler itself with a binary glibc for that target platform.

I use mc to browse into the src.rpm file, and copy out the source tarball and the patches. Then, using the spec file, I patch the source trees for binutils and gcc just like redhat patched them.

Configuring and compiling bintuils

After unpacking and patching binutils, run configure in the root of the source tree with the --target set to i386-redhat-linux:

./configure --prefix=/sw --target=i386-redhat-linux

After a make and a make install, you should find the following files:

    /sw/bin/i386-redhat-linux-addr2line
    /sw/bin/i386-redhat-linux-ar
    /sw/bin/i386-redhat-linux-as
    /sw/bin/i386-redhat-linux-c++
    /sw/bin/i386-redhat-linux-c++filt
    /sw/bin/i386-redhat-linux-cpp
    /sw/bin/i386-redhat-linux-g++
    /sw/bin/i386-redhat-linux-gcc
    /sw/bin/i386-redhat-linux-gcc-3.3.2
    /sw/bin/i386-redhat-linux-gccbug
    /sw/bin/i386-redhat-linux-gcov
    /sw/bin/i386-redhat-linux-ld
    /sw/bin/i386-redhat-linux-nm
    /sw/bin/i386-redhat-linux-objcopy
    /sw/bin/i386-redhat-linux-objdump
    /sw/bin/i386-redhat-linux-ranlib
    /sw/bin/i386-redhat-linux-readelf
    /sw/bin/i386-redhat-linux-size
    /sw/bin/i386-redhat-linux-strings
    /sw/bin/i386-redhat-linux-strip
    /sw/i386-redhat-linux/bin/
    /sw/i386-redhat-linux/share/
    /sw/i386-redhat-linux/man/
    /sw/i386-redhat-linux/info/
    /sw/i386-redhat-linux/man/
    /sw/i386-redhat-linux/lib/

The basic assembler, linker, and binary manipulation tools are now installed.

Installing the Runtime Libraries and Header Files

The runtime files needed for any glibc-based linux cross-compiler target are all the libraries that are part of the glibc package that are in /lib and /usr/lib. These files can be extracted directly from the glibc rpm using the wonderful mc file manager on a redhat linux machine. Press ENTER to open the rpm within mc, and then press ENTER to open the contents.cpio file. Inside that archive you will find the actual files. We need to copy /lib/lib* and /usr/lib/lib* all to the /sw/i386-redhat-linux/lib folder. Note that because we are flattening the library files out into one directory we will have to fix broken symlinks.

Header files usually come from glibc-devel and glibc-headers. Put all header files and subdirectories in /sw/i386-redhat-linux/include.

Configuring and Building GCC

Unpack the gcc source code and patch to taste. Then, inside the gcc source root, it is important that you create a directory and run configure inside that directory. Do not run configure in the root of gcc, or you will have build problems. For example:

cd gcc-3.3.2-1
mkdir i386-redhat-linux
cd i386-redhat-linux
../configure --prefix=/sw --target=i386-redhat-linux

To determine the appropriate configure options, it is sometimes helpful to use the native gcc on the target platform to determine the options that it was built with. To do this, run gcc with a -v option:

$ gcc -v
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/3.3.2/specs
Configured with: ../configure --target=i386-redhat-linux --prefix=/usr 
--enable-languages=c,c++,objc --enable-shared --enable-threads=posix 
--with-system-zlib --enable-__cxa_atexit
Thread model: posix
gcc version 3.3.2 20031022 (Red Hat Linux 3.3.2-1)

Although most native gcc distributions will compile will all possible languages enabled, most often you only want C, C++ and possibly objc or java. Use the --enable-languages switch to specify the languages you want the cross compiler to support. Except for the --prefix switch, you can pass all the flags to configure that the native compiler used. Just add the appropriate --target switch.

A working example of configuration is the following:

../configure --target=i386-redhat-linux --prefix=/sw \
--enable-languages=c,c++ --enable-shared --enable-threads=posix \
--with-system-zlib --enable-__cxa_atexit

After configure, a make and make install will install the cross compiler. At this point your cross compiler will be available as i386-redhat-linux-gcc or i386-redhat-linux-g++ etc.

Using Your i386-redhat-linux Cross Compiler

Use the cross compiler as you would use the compiler normally, except that you prefix the compiler with “i386-redhat-linux.” A simple example:

i386-redhat-linux-gcc -o hello hello.c

Cross-compiling Autoconf-enabled Packages

To compile an autoconf configured program to run on a target architecture using your cross compiler, and if the configure.in file supports it, you configure using the --target and --host switches together:

./configure --target=i386-redhat-linux --host=i386-redhat-linux

Building an i386-pc-mingw32 Cross Compiler

Obtaining the Source

The source code for binutils and the latest gcc (already patched) along with the mingw runtime and header files from http://www.mingw.org. The current runtime file (you want the binary version, not the source) is mingw-runtime-3.2.tar.gz, and the current header files are in w32api-2.4.tar.gz.

Building bintuils

Binutils is built much the same was as the i386-redhat-linux target, except the target in this case is either i386-mingw32 or i386-pc-mingw32. Some configure scripts match *-*-mingw32, so i386-pc-mingw32 is probably a better architecture choice than the shorter version. The configure command would look like so:

./configure --prefix=/sw --target=i386-pc-mingw32

Build using make and install using make install. The installation will create the following files and directories:

    /sw/bin/i386-pc-mingw32-addr2line
    /sw/bin/i386-pc-mingw32-ar
    /sw/bin/i386-pc-mingw32-as
    /sw/bin/i386-pc-mingw32-c++
    /sw/bin/i386-pc-mingw32-c++filt
    /sw/bin/i386-pc-mingw32-cpp
    /sw/bin/i386-pc-mingw32-g++
    /sw/bin/i386-pc-mingw32-gcc
    /sw/bin/i386-pc-mingw32-gcc-3.3.1
    /sw/bin/i386-pc-mingw32-gccbug
    /sw/bin/i386-pc-mingw32-gcov
    /sw/bin/i386-pc-mingw32-ld
    /sw/bin/i386-pc-mingw32-nm
    /sw/bin/i386-pc-mingw32-objcopy
    /sw/bin/i386-pc-mingw32-objdump
    /sw/bin/i386-pc-mingw32-ranlib
    /sw/bin/i386-pc-mingw32-readelf
    /sw/bin/i386-pc-mingw32-size
    /sw/bin/i386-pc-mingw32-strings
    /sw/bin/i386-pc-mingw32-strip
    /sw/i386-pc-mingw32/bin/
    /sw/i386-pc-mingw32/share/
    /sw/i386-pc-mingw32/man/
    /sw/i386-pc-mingw32/info/
    /sw/i386-pc-mingw32/man/
    /sw/i386-pc-mingw32/lib/

Installing the Runtime and Header Files

The runtime comes as a binary tarball from http://www.mingw.org. As mentioned the current version is mingw-runtime-3.2.tar.gz. This file can be untarred directly in the /sw/i386-pc-mingw32 folder that the binutils install created:

cd /sw/i386-pc-mingw32
tar -xvzf /path/to/mingw-runtime.tar.gz

The mingw runtime consists of stubs for the GNU linker to use that correspond to the basic MSVCRT.DLL and friends shared libraries. You don't actually need the DLL itself to build a program that dynamically link against it.

The other required component is the collection of header files. This is contained in the w32api-2.4.tar.gz file. All of the standard C and C++ header files are there, and also Microsoft-specific header files written from scratch by the mingw developers. Untar the w32api tarball in the /sw/i386-pc-mingw32 directory just like we did with the runtime tarball:

cd /sw/i386-pc-mingw32
tar -xvzf /path/to/w32api-2.4.tar.gz

Configuring and Building the Mingw32 GCC

Untar the source and make a directory inside the root of the source tree and configure in a similar manner to the i386-redhat-linux target. The native mingw gcc compiler, when passed the -v flag, returns:

    Reading specs from ../lib/gcc-lib/mingw32/3.2.3/specs
    Configured with: ../gcc/configure --with-gcc --with-gnu-ld --with-gnu-as 
    --host=mingw32 --target=mingw32 --prefix=/mingw --enable-threads 
    --disable-nls --enable-languages=c++,f77,objc --disable-win32-registry 
    --disable-shared --enable-sjlj-exceptions
    Thread model: win32
    gcc version 3.2.3 (mingw special 20030504-1)

Configuring and making our mingw cross compiler would look like this:

cd gcc-3.3.1-20030804-1
mkdir i386-pc-mingw32
cd i386-pc-mingw32
../configure --prefix=/sw --target=i386-pc-mingw32 --with-gcc --with-gnu-ld \
   --with-gnu-as --enable-threads --disable-nls --enable-languages=c++,f77,objc \
   --disable-win32-registry --disable-shared --enable-sjlj-exceptions
make
make install

Adding Libraries To the Cross Compiling Environment

Linux Targets

You can add libraries (already compiled for the target platform) to the directory PREFIX/ARCH/lib. In our case, that is /sw/i386-redhat-linux/lib. Copy the libraries (complete with symlinks) just as they appear in the devel package. Header files should all go in PREFIX/ARCH/include, for example, /sw/i386-redhat-linux/include.

Mingw Target

The libraries and headers go in the same place as in the linux architectures, except that you don't need .dll files. You do need the .dll.a stubs, however.

Dealing with pkg-config

pkg-config is a convenient way of dealing with include files and linker paths, and it works in a cross-compiling environment just as well as in a native environment, with a few caveats. You do not want to use the same .pc files for the cross-compile target as you do for your native host. If you do, the program may compile but it will not link. Instead, put .pc files in PREFIX/ARCH/lib/pkgconfig. Then edit the pc file and make sure that the paths are set to your real paths as they really are in the cross-compiling environment. For example, a .pc file may say that its include path is /usr/include/gtk-2.0, but in the cross-compile environment it should be set to /sw/i386-redhat-linux/include/gtk-2.0 in this case.

Once pkg-config is set up right, simply set the PKG_CONFIG_DIR environment variable before you run configure or make the program.


Navigation
Personal Tools