目标

在openEuler RISC-V上基于OE grub2源码完成grub2编译替换,能够正常拉起系统。

环境

OS: openEuler 24.03 SP2 RISC-V

Hardware: RISC-V QEMU 9.1.3

oerv的grub2命令行通过按下c进入,用户名是root,默认密码openEuler#12

openEuler与Fedora都属于redhat系列,fedora官方提供了rv-fedora上编译grub2的文档,可以参考。

获取源码

虽然理论上可以直接基于grub2上游源码编译,但是每个Linux发行版会针对自己的特点为grub2添加补丁,为了保持一致,建议通过openEuler的包管理器dnf直接拉取src package

### 该命令会将grub2的源码包下载到当前目录下
dnf download --source grub2 --downloaddir="./"

然后通过rpm -i命令解包源码到当前目录(缺少的依赖手动安装即可)。

### 获取rpmbuild使用的源码
rpm -i ${GRUB2_SRC_RPM_NAME}

可以在当前目录下看到rpmbuild目录,结构如下,SPECS存放了编译命令,SOURCES存放了grub2源码

rpmbuild/
├── SOURCES
└── SPECS

注意,这里解压出来的源码是源码tar包+patches(补丁)的格式,给rpmbuild用的,不适合修改源码。因此继续使用rpmbuild -bp命令完成源码解压和打patch。

### 获取编译使用的源码
rpmbuild -bp rpmbuild/SPECS/grub2.spec

### 可能需要的依赖
dnf install -y gcc make rpm-build autoconf automake bison bzip2-devel device-mapper-devel flex freetype-devel fuse-devel gettext-devel git help2man libtasn1-devel libusb-devel ncurses-devel "pesign >= 0.99-8" rpm-devel texinfo xz-devel

有兴趣的可以关注rpmbuld prepare过程中打印的log,基本是在初始化代码环境,包括解压,初始化git仓库(因为补丁是基于git diff生成的),打补丁,以及生成配置文件。此时rpmbuild下生成了新目录,我们主要使用存放完成准备源码的BUILD目录

rpmbuild/
├── BUILD
├── BUILDROOT
├── RPMS
├── SOURCES
├── SPECS
└── SRPMS

EFI分区和BOOT分区

从使用上理解,可以将EFI分区视为grub镜像的查找目录,而BOOT目录是GRUB启动时的根文件系统/目录,用于查找Linux内核镜像和rootfs/initrd

为了确保GRUB镜像能够正确被EDK2识别,在通过grub/grub2-mkimage时制作的grub镜像时,需要确保镜像在EFI分区下路径 = 制作时指定的路径,否则启动时会出现下图找不到grub镜像的问题。
在这里插入图片描述
大部分系统以及教程都建议boot分区和efi分区分离,编译和安装时grub基本默认/boot/efi下为efi分区,/boot为boot分区。

但是,openEuler RISC-V目前EFI分区和BOOT分区是同一个分区,都是直接挂载在/boot目录下,但是EFI镜像制作时指定的目录(即图中的EFI/openeuler/grubriscv64.efi)则是相对/boot/efi目录的路径导致oerv找不到默认启动项使用的grub镜像(即图中报错),实际使用的是/boot/efi/BOOT下grub镜像,只修改/boot/efi/EFI/openEuler下面的内容不会生效。

感兴趣的读者可以尝试直接将完整的/boot/efi/EFI/openEuler目录拷贝到/boot/efi/openeuler下,按照我们之前的分析,这样就能够实现grub镜像构建时路径和加载时路径一致,确保openEuler使用的是预期的grub镜像(检查启动时日志可以发现已经没有报错了)。

源码修改

下面修改源码,为了方便验证,我们修改以下两个部分:

  1. grub2版本号:在configure.acAC_INIT中定义(注意修改configure.ac后必须运行autogen.sh才能生效,详见下一节)。
  2. hello mod:修改hello模块(在grub-core/hellp/hello.c中),自定义打印信息。

