太难了!制作交叉编译器 x86_64-w64-mingw32-gcc 踩坑记录
2025-07-13 11:08:28

研究 GCC 交叉编译器有些天了,相关的资料不算很多,可能是因为太折腾了,这个话题没有太多人讨论。没有特别需求的话大家更愿意用系统自带的交叉编译器,开箱即用。


网络上相关资料更多都是在制作 Linux x86_64 和 Linux arm64 之间的交叉编译器,我也尝试过,但流程很复杂,每个人的编译环境又不同,很容易编译失败。
因为我平时主要在 Windows 平台上工作,所以尝试在 Linux 上编译一个 Windows 上运行的 GCC,经过数十次编译失败,耗费N个小时后,终于成功了!


首先有些基本概念要了解一下:

  • 有个工具包叫Binutils,它是汇编器、链接器的源码,是必须要编译的,它不依赖 glibc 和 Linux 头文件,比较独立,所以编译一般不会出问题。
  • 如果目标是 Linux 平台,则编译过程比较复杂,gcc、glibc要分阶段编译,比较复杂,容易失败。而目标平台是 Windows 的话则简单很多。

准备

我的编译环境是 Ubuntu 16.04

1
2
3
4
5
6
# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.7 LTS
Release: 16.04
Codename: xenial

之所以选择这么低版本的系统是因为其 glibc 版本足够低,编译出的工具链可以运行在更高的系统之上,反之则不行。

1
2
3
4
5
6
# ldd --version
ldd (Ubuntu GLIBC 2.23-0ubuntu11.3) 2.23
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.


Binutils 版本选择的是 2.44:https://mirrors.ustc.edu.cn/gnu/binutils/
GCC 版本选择的是 15.1.0:https://mirrors.ustc.edu.cn/gnu/gcc/gcc-15.1.0/
mingw-w64 13.0.0:https://www.mingw-w64.org/source/


预先设置一些环境变量,避免后面重复输入

1
2
3
4
5
export BUILD=x86_64-linux-gnu
export HOST=x86_64-linux-gnu
export TARGET=x86_64-w64-mingw32
export PREFIX=/opt/cross
export SYSROOT=$PREFIX/$TARGET

编译 Binutils

1
2
3
4
5
6
7
cd /opt
tar xf binutils-2.44.tar.xz
cd binutils-2.44/
mkdir build && cd build
../configure --prefix=$PREFIX --disable-nls --disable-multilib --build=$BUILD --host=$HOST --target=$TARGET
make -j$(nproc)
make install-strip

binutils 不依赖内核头文件和 glibc,几乎不会失败。

编译 Stage 1 GCC

编译第一阶段的 GCC,仅编译 C 部分,它的目的是编译后面的 mingw crt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cd /opt
tar xf gcc-15.1.0.tar.xz
cd gcc-15.1.0/
./contrib/download_prerequisites
mkdir build1 && cd build1
../configure --prefix=$PREFIX \
--with-sysroot=$SYSROOT \
--enable-languages=c \
--disable-shared \
--disable-threads \
--disable-libstdcxx \
--disable-multilib \
--disable-werror \
--disable-checking \
--build=$BUILD \
--host=$HOST \
--target=$TARGET

