Windows 交叉编译 Linux 程序
最近碰到一个需求是要在 Windows 上交叉编译 Linux 程序,用于做 Windows 代码的跨平台编译检查,发现里面弯弯绕绕还挺多的(主要是 Windows 和 Linux 系统层面的一些差异需要注意),就通过一篇博客记录一下整个交叉编译的流程,也加深对交叉编译流程的理解。
整个交叉编译包含2个主要步骤:sysroot 准备和交叉编译工具链的构建。
sysroot 在 WSL 上使用 debootstrap 创建
交叉编译工具链可通过两种方式生成:
- 通过 Cygwin 和 crosstool-ng 生成
- 通过 Linux 和 crosstool-ng 以 canadian cross 方式生成
安装 WSL
使用 WSL(Windows Subsystem for Linux)主要是为了以下2件事:
创建交叉编译的 sysroot
验证交叉编译产物结果
通过 WSL 可以方便地与 Windows 文件系统进行交互
导入 WSL 镜像
可从清华源获取 Ubuntu 的 WSL 镜像(本次使用的是 Ubuntu 20.04.6):
使用如下命令导入 WSL 镜像:
1 | wsl --import <发行版名称> <本机目录> <镜像路径> |
完整命令示例如下:
1 | wsl --import Ubuntu-20.04 D:\VM\WSL\Ubuntu-20.04 D:\Download\ubuntu-20.04.6-wsl-amd64.wsl |
导入完成后可通过如下命令启动指定 Linux 发行版:
1 | wsl -d <发行版名称> |
直接 --import 的实例默认只有
root,与商店安装的「自动创建用户」不同,需要手动建普通用户并设为默认登录用户。
(可选)创建普通用户
在 WSL 内执行如下命令创建用户
1 | adduser <user-name> |
编辑 /etc/wsl.conf 设置默认用户:
1 | [boot] |
在 Windows 侧执行 wsl --shutdown 后重新进入
WSL,确认已以普通用户登录。
(可选)更换软件源
参照阿里云
Ubuntu 软件源说明,根据实际使用的 Ubuntu 版本替换
/etc/apt/sources.list 中地址即可
替换之后记得执行 update 和 upgrade
1 | sudo apt update |
准备 sysroot
sysroot(system root) 是用于编译/交叉编译的“目标系统根目录视图”,仅包含编译时需要的部分(如头文件、库文件、动态链接器等),无法通过 chroot 方式执行程序。
rootfs(root
filesystem)则是用于运行阶段的完整系统根目录,包含完整的
Linux
系统结构(/bin,/sbin,/etc,/usr,/var
等),可以通过 chroot 方式进入并执行程序。
对 rootfs 裁剪掉不必要的文件后可得到 sysroot。
安装依赖
1 | sudo apt install debootstrap systemd-container qemu-user-static binfmt-support |
- debootstrap:从镜像站拉取包并初始化 rootfs。
- systemd-container:以容器方式进入 rootfs,并自动挂载目录。
- qemu-user-static:用于支持在非本机 ISA(Instruction Set Architecture,指令集架构) 的 rootfs 里执行目标程序(例如做 aarch64 的 sysroot 时很有用)
- binfmt-support:让 Linux 支持运行非本机 ISA 的 ELF 程序,可以自动调用 qemu 执行 arm 应用。
初始化 rootfs
通过如下命令初始化 rootfs
1 | sudo debootstrap \ |
执行完成后会提示 “Base system installed successfully”(基础系统成功安装)

