制作 Windows 上的 x86_64-linux-gnu 交叉编译器
2025-07-26 17:16:54

上次在 Ubuntu 16 系统上成功制作了生成 Win32 程序的交叉编译器,这次就可以用它来制作运行在 Windows 上但target=x86_64-linux-gnu的交叉工具链。
我的目的是能在 Windows 上编译出 Linux 下的程序。由于跨了三个平台(虽然build和target相同),所以称为加拿大编译(canadian)。

准备

与上次不同的是,这次的target值是x86_64-linux-gnu,所以需要准备 Linux 内核头文件和 glibc 库。
由于我期望的目标系统就是当前编译的宿主机,所以就以当前系统的软件版本为准即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.7 LTS
Release: 16.04
Codename: xenial

# uname -a
Linux vm-ubuntu16-amd64 4.15.0-142-generic #146~16.04.1-Ubuntu SMP Tue Apr 13 09:27:15 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

# 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/
Linux内核版本是 4.15:https://mirrors.ustc.edu.cn/kernel.org/linux/kernel/v4.x/linux-4.15.tar.xz
glibc版本是 2.23:https://mirrors.ustc.edu.cn/gnu/glibc/glibc-2.23.tar.xz


安装必要的软件包

1
sudo apt -y install gawk p7zip-full

设置必要的环境变量

1
2
3
4
5
6
7
8
# 将上次制作的交叉工具链加入到 PATH 环境变量中
export PATH=/opt/x86_64-w64-mingw32-15.1/bin:$PATH

export BUILD=x86_64-linux-gnu
export HOST=x86_64-w64-mingw32
export TARGET=x86_64-linux-gnu
export PREFIX=/opt/cross
export SYSROOT=$PREFIX/$TARGET

另外要注意的是,Ubuntu 16 自带的 GCC 是 5.4.0 版本,而我的交叉编译器是 15.1.0 版本,为了顺利构建,建议保持本机GCC与交叉的GCC版本一致,避免出现一些奇怪的问题。
确保gccx86_64-linux-gnu-gcc指向同一个版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/x86_64-linux-gnu/15/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../configure --program-suffix=-15 --with-gcc-major-version-only --enable-languages=c,c++ --enable-shared --disable-nls --disable-multilib --disable-werror --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 15.1.0 (GCC)

# x86_64-linux-gnu-gcc -v
Using built-in specs.
COLLECT_GCC=x86_64-linux-gnu-gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.12' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.12)

由于我之前的配置不完整,导致gccx86_64-linux-gnu-gcc指向了不同版本的 GCC,这会给后面的编译造成麻烦。
所以要设置一下

1
2
3
4
5
6
7
8
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 5
update-alternatives --install /usr/bin/gcc gcc /usr/local/bin/gcc-15 15
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 5
update-alternatives --install /usr/bin/g++ g++ /usr/local/bin/g++-15 15
update-alternatives --install /usr/bin/x86_64-linux-gnu-gcc x86_64-linux-gnu-gcc /usr/bin/gcc-5 5
update-alternatives --install /usr/bin/x86_64-linux-gnu-gcc x86_64-linux-gnu-gcc /usr/local/bin/gcc-15 15
update-alternatives --install /usr/bin/x86_64-linux-gnu-g++ x86_64-linux-gnu-g++ /usr/bin/g++-5 5
update-alternatives --install /usr/bin/x86_64-linux-gnu-g++ x86_64-linux-gnu-g++ /usr/local/bin/g++-15 15

再看看上次制作的交叉编译器

1
2
3
4
5
6
7
8
9
# x86_64-w64-mingw32-gcc -v
Using built-in specs.
COLLECT_GCC=x86_64-w64-mingw32-gcc
COLLECT_LTO_WRAPPER=/opt/x86_64-w64-mingw32-15.1/bin/../libexec/gcc/x86_64-w64-mingw32/15.1.0/lto-wrapper
Target: x86_64-w64-mingw32
Configured with: ../configure --prefix=/opt/cross --with-sysroot=/opt/cross/x86_64-w64-mingw32 --enable-languages=c,c++ --enable-threads=win32 --enable-shared --disable-multilib --disable-werror --disable-checking --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-w64-mingw32
Thread model: win32
Supported LTO compression algorithms: zlib
gcc version 15.1.0 (GCC)

OK了,没问题。

编译 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

安装 Linux 内核头文件

1
2
3
4
cd /opt
tar xf linux-4.15.tar.xz
cd linux-4.15/
make ARCH=x86_64 headers_install INSTALL_HDR_PATH=$SYSROOT

头文件会安装到$SYSROOT/include下。

编译 glibc

