I’m currently trying to build a cross compiler (and other required tools) on FreeBSD. The compiler will run on FreeBSD/amd64 and should produce i386 binaries. This wouldn’t be too hard since that task can easily be accomplished by using the FreeBSD source tree. However, I need the toolchain to produce binaries in the PE/COFF format instead of the default ELF format.
Building the toolchain is somewhat tricky, at least I found it to be poorly documented. But maybe I just looked in the wrong places. Building the toolchain requires:
- Building binutils.
- Locating some header files.
- Building the compiler.
- Building a C Library.
- Building the compiler again so it uses the C library that was just built.
For my needs, I found that the last two steps weren’t needed. I wrote a script that downloads the sources, extracts the archives and builds the toolchain. Here’s the script in full quote (I really wish there was a way to upload files to this thing):
#/bin/sh # # Copyright (c) 2008 Philip Schulz# # # This script downloads, builds and installs GCC and GNU Binutils that can # produce x86 binaries in PE/COFF Format. The cross toolchain needs some headers # that aren't usually present on the host system. However, those headers can be # obtained from the cygwin sources, that's why a snapshot of the cygwin sources # is downloaded. # # After the script finishes, the tools will be located at # ${PREFIX}/${TARGET_ARCH}/bin. Some other binaries will be installed in # ${PREFIX}/bin with their own prefix of ${TARGET_ARCH} but I don't know that # they are for. # # Prefix where the Cross-Tools will live PREFIX="${PREFIX:-/opt}" # Target architecture. TARGET_CPU="${TARGET_CPU:-i386}" TARGET_ARCH=${TARGET_CPU}-tiano-pe # Program that can fetch the files. FETCH_COMMAND="/usr/bin/ftp -V" # GNU Make GNU_MAKE=`which gmake` ################################################################################ # # GCC settings. # ################################################################################ # What version of GCC will be fetched, built and installed GCC_VERSION=gcc-4.2.3 # What mirror to use. GCC_MIRROR=ftp://ftp-stud.fht-esslingen.de/pub/Mirrors/ftp.gnu.org # File name of the GCC sources. Should probably not be changed. GCC_ARCHIVE=$GCC_VERSION.tar.bz2 # Where the GCC Sources can be fetched from. Should probably not be changed. GCC_URL=$GCC_MIRROR/gcc/$GCC_VERSION/$GCC_ARCHIVE # Arguments for the GCC configure script. Should probably not be changed. GCC_CONFIGURE_ARGS="--prefix=${PREFIX} --target=${TARGET_ARCH} " GCC_CONFIGURE_ARGS+="--with-gnu-as --with-gnu-ld --with-newlib " GCC_CONFIGURE_ARGS+="--disable-libssp --disable-nls --enable-languages=c " GCC_CONFIGURE_ARGS+="--program-prefix=${TARGET_ARCH}- " GCC_CONFIGURE_ARGS+="--program-suffix=-4.2.3 " ################################################################################ # # Binutils settings. # ################################################################################ # What version of the GNU binutils will be fetched, build and installed BINUTILS_VERSION=binutils-2.18 # What mirror to use. BINUTILS_MIRROR=ftp://ftp-stud.fht-esslingen.de/pub/Mirrors/ftp.gnu.org # File name of the binutils sources. Should probably not be changed. BINUTILS_ARCHIVE=$BINUTILS_VERSION.tar.gz # Where the GCC Sources can be fetched from. Should probably not be changed. BINUTILS_URL=$BINUTILS_MIRROR/binutils/$BINUTILS_ARCHIVE # Arguments for the GCC configure script. Should probably not be changed. BINUTILS_CONFIGURE_ARGS="--prefix=${PREFIX} --target=${TARGET_ARCH} " BINUTILS_CONFIGURE_ARGS+="--disable-nls " ################################################################################ # # Cygwin settings. # ################################################################################ CYGWIN_SNAPSHOT=20080129 CYGWIN_ARCHIVE=cygwin-src-${CYGWIN_SNAPSHOT}.tar.bz2 CYGWIN_MIRROR=http://cygwin.com/ CYGWIN_URL=${CYGWIN_MIRROR}snapshots/${CYGWIN_ARCHIVE} CYGWIN_DIR=cygwin-snapshot-${CYGWIN_SNAPSHOT}-1 ################################################################################ # # Batch code. # ################################################################################ # # Fetches the files. # do_fetch() { if [ ! ( -f $GCC_ARCHIVE ) ] ; then echo "Fetching ${GCC_URL}" ${FETCH_COMMAND} ${GCC_URL} else echo $GCC_ARCHIVE already locally present. fi if [ ! ( -f $CYGWIN_ARCHIVE ) ] ; then echo "Fetching ${CYGWIN_URL}" ${FETCH_COMMAND} ${CYGWIN_URL} else echo $CYGWIN_ARCHIVE already locally present. fi if [ ! ( -f $BINUTILS_ARCHIVE ) ] ; then echo "Fetching ${BINUTILS_URL}" ${FETCH_COMMAND} ${BINUTILS_URL} else echo $BINUTILS_ARCHIVE already locally present. fi } # # Extracts the archives. # do_extract() { # Remove already extracted files first. rm -rf ${GCC_VERSION} rm -rf ${CYGWIN_DIR} rm -rf ${BINUTILS_VERSION} # Extract the archives if [ -f $GCC_ARCHIVE ] ; then echo "Extracting ${GCC_ARCHIVE}" tar -jxf ${GCC_ARCHIVE} fi if [ -f $CYGWIN_ARCHIVE ] ; then echo "Extracting ${CYGWIN_ARCHIVE}" tar -jxf ${CYGWIN_ARCHIVE} fi if [ -f $BINUTILS_ARCHIVE ] ; then echo "Extracting ${BINUTILS_ARCHIVE}" tar -xzf ${BINUTILS_ARCHIVE} fi } BUILD_DIR_PREFIX=build- # # Builds Binutils. # do_binutils_build() { BUILD_DIR_BINUTILS=${BUILD_DIR_PREFIX}binutils-${TARGET_ARCH} # Remove dir if it exists. if [ -d $BUILD_DIR_BINUTILS ] ; then rm -rf $BUILD_DIR_BINUTILS fi echo "Building binutils..." # Changing directory, so use a sub-shell (?) ( # Create a the build directory. mkdir ${BUILD_DIR_BINUTILS} && cd ${BUILD_DIR_BINUTILS}; # Configure, build and install binutils ../${BINUTILS_VERSION}/configure ${BINUTILS_CONFIGURE_ARGS} && ${GNU_MAKE} -j 12 -w all && ${GNU_MAKE} -w install ) # Remove build dir rm -rf $BUILD_DIR_BINUTILS echo "Binutils Build done." } # # "Builds" cygwin. Actually, it only copies some headers around. # do_cygwin_build() { HEADERS=${PREFIX}/${TARGET_ARCH}/sys-include mkdir -p $HEADERS && cp -rf ${CYGWIN_DIR}/newlib/libc/include/* $HEADERS && cp -rf ${CYGWIN_DIR}/winsup/cygwin/include/* $HEADERS } # # Builds GCC # do_gcc_build() { BUILD_DIR_GCC=${BUILD_DIR_PREFIX}gcc-${TARGET_ARCH} # Remove dir if it exists. if [ -d $BUILD_DIR_GCC ] ; then rm -rf $BUILD_DIR_GCC fi echo "Building GCC..." # Changing directory, so use a sub-shell (?) ( # Create a the build directory. mkdir ${BUILD_DIR_GCC} && cd ${BUILD_DIR_GCC}; # Configure, build and install GCC. ../${GCC_VERSION}/configure $GCC_CONFIGURE_ARGS && ${GNU_MAKE} -j 12 -w all && ${GNU_MAKE} -w install ) rm -rf $BUILD_DIR_BINUTILS echo "GCC Build done." } do_fetch do_extract do_binutils_build do_cygwin_build do_gcc_build
Unfortunately, the gcc binary built by the script, located in /opt/i386-tiano-pe/bin, can’t produce binaries. Invoking the compiler on a source file (“Hello, World!” program) dies with:
$ /opt/i386-tiano-pe/bin/gcc main.c -o main /opt/lib/gcc/i386-tiano-pe/4.2.3/../../../../i386-tiano-pe/bin/ld: crt0.o: No such file: No such file or directory collect2: ld returned 1 exit status
I assume this is because I skipped the last two steps in the list at the beginning of this post. However, using the compiler to generate an assembler file (parameter -S) and then running the assembler on that file to produce an object file does indeed produce a PE/COFF object file.
$ cd /opt/i386-tiano-pe/bin $ ./gcc -S ~/main.c $ ./as -o main.o main.s $ file ./main.o ./main.o: MS Windows COFF Intel 80386 object file