ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Windows WSL 下 ARM64 交叉编译与 deb 打包实战

Windows WSL 下 ARM64 交叉编译与 deb 打包实战 在 Windows 上写代码最后要把程序丢到一块 ARM64 的开发板或者工控机上跑这个场景这些年我遇到太多次了。最偷懒的做法当然是在板子上直接编译但一块四核小板跑一次完整构建可能你泡完茶回来还没编完要是项目里再带上 Qt、OpenCV 这种体量的依赖基本就没法干活。所以我后来固定下来的流程是Windows 上用 WSL装一套 aarch64 交叉工具链把源码编成 arm64 的二进制再用 dpkg-buildpackage 打成标准 deb 包最后传到板子上一行dpkg -i装完。板子从头到尾只负责运行和验证编译这件事全在 Windows 这边完成。这套流程听起来简单但中间有几个坎WSL 装完之后要不要换源、交叉工具链该用 apt 的还是官方 tarball、sysroot 从哪来、CMake 为什么会偷偷链接到宿主机的 x86 库、debian/control里的 Architecture 字段到底该写 arm64 还是 armhf。这些细节一个没注意就会在板子上收获一个Exec format error或者GLIBC_2.34 not found。下面我就把这条链路从头到尾拆一遍包括每一步为什么这么做、参数怎么选、坑在哪、怎么排查。不管你是刚接触交叉编译的新手还是已经打包过几次但总在依赖上翻车的老手应该都能从里面捞到点东西。1. 为什么要在 Windows 上做 arm64 的 deb 包1.1 三条常见路线摆在一起对比在决定用哪套方案之前先把手上能选的路都列出来因为很多人的第一反应是直接在板子上编译不就行了然后被编译时间教育一顿。路线构建耗时中型 C 项目环境一致性上手成本适合场景板子上原生编译40 分钟到 3 小时最好所见即所得最低只改几行代码的小补丁、调试期虚拟机 qemu 全量模拟 arm641.5 到 5 倍原生时间好apt 装依赖最省心中等依赖特别多、第一次跑通项目WSL 交叉工具链接近 x86 原生速度需要自己维护 sysroot中等偏高日常迭代、CI 复用、批量出包这张表里最容易被低估的是第二行。纯 qemu 模拟有个好处你在模拟环境里apt install libfoo-dev装的就是 arm64 的库不用折腾 sysroot构建脚本和板子上完全一致。代价就是慢而且是全流程慢包括链接。我做过一个带 Qt 的项目全量模拟下链接阶段单次就要六分钟改一次等一次心态会崩。交叉编译的核心优势就是快——编译本身跑在原生 x86 上只有极少数需要跑目标平台程序的环节比如代码生成器才用 qemu 转一下。但代价是你得自己准备一套 arm64 的头文件和库也就是 sysroot还得保证构建系统不会串味去拿宿主机的库。这就是后面几节要重点解决的问题。顺便说一句arm64 在这一点上比当年的 armhf 舒服太多了。armhf 时代有 gnueabihf 和 gnueabi 的 ABI 之差还有硬浮点软浮点的坑交叉工具链选错一个字母出来的二进制就跑不起来。arm64 的 Linux ABI 是统一的 LP64aarch64-linux-gnu这一个 target 基本通吃工具链选错的概率低了很多。1.2 deb 包相比直接拷二进制的真实价值很多人会问交叉编译出来的二进制直接 scp 过去不就完了为什么要费劲打成 deb我一开始也是这么想的直到设备从一台变成二十台。直接拷二进制的第一个问题是依赖。你的程序依赖libfoo.so.1你自己的开发板上装了客户那台没装运行起来就是error while loading shared libraries。你要么写文档让人家自己装要么把 so 一起拷过去再设LD_LIBRARY_PATH两条路都很脏。deb 包在debian/control的Depends里把这些依赖写清楚dpkg -i的时候会直接告诉你缺什么apt-get -f install会帮你补齐。第二个问题是可追溯。dpkg -l | grep myapp能看到版本号dpkg -L myapp能看到这个包往系统里塞了哪些文件dpkg -s myapp能看到描述和依赖。出问题的时候回滚就是装一个旧版本的 deb不需要回忆上次我拷了哪几个文件到哪个目录。第三个问题是和系统集成。你要装一个 systemd 服务、一份 udev 规则、一个 desktop 文件用 deb 的debian/install、debian/myapp.service声明一下dh_installsystemd会自动处理 enable卸载的时候也会自动清掉。手工操作这些换台机器就得重新来一遍还容易漏。第四个是批量部署。二十台设备把 deb 放在内网 HTTP 服务器或者本地源里一句apt install全搞定。如果走 CI构建产物天然就是一个 deb产出即交付物中间不需要任何人工转换。1.3 交叉编译真正的难点在哪把难点说清楚后面看步骤的时候心里才有底。第一是 sysroot 的完整性。你的程序链接时要找libz.so头文件里#include zlib.h这两样东西都得在 sysroot 里而且必须是 arm64 版本的。缺一个就是cannot find -lz或者fatal error: zlib.h: No such file or directory。第二是工程配置的串味。宿主机的/usr/lib/x86_64-linux-gnu里有一堆 .so/usr/lib/x86_64-linux-gnu/pkgconfig里有一堆 .pc 文件。你的构建系统如果不加约束pkg-config --libs openssl拿到的就是宿主的 x86 库路径和版本链接期不报错直到你拷到板子上才发现有问题或者交叉链接器直接报file in wrong format。第三是 C 库版本匹配。你在 WSL 的 Ubuntu 24.04 上装了默认的交叉工具链它带的 glibc 是 2.39板子上跑的镜像是三年前的老系统glibc 2.28。编译链接一路顺利到板子上运行报version GLIBC_2.34 not found。这个错最气人因为整个构建过程没有任何警告。第四是构建系统各自的小脾气。Autotools 认--hostCMake 要 toolchain fileMeson 要 cross fileGo 只要设两个环境变量Rust 要装 target 再加.cargo/config.toml。每个项目的改法都不一样这也是为什么我更倾向把交叉配置固化成文件放进仓库——换个人接手五分钟就能复现环境。难点说完了你会发现它们都是一次性投入。环境搭好之后日常迭代就是改代码、敲一条dpkg-buildpackage剩下的时间都是在看编译日志。这笔账很划算。2. 环境搭建WSL、交叉工具链与 sysroot2.1 WSL 装完之后先做这五件事WSL 的安装现在简单到一条命令。在 Windows 的 PowerShell 或者终端里管理员权限执行wsl --install -d Ubuntu-22.04 wsl --set-default-version 2 wsl --updatewsl --update这条别跳过。很多人会遇到WSL needs updating, your version of Windows Subsystem for Linux is too old这种提示尤其在某些长期没更新的机器上或者装完新发行版之后。本质上就是 WSL 的内核组件或者 store 版本落后了wsl --update一次性解决。企业环境下如果被策略挡住可能还需要手动下载离线更新包这个具体看环境。装完之后在 WSL 里做四件事。第一件把源码放到 Linux 文件系统里不要放在/mnt/c/...。WSL2 访问 Windows 盘走的是 9p 协议元数据操作特别慢一个几千文件的项目git status都能卡好几秒编译时的增量扫描更明显。我习惯在~/projects下干活需要和 Windows 侧共享代码时用 VS Code 的 Remote - WSL 插件编辑体验在 Windows文件实际在 Linux 这边。第二件配一下.wslconfig。默认 WSL2 会吃掉最多一半物理内存编译大项目的时候容易把 Windows 拖死。在C:\Users\你的用户名\.wslconfig里写[wsl2] memory12GB processors8 swap8GB localhostForwardingtrueprocessors别设成全部核心数留一两个给 Windows 本身不然编译的时候整个桌面都在卡。swap给大一点链接阶段的内存峰值经常比编译阶段高得多。第三件确认 Linux 发行版的版本和你要对齐的目标系统尽量接近。目标板子跑 Ubuntu 22.04 arm64那 WSL 这边就装 Ubuntu 22.04这样工具链默认带的 glibc 版本2.35和目标系统一致省掉一大堆版本问题。别图新装 24.04 去给 20.04 的板子出包。第四件装编译打包的基础包sudo apt update sudo apt install -y build-essential debhelper devscripts lintian \ fakeroot cmake ninja-build pkg-config file rsyncdevscripts里有dch、debclean这些顺手工具lintian用来检查打出来的包有没有明显毛病file和rsync后面排查问题时天天用。提示WSL 里的 apt 源如果是默认的官方源国内拉包会比较慢可以换成就近的镜像。这一步不影响功能纯粹是省时间。换源之后记得sudo apt update一次并且留意源里是否有 aarch64 的 ports 仓库后面要用。2.2 交叉工具链apt 装还是用官方 tarball这一步的选择标准只有一条工具链自带的 glibc 版本必须小于等于目标板子上的 glibc 版本。高版本 glibc 编译出来的程序用低版本 glibc 的运行时是加载不了的符号版本对不上。先在板子上查# 在开发板上执行 uname -m # 期望看到 aarch64如果是 armv7l 说明是 32 位系统 ldd --version # 看 glibc 版本比如 2.31 dpkg --print-architecture cat /etc/os-release把这三行结果记到项目 README 里这是整个交叉编译环境的基准线。然后回 WSL看 apt 里的工具链版本sudo apt install -y gcc-aarch64-linux-gnu g-aarch64-linux-gnu \ binutils-aarch64-linux-gnu cpp-aarch64-linux-gnu aarch64-linux-gnu-gcc -v 21 | tail -3 aarch64-linux-gnu-gcc -dumpversionUbuntu 22.04 上的gcc-aarch64-linux-gnu是 GCC 11带 glibc 2.35Ubuntu 20.04 上是 GCC 9glibc 2.31。如果你的板子是 Ubuntu 22.04 或更新直接 apt 装最省事版本匹配Debian 打包的Build-Depends也能正常满足。如果板子上的系统很老比如 glibc 2.28那这条工具链编出来的东西就跑不起来。这时候有两个选择一是装一个旧发行版的 WSL 发行版专门做这件事二是在 WSL 里用容器比如debian:buster的镜像做构建。前者简单但切换麻烦后者干净但要装 Docker。还有一种做法是从 ARM 官方下载独立工具链 tarball它对 glibc 版本的控制更明确下载解压加 PATH 就能用# 示意命令具体版本号按官方最新发布替换 tar -xf gcc-arm-*-x86_64-aarch64-none-linux-gnu.tar.xz -C ~/toolchains/ export PATH$HOME/toolchains/gcc-arm-*-x86_64-aarch64-none-linux-gnu/bin:$PATH aarch64-none-linux-gnu-gcc --version注意这个工具链的 target 前缀是aarch64-none-linux-gnu而不是aarch64-linux-gnu在 Makefile 和 CMake 里要统一替换混用会导致找不到编译器。2.3 sysroot 的三种拿法以及我为什么推荐前两种sysroot 说白了就是目标板上/usr/include和/usr/lib的一份拷贝交叉编译时编译器用它替代宿主机的这两目录。方法一从板子上 rsync 下来。这是最贴近真实的一套库板子上有什么编译时就有什么。mkdir -p ~/sysroot/rpi4 rsync -avz --copy-unsafe-links \ --exclude*.a \ pi192.168.1.50:/usr/include/ ~/sysroot/rpi4/usr/include/ rsync -avz --copy-unsafe-links \ pi192.168.1.50:/usr/lib/ ~/sysroot/rpi4/usr/lib/ rsync -avz --copy-unsafe-links \ pi192.168.1.50:/lib/ ~/sysroot/rpi4/lib/ rsync -avz --copy-unsafe-links \ pi192.168.1.50:/usr/share/pkgconfig/ ~/sysroot/rpi4/usr/share/pkgconfig/--copy-unsafe-links很重要因为/usr/lib里大量.so是符号链接直接拷链接会在新环境里指向不存在的路径。整个 sysroot 大概几百 MB 到 1 GBrsync 增量同步很快板子上装了新库再跑一次就同步过来了。方法二用 dpkg 的多架构支持。在 WSL 里开 arm64 架构然后从 ports 仓库装 arm64 的库sudo dpkg --add-architecture arm64 # 给现有源加上 [archamd64] 限定 # 再新增一条 [archarm64] 的 ports 源例如 # deb [archarm64] http://ports.ubuntu.com/ubuntu-ports jammy main universe sudo apt update sudo apt install -y libssl-dev:arm64 libz-dev:arm64这条路的好处是依赖解析自动完成libssl-dev:arm64会自动带上libssl3:arm64。坏处是很多-dev包在两个架构间不共存——它们都往/usr/include写同名头文件dpkg 会直接冲突。所以这套更适合依赖极少的项目依赖一多就回到方法一。方法三arm64 容器 qemu 全量模拟。在 WSL 里装 Docker注册 binfmtsudo apt install -y qemu-user-static binfmt-support docker run --rm --privileged multiarch/qemu-user-static --reset -p yes docker run --platform linux/arm64 -it ubuntu:22.04 bash进去之后apt install装的所有东西都是 arm64 的构建脚本和板子上完全一致一行都不用改。代价是慢编译时间大概是原生 x86 的两到三倍。我的经验是项目依赖在十个包以内用方法一依赖几十个包、经常要新增依赖就别硬扛了直接用方法三把容器镜像固化成Dockerfile提交到仓库团队里谁都能一秒复现环境。2.4 先写个 hello world 验证工具链在动真项目之前务必先跑一个最小验证。这一步能省下后面几个小时的困惑。cat hello.c EOF #include stdio.h int main(void) { printf(hello from aarch64\n); return 0; } EOF aarch64-linux-gnu-gcc -O2 -o hello hello.c --sysroot$HOME/sysroot/rpi4 file hello # 期望输出: ELF 64-bit LSB pie executable, ARM aarch64, ...file的输出里出现ARM aarch64就说明工具链和 sysroot 至少在基本编译上是通的。想在 WSL 里直接跑一下看看效果用 qemu 用户态模拟qemu-aarch64-static -L $HOME/sysroot/rpi4 ./hello # hello from aarch64-L指定动态链接器的搜索路径不带这个参数会报No such file or directory——这个报错很有迷惑性因为它看起来像是找不到可执行文件实际上找不到的是/lib/ld-linux-aarch64.so.1。再确认一下解释器路径readelf -l hello | grep interpreter # [Requesting program interpreter: /lib/ld-linux-aarch64.so.1]这三步走通说明地基没问题可以开始接真实项目了。3. 交叉编译实操让构建系统认准目标平台3.1 Makefile 和 Autotools 项目怎么改普通的 Makefile 项目最简单把编译器前缀通过变量传进去就行make CCaarch64-linux-gnu-gcc \ CXXaarch64-linux-gnu-g \ ARaarch64-linux-gnu-ar \ STRIPaarch64-linux-gnu-strip \ CFLAGS--sysroot$HOME/sysroot/rpi4 -O2 \ LDFLAGS--sysroot$HOME/sysroot/rpi4 \ -j$(nproc)如果项目的 Makefile 里把CC ? gcc写成了CC gcc那命令行传参会被覆盖。这种情况改 Makefile 用?或者在make前面加环境变量CC... makemake 的命令行参数优先于文件内的赋值但环境变量不优先。这个细节很多人第一次会踩。Autotools 项目规范得多认--host就够了./configure --hostaarch64-linux-gnu \ --buildx86_64-linux-gnu \ --prefix/usr \ --sysroot$HOME/sysroot/rpi4 \ --disable-static make -j$(nproc) V1--host和--build要分开写。--build是执行构建的机器x86_64--host是程序运行的机器aarch64。只写--host大部分时候也能工作但 configure 脚本里一些需要本地执行的辅助程序比如代码生成器的编译判断可能出错两个都写更稳。--sysroot是 Automake 层面的通用选项会同时影响编译和链接。如果某个项目的 configure 不认这个参数就退回环境变量export CFLAGS--sysroot$HOME/sysroot/rpi4 export CXXFLAGS--sysroot$HOME/sysroot/rpi4 export LDFLAGS--sysroot$HOME/sysroot/rpi4 export CPPFLAGS--sysroot$HOME/sysroot/rpi4V1是为了让 make 把完整的编译命令行打出来。交叉编译排查问题时能看到实际传给 gcc 的参数比什么都重要别嫌日志长。3.2 CMake 项目的 toolchain file 怎么写CMake 项目要单独写一个 toolchain file这份文件我一般放在仓库的cmake/toolchain-aarch64.cmakeset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g) set(SYSROOT $ENV{HOME}/sysroot/rpi4) set(CMAKE_SYSROOT ${SYSROOT}) set(CMAKE_FIND_ROOT_PATH ${SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) set(CMAKE_PREFIX_PATH ${SYSROOT}/usr) set(CMAKE_INSTALL_PREFIX /usr)逐行说一下为什么要这么写。CMAKE_SYSTEM_NAME设成 Linux 会触发 CMake 的交叉编译模式这是最关键的开关不设的话 CMake 会用一堆try_run在宿主机上跑测试结果全错。CMAKE_SYSTEM_PROCESSOR设 aarch64 主要是给项目里的条件判断用。CMAKE_SYSROOT会变成编译器的--sysroot同时影响编译和链接阶段的头文件、库搜索路径。CMAKE_FIND_ROOT_PATH一组四个变量是防串味的核心。MODE_PROGRAM设NEVER表示找可执行程序时只看宿主机——因为构建过程中要跑protoc、moc、代码生成脚本这些东西必须是 x86 的不能跑到 sysroot 里找一个 arm64 版本出来就算找到了也跑不了。MODE_LIBRARY和MODE_INCLUDE设ONLY表示只在 sysroot 里找库和头文件杜绝拿到/usr/lib/x86_64-linux-gnu下东西的可能。这四个变量搞反也是常见的构建事故来源。用的时候cmake -S . -B build-arm64 \ -DCMAKE_TOOLCHAIN_FILEcmake/toolchain-aarch64.cmake \ -DCMAKE_BUILD_TYPERelease cmake --build build-arm64 -j$(nproc)CMAKE_BUILD_TYPE这个别漏。不少 CMake 项目在没有指定类型时默认不加任何优化、也不定义NDEBUG出来的二进制体积大、性能差而且带一堆断言检查。用在正式设备上不合适。3.3 pkg-config 是交叉编译里最容易翻车的一环pkg-config这套机制本身没有架构概念它默认从/usr/lib/x86_64-linux-gnu/pkgconfig、/usr/lib/pkgconfig、/usr/share/pkgconfig这些目录找.pc文件。你在 WSL 里装过libssl-dev那它就会返回 x86 的-lssl -lcrypto交叉链接器拿到这个路径后要么报file in wrong format要么在某些情况下静默链接到宿主的库比如静态库.a在两边路径相同的时候。正确的做法是彻底改掉它的搜索目录而不是追加export PKG_CONFIG_LIBDIR$HOME/sysroot/rpi4/usr/lib/aarch64-linux-gnu/pkgconfig:$HOME/sysroot/rpi4/usr/share/pkgconfig export PKG_CONFIG_SYSROOT_DIR$HOME/sysroot/rpi4 export PKG_CONFIG_PATH注意这里是PKG_CONFIG_LIBDIR而不是PKG_CONFIG_PATH。区别很重要PKG_CONFIG_PATH是在默认路径之前追加搜索目录默认路径还在宿主的.pc依然会被找到PKG_CONFIG_LIBDIR是替换掉默认路径只有你指定的目录会被搜索。这是最干净的做法。PKG_CONFIG_SYSROOT_DIR会把.pc文件里写的-I/usr/include自动加上 sysroot 前缀变成-I/home/you/sysroot/rpi4/usr/include。没有这个变量.pc里的绝对路径会指向宿主机的/usr/include。验证一下是否生效pkg-config --cflags --libs openssl # 期望看到 -I/home/you/sysroot/rpi4/usr/include ... -L/home/you/sysroot/rpi4/usr/lib/aarch64-linux-gnu -lssl -lcrypto如果输出里出现任何不含 sysroot 的-L/usr/lib或者-I/usr/include说明还有.pc文件是从宿主机读到的回头检查PKG_CONFIG_LIBDIR。在 CMake 项目里这三个环境变量要么在 toolchain file 里用set(ENV{...})设置要么在debian/rules里 export要么直接在调用 cmake 前 export。个人习惯是写进debian/rules因为打包才是最终交付路径把环境固化在打包脚本里CI 上跑出来的结果和本地一致。注意交叉编译排查链接问题时-Wl,-t能让链接器打印它实际读到的每一个文件。看到/usr/lib/x86_64-linux-gnu/libz.so这样的路径就是串味无疑。这个参数比任何日志分析都快。4. 打包成 debdebian 目录和 dpkg-buildpackage4.1 一个能用的最小 debian 目录项目根目录下建一个debian/目录最小集合是这几个文件debian/ ├── changelog ├── control ├── copyright ├── rules └── install # 可选CMake 项目通常不需要rules必须是可执行的chmod x debian/ruleschangelog的格式要求比较严格第一条必须是版本号和发行版标识最后一行是维护者和 RFC 2822 格式的日期。手写容易出错用dch生成更稳dch --create --package hello-arm --newversion 1.0.0-1 --distribution unstable生成之后长这样hello-arm (1.0.0-1) unstable; urgencymedium * Initial release. -- Dev Team devexample.com Tue, 11 Mar 2025 09:30:00 0800版本号1.0.0-1里短横线前面是上游版本后面是打包修订号。这个修订号在板子上做版本对比的时候很有用——同样跑 1.0.0谁是第一次打包、谁是第三次修依赖一眼就能看出来。我自己有个习惯每次改debian/下的任何东西都要把修订号加一绝对不覆盖上一版。因为设备上万一要回滚你手上得有那个旧包的副本。copyright文件严格说不写也能打出来但 lintian 会给个 warning正式的交付里最好补上。格式是机器可读的 DEP-5内容不复杂机器生成的模板改改就行。4.2 control 文件里几个必须写对的字段Source: hello-arm Section: utils Priority: optional Maintainer: Dev Team devexample.com Build-Depends: debhelper-compat ( 13), cmake, gcc-aarch64-linux-gnu, g-aarch64-linux-gnu Standards-Version: 4.6.2 Rules-Requires-Root: no Homepage: https://example.com/hello-arm Package: hello-arm Architecture: arm64 Depends: ${shlibs:Depends}, ${misc:Depends}, libzstd1 Description: Minimal arm64 demo package Built on a Windows host inside WSL, cross compiled with the aarch64-linux-gnu toolchain, and packaged as a native arm64 deb.几个容易写错的地方。Architecture这里写arm64不是aarch64。Debian 体系里 arm64 是架构名aarch64 是 GNU triplet 里的 CPU 名。这两个混用是最常见的低级错误写了aarch64会直接报 unknown architecture。${shlibs:Depends}是自动依赖。dh_shlibdeps会扫描你打包出来的可执行文件用objdump读它的NEEDED条目把对应的库包名和版本约束自动填进去。这个机制非常重要它保证了依赖不会漏。但前提是它能正确解析——交叉编译场景下要确保dh_shlibdeps扫描的是目标架构的二进制而不是误扫了宿主的什么文件。默认行为是对的扫描debian/hello-arm/下安装好的文件。DEPENDS里手写的那部分例子里的libzstd1只在自动机制覆盖不到的时候用比如运行时dlopen加载的插件、或者通过配置文件引用的外部程序。这两个来源都加上是最稳的做法。Build-Depends里的交叉工具链有个细节dpkg-buildpackage -a arm64在开始构建前会检查Build-Depends是否满足。gcc-aarch64-linux-gnu这个包本身是 amd64 架构的它是个跑在 x86 上、生成 arm64 代码的编译器所以能正常满足。但如果Build-Depends里写了libssl-dev这种架构相关的包检查就会去找 arm64 版本的libssl-dev而这个包在 amd64 系统上通常装不了。这种情况要么用:any/:native限定符明确指定要么在命令行加-d跳过检查。跳过检查在生产流程里不太好但要清楚这是怎么回事别以为是 dpkg 坏了。Rules-Requires-Root: no建议加上。加上之后构建过程不会试图获取 root 权限不需要 fakeroot 包装CI 环境里更省事。前提是你的debian/rules里没有做需要 root 的操作比如chown root:root现代 debhelper 基本都不需要。4.3 rules 文件怎么把交叉参数传下去debian/rules是打包流程的入口写得对不对直接决定出来的包是不是 arm64 的。我的模板是这样#!/usr/bin/make -f export DEB_BUILD_MAINT_OPTIONS hardeningall export DEB_CFLAGS_MAINT_APPEND -O2 export DEB_LDFLAGS_MAINT_APPEND -Wl,--as-needed export PKG_CONFIG_LIBDIR $(CURDIR)/sysroot/usr/lib/aarch64-linux-gnu/pkgconfig:$(CURDIR)/sysroot/usr/share/pkgconfig export PKG_CONFIG_SYSROOT_DIR $(CURDIR)/sysroot export PKG_CONFIG_PATH %: dh $ override_dh_auto_configure: dh_auto_configure -- \ -DCMAKE_TOOLCHAIN_FILE$(CURDIR)/cmake/toolchain-aarch64.cmake \ -DCMAKE_BUILD_TYPERelease override_dh_strip: dh_strip --no-automatic-dbgsym逐个说。DEB_BUILD_MAINT_OPTIONS hardeningall打开编译期加固包括栈保护、FORTIFY_SOURCE、RELRO 之类。这些默认在 Debian 打包里是开的写出来是为了明确表达意图也防止某个环节把它关掉。PKG_CONFIG_LIBDIR这里指向仓库内的sysroot/目录而不是$HOME下的这是一个刻意的选择。如果只在个人机器上打包指向$HOME没问题但如果这个仓库要在 CI 或者同事的机器上构建$HOME路径就不成立了。把 sysroot 放进仓库用.gitignore排除大文件或者用 externals 脚本拉取能让构建自包含。代价是仓库变大所以实际项目里通常是用一个scripts/fetch-sysroot.sh从内部服务器拉debian/rules引用固定路径。override_dh_auto_configure是覆盖 debhelper 的默认配置步骤。默认情况下dh_auto_configure会检测到CMakeLists.txt然后调用 cmake但它不认 toolchain file必须显式传参。dh_auto_configure --后面的内容会原样传给 cmake这是 debhelper 的标准扩展方式。override_dh_strip里的--no-automatic-dbgsym是控制包体积的。默认情况下 debhelper 会把调试符号单独拆成一个-dbgsym包主包里不含符号表。如果直接发布多出来一个包需要一起传加上这个参数就不生成符号包主包被 strip 过体积能小一大截。调试期想保留符号把这块注释掉就行。4.4 构建、检查、发布构建命令dpkg-buildpackage -a arm64 -b -us -uc -d参数含义-a arm64指定目标架构是 arm64这是整条链路的关键开关它会让dpkg-architecture系列变量全部算成 arm64 的值debhelper 也会据此配置交叉工具链。-b表示只构建二进制包不做源码包个人交付场景下够用。-us -uc表示不签名源码包和变更文件本地开发不需要 GPG 签名。-d跳过构建依赖检查前面解释过什么时候需要。产物在上一级目录../hello-arm_1.0.0-1_arm64.deb装之前先自己看一眼包里有什么dpkg-deb -I ../hello-arm_1.0.0-1_arm64.deb # 看 control 信息 dpkg-deb -c ../hello-arm_1.0.0-1_arm64.deb # 看文件列表 lintian ../hello-arm_1.0.0-1_arm64.deb # 静态检查dpkg-deb -I的输出里重点看三样Architecture: arm64对不对、Depends有没有自动填上、Installed-Size是不是离谱地大。最后一条是体积问题的第一道防线一个 hello world 打出来几十兆八成是把调试符号或者静态库带进去了。dpkg-deb -c看文件列表确认二进制落在/usr/bin/服务文件落在/lib/systemd/system/没有意外多出来的东西。有个常见情况是把整个构建目录打包进去了几百兆跑起来才发现。lintian会给一堆 warning不用全部消掉但binary-from-other-architecture这类必须处理——它会直接告诉你包里的二进制不是 arm64 的。这个检查把很多问题拦在了上板子之前。想验证二进制架构还有个更直接的办法dpkg-deb -x ../hello-arm_1.0.0-1_arm64.deb /tmp/inspect file /tmp/inspect/usr/bin/hello-arm输出里带ARM aarch64就对了。如果是x86-64说明交叉编译根本没生效多半是 toolchain file 没被读到。5. 送到板子传输、安装与依赖处理5.1 传输通道怎么选Windows 10 1809 之后自带 OpenSSH 客户端PowerShell 里直接能 scpscp .\hello-arm_1.0.0-1_arm64.deb pi192.168.1.50:/tmp/如果是在 WSL 里操作路径按 Linux 写scp ../hello-arm_1.0.0-1_arm64.deb pi192.168.1.50:/tmp/不想敲命令就用 WinSCP 或者 VS Code 的 Remote - SSH拖文件过去。我个人在调试期用 VS Code 的 SSH 插件改代码、传包、开终端在一个窗口里解决效率高。如果板子是通过 USB 直连、走 adb 通道的很多安卓底层的开发板是这样就用adb pushadb push hello-arm_1.0.0-1_arm64.deb /data/local/tmp/传完之后养成习惯做一次校验# 两边都跑一下对比输出 sha256sum hello-arm_1.0.0-1_arm64.deb这一步看起来多余但传输中断导致 deb 文件截断的情况真的遇到过。dpkg -i遇到损坏的包会报corrupted filesystem tarfile排查起来不如提前对比哈希来得快。提示包名里带、~、空格这些字符时scp 和 shell 都可能出问题。deb 的版本号本身不允许空格但在 URL 里需要转义。稳妥做法是构建完先mv成一个简单名字再传比如pkg-latest.deb。5.2 dpkg -i 之后依赖报错怎么办板子上的安装就一行sudo dpkg -i /tmp/hello-arm_1.0.0-1_arm64.deb如果依赖都满足会打印配置过程然后结束systemctl status看一下服务起没起来就行。依赖没满足是最常见的情况输出会明确列出来比如dpkg: dependency problems prevent configuration of hello-arm: hello-arm depends on libzstd1 ( 1.4.0); however: Package libzstd1 is not installed.板子能联网的情况最简单sudo apt-get -
返回列表