1
2
3
4
5
6
7
cd /opt
tar xf glibc-2.23.tar.xz
cd glibc-2.23/
mkdir build && cd build
../configure --prefix=$SYSROOT --with-headers=$SYSROOT/include --disable-multilib --disable-werror --build=$BUILD --host=$TARGET
make -j$(nproc)
make install

这里值得一提的是--host=$TARGET,glibc 没有target选项,glibc 的--host等同于 gcc 的--target


这里在配置--prefix时有个坑,我最初是这样编译安装的:

1
2
3
4
ln -s $SYSROOT $SYSROOT/usr
../configure --prefix=/usr --with-headers=$SYSROOT/include --disable-multilib --disable-werror --build=$BUILD --host=$TARGET
make -j$(nproc)
make install DESTDIR=$SYSROOT

首先创建了$SYSROOT/usr这个目录的软链接,链接到$SYSROOT自身,这样在后面 GCC 编译时会从$SYSROOT/usr/include中搜索头文件(实际是$SYSROOT/include)。
这样看上去最后安装的路径也应该是对的,可是最后执行 GCC 的make install时会提示libpthread的链接错误,感觉是和 glibc 的--prefix有关系,它会从/usr/lib64等地方去搜索库,而不是$SYSROOT/lib64这里搜索,所以-prefix一定要是$SYSROOT

编译 GCC

关键点来了,前面我们将 glibc 安装到$SYSROOT下了,但是 GCC 在编译时会从$SYSROOT/usr下搜索头文件,所以我们要临时创建usr目录的软链接

1
ln -s $SYSROOT $SYSROOT/usr

这样当 GCC 访问$SYSROOT/usr/include时,实际访问的就是$SYSROOT/include目录。

开始编译 GCC

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

打包

1
2
3
4
5
# 删除前面为了编译 GCC 而创建的 usr 目录
rm -rf $SYSROOT/usr

# 打包为 .7z 文件
7z a -l cross.7z $PREFIX
  • -l:打包软链接指向的实际文件,而不是链接本身。


打包完成后就可以拷贝到 Windows 系统上使用了。

测试修改

在 Windows 上编译程序时发生错误

1
2
3
4
5
C:\Windows\system32\cmd.exe /C "cd . && C:\jack\cross-toolchain\Linux-x86_64\bin\x86_64-linux-gnu-g++.exe   CMakeFiles/cmTC_22c52.dir/testCXXCompiler.cxx.o -o cmTC_22c52   && cd ."
C:/jack/cross-toolchain/Linux-x86_64/bin/../lib/gcc/x86_64-linux-gnu/15.1.0/../../../../x86_64-linux-gnu/bin/ld.exe: cannot find /opt/cross/x86_64-linux-gnu/lib/libm.so.6 inside C:/jack/cross-toolchain/Linux-x86_64/bin/../x86_64-linux-gnu
C:/jack/cross-toolchain/Linux-x86_64/bin/../lib/gcc/x86_64-linux-gnu/15.1.0/../../../../x86_64-linux-gnu/bin/ld.exe: cannot find /opt/cross/x86_64-linux-gnu/lib/libmvec_nonshared.a inside C:/jack/cross-toolchain/Linux-x86_64/bin/../x86_64-linux-gnu
C:/jack/cross-toolchain/Linux-x86_64/bin/../lib/gcc/x86_64-linux-gnu/15.1.0/../../../../x86_64-linux-gnu/bin/ld.exe: cannot find /opt/cross/x86_64-linux-gnu/lib/libmvec.so.1 inside C:/jack/cross-toolchain/Linux-x86_64/bin/../x86_64-linux-gnu
collect2.exe: error: ld returned 1 exit status

当链接器链接-lm时,它会找到libm.so这个脚本,读取里面的绝对路径/opt/cross/.../libm.so.6,然后尝试去加载它。因为 Windows 系统上不存在/opt/cross这个目录,所以链接失败。
需要手动编辑这些包含了绝对路径的链接器脚本。将里面所有的绝对路径修改为相对路径,即只保留文件名。
还好工作量不大,只有3个文件需要修改:

  1. x86_64-linux-gnu\lib\libc.so
  2. x86_64-linux-gnu\lib\libm.so
  3. x86_64-linux-gnu\lib\libpthread.so


好了,至此大功告成!再次将修改后的工具链打个包,方便日后使用。

总结

我花费了很多业余时间才成功制作出工具链。可能是一个环境变量未设置,可能是一个编译选项没配好,可能是软件版本的差异,都有可能导致最后无法编译成功,而每一次碰壁都可能要从头再来,因为编译过程比较耗时,所以折腾这个工具链投入了大量的时间试错,真的很不容易。