Blzut3's Weblog

Weblog ECWolf ECWolf Wiki

Building a Cross Compiler for PowerPC Macs

Update 2020/11/27: Updated instructions for GCC 10 and Xcode 11.

Update 2022/1/2: Although I've built upstream GCC 10 fine, I've been given a note that at least one Darwin PowerPC specific branch exists which may contain useful patches.

This guide is for building a modern GCC cross compiler on modern macOS. As of right now this guide is for building GCC 10 on macOS 10.14 "Mojave".

With this guide it will be possible to build modern C++ applications targetting the old PowerPC Macintosh computers. It will even let you use these features while targetting the G3 processors and the 10.4u SDK!

Getting the PowerPC SDKs

The first step is to get the old PowerPC toolchain and SDKs installed. This is fairly easy as there's an XcodeLegacy project that accomplishes just this. As of this writing though there are a few issues with it with Mojave and Xcode 11, but they're fairly easy to solve.

It appears that Xcode 11 isn't fully compatible with the toolchain plugin that it creates. Since I don't care about the Xcode IDE integration I simply removed the plugin.

mv '/Applications/Xcode.app/Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/GCC 4.0.xcplugin'{,~}
mv '/Applications/Xcode.app/Contents/PlugIns/Xcode3Core.ideplugin/Contents/SharedSupport/Developer/Library/Xcode/Plug-ins/GCC 4.2.xcplugin'{,~}

Finally, as mentioned in the project's README, Xcode 7 removed PowerPC support from dsymutil. As the README suggests, this is not normally a big deal, but GCC will try to use it while compiling its libraries so we'll need to grab the old one from Xcode 6.4. Once you mount the Xcode 6.4 dmg, the tool can be found at "Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/dsymutil". Rename this to dsymutil-ppc and place this where it can be found in the PATH.

Building Native GCC

When building GCC, it will normally do a three phase build. First your system compiler builds GCC, then it uses the resulting GCC to build itself, finally it uses the result of that phase to build itself again and checks that the result is the same. When we build a cross compiler this is not possible since the resulting binaries can't be run on the host system. So while not strictly necessary it is probably a good idea to build a native GCC of the same version you're trying to build a cross compiler out of.

This is actually quite simple. Grab the source code to GCC 10.2 and extract it. You will also need to grab the dependencies GMP, MPC, MPFR, and isl. There's a script in the contrib directory called download_prerequisites that will do this for you. However you get these libraries extract them into your GCC source directory and rename the directories to gmp, mpc, mpfr, and isl. GCC will then pick these up and build them for you.

Now outside of the source dirctory create a new directory. Lets call it gcc-build, but the name doesn't matter, it just must be outside of the source directory. From inside there run the following:

../gcc-10.2.0/configure --prefix=/opt/gcc/x86/10 --disable-nls --disable-multilib --enable-languages=c,c++,objc,obj-c++,lto --with-dwarf2
make -j $(sysctl -n hw.ncpu)
sudo make install-strip

You can remove "--disable-multilib" if you desire to have your native compiler target both 32 and 64-bit.

If you get an error about /usr/include then make sure you install the Xcode command line tools and the SDK headers pkg.

xcode-select --install
open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg

Once installed you'll want to remove the fixincludes headers. They're apparently needed for GCC to build, but once installed the end result is that -isysroot won't work on your new compiler.

sudo find /opt/gcc/x86/10/lib/gcc -name '*.h' -exec grep -q 'It has been auto-edited by fixincludes from' {} \; -delete

Note: These would also be the steps to build GCC on a PowerPC Mac. You may run into quirks due to the platform being less well supported, but barring bugs which are outside of the scope of this article, there should be no additonal steps needed.

Building PowerPC GCC

Now that you have your native compiler, we can now build the cross-compiler. First we need to setup a few things.

sudo mkdir -p /opt/gcc/ppc/10/powerpc-apple-darwin10.8.0/bin
sudo ln -s /Developer/SDKs/MacOSX10.4u.sdk/usr/include /opt/gcc/ppc/10/powerpc-apple-darwin10.8.0/include
sudo ln -s /Developer/SDKs/MacOSX10.4u.sdk/usr/lib /opt/gcc/ppc/10/powerpc-apple-darwin10.8.0/lib
for i in ar as lipo nm ranlib; do
    sudo ln -s "/usr/bin/$i" "/opt/gcc/ppc/10/powerpc-apple-darwin10.8.0/bin/$i"
done

