Updated Feb 26, 2014 because I’m dumb.
Cross-Compiling shouldn’t be so Hard
I’m creating this post in the off-chance that someone else with a Raspberry Pi who can’t figure out how to cross-compile from Windows will find this useful (or find it at all).
I have been working on and off on a project that will run on Windows, x86 Linux, and ARM Linux. Problem is, my Linux machines are a little slow. My x86 Linux is running on an Everex Cloudbook sporting a 1500MHz VIA C7-M processor with 512MB of memory. My ARM Linux is running on a Rapsberry Pi with an ARM11 CPU clocked at 700MHz with 512MB of RAM. Compiling on my Windows PC is much faster (even if MinGW make won’t run multiple threads at once). Cross-compiling for x86 Linux isn’t really fruitful, as it’s not my main target. However, cross-compiling for my Raspberry Pi would be tremendously beneficial (a 30-second compile on my PC takes 5-10 minutes on the RPi).
Find a Working Cross Compiler
Now, most websites online talk about cross-compiling from x86 Linux to ARM Linux. The RPi creators even have their own cross-compiler available. There are, however, 2 problems with this. First, I don’t want to install Linux on my PC, virtual or otherwise. Second, their GCC cross-compiler is only at version 4.6, and I need C++11 features only available in GCC 4.7+. My solution was to start with a pre-built GCC 4.7 ARM-Linux cross-compiler and modify things until I got something working on my RPi. Below are the steps I had to take to finally get it working.
1) Find a x86 Windows to ARM Linux cross-compiler. This was fairly simple. A quick google search led me to http://linaro.org. They have GCC 4.7 and GCC 4.8 ARM Linux cross-compiler binaries for Windows.
2) Find the compiler options I would need to tell Linaro GCC to compile specifically for the Raspberry Pi. This wasn’t too difficult. Linaro is pre-built to target ARMv7 instructions and a Cortex-9 CPU, but the following compiler flags take care that issue. These commands (some of which may not actually be necessary) tell the compiler to target the RPi hardware.
1 |
-mcpu=arm1176jzf-s -mthumb -mtune=arm1176jzf-s -mfpu=vfp -marm -march=armv6k -mfloat-abi=hard |
3) Make the compiler link against the RPi static libraries. This was the hardest part. Using steps 1 and 2 I was able to get a file to compile for the RPi, but the problem is that Linaro is built against version 2.6.32 of the Linux Kernel, while the Raspbian image I am using is based on 2.6.26. This meant the generated executable was incompatible. The solution was to copy the static libraries from my RPi to my Windows PC and make the linker use them. This was a two-step process. Note that it took much trial and error, so some of this might be unneeded, but this is the process I got working on my machine.
3a) Copy the libraries: This involved tar-ing the following directories and using SCP to get them off the RPi (I tar-ed instead of “zipping” because data transfer was faster than trying to get the RPi to compress the data). Also note that you must tell tar to follow both symlinks and hardlinks, as both methods are in use. This doubles/triples the size, but you cannot preserve Linux links on a Windows file system.
- /usr/lib/arm-linux-gneuabihf
- /usr/lib/gcc/arm-linux-gneaubihf/4.7/*
- /opt/vc/lib
- /lib/arm-linux-gneaubihf
3b) Remove all libraries that came with Linaro and replace them with the RPi libraries. This is necessary because Linaro was built against a different version of the Linux Kernel than my version of Raspbian. This was not apparent at first, and the seg-faults I was getting on RPi were of no help. What was a help was doing “ file <executable> ” from the RPi, which showed that my executable was linked against 2.6.32, while the RPi is only 2.6.26. I then had to delete the following files from my Linaro Installation:
- <install>\lib\gcc\arm-linux-gnueabihf\4.7.3\*.o|*.a
- <install>\lib\gcc\arm-linux-gnueabihf\4.7.3\arm-linux-gnueabihf
- <install>\arm-linux-gnueabihf\lib
- <install>\arm-linux-gnueabihf\libc\lib
- <install>\arm-linux-gnueabihf\libc\usr\lib
(don’t get me started on how idiotic this layout is. There is actually a “arm-linux-gneaubihf\libc\usr\lib\arm-linux-gneaubihf\usr\lib” directory!?!)
UPDATE: I did NOT need to copy the files in this step (see step 3c) I then placed the RPi libraries as follows:
/lib/arm-linux-gneaubihf became <install>\arm-linux-gneaubihf\libc\lib\arm-linux-gneaubihf/usr/lib/gcc/arm-linux-gneaubihf/4.7/* were copied to the new <install>\arm-linux-gneaubihf\libc\lib directory/usr/lib/arm-linux-gneaubihf became <install>\arm-linux-gneaubihf\libc\usr\lib\arm-linux-gneaubihf/opt/vc/lib wasn’t copied, I guess I didn’t need it after all. It might be needed later.
3c) UPDATE: Turns out that in my repeated trial and error, I skipped a step that had been working previously. Instead of copying the RPi libraries to Linaro, I actually just needed to specify the locations by passing the following lines to G++ (D:\rpi-libs is my Windows-local copy of the RPi static libraris):
1 2 3 4 5 6 7 |
-LD:/rpi-libs/lib/arm-linux-gnueabihf \ -LD:/rpi-libs/usr/lib/arm-linux-gnueabihf \ -LD:/rpi-libs/usr/lib/gcc/arm-linux-gnueabihf/4.7 \ -LD:/rpi-libs/opt/vc/lib/ |
3d) UPDATE: One final problem is that every .o file must be passed to the linker IF that file does not exist in a location that was specified in the configuration when the linker was built. As such, I found I needed to copy the following files to my SOURCE directory because they were HARD CODED into the linker. I could NOT pass these in as object to the compiler, because then they would be listed twice (again, they were HARD CODED), and then the compiler still wouldn’t be able to find the file (I would pass it a /path/to/crt1.o, but it would still look for crt1.o):
1 2 3 |
D:\rpi-libs\usr\lib\arm-linux-gnueabihf\crt*.o D:\rpi-libs\usr\lib\gcc\arm-linux-gnueabihf\4.7\crt*.o |
Of course, via my make file, I can copy these files before compilation, then delete them afterwards and I’m none the wiser. Still, it’s a terrible hack that I can only work around by a) copying my files to the predefined paths built into the Linaro GNU linker or b) building my own linker. I’m lazy, and this solution works.
Finally, Success
Once I had performed the above steps, I was able to build my executable and SCP it to my RPi. A quick “file <executable>” showed that it had been linked against the proper libraries. A quicker “chmod +x” and my executable was chugging away (or actually, waiting for a connection, but still, success!!). Now when I make changes from my Windows PC, I can quickly compile them for my Raspberry Pi. No more waiting for the little ARM processor to chug-away.
What I Learned
First of all, I learned no one seems to cross-compile FROM Windows. In fact, searching “windows linux cross compiler”, “linux cross compiler windows binaries”, etc.. all turn up links for people wanting to “Build Windows applications from Linux”. I still don’t have an x86 cross-compiler, and I refuse to install Cygwin. How is there not a native Windows executable for an x86 Linux cross-compiler? Maybe there is and it’s buried under the Linux to Windows links?
Second, GNU Linker is hard-coded to search library paths, and there is no command-line option to fix this. If I was on Linux, supposedly setting the LIBARY_PATH environment variable would have worked, but that’s a hack. You should be able to directly tell the linker what you want to do.
Finally, it seems no one is bothered by the fact that the default solution seems to be “make your own cross-compiler”. Why would thousands of individual developers each need to create their own cross-compiler. It is nice that the Raspberry Pi creators have a cross-compiler, but it’s stuck at GCC 4.6 and only works under Linux. It would be nice if they at least had GCC 4.7 and GCC 4.8, then maybe I could have been motivated to install a Linux VM.
My Linaro GCC 4.7 Raspbian Linux 2.6.26 Cross-Compiler Windows Binaries (whew)
UPDATE: These files still work, but I think I updated my steps above with a more portable solution. I have uploaded my files here: http://svn.hellwig.us/binaries/Linaro/linaro-14.01-gcc-4.7-arm-RPi-raspbian-2.6.26.zip. Simply unzip this directory somewhere to your computer. Point your makefile or environment to the “<install>\bin” directory, and you should be good to go building RPi executables from your Windows PC. I did not modify any of the Linaro executables or header files (header files might cause some problems down the line,but hopefully not many). I did not modify any of the Raspbian static library files. I simply merged the Linaro toolchain with the Raspbian libraies and viola, a cross-compiler was born. Don’t forget, if you use this, that you’ll need to specify certain compiler options to target the RPi hardware specifically.