前言

这篇文章介绍 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
    2
    3
    4
    git clone https://github.com/openwrt/openwrt.git
    cd openwrt
    ./scripts/feeds update -a
    ./scripts/feeds install -a

    如果想要顺便编译额外的软件包,直接丢到 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
    2
    make download -j24
    make -j24
  • 然后就没有然后了,等着就行。它会先编译 host 上的一些工具(grep 之类),然后编译交叉编译工具链,再编译内核、uboot 等,最后编译软件包,生成镜像。编译好的东西都放在 bin 下面。

使用 SDK 编译软件包

如果要后续编译软件包,一般要使用编译镜像时生成的 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 编译的软件包,你的镜像很可能是不能用的,但也很可能是可以用的,这个留到后面讨论。

准备好 SDK 就可以开始了。官方编译 OpenWrt 的 SDK 和镜像放在一起,拉到页面最下面就能看到。自己编译的话,记得打包 SDK。

另外提一个技巧,如果使用 WSL,可以将 SDK 放到 C 盘根目录,然后使用命令 cd ~ && cp /mnt/c/*.tar.xz . 将它复制到 WSL 中。

解压 SDK 后,照着下面的流程编译。

  • 下载、安装软件源,以及把想要编译的软件丢进去。

    1
    2
    3
    ./scripts/feeds update -a
    ./scripts/feeds install -a
    git clone https://github.com/CHN-beta/xmurp-ua.git package/xmurp-ua

    如果只是编译 kmod-xmurp-ua 这样没啥依赖的小包,其实无需更新软件源。

  • 配置一下。

    1
    make defconfig

    当然,也可以 make menuconfig 进去改一些设置。SDK 会默认把所有软件包都勾上 M。

  • 然后编译就好了。

    1
    make package/xmurp-ua/compile V=sc

官方的教程到此为止。

SDK 编译软件包的黑魔法

下面是我给别人编译时遇到过的问题和解决方案。解决这些问题可能需要比正常编译更高一些的 Linux 熟悉程度。

手工指定 ARCHCROSS_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 下面找找就能找到了。

mipsmipsel 之争

按说这两个只是差个字节序,我不明白为啥要整两个,反正这样一整就整出毛病了。有时会提示在 build_dir 的某个关于 mips 的目录下面找不到什么什么东西,你去 build_dir 下面一看,有两个名字差不多的目录,一个关于 mips 一个关于 mipsel。如果把现在编译了一半的烂摊子全删了,重新解压 SDK,会发现 build_dir 下面只有那个 mipsel,说明刚刚那个 mips 是编译的时候生成的。

解决办法是,把现在编译了一半的烂摊子全删了,重新解压 SDK,然后建立个软链接,让 mips 那个目录指向 mipsel 那个目录,然后正常编译即可。

1
2
3
4
5
6
7
8
cd ..
rm -rf openwrt-sdk-*
tar -xf opeenwrt-sdk-*
cd openwrt-sdk-*
cd build_dir
ln -s target-mipsel_xxxxx target-mips_xxxxx
cd ..
make package/xmurp-ua/compile V=sc ARCH=mips CROSS_COMPILE=/home/chn/Desktop/openwrt-SDK-xxxxx/staging_dir/toolchain-mipsel_xxxxx/bin/mipsel-openwrt-linux-

这个问题似乎还有另外一个解决办法:在 build_dir/target-mipsel_xxxxx/linux-ramips_xxxxx/linux-x.xx.xx/arch 里,会看到一个名为 mips 的目录;在这里建立个软链接 mipsel 指向 mipsmake 时填 ARCH=mipsel,也能编译过,但实测不能用。

古典主义者的坚守

就像在 2020 年还有人抱着 GCC 4.8 不放那样,现在还有人用 15.x 版本的 OpenWrt(那几版的代号听上去不明觉厉,叫 Chaos Calmer)。现在软件包的 Makefile 和那时的是不通用的,自己对照着随便哪个软件包的 Makefile 改改格式就好。对于 xmurp-ua,我提供了一个文件叫 Chaos_Calmer.Makefile,用它来替换 Makefile 就可以了。

失效和 host 二进制文件

在编译固件时,OpenWrt 编译系统会先编译一些 host 上运行的小工具(grep 之类),然后编译交叉编译工具链,最后再编译内核和各个软件包。这些 host 上的小工具也会一起被打包进 SDK,但它们不见得可以在新的 host 上正常运行,不知道是打包的问题还是环境改变了的问题。可能会报告缺少某个动态链接库,可能会报告某个符号找不到,也可能会二话不说 core dump。解决的思路是,找到出问题的二进制文件,然后把系统自带的链接过来。要是找不到,把 /usr/bin 整个链接过来,有时也可以用。

1
ln -sf /usr/bin staging_dir/host

还见过一个 objtools 缺失,也是一样的解法。

无 SDK 编译软件包

无 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
2
3
4
5
6
7
8
9
10
11
12
13
14
struct test
{
int a;
int b;
int c;
};
struct test get(void)
{
struct test obj;
obj.a = 1;
obj.b = 2;
obj.c = 3;
return obj;
}

再新建文件 test.c,写入下面的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
struct test
{
int b;
int a;
int c;
};
struct test get(void);
int main(void)
{
printf("%d", get().a);
return 0;
}

然后编译,运行。

1
2
3
gcc -shared -o libtest.so -Wall -Wextra libtest.c
gcc -L. -ltest -o test -Wall -Wextra test.c
LD_LIBRARY_PATH=. ./test

你会发现输出的并不是 1 而是 2。如果编译内核时的头文件和编译内核模块时的头文件不一样,就可能发生类似于这样的问题。

付费代编译

来来来,插播一条广告,看看能不能致富。

可以找我代编译软件包,联系方式在网站主页可以看到。要多少钱合适呢?我也不知道,看着给就行。

给个我心里的参考价吧:有 SDK 的 10 元,没有 SDK 的 50 元。因为有 SDK 的情况下,编译很简单,尤其是简单的内核模块,几分钟就可以。没有 SDK 的情况下,就要折腾了,从源代码编译起的话,少说也要半个小时到一个小时。当然,原则还是“看着给”,没钱就少一些或者不给,有钱就多一些,就可以;或者,按照你工作十分钟或者一小时的价格来付就好,毕竟人的收入差距很大,明码标价有失公平。

只尽力编译,不保证编译出来的东西能用,更不负责教会你会用。当然,有问题可以问。其实我编译时用的技巧都写在这里了,也没啥可问的。我要是真靠这个谋生的话,这算是公开商业机密了吧,哈哈。

更新记录

  • 40735:完成正文。