Inside of /opt/gcc/ppc/10/powerpc-apple-darwin10.8.0/bin create a file called ld with the following contents and give it execute permissions. This is necessary since XcodeLegacy needs to occasionally use the MACOSX_DEPLOYMENT_TARGET environment variable to use the right linker, and we need to use different linkers for the PowerPC libraries and GCC itself.

#!/bin/bash

if [[ $MACOSX_PPC_DEPLOYMENT_TARGET ]]; then
    export MACOSX_DEPLOYMENT_TARGET=$MACOSX_PPC_DEPLOYMENT_TARGET
fi

exec /usr/bin/ld "$@"

Similarly create a file called strip with the following contents and give it execute permissions. The strip binary calls out to ld so we'll force it to use the PowerPC ld. Note that the version number given to MACOSX_DEPLOYMENT_TARGET doesn't affect anything here.

#!/bin/bash

# Strip calls out to ld so use PPC compatible ld
export MACOSX_DEPLOYMENT_TARGET=10.4

exec /usr/bin/strip "$@"

Make sure that dsymutil-ppc is available on the PATH. We need to modify ../gcc-10.2.0/gcc/config/darwin.h to call dsymutil with the new name, so look for "#define DSYMUTIL" and modify it like so:

#define DSYMUTIL "\ndsymutil-ppc"

Create a new empty build directory. Run the following to build the cross compiler:

export MACOSX_PPC_DEPLOYMENT_TARGET=10.4
CC=/opt/gcc/x86/10/bin/gcc CXX=/opt/gcc/x86/10/bin/g++ ../gcc-10.2.0/configure \
    --prefix=/opt/gcc/ppc/10 --disable-nls --disable-multilib --enable-languages=c,c++,objc,obj-c++,lto --with-dwarf2 \
    --target=powerpc-apple-darwin10.8.0 --with-sysroot=/Developer/SDKs/MacOSX10.4u.sdk \
    C{,XX}FLAGS_FOR_TARGET="-isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4 -g -O2" \
    LDFLAGS_FOR_TARGET="-isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4"
make -j $(sysctl -n hw.ncpu)
sudo make install-strip

You may see an error at the end of the build: "error: : error: unable to get target for '', see --version and --triple." This is because the Makefile calls the wrong dsymutil. While it is possible to do some more gymnastics to avoid that error, it's ultimately non-fatal and harmless so just ignore it and continue onto the install. Per the native step, remove the fixincludes for best results after the install completes.

Building PowerPC 64-bit (G5) Compiler (Optional)

Unlike with x86, GCC does not build multilib for PowerPC. If you are so inclined, you can also build a PowerPC64 compiler. The procedure is exactly the same except that you'll want to set "--target=powerpc64-apple-darwin10.8.0" and use the 10.5 SDK and set the DEPLOYMENT_TARGET/macosx-version-min to 10.5 as well. (As this is the only release of OS X supporting 64-bit PowerPC.) Of course you'll also have to use powerpc64-apple-darwin10.8.0 in the directory you set up before compiling.

I don't have a G5 Mac to test, so I have to assume that since it's building binaries they work. There was one immediate issue I could see that needed to be solved in that libgcc_s.10.5.dylib from the 10.5 SDK was missing the ppc64 binary. Unless that's done through some non-obvious method it doesn't look like it's XcodeLegacy's doing, so I assume the solution is to just restore the backup file.

sudo mv /Developer/SDKs/MacOSX10.5.sdk/usr/lib/libgcc_s.10.5.dylib{.bak,}

Using the New Compiler

You will now be able to set CC to "/opt/gcc/ppc/10/bin/powerpc-apple-darwin10.8.0-gcc" and CXX to "/opt/gcc/ppc/10/bin/powerpc-apple-darwin10.8.0-g++" to use the new cross compiler. However, it is important to keep in mind that the resulting binaries will dynamically link to the new libstdc++ and libgcc that you built. Obviously the target machine will not have these libraries, so it is recommended to link them statically. Fortunately this is made quite easy. Simply compile with "-static-libstdc++" and "-staic-libgcc".

Although I forget exactly which version it happened in, GCC started adding padding to the end of structures in more cases where it didn't in 4.0/4.2. If you're doing the technically undefined reading of files by casting to a struct you'll likely get hit by this. You may need to use attributes to remove the padding. So far all cases of code crashing with the newer compilers I've found are caused by this or the order of initialization changing.

Another thing to keep in mind is that there's a regression with C++ exceptions in Objective-C++ code so your code may need to be rewritten to avoid this issue if you use C++ execeptions (generally possible by pulling relevant portions out of Objective-C++ and into C++).

 

© 2004-2024 Braden "Blzut3" Obrzut