初始化 aarch64 rootfs
在 x86_64 机器上也可以直接创建 arm 的 rootfs,只不过中间步骤略微繁琐一点,需要通过 qemu 进行转义,同时 arm 的 ubuntu 软件源地址也和 x86_64 的不一致(https://mirrors.aliyun.com/ubuntu-ports/),需要额外注意。
操作步骤如下:
- 初始化 rootfs(注意
--arch和--foreign参数,为了区分,这里创建的是 Ubuntu 18 的 rootfs)
1 | sudo debootstrap \ |
- 拷贝 qemu-aarch64-static,便于 x86_64 上进入 aarch64 rootfs 执行程序
1 | sudo cp /usr/bin/qemu-aarch64-static /opt/rootfs-bionic-arm64/ |
- 进入 rootfs 完成 debootstrap 后续操作
1 | sudo systemd-nspawn \ |
完成后输出如下:

进入 rootfs
直接通过 chroot 可以进入 rootfs 安装程序,但是通常运行时需要手动挂载 dev、run、proc 等目录,为简化操作,通过 systemd-nspawn 以容器方式进入 rootfs,命令如下:
1 |
|
将脚本保存到本地执行即可,成功进入 rootfs 后输出如下所示:

进入 aarch64 rootfs
启动脚本和 x86-64 类似,只需要 rootfs 内包含用于转译的 qemu-aarch64-static 程序即可
1 |
|
启动后效果如下:

安装依赖
由于 debootstrap 创建 rootfs 时写入的默认软件源往往不完整,在安装依赖前需要重新调整软件源,命令如下所示:
1 | tee /etc/apt/sources.list > /dev/null <<'EOF' |
然后通过如下命令更新软件源和软件。
1 | apt update |
为满足后续验证需要,我们在 rootfs 内安装下列依赖:
1 | apt install -y \ |
同时配置 pip 镜像并安装 conan、cmake 和 ninja(通过 pip 安装的 cmake 和 ninja 版本较新,便于使用):
1 | pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ |
若后续要在 sysroot 内编译或验证 GLFW 等依赖 X11/Wayland 的库,可额外安装开发包(体积较大,按需装):
1 | apt install -y libwayland-dev libxkbcommon-dev xorg-dev |
配置完成后使用 exit 命令退出 rootfs。
创建 sysroot
rootfs 创建完成后,可拷贝 lib、include 和 pkgconfig 目录以生成 sysroot,具体命令如下:
1 |
|
其中 ensure_lib64_ld_linux_symlink 和
fix_absolute_symlinks_for_sysroot 是必要的,因为 rootfs
通过 chroot
执行,其软链接指向根路径的地址是有效的,而在交叉编译中,不可能通过
chroot
方式执行,因此需要手动修正这些软链接的地址,将其转换为相对路径。
打包和解压
打包命令(在 WSL 中执行)
1 | tar --numeric-owner \ |
解压命令(在 Cygwin 中执行)
1 | sysroot_dir="<sysroot 在 Cygwin 中的位置,例如 /cygdrive/d/sysroot-focal>" |
Cygwin 构建交叉编译工具链
安装 Cygwin
为在 Windows 上编译 Linux 的可执行程序,需要模拟出 Linux 运行环境(基本上就是模拟出 POSIX 接口),由于 MSYS2 的工具链更新太过激进,编译低版本的工具链可能出现各种各样的问题,这里选择 Cygwin 作为交叉编译的基础环境。
安装依赖
从官网下载
setup-x86_64.exe,在包列表中勾选下列依赖(具体版本参考下文的完整安装列表):
- autoconf, automake, bison, flex, gawk,
- help2man, texinfo, diffutils, patch, make,
- cmake, ninja, gcc-g++, git, wget, xz, zip, unzip,
- libtool, gperf, libncurses-devel, python312-devel
完整安装列表见:cygcheck-c.txt(通过 cygcheck -c
输出)
建议将 setup-x86_64.exe 放在 Cygwin
的安装目录下面,因为该程序将作为 cygwin
的包管理器,有可能需要频繁使用该程序安装包。
设置环境变量
在 Cygwin 的 shell 配置文件(如
~/.bashrc)末尾中加入下列内容:
1 | export CYGWIN=winsymlinks:sys |
其中 代理地址 用于 crosstool-ng 编译时下载源码。
(可选)卸载 Cygwin
仅删除安装目录时可能残留注册表项;可用下列 PowerShell 脚本(需
PowerShell 7+,删 HKLM
需管理员权限)。
保存为 uninstall_cygwin.ps1 后执行:
1 | #requires -Version 7.0 |
配置工作目录
执行下列命令设置文件夹路径大小写敏感(需要管理员权限,Win11 下使用开发者模式也可以设置)
1 | fsutil.exe file SetCaseSensitiveInfo "D:\workspace" enable |
如果觉得路径太长,可以直接将该路径链接到 ~
1 | ln -s /cygdrive/d/workspace ~/workspace |
安装 crosstool-ng
解压后在源码目录外建 build:
1 | mkdir build && cd build |
将 bin 目录加入 PATH:
1 | export PATH="<ct-ng 安装目录>/bin:/usr/local/bin:/usr/bin" |
例如:
1 | export PATH="/cygdrive/d/ctng-build/install/bin:/usr/local/bin:/usr/bin" |
编译并安装完成后通过如下命令检查是否安装成功:
1 | ct-ng version |

配置工具链
使用菜单进行配置或直接使用已经配置好的 config.HOST-x86_64-cygwin-gnu-x86_64-pc-linux-gnu.txt:
1 | ct-ng menuconfig |
配置时的一些注意事项:
- Linux 头文件版本:在满足程序需求的前提下尽量别追新,保持兼容性(本次选择的 Linux 内核版本为 4.4.302)。
- glibc / gcc / binutils
组合要与目标环境匹配;一般直接对齐某个发行版即可(例如 Ubuntu
20.04:
glibc 2.31+gcc 9+binutils 2.34一类组合,具体以 sysroot 中 glibc 和 binutils 版本为准)。 - 下载 tarball 慢时,可在配置里改为国内镜像(例如清华源)。
构建工具链
通过下列命令执行构建
1 | ct-ng build |
构建时间较长(1h~2h),日志里若出现下载失败,多半是网络或镜像问题。
构建完成后日志输出如下:

如果编译时碰到 internal compiler error 且位置随机,多半是 cygwin 并行执行时出现问题,建议将并行数降低或改为串行执行。可通过如下命令进行调整。
1 | ct-ng build.1 |
基本功能验证
确认编译器版本(可先将 toolchain 的 bin 目录加入 Path 中):
1 | x86_64-linux-gnu-gcc --version |
输出如下:

然后通过一个简单的 C 程序(hello.c)验证编译效果
1 |
|
通过 gcc 进行编译,会默认查找头文件并链接到 glibc
1 | x86_64-linux-gnu-gcc hello.c -o hello |
并通过 file 命令查看二进制情况
1 | file hello |
输出如下:

将 hello 程序拷贝至 WSL 中,通过 ldd 检查 libc
链接情况

最后执行 hello,预期应该会观察到有 hello from Cygwin!
输出,同时返回值为 0

Linux 构建交叉编译工具链
Cygwin 上编译的程序会依赖 Cygwin-1.dll 做 POSIX 接口到 WinAPI 的翻译,导致其性能和稳定性都不如原生 Windows 程序高,做一些简单的验证没问题,但是在复杂场景上会因为各种兼容性问题导致编译的 .o 损坏、编译器 ICE(internal compile error)等,为了更好的适配实际场景,可以通过在 Linux 上编译出 Windows 上运行的交叉编译工具链。
由于编译工具链的平台和工具链运行的平台不一致,这种编译方式就成为 Canadian Cross 编译方式
交叉编译中有3个平台概念(triplet):
build:编译工具链 的平台
host:运行工具链 的平台
target:运行编译产物 的平台
如果 build 平台和 host 平台不一致,就需要2步才能完成交叉编译工具链的构建:
构建出 (<build>-<build>-<host>) 的工具链,称为前置准备工具链(prerequisite tools)
或者至少要求能有一个在 build 平台上能编译 host 平台程序的工具,基本也就是一个精简/全量的交叉编译工具了
利用 step 1 中准备的工具链,构建出最终需要的(<build>-<host>-<target>)交叉编译的工具链
这种方式相当于是用 交叉编译工具 构建 交叉编译工具,有点套娃的意思。
这种方式听起来很复杂,相比 Cygwin 方案需要多准备一些东西,但 crosstool-ng 已经封装好了相关的编译脚本,只需要将依赖的软件包准备好,剩下的操作基本就和 Cygwin 上一致了,而且由于是原生 Linux 系统,相对于 Cygwin 的运行速度更快,且更稳定。
安装依赖
以 Ubuntu 20.04 为例,建议分两组安装依赖。
先安装基础依赖:
1 | sudo apt update |
再安装交叉编译windows工具链使用的的 mingw 依赖:
1 | sudo apt install -y \ |
crosstool-ng 的安装方式同 Cygwin 一致,这里不过多赘述。
配置工具链
当前使用的 triplet 为:
build:x86_64-pc-linux-gnu(WSL / Ubuntu)host:x86_64-w64-mingw32(工具链运行在 Windows)target:x86_64-pc-linux-gnu(最终程序运行在 Linux x86_64 上)
如果需要交叉编译 aarch64,只需要将 target 切换为
aarch64-pc-linux-gnu 即可
也就是 x86_64-w64-mingw32,x86_64-pc-linux-gnu(Canadian
Cross)。
先查看可用 samples(建议先确认名称再创建):
1 | ct-ng list-samples | grep "mingw32.*linux-gnu" |
输出结果如下:

创建工作目录并基于 sample 生成 .config:
1 | mkdir -p ~/devenv/mingw-cross |
在此基础上可通过 menuconfig 做交互调整:
1 | ct-ng menuconfig |
修改后建议执行一次默认化补全并核对:
1 | ct-ng olddefconfig |
输出如下

本次实践里比较关键的版本组合(对标 Ubuntu 20.04,如果需要其他的对标版本,例如 Ubuntu 18.04,则根据实际需要调整即可):
- Linux headers:
4.4.302 - glibc:
2.31(Ubuntu 18 使用 2.27) - binutils:
2.34(Ubuntu 18 使用 2.30) - gcc:
9.5.0
为降低复杂度,可先关闭不必要组件(例如
ltrace/strace),gdb
推荐先使用相对稳定版本(如 12.1)。
配置时的几个注意事项:
ct-ng x86_64-w64-mingw32,x86_64-pc-linux-gnu只负责生成初始.config,后续改动都要在同一目录内进行。- 直接编辑
.config后,一定执行ct-ng olddefconfig,否则部分新旧键可能不一致。 CT_PREFIX_DIR建议指向独立目录(例如${HOME}/x-tools/...),避免覆盖历史构建结果。CT_LOCAL_TARBALLS_DIR建议设置缓存目录,重复构建可显著减少下载时间。- 如果在 Ubuntu 20.04 上对齐 sysroot,建议保持
glibc/binutils/linux headers与目标基线一致,避免链接期和运行期偏差。
此时会在当前目录生成
.config。本次实际使用并验证通过的配置如下:
- config.HOST-x86_64-w64-mingw32-aarch64-pc-linux-gnu:对标 Ubuntu 18.04
- config.HOST-x86_64-w64-mingw32-x86_64-pc-linux-gnu:对标 Ubuntu 20.04
打包工具链和 sysroot
这种方式构建出来的工具链可以直接在 Windows 环境下运行,而不依赖 msys2 / cygwin 等环境,只需要额外处理软链接问题即可。
最简单的处理方式就是将软链接全部转换为实际的文件(dereference),虽然会造成空间的浪费,但是这种方案实现起来简单,而且处理之后可以直接在 windows 上使用,但Windows 上仍需要保证文件夹的路径大小写敏感。处理脚本为 dereference_symlink.py。
交叉编译验证
最后,我们需要结合 sysroot 和编译好的 toolchain,对 CMake 项目进行编译检查,并通过项目内置的单元测试检查功能是否正常。
对于 CMake 的 toolchain 文件,由于我们切换为 WSL 中生成的
sysroot,需要调整 toolchain.cmake 中部分路径和编译选项
,完整内容如下(toolchain-focal.cmake):
1 | set(CMAKE_SYSTEM_NAME Linux) |
相比 ct-ng 自动生成的 sysroot,Ubuntu
下会多一层标识系统架构的三元组目录(<架构-系统-libc 类型>,例如
x86_64-linux-gnu),需要修正库文件的路径:
- 通过
-B指定工具链组件位置(例如crt1.o、crti.o等),将/usr/lib转换为/usr/lib/x86_64-linux-gnu - 通过
-Wl,-rpath-link,<lib-dir>指定链接时间接依赖搜索的系统库位置,将/usr/lib转换为/usr/lib/x86_64-linux-gnu
在 CMake 配置阶段通过 -DCMAKE_TOOLCHAIN_FILE=...
指定上述 toolchain 文件路径即可。
为方便直接测试,可将 WSL 与 Cygwin 中的 workspace 用软链接对齐为同一路径,从而保证 ctest 记录的可执行文件路径一致(在 WSL 中运行位于 Windows 文件系统上的构建产物时,通常会有一定性能损耗)。
可通过如下命令创建软链接
1 | cd ~ |
{fmt}
源码:https://github.com/fmtlib/fmt/releases/download/12.1.0/fmt-12.1.0.zip
编译命令(Cygwin 中执行):
1 | cmake -S . -B build \ |

WSL 中 ctest 执行结果:

Catch2
源码:https://github.com/catchorg/Catch2/archive/refs/tags/v3.14.0.tar.gz
编译命令(Cygwin 中执行):
1 | cmake -S . -B build \ |


WSL 中 ctest 执行结果(CMake 配置阶段读取到了宿主机器上安装的 Python,但 WSL 内暂未安装 Python 3.12,导致 Catch2 的这几个测试用例无法运行,直接跳过):

sqlite
源码:https://sqlite.org/2026/sqlite-amalgamation-3530100.zip
由于 sqlite 的测试依赖 tcl,且需要编译 sqlite-tcl 插件才行,较为繁琐,这里仅做基本的功能验证。
准备一个 CMakeLists.txt 用于编译 sqlite
1 | cmake_minimum_required(VERSION 3.16) |
测试的 sql 文件
1 | CREATE TABLE t(x INTEGER); |
编译命令(Cygwin 中执行):
1 | cmake -S . -B build \ |

WSL 中 ctest 执行结果:

WSL 中 sqlite3_cli 调用输出示例:

Abseil
- Abseil 源码:https://github.com/abseil/abseil-cpp/releases/download/20260107.1/abseil-cpp-20260107.1.tar.gz
- GoogleTest 源码:https://github.com/google/googletest/releases/download/v1.17.0/googletest-1.17.0.tar.gz
Abseil 的测试依赖 GoogleTest,需要将两个源码都解压出来
编译命令(Cygwin 中执行):
1 | cmake -S . -B build -G Ninja \ |

WSL 中 ctest 执行结果:

GLFW
源码:https://github.com/glfw/glfw/archive/refs/tags/3.4.tar.gz
编译命令(Cygwin 中执行):
1 | cmake -S . -B build -G Ninja \ |

由于 GLFW 不支持 ctest,只能手动执行 build/tests
下的单元测试,而且必须手动关闭弹出的 GUI 窗口。
1 | set -euo pipefail |

同时下图展示了 WSL 下运行 glfw heightmap 案例的效果:
