上次 在 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 No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 16.04.7 LTS Release: 16.04 Codename: xenial Linux vm-ubuntu16-amd64 4.15.0-142-generic 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 export PATH=/opt/x86_64-w64-mingw32-15.1/bin:$PATH export BUILD=x86_64-linux-gnuexport HOST=x86_64-w64-mingw32export TARGET=x86_64-linux-gnuexport PREFIX=/opt/crossexport SYSROOT=$PREFIX /$TARGET
另外要注意的是,Ubuntu 16 自带的 GCC 是 5.4.0 版本,而我的交叉编译器是 15.1.0 版本,为了顺利构建,建议保持本机GCC与交叉的GCC版本一致,避免出现一些奇怪的问题。 确保gcc
和x86_64-linux-gnu-gcc
指向同一个版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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) 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)
由于我之前的配置不完整,导致gcc
和x86_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 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 /opttar 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 /opttar 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 /opttar 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 rm -rf $SYSROOT /usr7z a -l cross.7z $PREFIX
打包完成后就可以拷贝到 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个文件需要修改:
x86_64-linux-gnu\lib\libc.so
x86_64-linux-gnu\lib\libm.so
x86_64-linux-gnu\lib\libpthread.so
好了,至此大功告成!再次将修改后的工具链打个包,方便日后使用。
总结 我花费了很多业余时间才成功制作出工具链。可能是一个环境变量未设置,可能是一个编译选项没配好,可能是软件版本的差异,都有可能导致最后无法编译成功,而每一次碰壁都可能要从头再来,因为编译过程比较耗时,所以折腾这个工具链投入了大量的时间试错,真的很不容易。