编译安装

编译有两条路线:

  • 直接使用rpmbuild:oerv所有软件包提供的spec都不支持交叉编译。因为openEuler OBS出RISC-V包是基于docker + qemu_user在x86上模拟RISC-V原生编译出软件包,可以通过以下方式使用rpmbuild出包:

    1. 模仿OBS的行为,在qemu-user驱动的RISC-V的容器镜像中编译。

    2. 在硬件环境如qemu上直接rpmbuild,性能也还可以接受。

    同时,直接使用rpmbuild不方便快速验证代码,因为rpmbuild完全基于patch应用代码修改,每次修改之后需要按照生成patch->添加patch并修改spec->重新构建的流程操作,效率低下。

  • 手动进行编译对于需要快速验证的同学推荐手动进行编译。手动编译可以通过在configure时修改host/target选项支持交叉编译,因为grub2的编译比较简单,我们这里直接使用qemu原生编译。

    0. 清理环境

    清理下配置,有时候configure会因为环境被污染失败

    ### 定义grub2代码根目录
    GRUB2_ROOT=
    ### 清理环境
    make -C ${GRUB2_ROOT} distclean
    

    1. 生成configure

    这一步必须在代码根目录下执行。因为autogen会在工作目录下查找gnulib依赖,oe-grub2直接将依赖打包在源码中,不需要额外执行bootstrap获取依赖。
    注意任何对configure.ac的修改都需要运行autogen才能生效。

    ### 自动生成配置文件
    cd ${GRUB2_ROOT} && bash ./autogen.sh
    

    3. 配置

    强烈建议仅在QEMU等测试环境中使用这段配置,为了方便它是直接从rpmbuild -bc日志中摘下来的,使用这个配置直接编译安装会覆盖当前系统grub工具(其它oe版本同样可以使用rpmbuild -bc命令查看日志输出来获取configure命令)。

    注意:

    1. oe在grub源码下创建了grub-riscv64-efi-2.12目录作为output目录,进入该目录后执行configure脚本完成配置,也可以直接在代码根目录下执行,编译和output目录不同而已。
    2. 在bootstrap时OBS会使用riscv64-openEuler-linux-gnu编译grub,但是在使用环境上是没有的(标准的三元组,因为OBS是原生编译,所以build/host/target是一样的)。我们直接使用默认gcc,移除三元组的配置
    cd ${GRUB2_ROOT}/grub-riscv64-efi-2.12 && ../configure \
        --program-prefix= \
        --disable-dependency-tracking \
        --prefix=/usr \
        --exec-prefix=/usr \
        --bindir=/usr/bin \
        --sbindir=/usr/sbin \
        --sysconfdir=/etc \
        --datadir=/usr/share \
        --includedir=/usr/include \
        --libdir=/usr/lib \
        --libexecdir=/usr/libexec \
        --localstatedir=/var \
        --sharedstatedir=/var/lib \
        --mandir=/usr/share/man \
        --infodir=/usr/share/info \
    	"CC=gcc -fPIE -Wl,-z,noexecstack" \
        "HOST_CFLAGS=-fno-strict-aliasing -g3 -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_GLIBCXX_ASSERTIONS -fstack-clash-protection -specs=/usr/lib/rpm/generic-hardened-cc1 -I$(pwd) -fno-stack-protector" \
        "HOST_CPPFLAGS= -I$(pwd) -fPIC" \
        "HOST_LDFLAGS=-Wl,-z,relro -Wl,-z,now -specs=/usr/lib/rpm/generic-hardened-ld" \
        "TARGET_CFLAGS=-fno-strict-aliasing -g3 -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_GLIBCXX_ASSERTIONS -fstack-clash-protection -I$(pwd) -fno-stack-protector" \
    "TARGET_CPPFLAGS= -I$(pwd)" \
        --with-platform=efi \
        --with-grubdir=grub2 \
        --program-transform-name=s,grub,grub2, \
        --disable-werror
    

    4. 编译grub

    在配置目录下进行编译,编译完成后安装grub2工具到configure时配置的目录。注意这里并不是安装镜像

    make -j$(nproc) && make install
    

    5. 安装grub

    5.1 自动安装grub

    可以直接使用grub2-install命令安装grub2 mod和镜像。同时该命令还会生成对应的grub.cfggrubenv。前者用于指导grub加载linux,后者用于启用grub环境变量(如debug)。无特殊要求时,可以直接使用以下命令安装

    EFI_DIR="/boot"
    BL_ID="openEuler"
    ./grub-install \
     	--target=riscv64-efi \ 
    	--efi-directory=${EFI_DIR} \ 
    	--bootloader-id=${BL_ID}
    

    注意efi-directory参数和bootloader-id

    • 前者指定了EFI分区的挂载目录,这里OERV自己的处理明显有问题,OERV的EFI分区挂载在/boot目录下,但是仍然按照/boot/efi目录进行操作。
    • 后者指定了在EDK2启动时当前grub对应的启动项名称,我们这里直接替换了第一个启动项,如果不希望覆盖可以换成不一样的。
    • 同时,我们这里没有指定安装目录,grub的mods和env,cfg等会缺省安装到${EFI_DIR}/grub2目录下(因为是安装grub2),即每次安装都会替换mods和cfg等。

    完成自动安装后,我们能够在${EFI_DIR}/${BL_ID} 下找到安装的grub2镜像,默认名字grubriscv64.efi。并在${EFI_DIR}/grub2下找到cfg和mods等。

    5.2 生成grub配置

    grub install时生成的cfg不一定能够正常识别,重新使用grub2-mkconfig命令生成。
    注意目标cfg目录需要和install目录保持一致。

    EFI_DIR="/boot"
    BL_ID="openEuler"
    sudo grub2-mkconfig -o ${EFI_DIR}/grub2/grub.cfg
    
    5.3 (非必须)构建grub镜像

    制作镜像非必须,直接使用grub2-install安装的镜像已经可以正常使用。但是如果需要打包mods到镜像中,需要使用grub2-mkimage工具单独制作镜像,下面是OE grub的mkimage命令。

    EFI_DIR="/boot"
    BL_ID="openEuler"
    ./grub-mkimage \
      	-O riscv64-efi \
    	-o grubriscv64.efi.orig \
    	-p /EFI/${BL_ID}\
    	-d grub-core \
    	all_video boot blscfg btrfs cat configfile cryptodisk echo efifwsetup efinet ext2 f2fs fat font gcry_rijndael gcry_rsa gcry_serpent gcry_sha256 gcry_twofish gcry_whirlpool gfxmenu gfxterm gfxterm_background gzio halt http iso9660 jpeg loadenv loopback linux lvm lsefi lsefimmap luks luks2 mdraid09 mdraid1x minicmd net normal part_apple part_msdos part_gpt password_pbkdf2 png reboot search search_fs_uuid search_fs_file search_label serial sleep test tftp video xfs tpm tpcm_kunpeng \
    	--sbat ././sbat.csv \
    	-c ${EFI_DIR}/grub.cfg
    

    注意**-p和-c参数**:

    注:在整个编译和配置过程中没有看到报错,命令应当无误。

验证

完成grub2编译和镜像/mods安装后,首先尝试执行任一grub2工具(如grub2-install)的version命令。检查版本是否与自定义版本一致,(如果不一致检查是否修改了configure.ac后未运行autogen生成配置文件)

grub2-install --version

然后重启系统,在系统启动时按下c进入grub,接下来输入用户名(root),默认密码(openEuler#12),进入命令行界面。

  1. 查看grub版本是否与自定义版本一致。
  2. 执行hello命令,检查输出是否与编译时一致。
Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