标签 openwrt 下的文章

上一篇记录了在 Ubuntu 24.04 ARM64 主机上编译 ImmortalWrt 时遇到的 Go bootstrap 问题。

后来又尝试编译 FriendlyARM NanoPi R2S 的固件,结果又碰到了一个 ARM64 编译主机特有的问题。

R2S 使用 Rockchip RK3328,ImmortalWrt 中选择:

Target System: Rockchip
Subtarget: ARMv8
Target Profile: FriendlyARM NanoPi R2S

正常配置完成以后开始编译,但是在生成 Rockchip 启动相关文件的时候失败了。

最后发现问题出在 rkbin

rkbin

Rockchip 平台构建固件的时候会使用到:

rkbin

项目地址:

https://github.com/rockchip-linux/rkbin

这个仓库里面除了 DDR、BL31 等二进制文件以外,还自带了一些编译过程中需要执行的工具。

问题是这些工具并不是根据当前 Build Host 编译出来的,而是 Rockchip 直接提供的预编译程序。

其中部分工具是:

x86_64

而我的编译主机是:

Ubuntu 24.04 ARM64

所以编译过程中执行这些工具时会失败。

类似:

cannot execute binary file: Exec format error

loaderimage 为例,可以先找到它的位置:

find ./ -name 'loaderimage'

在我的编译目录中会看到:

./staging_dir/target-aarch64_generic_musl/image/loaderimage
./build_dir/target-aarch64_generic_musl/rkbin-rk3328/rkbin-2025.06.13~74213af1/tools/loaderimage

其中 build_dir 下面这个就是 rkbin 包中自带的工具。

因为 rkbin 的版本以后可能会变化,所以后面直接用通配符来检查:

file ./build_dir/target-aarch64_generic_musl/rkbin-rk3328/*/tools/loaderimage

可以看到类似:

ELF 64-bit LSB executable, x86-64

这里需要区分一下 Target 和 Build Host。

虽然 R2S 本身也是 ARM64:

Build Host: ARM64
Target:     ARM64

但是编译过程中使用的 rkbin 工具却是:

rkbin Tool: x86_64

所以真正的问题不是生成的 R2S 固件架构,而是 ARM64 编译主机无法直接执行 x86_64 的 Host Tool。

使用 QEMU

解决方法是使用 QEMU user mode 来运行这些 x86_64 程序。

安装:

sudo apt update
sudo apt install qemu-user-static

上一篇安装编译环境的时候已经安装过:

qemu-utils

不过 qemu-utils 和这里不是一回事。

这里需要的是:

qemu-user-static

安装完成后可以确认:

which qemu-x86_64-static

正常可以看到:

/usr/bin/qemu-x86_64-static

不过只安装这个还不够。

QEMU_LD_PREFIX

最开始我安装完 qemu-user-static 以后,编译仍然有问题。

最后还需要设置:

export QEMU_LD_PREFIX=/usr/x86_64-linux-gnu

原因是 rkbin 里面的一些 x86_64 工具并不是完全静态链接的程序。

它们运行时还需要 x86_64 的动态链接器以及对应的运行库。

但是当前系统本身是 ARM64,所以系统默认的 /lib/usr/lib 中主要是 ARM64 的库。

需要通过:

export QEMU_LD_PREFIX=/usr/x86_64-linux-gnu

告诉 QEMU 去哪里寻找 x86_64 的运行环境。

例如 x86_64 ELF 通常需要:

/lib64/ld-linux-x86-64.so.2

设置 QEMU_LD_PREFIX 以后,QEMU 会去类似下面的位置寻找:

/usr/x86_64-linux-gnu/lib64/ld-linux-x86-64.so.2

上一篇为了在 ARM64 主机上编译 ImmortalWrt,已经安装过:

sudo apt install gcc-x86-64-linux-gnu g++-x86-64-linux-gnu

所以:

/usr/x86_64-linux-gnu

这套 x86_64 cross sysroot 已经存在,可以直接使用。

最终需要的就是:

sudo apt install qemu-user-static

export QEMU_LD_PREFIX=/usr/x86_64-linux-gnu

如果以后经常在这台机器上编译,也可以直接加入:

~/.bashrc

例如:

echo 'export QEMU_LD_PREFIX=/usr/x86_64-linux-gnu' >> ~/.bashrc
source ~/.bashrc

binfmt

Ubuntu 安装 qemu-user-static 后,可以通过 Linux 的 binfmt_misc 自动处理其它架构的 ELF。

检查:

ls /proc/sys/fs/binfmt_misc/

可以看看有没有:

qemu-x86_64

也可以:

cat /proc/sys/fs/binfmt_misc/qemu-x86_64

如果已经注册,那么编译系统执行 rkbin 中的 x86_64 工具时,不需要专门改成:

qemu-x86_64-static ./loaderimage

系统会自动调用 QEMU。

而:

export QEMU_LD_PREFIX=/usr/x86_64-linux-gnu

则负责让 QEMU 找到对应的 x86_64 loader 和运行库。

简单来说就是:

ARM64 Ubuntu
    |
    v
binfmt_misc
    |
    v
qemu-x86_64
    |
    | QEMU_LD_PREFIX
    v
/usr/x86_64-linux-gnu
    |
    v
rkbin x86_64 Tool

这样对于 ImmortalWrt 自己的 Makefile 来说基本是透明的,不需要修改 rkbin,也不需要修改编译脚本。

验证

这里主要检查 build_dir 里面 rkbin 自带的原始工具:

file ./build_dir/target-aarch64_generic_musl/rkbin-rk3328/*/tools/loaderimage

