OpenWrt:从源码到固件,编译自己的OpenWrt系统

本文说明如何一步一步地从源码编译出自己的OpenWrt镜像,并升级到设备上。

准备工作

准备一个GNU/Linux, BSD 或 MacOSX 操作系统。并且,在环境中准备好以下官方教程中要求的工具:

https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem

我的环境是Ubuntu22,执行

1
2
sudo apt update 
sudo apt install build-essential clang flex bison g++ gawk gcc-multilib g++-multilib gettext git libncurses-dev libssl-dev python3-distutils rsync unzip zlib1g-dev file wget

编译系统

下载源码

下载代码:

1
2
3
git clone https://github.com/openwrt/openwrt.git
cd openwrt
git pull

切换分支,一般选择最新的稳定版本:

1
2
3
git branch -a
git tag
git checkout v23.05.0

下载软件包

运行./scripts/feeds update -a命令,下载或更新在feeds.conf/feeds.conf.default中定义的所有最新软件包。

受限于国内的访问国际互联网的环境中,feeds update这一步特别容易失败。可以通过以下几条方式来提高成功率:
git config --global http.postBuffer 524288000
git config --global http.lowSpeedLimit 1000
git config --global http.lowSpeedTime 600
以上配置的含义为:配置git缓冲区为500M,配置git访问超时的条件为:速率小于1KB/s,且持续600秒

运行./scripts/feeds install -a命令使安装上述软件包在后续的make menuconfig中生效。

配置编译选项

使用已有固件的编译配置

网络上已编译出的固件通常都会把编译配置一并提供(config.buildinfo或config.seed),可以直接使用。

我的目标机是一台小米WR30U,使用mtk的filogic芯片方案,从OpenWrt官网找到对应的编译配置并下载,置于OpenWrt工程根目录下的.config文件中:

1
wget https://downloads.openwrt.org/releases/23.05.0/targets/mediatek/filogic/config.buildinfo -O .config

但这份配置中包含了filogic芯片方案的所有设备的配置,还需进行裁剪和修改。

在以上配置的基础上,运行make menuconfig命令来完成进一步的自定义配置。完成后配置会更新至.config文件中。

在Target Profile中,仅保留Xiaomi WR30U的设备支持(我的设备是Xiaomi Mi Router WR30U (112M UBI with NMBM-Enabled layout)),把其它设备的支持删除。

menuconfig

编译

使用make download预先下载编译过程中需要的代码和依赖项等资源。

. . . . . . . 经过漫长的等待下载完成。到此为止,我们已做好了所有的编译准备。

正式开始编译吧,运行make命令来构建固件。该命令将下载所有源代码和依赖项(即使之前已经make download,也还有其它包需要下载),构建交叉编译工具链,然后为目标系统交叉编译出OpenWrt内核和应用程序。

1
make -j4 V=s

编译选项说明

  • -jN: make命令可以加上-j参数用于指定使用多少cpu核编译,可以加速编译过程。例如:make download -j4make -j5
  • V=s:make命令可以加上V=s可以输出更多的编译错误信息。

如果顺利的话,这里make完就可以编译出镜像了。但是实际不太可能完全顺利。每个人遇到的问题可能不一样。我遇到的问题和修复方法,我放在后面附录中。

编译输出

. . . . . . . 经过漫长的编译过程(我的环境中编了5个小时),编译结果存放于openwrt/bin/targets/目录下。几类编译产出的镜像说明如下:

  1. factory:用于替换厂商的原厂固件,兼容原厂的安装包格式。通常使用原厂的web GUI进行升级。
  2. sysupgrade:用于升级替换已有的OpenWrt版本,这是最常用的镜像。
  3. initramfs-kernel:用于开发或特殊情况下的一次性引导,作为安装常规sysupgrade版本的过渡步骤。由于initramfs版本完全运行在RAM中,不会在闪存中存储任何设置,因此不适合用于操作性使用。

升级固件

主要参考刷机教程中提及的办法,在路由器断电后,用针按住 reset 不放,再接上电源,等待 10s 左右松开,就能进入 uboot。电脑用网线和 wr30u 的网口1连接,电脑在网络设置里将以太网设置为静态。IP地址:192.168.1.2,子网掩码:255.255.255.0,浏览器打开192.168.1.1访问uboot后台。

uboot下上传固件

现在选择编译出的openwrt主程序(openwrt-23.05.0-mediatek-filogic-xiaomi_mi-router-wr30u-112m-nmbm-squashfs-sysupgrade.bin),upload 后 update 更新即可。

升级完成后,把网络连到LAN口上(非1口),在控制台上用ssh root@192.168.1.1登陆,可见已成功升级至编译出的OpenWrt固件。

ssh登陆OpenWrt设备

也可以通过浏览器访问192.168.1.1,进入web管理页面,默认用户名密码是root/password

OpenWrt LuCI首页

将自己编译的固件与从官方网站上下载的固件https://downloads.openwrt.org/releases/23.05.0/targets/mediatek/filogic/openwrt-23.05.0-mediatek-filogic-xiaomi_mi-router-wr30u-112m-nmbm-squashfs-sysupgrade.bin 升级后的结果做了一下对比,路由器功能完全一致。

附录

参考资料

编译失败记录

内核编译Warning

默认情况下,内核编译有加上-Werror选项,因此遇到Warning就会停下来。例如编译失败日志:

1
2
/home/spencer/openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/dmx_usb_module-19.12.1/dmx_usb.c: In function 'dmx_usb_write':
./include/linux/kern_levels.h:5:25: error: format '%d' expects argument of type 'int', but argument 4 has type 'size_t' {aka 'long unsigned int'} [-Werror=format=]

于是找到-Werror添加的地方,删除掉。即在openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/linux-5.15.132/Makefile中,把以下这行注释掉:

1
#KBUILD_CFLAGS-$(CONFIG_WERROR) += -Werror

acl编译失败

遇到acl编译失败,到社区上搜索,已有高人给出了答案:

https://github.com/openwrt/packages/issues/21051
https://github.com/openwrt/packages/pull/21031/commits/dcc6d70f735474d49345d8d4b43a8098bb217220

cjdns编译失败

1
2
3
4
5
6
7
cc1: note: someone does not honour COPTS correctly, passed 16 times
net/SwitchPinger_admin.c: In function 'adminPing':
net/SwitchPinger_admin.c:103:133: error: dangling pointer 'err' to an unnamed temporary may be used [-Werror=dangling-pointer=]
103 | Dict d = Dict_CONST(String_CONST("error"), String_OBJ(err), NULL);
| ^
net/SwitchPinger_admin.c:82:26: note: unnamed temporary defined here
82 | err = String_CONST("path was not parsable.");

解决办法:
https://github.com/coolsnowwolf/lede/issues/10817

OpenWrt系列教程