1. 前言
我需要交叉编译一个gnutls
的开源库,该库还依赖了其他的库,可能是我下载的源码都是最新的,有些库的编译框架发生了变化,最后折腾了很久还是失败了。然后了解到Buildroot
,这是一个很强大的集成环境,其中就有gnutls
这个包,索性去研究一下。这并不是我第一次听说Buildroot
,在Qemu(1) — Ubuntu下运行CK860 Qemu这篇文章中,我就使用过buildroot编译后的产物,但那个不是我编出来的,那就亲手编译一次吧。
2. 下载代码
Buildroot
官方的库中不支持csky
架构,在github上有一个c-sky/buildroot库,该库在Buildroot
官方库的基础上,添加了对csky
架构的支持。查看c-sky/buildroot
的Makefile
文件,可以看到它添加支持的过程。
3. 构建docker镜像
在wsl(6) – 安装docker文章中描述了docker的一些事情,使用docker进行各种环境部署是很方便的,既不用担心会把当前的系统弄得太乱,也能比较好的记录过程,方便以后的自动化。下面是我完成部署后总结的Dockerfile
,其中主要是收集了一些buildroot环境需要的命令和库,方便一次性安装。buildroot
目录比较大,就放在wsl
目录下,启动docker时挂载上去,目前这个docker镜像800多M。
# 基于ubuntu_base镜像
FROM ubuntu_base
# 切换到root用户
USER root
# 安装buildroot构建环境所需库
# file patch wget cpio rsync bc bzip2 git是buildroot构建所需要的
# python3在buildroot构建openssl库时会用到,且需要将其链接为python
# libbrlapi-dev libvdeplug-dev libaio-dev libpixman-1-dev libbluetooth-dev libsnappy-dev是qemu运行所需要的库,其中有些库还需要创建新的软连接
# xz-utils zlib1g-dev libnuma-dev liblzo2-dev m4这些在构建libpng libnettle源码时使用
# libncurses-dev在进行make menuconfig时需要
# bridge-utils uml-utilities isc-dhcp-client创建和管理网桥时需要,通过网桥可以将qemu与外界联系起来
# telnet可用于连接qemu
# libncurses.so.5用于支持csky-gdb的运行
# wget下载时提示网站证书检验失败,需要添加--no-check-certificate参数
RUN apt-get update \
&& apt-get install -y --no-install-recommends file patch wget cpio rsync bc bzip2 git python3 \
libbrlapi-dev libvdeplug-dev libaio-dev libpixman-1-dev libbluetooth-dev \
libsnappy-dev xz-utils zlib1g-dev libnuma-dev liblzo2-dev m4 \
libncurses-dev bridge-utils uml-utilities isc-dhcp-client telnet \
&& ln -s /usr/bin/python3 /usr/bin/python \
&& ln -s libbrlapi.so /lib/x86_64-linux-gnu/libbrlapi.so.0.6 \
&& ln -s libaio.so /usr/lib/x86_64-linux-gnu/libaio.so.1 \
&& ln -s libncurses.so.6 /usr/lib/x86_64-linux-gnu/libncurses.so.5 \
&& wget --no-check-certificate https://ppa.launchpadcontent.net/linuxuprising/libpng12/ubuntu/pool/main/libp/libpng/libpng_1.2.54.orig.tar.xz \
&& tar -xf libpng_1.2.54.orig.tar.xz; cd libpng-1.2.54 \
&& ./configure; make -j8; make install; cd ..; rm -rf libpng-1.2.54 libpng_1.2.54.orig.tar.xz \
&& ln -s /usr/local/lib/libpng12.so.0.54.0 /usr/lib/libpng12.so.0 \
&& wget --no-check-certificate https://ftp.gnu.org/gnu/nettle/nettle-3.2.tar.gz \
&& tar -xf nettle-3.2.tar.gz; cd nettle-3.2 \
&& ./configure; make -j8; make install; cd ..; rm -rf nettle-3.2 nettle-3.2.tar.gz \
&& ln -s /usr/local/lib64/libnettle.so.6 /lib/libnettle.so.6 \
&& apt-get clean \
&& apt-get autoclean
# 切换为xflm
USER xflm
Dockerfile中提到的ubuntu_base
基础镜像,可参考在wsl(6) – 安装docker文章。使用docker命令依据Dockerfile构建一个新的镜像buildroot
。
$ docker build -t buildroot .
4. 在docker中编译
启动ubuntu_buildroot
,并挂载buildroot目录。
# 将csky的buildroot目录挂载到容器/buildroot上
$ docker run -it -v /home/xflm/workspace/buildroot:/buildroot --name=buildroot buildroot
# 此处即进入容器中,在容器中进入/buildroot目录
xflm@b1b77bb58c37:~$ cd /buildroot
# 启动buildroot的编译,期间会出现几次错误,需要逐一排解
xflm@b1b77bb58c37:~$ make CONF=thead_860_compat_5.10_glibc_br_defconfig
5. 问题记录
编译完成后,当前目录结构如下,之后的问题记录中路径都是以工作目录为相对路径。
# 工作目录,也即csky的buildroot目录
$ ls
LICENSE README.md buildroot-9d1d4818c39d97ad7a1cdf6e075b9acae6dfff71 configs_enhanced fs patches
Makefile board configs dl package thead_860_compat_5.10_glibc_br_defconfig
# 官方的buildroot目录
$ ls buildroot-9d1d4818c39d97ad7a1cdf6e075b9acae6dfff71
CHANGES Config.in DEVELOPERS Makefile.legacy arch boot configs docs linux support toolchain
COPYING Config.in.legacy Makefile README board callchain_test dl fs package system utils
# 编译输出目录
$ ls thead_860_compat_5.10_glibc_br_defconfig/
Makefile build host images staging target
# 查看下各目录的大小,有些目录我做过修改,数值不是最开始编译后的大小,但相差不大
$ du -sh *
12K LICENSE
4.0K Makefile
8.0K README.md
140K board
74M buildroot-9d1d4818c39d97ad7a1cdf6e075b9acae6dfff71
124K configs
148K configs_enhanced
864M dl
12K fs
5.2M package
84K patches
8.2G thead_860_compat_5.10_glibc_br_defconfig
5.1 关于出错
我编译的时候遇到了fakeroot
和m4
两个包编译出错,可以根据基于WSL和玄铁官方仓库c-sky/buildroot构建玄铁CPU系统镜像文章中的方法进行修改,也可以去buildroot仓库下载更新的package,替换掉buildroot-9d1d4818c39d97ad7a1cdf6e075b9acae6dfff71/package/
下面的包。
- 比如
m4
包,其目前最新的发布版本为2025.02-rc1
,按照下面(很有规律)的网址即可找到该包的内容。
https://gitlab.com/buildroot.org/buildroot/-/tree/2025.02-rc1/package/m4?ref_type=tags - 点击【Code】》【Download this directory】》【tar.gz】即可下载该目录的内容为:buildroot-2025.02-rc1-package-m4.tar.gz
https://gitlab.com/buildroot.org/buildroot/-/archive/2025.02-rc1/buildroot-2025.02-rc1.tar.gz?path=package/m4 - 删除
buildroot-9d1d4818c39d97ad7a1cdf6e075b9acae6dfff71/package/m4
文件夹。 - 解压buildroot-2025.02-rc1-package-m4.tar.gz,将解压后的目录移动为
buildroot-9d1d4818c39d97ad7a1cdf6e075b9acae6dfff71/package/m4
- 执行
make CONF=thead_860_compat_5.10_glibc_br_defconfig
,buildroot会下载新的m4
软件源码,然后进行编译,编译的过程中提示了autoconf
的版本较低,所以我又按照同样的方式替换了autoconf
的包。
在编译内核时遇到了usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss+0x50): multiple definition of 'yylloc‘
这个错误,根据编译Linux内核出现:usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss+0x50): multiple definition of yylloc‘;解决了。修改thead_860_compat_5.10_glibc_br_defconfig/build/linux-5.10.4/scripts/dtc/dtc-lexer.lex.c
的629
行将YYLTYPE yylloc;
修改为extern YYLTYPE yylloc;
。
5.2 关于下载慢
dl/
中存放着已下载并校验通过的源码包,在编译前该目录是空的,编译的过程中会根据需要自动下载,buildroot中每个包都有多个下载地址,下载时会在日志中打印出来,当地址不通时,buildroot等待会超时,然后切换到其他地址。
若有些地址能下载,但是下载的很慢,则可以CTRL+C终止buildroot,将下载地址拷贝到迅雷中进行下载,下载完毕,手动拷贝到dl/
对应的目录中,然后再重新执行make CONF=thead_860_compat_5.10_glibc_br_defconfig
,buildroot会跳过之前的下载步骤,直接进入解压编译环节。
5.3 关于工具链
thead_860_compat_5.10_glibc_br_defconfig/host/bin/
目录下有许多可执行程序,这些程序是运行在宿主机上的,其中包括csky-abiv2-linux-gcc
工具链,使用该工具链时,无需再添加-mcpu=ck860
。
# 查看工具链默认的sysroot
$ ./thead_860_compat_5.10_glibc_br_defconfig/host/bin/csky-abiv2-linux-gcc --print-sysroot
/buildroot/thead_860_compat_5.10_glibc_br_defconf/host/csky-buildroot-linux-gnuabiv2/sysroot/ck860/./
# 该工具链其实是一个软连接
$ ll ./thead_860_compat_5.10_glibc_br_defconfig/host/bin/csky-abiv2-linux-gcc
lrwxrwxrwx ./thead_860_compat_5.10_glibc_br_defconfig/host/bin/csky-abiv2-linux-gcc -> toolchain-wrapper*
# 查看toolchain-wrapper中的字符串,推测是由该程序设置了csky-abiv2-linux-gcc的sysroot值
$ strings ./thead_860_compat_5.10_glibc_br_defconfig/host/bin/toolchain-wrapper
...
-mcpu=ck860
--sysroot
# 真正的工具链在这里
$ ./thead_860_compat_5.10_glibc_br_defconfig/host/opt/ext-toolchain/bin/csky-abiv2-linux-gcc --print-sysroot
/buildroot/thead_860_compat_5.10_glibc_br_defconfig/host/opt/ext-toolchain/bin/../csky-linux-gnuabiv2/libc/
6. 添加新的软件包
# 执行下面的命令,进入图形界面,勾选需要的软件包
$ make -C thead_860_compat_5.10_glibc_br_defconfig menuconfig
# 勾选后保存改动,启动编译
$ make -C thead_860_compat_5.10_glibc_br_defconfig
7. 向根文件系统镜像中添加文件
此处添加一个package/user-copy
来实现文件的拷贝。
- 创建
buildroot-9d1d4818c39d97ad7a1cdf6e075b9acae6dfff71/package/user-copy/
并进入该目录。
$ mkdir buildroot-9d1d4818c39d97ad7a1cdf6e075b9acae6dfff71/package/user-copy/
$ cd buildroot-9d1d4818c39d97ad7a1cdf6e075b9acae6dfff71/package/user-copy/
- 创建
Config.in
,用于将我们自定义的package
添加到menuconfig
中。
config BR2_PACKAGE_USER_COPY
bool "user copy files to rootfs"
help
A package to copy user files to rootfs.
- 编辑
../Config.in
,在末尾添加。
menu "User operation"
source "package/user-copy/Config.in"
endmenu
- 创建
user_copy.mk
,这里用到了install
命令,它和cp
命令相似,此处目的是在thead_860_compat_5.10_glibc_br_defconfig/target
下创建root/share
目录,添加etc/profile.d/profile_user.sh
文件。
USER_COPY_VERSION = 1.0
USER_COPY_SITE = $(TOPDIR)/package/user-copy
USER_COPY_SITE_METHOD = local
define USER_COPY_INSTALL_TARGET_CMDS
$(INSTALL) -d $(TARGET_DIR)/root/share
$(INSTALL) -D -m 0644 $(USER_COPY_SITE)/profile_user.sh $(TARGET_DIR)/etc/profile.d/profile_user.sh
endef
$(eval $(generic-package))
- 创建
profile_user.sh
,用于进行共享文件夹的挂载以及网络配置。
#!/bin/bash
mount | grep /root/share > /dev/null || mount -t 9p -o trans=virtio,version=9p2000.L hostshare /root/share
ifconfig | grep eth0 > /dev/null || ifconfig eth0 192.168.101.201
test -f ~/.bashrc && . ~/.bashrc
- 执行配置,然后编译。
# 执行下面的命令,进入图形界面,选中【User operation】》【user copy files to rootfs】
$ make -C thead_860_compat_5.10_glibc_br_defconfig menuconfig
# 勾选后保存改动,启动编译
$ make -C thead_860_compat_5.10_glibc_br_defconfig
# 若user-copy发生了改变,make可能并不会触发动作,此时可以针对user-copy进行rebuild,然后再执行make
$ make -C thead_860_compat_5.10_glibc_br_defconfig user-copy-rebuild
$ make -C thead_860_compat_5.10_glibc_br_defconfig
6. 使用qemu运行
参考Qemu(1) — Ubuntu下运行CK860 Qemu,在当前目录新建一个qemu/share
的目录。
- 新建
qemu/qemu.sh
文件。
#!/bin/bash
# 获取qemu.sh的绝对路径,qemu启动参数中涉及的文件路径需要使用绝对路径
QEMU_DIR=$(dirname $(readlink -f "$0"))
CONF=thead_860_compat_5.10_glibc_br_defconfig
echo ">>>>>> 同时按下CTRL+a,松开后按下x强制关机,关机前请执行sync命令,执行reboot也能关机 <<<<<<"
OUTPUT_DIR=$QEMU_DIR/../$CONF
# qemu的启动参数,此处还配置了qemu的共享目录和网络
sudo $OUTPUT_DIR/host/csky-qemu/bin/qemu-system-cskyv2 \
-M virt \
-cpu c860 \
-smp 4 \
-m 1G \
-kernel $OUTPUT_DIR/images/Image \
-nographic \
-append "console=ttyS0,115200 rdinit=/sbin/init rootwait root=/dev/vda ro" \
-drive file=$OUTPUT_DIR/images/rootfs.ext2,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0 \
-fsdev local,security_model=passthrough,id=fsdev0,path=$QEMU_DIR/share \
-device virtio-9p-device,id=fs0,fsdev=fsdev0,mount_tag=hostshare \
-netdev tap,script=no,downscript=no,id=net0 \
-device virtio-net-device,netdev=net0
- 使用
sudo
运行qemu,即可启动并进入控制台。
$ sudo ./qemu/qemu.sh
...
Welcome to Buildroot
buildroot login: root # 此处输入root用户,回车后即可进入,因为是root用户命令提示符为"#"
# pwd
/root
# ls
share
- 新开一个wsl的窗口。
# 新开一个buildroot容器的窗口
$ docker exec -it buildroot bash
# 设置tap网络地址
$ sudo ifconfig tap0 192.168.101.200
# 通过telnet连接qemu
$ telnet 192.168.101.201
...
buildroot login: root # 此处输入root用户,回车后即可进入,因为是root用户命令提示符为"#"
# pwd
/root
- qemu中对文件系统的修改都会保存在
rootfs.ext2
文件中,下次启动时仍然有效,buildroot重新编译后会生成新的rootfs.ext2
文件进行覆盖。
7. 配置网桥
wsl上配置网桥一直未成功,在VMware虚拟机上是可行的,这可能与wsl有关系,暂时先不弄了。
参考
基于WSL和玄铁官方仓库c-sky/buildroot构建玄铁CPU系统镜像
编译Linux内核出现:usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss+0x50): multiple definition of `yylloc‘;
Ubuntu 安装libpng12的方法
buildroot 增加自己的应用程序
Buildroot 添加自定义模块-内置文件到文件系统
如何配置 QEMU 虚拟机网络
install 命令用法详解