• 使用QEMU调试ARM64 Linux内核v6.0.9


    环境准备

    开发环境:Ubuntu 20.04.5 LTS,推荐修改阿里云的apt源,遇到编译依赖方便安装。
    环境准备:在Windows上基于WSL2搭建Linux开发环境
    本文用到的软件选用的是截至当前(2022-11-19)官网发布的最新的release版本,详细如下:

    软件版本官网发布日期说明
    Linux6.0.9https://kernel.org2022-11-16Linux内核
    BusyBox1.35.0https://busybox.net2021-11-26集成工具集,方便制作根文件系统
    QEMU7.2.0-rc1https://www.qemu.org2022-11-15仿真ARM64开发板

    新建一个工作目录,编译准备所有所需软件,最终所需文件如下:

    ├── busybox-1.35.0
    ├── busybox-1.35.0.tar.bz2
    ├── linux-6.0.9
    ├── linux-6.0.9.tar.xz
    ├── make_initrd.sh
    ├── qemu-7.2.0-rc1
    ├── qemu-7.2.0-rc1.tar.xz
    └── qemu.sh

    说明1:make_initrd.sh 是为了自动化制作根文件系统
    说明2:qemu.sh 是使用QEMU拉起Linux的shell脚本

    本文后面将详细介绍如何编译和使用每一个软件,以及上面提到的两个脚本的内容和功能。

    编译ARM64 Linux

    下载Linux源码

    官网下载当前最新的release版本:

    wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.0.9.tar.xz
    tar xvf linux-5.19.11.tar.xz
    
    • 1
    • 2

    交叉编译ARM64 Linux

    使用menuconfig勾选RAM disks支持,并调整大小为: 65536 kb:

    make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 O=build menuconfig -j32
        Device Drivers > Block devices
    	  <*>   RAM block device support
            (16)    Default number of RAM disks (NEW)
            (65536) Default RAM disk size (kbytes)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    说明:

    • O=build 表示编译的文件输出到build目录,不跟源码混在一起;
    • -j32 表示开启32线程编译,注意线程太多可能会偶现时序导致的编译错误;

    交叉编译ARM64 Linux内核,编译完毕后,对应目录会有生成的内核镜像:

    make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 O=build -j32
    file build/arch/arm64/boot/Image 
    
    • 1
    • 2

    使用AMD 5800电脑,大概5min就可以编译完毕。

    编译ARM64 BusyBox

    下载BusyBox源码

    官网下载最新的release版本:

    wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
    tar xvf busybox-1.35.0.tar.bz2 
    
    • 1
    • 2

    交叉编译ARM64 BusyBox

    使用menuconfig修改为静态链接:

    make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 menuconfig -j32 
        Settings
    	  [*] Build static binary (no shared libs)
    
    • 1
    • 2
    • 3

    编译BusyBox可执行文件,并输出到_install目录:

    make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 install -j32
    file _install/bin/busybox
    
    • 1
    • 2

    基于BusyBox制作initrd

    制作initrd步骤略多。这里make_initrd.sh就是用来自动制作initrd的脚本,内容:

    #!/bin/bash
    
    MOUNT_DIR=mnt
    CURR_DIR=`pwd`
    
    rm initrd.ext4
    dd if=/dev/zero of=initrd.ext4 bs=1M count=32
    mkfs.ext4 initrd.ext4
    
    mkdir -p $MOUNT_DIR
    mount initrd.ext4 $MOUNT_DIR
    cp -arf busybox-1.35.0/_install/* $MOUNT_DIR
    
    cd $MOUNT_DIR
    mkdir -p etc dev mnt proc sys tmp mnt etc/init.d/
    
    echo "proc /proc proc defaults 0 0" > etc/fstab
    echo "tmpfs /tmp tmpfs defaults 0 0" >> etc/fstab
    echo "sysfs /sys sysfs defaults 0 0" >> etc/fstab
    
    echo "#!/bin/sh" > etc/init.d/rcS
    echo "mount -a" >> etc/init.d/rcS
    echo "mount -o remount,rw /" >> etc/init.d/rcS
    echo "echo -e \"Welcome to ARM64 Linux\"" >> etc/init.d/rcS
    chmod 755 etc/init.d/rcS
    
    echo "::sysinit:/etc/init.d/rcS" > etc/inittab
    echo "::respawn:-/bin/sh" >> etc/inittab
    echo "::askfirst:-/bin/sh" >> etc/inittab
    chmod 755 etc/inittab
    
    cd dev
    mknod console c 5 1
    mknod null c 1 3
    mknod tty1 c 4 1
    
    cd $CURR_DIR
    umount $MOUNT_DIR
    echo "make initrd ok!"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    介绍一下这个脚本的实现和功能:

    1. 使用 ddinitrd.ext4 制作一个空的32M的ext4格式的文件系统;
    2. mount这个文件系统,然后拷贝busybox编译的文件进去;
    3. 创建Linux的关键的标准层级目录 Filesystem Hierarchy Standard (FHS)
    4. 创建 /etc/fstab,这是mount自动挂载配置文件,可参考:mount命令及/etc/fstab文件详解
    5. 创建 /etc/init.d/rcS,这是文件系统的初始化脚本,可参考:嵌入式linux /etc/init.d/rcS文件解读
    6. 创建 /etc/inittab,init进程会解析inittab文件,可参考:/etc/inittab文件的作用

    这片文章也挺不错:制作嵌入式根文件系统(常见问题详解)

    使用QEMU拉起ARM64 Linux

    下载QEMU源码

    官网下载最新的release版本:

    wget https://download.qemu.org/qemu-7.2.0-rc1.tar.xz
    tar xvf busybox-1.35.0.tar.bz2
    
    • 1
    • 2

    编译可拉起aarch64内核的QEMU

    进入qemu源码目录后,编译步骤如下:

    cd qemu-7.2.0-rc1
    mkdir build
    cd build
    ../configure --target-list=aarch64-softmmu
    make -j32
    file aarch64-softmmu/qemu-system-aarch64
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用QEMU virt拉起ARM64 Linux

    运行 qemu.sh 即可拉起 ARM64 Linux,使用的ARM机器就是是QEMU仿真的virt machine。
    注意:QEMU的virt machine默认CPU是cortex-a15,这是一个32位CPU。选择ARMv8的64位CPU可用cortex-a57。
    查看QEMU支持的machine和cpu方法如下:

    cd qemu-7.2.0-rc1/build/aarch64-softmmu
    ./qemu-system-aarch64 -M help      # 查看支持的machine
    ./qemu-system-aarch64 -cpu help    # 查看支持的CPU
    
    • 1
    • 2
    • 3

    启动脚本 qemu.sh 内容如下:

    #!/bin/bash
    
    qemu/build/aarch64-softmmu/qemu-system-aarch64 \
        -nographic \
        -M virt \
        -cpu cortex-a57 \
        -smp 2 \
        -m 4G \
        -kernel linux-6.0.9/build/arch/arm64/boot/Image \
        -append "nokaslr root=/dev/ram init=/linuxrc console=ttyAMA0 console=ttyS0" \
        -initrd initrd.ext4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    说明:上面append选项用来给内核传递命令行参数,nokaslr 表示关闭地址随机化,方便gdb调试内核。

    GDB调试ARM64 Linux

    QEMU是一个仿真器,有内置的GDB server,给内核提供了强大的调试功能。基本用法如下:

    修改 qemu.sh 脚本,加入 -s -S 参数,修改后如下:

    #!/bin/bash
    
    qemu/build/aarch64-softmmu/qemu-system-aarch64 \
        -nographic \
        -M virt \
        -cpu cortex-a57 \
        -smp 2 \
        -m 4G \
        -kernel linux-6.0.9/build/arch/arm64/boot/Image \
        -append "nokaslr root=/dev/ram init=/linuxrc console=ttyAMA0 console=ttyS0" \
        -initrd initrd.ext4 \
        -s -S
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这是执行 qemu.sh 脚本会停住,新开一个终端,使用如下命令调试

    cd linux-6.0.9/build
    gdb-multiarch vmlinux
    # 进入gdb交互界面后,执行
        target remote :1234
        b start_kernel
        c
    # 即可在内核入口函数断注
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    内核大部分时间都在cpu idle,一般在gdb交互界面随机按 ctrl+c就是停在这个状态,调试大概如下截图:
    gdb
    后面就可以非常方便的调试内核了:

    1. 在QEMU的交互console可以看一些硬件寄存器等信息;
    2. 在GDB的交互console可以像调试普通应用程序一样调试内核;

    需要注意的是:内核只能用O2编译,很多变量都被优化了,不很方便。

    可用下面方法来局部O0编译:

    • #pragma GCC optimize ("O0") 写在文件开头,O0编译本文件
    • __attribute__((optimize("O0"))) 写在函数开头,O0编译本函数

    具体可以参考:使用 O0 编译 调试 Linux内核的某些部分O0的内核

    这样如果遇到需要单步执行,或者看一些变量信息,会更加的方便。

  • 相关阅读:
    【SSM整合小案例(详细+源码)】
    同样Java后端开发三年,朋友已经涨薪到了30k,而我才刚到12K。必须承认多背背八股文确实有奇效!
    模板学堂|数据可视化仪表板大屏设计流程梳理
    实战SpringMVC之CRUD
    华为云云耀云服务器L实例评测|centos7.9配置java环境变量安装tomcat 部署war和jar
    elements ui vue table 多选操作
    【一】【SQL】表的增删查改(部分)
    树链剖分与线段树
    服务器密码以及用户名怎么修改
    云原生之深入解析如何使用Devtron简化K8S应用开发
  • 原文地址:https://blog.csdn.net/thisinnocence/article/details/127931774