I hoped this day would never come. I'm building GCC from source. Specifically a cross-compiler to ARM.

It's an automake project, so you'd think it'd work the same as other ones. You know,

./configure --target=arm-eabi
make

And you're done.

Hah, nope.

You run that and get some inscrutable error after 15 minutes of building. Turns out there are some missing dependencies. Luckily there's a script to download them for you in the gcc repo:

./contrib/download_prerequisites
./configure --target=arm-eabi
make

This throws another error. This bug report from 2007 closed as wontfix reveals that in-tree builds have been broken for at least two decades.

So an out-of-tree build is necessary with GCC.

make distclean
../gcc/configure --target=arm-eabi
make

But this throws some error too. Clearly we are going to need a guide here. I used the official GCC pages on configuration and building as well as an OSDev page on building a GCC cross-compiler.

The "building" page explains, "If you are not building GNU binutils in the same source tree as GCC, you will need a cross-assembler and cross-linker installed before configuring GCC."

Missing this seems like a common mistake worth writing a clear error message for. But what do I know.

After building and installing cross-targeted binutils to ~/.local, you might think we're getting close to done. Nuh-uh, we're just getting started. Run this:

make distclean
../gcc/configure --target=arm-eabi --prefix="$HOME/.local" --enable-languages=c
make -j8

And get error configure: error: Link tests are not allowed after GCC_NO_EXECUTABLES.

Fortunately there is a somewhat well-trafficked SO answer addressing this very message. The top answer recommends the --disable-bootstrap flag, although its diagnosis doesn't quite match my situation. But maybe it'll work anyway...

make distclean
../gcc/configure --target=arm-eabi --prefix="$HOME/.local" --enable-languages=c --disable-bootstrap
make -j8

Hah, nope.

But now there's an error message indicating that ../../../gcc/libssp/gets-chk.c failed to compile due to a missing include. Ah-hah, surely we're getting close now. Seems like it might be a bug in GCC. All I need to do is back to the last stable version, releases/gcc-15.1.0, then try those commands again.

Tried it, nope, nothing.

I handed my problem off to Claude Sonnet 4.0 and it recommended --disable-libssp flag, which actually worked, although it was recommended alongside several other flags which did not fix the error.

make distclean
rm -rf * .*
../gcc/configure --target=arm-eabi --prefix="$HOME/.local" --enable-languages=c --disable-bootstrap --disable-libssp
make -j8

Nice! We have a working C compiler.

However, we disabled C++ to make things simpler. Time to add that baby back.

make distclean
rm -rf * .*
../gcc/configure --target=arm-eabi --prefix="$HOME/.local" --enable-languages=c,c++ --disable-libssp --disable-bootstrap
make -j8

Now we get configure: error: Link tests are not allowed after GCC_NO_EXECUTABLES. again. Drat.

Back to the SO question. Another answer suggests it might have something to do with no suitable standard library. I tried few seemingly related flags individually, --with-newlib, --disable-shared, and --disable-multilib. But none of them got me any closer...

A message from the gcc help mailing list in 2012 contains an explanation of error message Link tests are not allowed after GCC_NO_EXECUTABLES:

The "Link tests are not allowed after GCC_NO_EXECUTABLES" could be told otherwise: "When the toolchain cannot create executables for the target, it is impossible to make any link tests in order to see what features the target C library has and what not!".

And it turns out this was the key. Though I didn't realized it until a little later...

Probably the test executables rely on the full standard library, I thought, which my compiler configuration isn't really even intended to support, as I'll be compiling code for a bare-metal context where certain stdlib fns like printf don't make sense (that is, they rely on syscalls which are not provided since there is no OS).

And this sort of function is probably required by the language spec itself. But the language must have a stripped down version, and that's what we want. Like a no-std environment in Rust.

But going back to the log immediately preceding the failure, this stuck out to me:

checking command to parse /home/jrpear/repos/gcc-cross/./gcc/nm -B output from  /home/jrpear/repos/gcc-cross/./gcc/xgcc -B/home/jrpear/repos/gcc-cross/./gcc/ -B/home/jrpear/.local/arm-eabi/bin/ -B/home/jrpear/.local/arm-eabi/lib/ -isystem /home/jrpear/.local/arm-eabi/include -isystem /home/jrpear/.local/arm-eabi/sys-include    object... failed
checking for ANSI C header files... no
checking for sys/types.h... no
checking for sys/stat.h... no
checking for stdlib.h... no
checking for string.h... no
checking for memory.h... no
checking for strings.h... no
checking for inttypes.h... no
checking for stdint.h... no
checking for unistd.h... no
checking for dlfcn.h... no
checking for objdir... .libs

It's not finding those headers. What if we try --without-headers so it doesn't look for them?

make distclean
rm -rf * .*
../gcc/configure --target=arm-eabi --prefix="$HOME/.local" --enable-languages=c,c++ --disable-libssp --disable-bootstrap --disable-shared --disable-multilib --without-headers
make -j8

It certainly progresses further than it did. Still see those nos, but they don't cause any problem. But then later we get an error:

checking for the value of EOF... configure: error: computing EOF failed

I grepped through the gcc codebase for the error message and found that it reported during a check which is only run when $is_hosted is true. I remembered a flag from the OSDev page --disable-hosted-libstdcxx. Maybe that's the secret here.

But what is hosted libcxx anyway?

For hosted implementations, the set of standard library headers required by the C++ standard is much larger than for freestanding ones.

---cppreference

It's a term from the C++ standard. Freestanding is for embedded, like Rust no_std environment, and hosted is for a program running on an OS, like the full Rust environment.

This is exactly the key I was thinking of earlier. Yes, I thought, this flag ought to do it.

make distclean
rm -rf * .*
../gcc/configure --target=arm-eabi --prefix="$HOME/.local" --enable-languages=c,c++ --disable-libssp --disable-bootstrap --disable-shared --disable-multilib --without-headers --disable-hosted-libstdcxx
make -j8

Bingo! Hallelujah! Yes, this works. We have a working C and C++ cross-compiler.

And it turns out you can remove --disable-bootstrap, --disable-shared, --disable-multilib and the build still works fine. Although --disable-multilib sped up compile times quite a bit.

So the final configuration is:

../gcc/configure --target=arm-eabi --prefix="$HOME/.local" --enable-languages=c,c++ --disable-libssp --without-headers --disable-hosted-libstdcxx

Wow, that was painful to find out. Some time I'll figure out how to add back in the Ada compiler as well, but that's for another day.

Major lessons?


Please send comments to blogger-jack@pearson.onl.