Easy Cross Compiling for Windows from Linux using Clang

Recently I wanted to build produce a Windows build from my Linux machine for a game engine I am developing. The engine is written in C and uses CMake to generate the build system. The only dependencies the engine has are stb, LZ4 and the Vulkan headers. Luckily, all of these projects neither use C++ nor any complicated build system; they can be dropped right into my project. After researching ways to cross compile, here are a couple options I found:

All of these options seemed heavy handed and would invariably add unecessary dependencies and complicate my build process. I wanted a simple solution that allowed me to build my software with minimal changes.

Understanding the basics

Have you ever wondered what variables come into play when compiling an application for a target machine from your host machine? For instance, if you wanted to produce a Windows AArch64 build from your PowerPC Linux host, you would need to consider the "triple-target", which includes three variables: the ISA, the system libraries, and the ABI.

The ISA is the lowest common denominator and determines what instructions a machine can execute. For example, a x86_64 computer cannot execute any instructions from an AArch64 computer. To compile your code with GCC, you must first compile a cross-compiler for the desired architecture of the target. With Clang, every host-target combination will have its own unique set of binaries, headers, and libraries. Clang is natively a cross-compiler due to its use of LLVM IR, which eliminates the extra step of having to compile the cross-compiler, but it still does not provide the necessary OS files.

This brings us to the next topic: system libraries. When you compile a program and execute it, the main function is not the first function to be called. The _start function is responsible for initializing the environment in which your program will execute. It defines the reset vectors, the layout of data in memory, the addresses of ISRs, initializes the CPU and stack pointer, and so on. On a full-blown OS, the OS would invoke a program loader, which also does some initialization, and then call _start. On Linux, this would be a function in the exec() family, typically execve() or execvp(), and for Windows, LdrInitializeThunk function in ntdll.dll. The implementation of the _start function is completely compiler and architecture-specific. On GNU/Linux, the file is called crt1.o.

Finally, we have the ABI of our binary, which specifies the binary format of object files, program libraries, and so on.

Now that we are aware of the different variables we can use what we know to develop a painless method for cross compiling for Windows on Linux.

A simple program

#include <stdio.h>

int main(int argc, char* argv[]) {
    printf("Hello");
    return 0;
}

Compile

$ clang -O0 -std=c99 -o main.out main.c

Debug

$ gdb main.out

More to come...


Comment Section