接着需要临时创建一个目录的链接,不这么做的话稍后会出现找不到 mingw-w64 头文件的错误,错误信息类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
if [ -d ../prev-gcc ]; then \
cd ../prev-gcc && \
make real-install-headers-tar DESTDIR=`pwd`/../gcc/ \
libsubdir=. ; \
else \
set -e; for ml in `cat fixinc_list`; do \
sysroot_headers_suffix=`echo ${ml} | sed -e 's/;.*$//'`; \
multi_dir=`echo ${ml} | sed -e 's/^[^;]*;//'`; \
fix_dir=include-fixed${multi_dir}; \
if ! false && test ! -d `echo /usr/x86_64-w64-mingw32${sysroot_headers_suffix}/mingw/include | sed -e :a -e 's,[^/]*/\.\.\/,,' -e ta`; then \
echo "The directory (BUILD_SYSTEM_HEADER_DIR) that should contain system headers does not exist:" >&2 ; \
echo " `echo /usr/x86_64-w64-mingw32${sysroot_headers_suffix}/mingw/include | sed -e :a -e 's,[^/]*/\.\.\/,,' -e ta`" >&2 ; \
case linux-gnu in \
darwin*) \
echo "(on Darwin this usually means you need to pass the --with-sysroot= flag to point to a valid MacOS SDK)" >&2; \
;; \
esac; \
tooldir_sysinc=`echo "/opt/x86_64-w64-mingw32-gcc15/lib/gcc/x86_64-w64-mingw32/15.1.0/../../../../x86_64-w64-mingw32/sys-include" | sed -e :a -e "s,[^/]*/\.\.\/,," -e ta`; \
if test "x`echo /usr/x86_64-w64-mingw32${sysroot_headers_suffix}/mingw/include | sed -e :a -e 's,[^/]*/\.\.\/,,' -e ta`" = "x${tooldir_sysinc}"; \
then sleep 1; else exit 1; fi; \
fi; \
/bin/bash ../../gcc/../mkinstalldirs ${fix_dir}; \
chmod a+rx ${fix_dir} || true; \
(TARGET_MACHINE='x86_64-w64-mingw32'; srcdir=`cd ../../gcc; ${PWDCMD-pwd}`; \
SHELL='/bin/bash'; MACRO_LIST=`${PWDCMD-pwd}`/macro_list ; \
gcc_dir=`${PWDCMD-pwd}` ; \
export TARGET_MACHINE srcdir SHELL MACRO_LIST && \
cd ../build-x86_64-linux-gnu/fixincludes && \
/bin/bash ./fixinc.sh "${gcc_dir}/${fix_dir}" \
`echo /usr/x86_64-w64-mingw32${sysroot_headers_suffix}/mingw/include | sed -e :a -e 's,[^/]*/\.\.\/,,' -e ta` ); \
done; \
fi
The directory (BUILD_SYSTEM_HEADER_DIR) that should contain system headers does not exist:
/usr/x86_64-w64-mingw32/mingw/include
make[2]: *** [Makefile:3643: stmp-fixinc] Error 1
make[2]: *** Waiting for unfinished jobs....
rm gcc.pod
make[2]: Leaving directory '/opt/gcc-15.1.0/build/gcc'
make[1]: *** [Makefile:4728: all-gcc] Error 2
make[1]: Leaving directory '/opt/gcc-15.1.0/build'
make: *** [Makefile:1068: all] Error 2

Makefile 文件中硬编码了一个规则:它强制性地在sysroot路径后面拼接上了一个/mingw/include子目录,然后在这里查找头文件。
$SYSROOT/mingw下创建目录链接

1
2
3
4
5
6
7
8
9
10
# 创建一个名为 include 的符号链接,指向真实的 include 目录
mkdir -p $SYSROOT/mingw
sudo ln -s $SYSROOT/include $SYSROOT/mingw/include

# 为 lib 目录也创建一个,以防后面链接阶段出现类似问题
sudo ln -s $SYSROOT/lib $SYSROOT/mingw/lib

# 开始编译
make -j$(nproc) all-gcc
make install-strip-gcc

编译mignw-w64 crt

从这一步开始,我们需要用到刚才制作的 GCC 编译器了,所以要将bin目录添加到PATH环境变量中:

1
export PATH=$PREFIX/bin:$PATH

开始编译

1
2
3
4
5
cd /opt/mingw-w64-v13.0.0/mingw-w64-crt
mkdir build && cd build
../configure --prefix=$SYSROOT --build=$BUILD --host=$TARGET
make -j$(nproc)
make install-strip

编译完整 GCC

这是最后一步,编译完整的 GCC。注意编译时新建一个文件夹,不要在第一次的 build 目录中进行

1
2
3
4
5
cd /opt/gcc-15.1.0/
mkdir build2 && cd build2
../configure --prefix=$PREFIX --with-sysroot=$SYSROOT --enable-languages=c,c++ --enable-threads=win32 --enable-shared --disable-multilib --disable-werror --disable-checking --build=$BUILD --host=$HOST --target=$TARGET
make -j$(nproc)
make install-strip

大功告成!最后可以删除$SYSROOT/mingw目录了,用不上了。

打包

最后用dpkg打包为.deb文件,方便安装到其他机器上使用。

相关阅读

「天龙八部」年轻人的第一个GCC交叉编译器