这篇文章介绍 OpenWrt 系统和软件包的编译,软件包会以 kmod-xmurp-ua 为例子。阅读需要一定的 Linux 基础。以下内容中的代码仅仅是示例,目的是让读者明白怎么回事,直接复制粘贴不可取。
OpenWrt 官方有一套开发者的教程,在这里,其中使用 SDK一节就是本篇内容的主要来源,如果嫌本篇文章说得太笼统,可以去看看官方的教程。但是,SDK 常常会有一些 bug,这时就需要一些“黑魔法”来达到目的,这就是写作这篇文章的意义,其它只是顺便。
本文章不使用任何知识共享协议授权,也就是说不允许转载。为啥不允许呢,一来这不是科普性质的东西,没啥主动传播的必要,需要的人肯定能找到这里来;二来是想借此给自己博客多争取一点人气,算是自己的一点私心。当然,在自己的文章里挂上这里的链接是可以的,并且欢迎。
本文仅仅发布在我自己的域名下(chn.moe)。如果你在什么论坛上发现相同的并,那不是我发的,那是他抄我。
64 位的 Linux,必须是 x86 架构(Termux 就不要想了)。
至于发行版,OpenWrt 官方推荐的是 Ubuntu,但其实基本上什么发行版都能编译。WSL 亲测可用,可能对大多数人也是最方便的选择。我使用的是 Arch。如果初学者想要顺便学习 Linux,老衲墙裂推荐 deepin。
编译当然需要装很多依赖,这里是官方给的列表,但实际上未必够用,所以推荐先编译,提示缺啥再安装。另外,Arch 可以使用 yay -S openwrt-devel
来装好,但一般也用不到那么多。
简单介绍一下,算作下文的基础。这个网上教程其实很多了。以官方仓库为例。编译过程建议魔法上网,不然下载得太慢了。
下载源代码,更新和安装软件源。
1 | git clone https://github.com/openwrt/openwrt.git |
如果想要顺便编译额外的软件包,直接丢到 package
下面就可以了。
1 | git clone https://github.com/CHN-beta/xmurp-ua.git package/xmurp-ua |
做一些设置。
1 | make menuconfig |
选择自己的路由器型号,然后选择想要编译哪些软件包,通过空格和上下左右操作。必要的软件包一般会自动选上,通常只要选自己额外需要的就好。星号表示编译并且内置到镜像中,M 表示编译但是不内置到镜像中,需要自己装。LuCI 不会自动勾上。还可以选择是否打包 SDK,默认不勾。
然后就可以开始编译了。
1 | make -j24 |
上面的 24
是线程数。
要是想要看到更详细的信息,就加上 V=sc
。
1 | make -j24 V=sc |
编译过程中会下载很多东西。也可以让它先把需要下载的都下载好,然后再编译。
1 | make download -j24 |
然后就没有然后了,等着就行。它会先编译 host 上的一些工具(grep 之类),然后编译交叉编译工具链,再编译内核、uboot 等,最后编译软件包,生成镜像。编译好的东西都放在 bin
下面。
如果要后续编译软件包,一般要使用编译镜像时生成的 SDK。
什么是 SDK?官方概括得很好:
The SDK is a pre-compiled toolchain designed to cross compile packages for a specific target without compiling the whole system from scratch.
Tasks you can do with the SDK:
- Compile custom software for a specific release while ensuring binary and feature compatibility
- Compile newer versions of certain packages for a specific release
- Recompile existing packages with custom patches or different features
然而,如果小白问“SDK 是什么”这个问题,这时你直接丢给他上面那一大段话是极不友善的。所以,我的直观的回答是:
准备好 SDK 就可以开始了。官方编译 OpenWrt 的 SDK 和镜像放在一起,拉到页面最下面就能看到。自己编译的话,记得打包 SDK。
另外提一个技巧,如果使用 WSL,可以将 SDK 放到 C 盘根目录,然后使用命令 cd ~ && cp /mnt/c/*.tar.xz .
将它复制到 WSL 中。
解压 SDK 后,照着下面的流程编译。
下载、安装软件源,以及把想要编译的软件丢进去。
1 | ./scripts/feeds update -a |
如果只是编译 kmod-xmurp-ua
这样没啥依赖的小包,其实无需更新软件源。
配置一下。
1 | make defconfig |
当然,也可以 make menuconfig
进去改一些设置。SDK 会默认把所有软件包都勾上 M。
然后编译就好了。
1 | make package/xmurp-ua/compile V=sc |
官方的教程到此为止。
下面是我给别人编译时遇到过的问题和解决方案。解决这些问题可能需要比正常编译更高一些的 Linux 熟悉程度。
ARCH
和 CROSS_COMPILE
这是编译失败时最常见的一种情况,表现就是,提示没有针对 x86 的 target 的 Makefile(但我们的目标根本不是 x86)。手动指定一些参数就可以了。
1 | make package/xmurp-ua/compile V=sc ARCH=mips CROSS_COMPILE=/home/chn/Desktop/lede-sdk-17.01.5-ar71xx-generic_gcc-5.4.0_musl-1.1.16.Linux-x86_64/staging_dir/toolchain-mips_24kc_gcc-5.4.0_musl-1.1.16/bin/mips-openwrt-linux-musl- |
要是不知道 ARCH
应该填啥,就注意看看刚刚的错误信息,提示的在哪里找不到 x86 的 Makfile,看一下那个目录下有哪个架构的 Makefile 就填哪个;或者翻 .config
这个文件,也能找得到。CROSS_COMPILE
参考我的示例,在 staging_dir
下面找找就能找到了。
mips
与 mipsel
之争按说这两个只是差个字节序,我不明白为啥要整两个,反正这样一整就整出毛病了。有时会提示在 build_dir
的某个关于 mips
的目录下面找不到什么什么东西,你去 build_dir
下面一看,有两个名字差不多的目录,一个关于 mips
一个关于 mipsel
。如果把现在编译了一半的烂摊子全删了,重新解压 SDK,会发现 build_dir
下面只有那个 mipsel
,说明刚刚那个 mips
是编译的时候生成的。
解决办法是,把现在编译了一半的烂摊子全删了,重新解压 SDK,然后建立个软链接,让 mips
那个目录指向 mipsel
那个目录,然后正常编译即可。
1 | cd .. |
这个问题似乎还有另外一个解决办法:在 build_dir/target-mipsel_xxxxx/linux-ramips_xxxxx/linux-x.xx.xx/arch
里,会看到一个名为 mips
的目录;在这里建立个软链接 mipsel
指向 mips
,make
时填 ARCH=mipsel
,也能编译过,但实测不能用。
就像在 2020 年还有人抱着 GCC 4.8 不放那样,现在还有人用 15.x 版本的 OpenWrt(那几版的代号听上去不明觉厉,叫 Chaos Calmer)。现在软件包的 Makefile 和那时的是不通用的,自己对照着随便哪个软件包的 Makefile 改改格式就好。对于 xmurp-ua,我提供了一个文件叫 Chaos_Calmer.Makefile
,用它来替换 Makefile
就可以了。
在编译固件时,OpenWrt 编译系统会先编译一些 host 上运行的小工具(grep
之类),然后编译交叉编译工具链,最后再编译内核和各个软件包。这些 host 上的小工具也会一起被打包进 SDK,但它们不见得可以在新的 host 上正常运行,不知道是打包的问题还是环境改变了的问题。可能会报告缺少某个动态链接库,可能会报告某个符号找不到,也可能会二话不说 core dump。解决的思路是,找到出问题的二进制文件,然后把系统自带的链接过来。要是找不到,把 /usr/bin
整个链接过来,有时也可以用。
1 | ln -sf /usr/bin staging_dir/host |
还见过一个 objtools
缺失,也是一样的解法。
无 SDK 编译用户态的程序应该不难,用相近的 SDK 就可以了,最多改一下依赖的软件包的版本。或者干脆用本机的交叉编译工具加静态链接,最省事,只要闪存够大的话;好吧,我承认我只是这样编译过一个 hello world。比如说:
1 | arm-linux-gnueabihf-gcc hello.c -static -o hello |
亲测能用。
编译内核软件包时稍微麻烦一点。opkg 在安装软件包时,会检查内核版本和内核的设置的 hash 是否与编译时一致(原理是,在软件包中写入一条类似于 kernel - 4.14.180-1-fa00c1231ac7d7840ec6ffe62dcad926
这样的依赖)。使用相近的 SDK,编译后使用 archivemount 或者其它可以修改压缩包内容的工具打开编译出来的软件包,在 control.tar.gz
里再打开 control
,改一下依赖即可。
如果找不到相近的 SDK,也可以从源代码开始制作。如果源代码是一个 git 仓库,可以 git log -p include/kernel-version.mk > kv.diff
,找到内核版本号相同的提交,checkout
,然后再编译。
其实,在生成镜像的同时,也会生成一个 config.buildinfo
,官方镜像的这个文件和 SDK 放在一起供下载。旧版的文件名不是这个,但意思差不多。使用相同的源代码,把这个文件下载下来重命名为 .config
,再 make defconfig
来补全设置,再正常编译,就可以得到直接可用的 SDK 了。如果要考虑软件包的版本,feeds.buildinfo
同理使用即可。
当然这样整出来的软件包不一定能用,因为你不知道人家编译时是否对内核头文件作了什么 patch。这和 C 语言的特性有关。
比如,新建文件 libtest.c
,写入下面的内容:
1 | struct test |
再新建文件 test.c
,写入下面的内容:
1 |
|
然后编译,运行。
1 | gcc -shared -o libtest.so -Wall -Wextra libtest.c |
你会发现输出的并不是 1 而是 2。如果编译内核时的头文件和编译内核模块时的头文件不一样,就可能发生类似于这样的问题。
来来来,插播一条广告,看看能不能致富。
可以找我代编译软件包,联系方式在网站主页可以看到。要多少钱合适呢?我也不知道,看着给就行。
给个我心里的参考价吧:有 SDK 的 10 元,没有 SDK 的 50 元。因为有 SDK 的情况下,编译很简单,尤其是简单的内核模块,几分钟就可以。没有 SDK 的情况下,就要折腾了,从源代码编译起的话,少说也要半个小时到一个小时。当然,原则还是“看着给”,没钱就少一些或者不给,有钱就多一些,就可以;或者,按照你工作十分钟或者一小时的价格来付就好,毕竟人的收入差距很大,明码标价有失公平。
只尽力编译,不保证编译出来的东西能用,更不负责教会你会用。当然,有问题可以问。其实我编译时用的技巧都写在这里了,也没啥可问的。我要是真靠这个谋生的话,这算是公开商业机密了吧,哈哈。