确认它是:

x86-64

然后直接运行测试:

./build_dir/target-aarch64_generic_musl/rkbin-rk3328/*/tools/loaderimage

如果不再出现:

Exec format error

而是能够正常输出程序自己的参数或者错误提示,说明 QEMU 已经可以正常执行这个 x86_64 程序了。

staging_dir 里面也会有一个:

./staging_dir/target-aarch64_generic_musl/image/loaderimage

这是构建过程中放到 staging area 中的版本,这里主要确认 build_dir 中 rkbin 包自带的工具能够正常运行即可。

之后重新编译:

make V=s -j1

确认能够通过之前失败的位置以后,再恢复正常并行编译:

make V=s -j$(nproc)

之后 R2S 固件就可以正常生成了。

鉴于目前OpenWrt官方已经更新到25.12版本了。而lede为了兼容性还是基于较旧的版本,并使用iptables等旧的核心模块。
做为x86_64软路由的用户,决定迁移到更活跃的ImmortalWrt分支上。紧跟官方步调。
使用的是ImmortalWrt的官方源:

https://github.com/immortalwrt/immortalwrt

大体步骤与文档中描述的基本一致,但是因为是在arm64的主机上。因此有几个点与x86机器编译不同,这里记录一下。

检出代码官方推荐基于release tag,这样的代码虽然不是最新,但是更为稳定。
比如基于最新的v25.12.1tag:

git clone -b v25.12.1 --single-branch --filter=blob:none https://github.com/immortalwrt/immortalwrt

编译的系统是Ubuntu 24.04 aarch64。
首先安装编译环境的包就与x86不太一样。具体是:

sudo apt update -y
sudo apt full-upgrade -y
sudo apt install -y ack antlr3 asciidoc autoconf automake autopoint binutils bison build-essential \
  bzip2 ccache clang cmake cpio curl device-tree-compiler ecj fastjar flex gawk gettext gcc-x86-64-linux-gnu \
  g++-x86-64-linux-gnu git golang gnutls-dev gperf haveged help2man intltool libelf-dev \
  libglib2.0-dev libgmp3-dev libltdl-dev libmpc-dev libmpfr-dev libncurses-dev libpython3-dev \
  libreadline-dev libssl-dev libtool libyaml-dev libz-dev lld llvm lrzsz mkisofs msmtp nano \
  ninja-build p7zip p7zip-full patch pkgconf python3 python3-pip python3-ply python3-docutils \
  python3-pyelftools qemu-utils re2c rsync scons squashfs-tools subversion swig texinfo uglifyjs \
  upx-ucl unzip vim wget xmlto xxd zlib1g-dev zstd

因为本人需要集成加强版geodata文件,要添加一个本地feeds:

src-link custom /home/ubuntu/openwrt/feeds

自用的feeds配置总结为:

src-link custom /home/ubuntu/openwrt/feeds
src-git lucky https://github.com/gdy666/luci-app-lucky.git
src-git passwall_packages https://github.com/Openwrt-Passwall/openwrt-passwall-packages.git;main
src-git passwall_luci https://github.com/Openwrt-Passwall/openwrt-passwall.git;main
src-git passwall2_luci https://github.com/Openwrt-Passwall/openwrt-passwall2.git;main

更新 feeds 什么的跟官方文档一样。

./scripts/feeds update -a
./scripts/feeds install -a

接下来,重点来了。必须多做一件与x86系统不同的配置,否则会报这个错误。

Makefile:366: * go-bootstrap cannot be installed on linux/arm64. Stop.

看报错信息,是编译过程无法安装go到arm64系统上。所以需要手动安装golang,并把路径设置到编译配置中。
在配置编译参数时:

make menuconfig

要加入以下设置:

Languages > Go > Configuration > External bootstrap Go root directory

按上面的环境安装后,go应该默认在 /usr/lib/go 目录下。将 /usr/lib/go 写入这项配置。

(/usr/lib/go) External bootstrap Go root directory

选好其它需要的配置后,开始编译:

make download -j8
make V=s -j1

慢慢等待编译完成吧。编译完成后输出路径:bin/targets

需要更新代码再次编译的话:

cd immortalwrt
git pull
./scripts/feeds update -a
./scripts/feeds install -a
make defconfig
make download -j8
make V=s -j$(nproc)

如果需要重新配置:

rm -rf ./tmp && rm -rf .config
make menuconfig
make V=s -j$(nproc)

在上一篇文章中,介绍了如何集成加强版的geodata文件到openwrt。
但是这个加强版的geodata每日更新。按之前的办法,要去github查看新版本的信息,手动更新。太麻烦了……

这里面提供一个python脚本,可以方便的自动更新Makefile文件到最新版本。

使用方法将脚本的内容保存成文件。比如up_geodata.sh文件。
然后运行脚本,指定要更新的Makefiel文件路径:

python3 up_geodata.sh -b Makefile

参数-b表示备份旧的文件,不想备份可以不加。
mac或者linux,还可以直接添加可执行权限,当成应用来运行:

chmod +x up_geodata.sh

以下是脚本内容:

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import requests
import re
import argparse
import shutil
import os
from datetime import datetime

# GitHub API URL
GITHUB_API_URL = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"

# 需要下载的 SHA256 文件
FILES = {"geoip": "geoip.dat.sha256sum", "geosite": "geosite.dat.sha256sum"}


def get_latest_version():
    """获取 GitHub 最新 Release 版本号"""
    response = requests.get(GITHUB_API_URL)
    if response.status_code == 200:
        release_data = response.json()
        return release_data['tag_name']  # 获取最新版本号
    else:
        raise Exception("Failed to fetch release data from GitHub API")


def download_sha256sum_file(version, file_key):
    """下载 SHA256 校验文件"""
    file_name = FILES[file_key]
    url = f"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/{version}/{file_name}"
    response = requests.get(url)

    if response.status_code == 200:
        return response.text.strip()  # 直接返回文本内容
    else:
        raise Exception(
            f"Failed to download {file_name} for version {version}")


def extract_hash(file_content):
    """解析 SHA256 文件中的哈希值"""
    return file_content.split()[0]  # 取第一列的哈希值


def backup_makefile(makefile_path):
    """备份 Makefile"""
    if os.path.exists(makefile_path):
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_path = f"{makefile_path}.bak_{timestamp}"
        shutil.copy(makefile_path, backup_path)
        print(f"✅ 备份已创建: {backup_path}")
    else:
        raise FileNotFoundError(f"❌ 未找到 Makefile: {makefile_path}")


def update_makefile(makefile_path, version, geoip_hash, geosite_hash):
    """修改 Makefile,更新版本号和哈希值"""
    with open(makefile_path, "r") as file:
        makefile_content = file.read()

    # **修正版本号**
    makefile_content = re.sub(r"(GEODATA_VER:=)\d+", r"\g<1>" + version,
                              makefile_content)

    # **修正 geoip.dat 的 HASH**
    makefile_content = re.sub(
        r"(define Download/geoip\s+.*?\n\s+HASH:=)[a-f0-9]+",
        r"\g<1>" + geoip_hash,
        makefile_content,
        flags=re.DOTALL)

    # **修正 geosite.dat 的 HASH**
    makefile_content = re.sub(
        r"(define Download/geosite\s+.*?\n\s+HASH:=)[a-f0-9]+",
        r"\g<1>" + geosite_hash,
        makefile_content,
        flags=re.DOTALL)

    with open(makefile_path, "w") as file:
        file.write(makefile_content)

    print(
        f"✅ Makefile 已成功更新: 版本 {version}, GeoIP Hash: {geoip_hash}, Geosite Hash: {geosite_hash}"
    )


def main():
    parser = argparse.ArgumentParser(
        description="更新 Makefile 中的 v2ray-geodata 版本号和 HASH")
    parser.add_argument("makefile", help="需要更新的 Makefile 路径")
    parser.add_argument("-b",
                        "--backup",
                        action="store_true",
                        help="是否备份 Makefile")
    args = parser.parse_args()

    makefile_path = args.makefile

    try:
        # **只有在使用 -b 时才进行备份**
        if args.backup:
            backup_makefile(makefile_path)

        # **获取最新版本号**
        version = get_latest_version()
        print(f"📌 最新版本号: {version}")

        # **获取 SHA256 哈希值**
        geoip_hash = extract_hash(download_sha256sum_file(version, "geoip"))
        geosite_hash = extract_hash(download_sha256sum_file(
            version, "geosite"))

        print(f"📌 GeoIP Hash: {geoip_hash}")
        print(f"📌 Geosite Hash: {geosite_hash}")

        # **更新 Makefile**
        update_makefile(makefile_path, version, geoip_hash, geosite_hash)

    except Exception as e:
        print(f"❌ 发生错误: {e}")


if __name__ == "__main__":
    main()

早年写过一篇旧的同名文章。介绍过如何在openwrt编译时,直接集成一个自己想要的geodata文件。
只是当年才疏学浅,用了最土的方式,直接修改openwrt源文件的方式来实现。
如今研究过openwrt的项目结构后,发现通过添加外部feeds来覆盖源目标的方式才是最正确的方法。

这个方法也适用于要添加其它的自定义项目。
首先,将要添加的项目的文件,这里geodata只有一个Makefile文件。保存到适当的位置。
以我保存的路径为例:

/home/ubuntu/openwrt/feeds/v2ray-geodata/Makefile

然后,在openwrt项目,根目录下的feeds.conf.default第一行添加以下一行:

src-link custom /home/ubuntu/openwrt/feeds

为什么要在第一行?因为feeds的优先级是根据自上而下的加载顺序决定的。最先加载的优先级最高,可以覆盖后面原生自带的相同项目。
这里也可以看出来,在/home/ubuntu/openwrt/feeds的路径下,还可以放入其它多个自己想要添加的项目。
最后就是正常的编译流程:

cd lede
git pull
./scripts/feeds update -a
./scripts/feeds install -a
make menuconfig
make download -j8
make V=s -j$(nproc)

下面是我的加强版geodata项目Makefile文件内容,可以直接保存使用。

# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021-2022 ImmortalWrt.org

include $(TOPDIR)/rules.mk

PKG_NAME:=v2ray-geodata
PKG_RELEASE:=1

PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=winger zhang <winger.zhang@gmail.com>

include $(INCLUDE_DIR)/package.mk

GEODATA_VER:=202503192212
GEOIP_FILE:=geoip.dat.$(GEODATA_VER)
define Download/geoip
  URL:=https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/$(GEODATA_VER)/
  URL_FILE:=geoip.dat
  FILE:=$(GEOIP_FILE)
  HASH:=e32b80017d1dea91bc36c4e7ed07a5e7f200356415e678a8a6f825fb521f7b68
endef

GEOSITE_FILE:=geosite.dat.$(GEODATA_VER)
define Download/geosite
  URL:=https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/$(GEODATA_VER)/
  URL_FILE:=geosite.dat
  FILE:=$(GEOSITE_FILE)
  HASH:=57c476f6e50737c5fdf68778ec1615fb2017f75886a6bb0252927c314e5904b7
endef

define Package/v2ray-geodata/template
  SECTION:=net
  CATEGORY:=Network
  SUBMENU:=IP Addresses and Names
  URL:=https://www.v2fly.org
  PKGARCH:=all
endef

define Package/v2ray-geoip
  $(call Package/v2ray-geodata/template)
  TITLE:=GeoIP List for V2Ray
  VERSION:=$(GEODATA_VER)-$(PKG_RELEASE)
  LICENSE:=CC-BY-SA-4.0
endef

define Package/v2ray-geosite
  $(call Package/v2ray-geodata/template)
  TITLE:=Geosite List for V2Ray
  VERSION:=$(GEODATA_VER)-$(PKG_RELEASE)
  LICENSE:=MIT
endef

define Build/Prepare
    $(call Build/Prepare/Default)
ifneq ($(CONFIG_PACKAGE_v2ray-geoip),)
    $(call Download,geoip)
endif
ifneq ($(CONFIG_PACKAGE_v2ray-geosite),)
    $(call Download,geosite)
endif
endef

define Build/Compile
endef

define Package/v2ray-geoip/install
    $(INSTALL_DIR) $(1)/usr/share/v2ray
    $(INSTALL_DATA) $(DL_DIR)/$(GEOIP_FILE) $(1)/usr/share/v2ray/geoip.dat
endef

define Package/v2ray-geosite/install
    $(INSTALL_DIR) $(1)/usr/share/v2ray
    $(INSTALL_DATA) $(DL_DIR)/$(GEOSITE_FILE) $(1)/usr/share/v2ray/geosite.dat
endef

$(eval $(call BuildPackage,v2ray-geoip))
$(eval $(call BuildPackage,v2ray-geosite))

最后,如何快速的更新Makefile文件,以编译打包最新的geodata文件。可以看我的另外一篇文章

如果按前面文章中描述的openwrt编译中,加添的feed源:

src-git kenzo https://github.com/kenzok8/openwrt-packages
src-git passwall https://github.com/xiaorouji/openwrt-passwall

那么默认集成的是普通的geodata文件。
而事实上有一份科学爱好者制作的加强版文件,可以更方便的制定各种策略。

我们可以通过修改Makefile来让编译出来的固件直接集成加强版文件。
对应的Makefile的路径为:

package/feeds/packages/v2ray-geodata/Makefile

编辑这个文件,找到Download/geoipDownload/geosite两个节点分别替换为以下内容:
geoip

GEOIP_VER:=202212042209
GEOIP_FILE:=geoip.dat.$(GEOIP_VER)
define Download/geoip
  URL:=https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/$(GEOIP_VER)/
  URL_FILE:=geoip.dat
  FILE:=$(GEOIP_FILE)
  HASH:=8c58d22cb94bf98a42b1b2dff8ac9c39f42f1e83f52dc1ab016c72e8a22c5fcb
endef

geosite

GEOSITE_VER:=202212042209
GEOSITE_FILE:=geosite.dat.$(GEOSITE_VER)
define Download/geosite
  URL:=https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/$(GEOSITE_VER)/
  URL_FILE:=geosite.dat
  FILE:=$(GEOSITE_FILE)
  HASH:=f9d475afa5f2f5bacef596a49fda433224fb8da30d9d7966a3bb1e8a7309472a
endef

然后开始正常编译就可以直接集成加强版的文件了。

因为加强版文件更新非常活跃,基本上每日更新。所以想自己集成最新的文件,只需要修改VER对应的版本号,和文件HASH的哈希值。至于哈希值从哪里来?那就要到他的发布页上去查看了。