qemu + busybox + gdb 构建linux内核调试环境

背景

最近在做Qos的相关开发工作,打算借鉴linux内核中tc的实现。奈何自己阅读代码能力有限,看了几天代码理解还是不够深入,所以打算构建一个内核调试的环境,单步运行加深对tc实现逻辑的理解

工具版本介绍

调试环境主要涉及以下工具,及对应版本号
vmware 16 pro
ubuntu 20.04
qemu 4.2.0
busybox 1.35.0
Linux 5.4.20

内核编译

关于内核编译方法,查看上一篇博客:vmware + ubuntu 20.04 构建内核代码编译环境
需要注意的是在做make menuconfig时按照下面的进行配置。因为是要做tc htb算法的调试,所以将tc相关的功能编译为内核模块。

General setup  --->

    ----> [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support

    Device Drivers  --->

       [*] Block devices  --->

               <*>   RAM block device support

               (65536) Default RAM disk size (kbytes)

Networking support  --->
    
    Networking options  --->
    
        [*] QoS and/or fair queueing  --->

            <M>   Hierarchical Token Bucket (HTB)
            
            <M>   Fair Queue Controlled Delay AQM (FQ_CODEL)

            <M>   Elementary classification (BASIC)

            <M>   Universal 32bit comparisons w/ hashing (U32)

            <M>   Flow classifier 

Device Drivers  --->
    
    --- Network device support
        
        --- Ethernet driver support
        
             [*]   Intel devices                                                                                                             

                 <*>     Intel(R) PRO/100+ support                                                                   
                 
                 <*>     Intel(R) PRO/1000 Gigabit Ethernet support                                                                                     
                 
                 <*>     Intel(R) PRO/1000 PCI-Express Gigabit Ethernet support                        

使用busybox制作initrd.img、rootfs.img

qemu模拟虚机,除了准备内核,还需要文件系统。需要准备两个文件系统镜像,initrd.img挂载到ramdisk。rootfs.img为真实的挂载磁盘的文件系统。initrd.img和rootfs.img的制作用到了busybox。

busybox编译

设置编译选项

make menuconfig

Busybox Settings  --->
      Build Options  --->
            [*] Build BusyBox as a static binary (no shared libs)

编译并安装busybox

make && make install

编译完成后的busybox就安装在源码根目录下的_install目录了

制作initrd.img镜像

创建虚机启动目录

mkdir ~/kernel
cd ~/kernel

再虚机启动目录下创建busybox目录,将_install目录下的内容copy至busybox下

mkdir busybox
cd busybox
cp ../../busybox-1.35.0/* ./ -rf

查看busybox安装的内容,作为文件系统,还缺少一些必须的目录,创建这些目录

ls
# bin  linuxrc  sbin  usr

mkdir dev etc proc sys tmp rootfs

创建设备

mknod console c 5 1
mknod ram b 1 0

在busybox目录下,创建init脚本

pwd
# /home/xxxx/kernel/busybox

vi init

#! /bin/sh
PATH="/bin:/sbin:/usr/bin:/usr/sbin"
mount -t sysfs sys /sys
mount -t proc proc /proc
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
mdev -s
mount /dev/sda /rootfs

echo "" > /proc/sys/kernel/hotplug
umount -f /proc
umount -f /sys

echo "begin to switch rootfs"
exec /sbin/switch_root /rootfs /linuxrc

为init添加可执行权限

chmod +x init

生成镜像

find .|cpio -o --format=newc > ../initrd.img

制作rootfs.img镜像

创建镜像,并格式化为ext4

pwd
# /home/xxxx/kernel/busybox

dd if=/dev/zero of=rootfs.img bs=1M count=64
mkfs.ext4 rootfs.img

mount镜像,并在镜像里添加所需文件

mkdir rootfs
mount rootfs.img rootfs

cd rootfs
# rootfs.img和initrd.img内容类似,所以先将initrd里的内容copy过来,再做修改
cp ../busybox/* ./ -rf

修改rootfs目录下的内容

rm -rf init
mkdir etc/init.d

vi etc/init.d/rcS

#! /bin/sh
PATH="/bin:/sbin:/usr/bin:/usr/sbin"
mount -t tmpfs dev /dev
mount -t sysfs sys /sys
mount -t proc proc /proc
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
mdev -s

umount目录

umount rootfs

使用qemu启动虚机

###编译qemu
解压qemu,并进入qemu源码目录

 ./configure --target-list=x86_64-softmmu,x86_64-linux-user

编译并安装qemu

make & make install

###启动虚机
将内核编译镜像copy至,虚机启动目录kernel下

cp ../linux-5.4.20/arch/x86_64/boot/bzImage ./

执行启动虚机命令

qemu-system-x86_64 -kernel bzImage -initrd initrd.img -append "console=ttyS0 rdinit=/init" -hdb rootfs.img -nographic

命令解释
-kernel bzImage 指定内核镜像
-initrd initrd.img 指定initrd镜像
-append “console=ttyS0 rdinit=/init” 执行initrd.img镜像里的/init脚本,也就是上文创建的/home/zhouzhx/kernel/busybox/init脚本
-hdb rootfs.img 将rootfs.img挂载为虚机内/dev/sda

虚机启动流程

先mount initrd.img,并执行init脚本。
init脚本里,比较重要的内容
mount /dev/sda /rootfs #将/dev/sda挂载到initrd.img里的/rootfs目录下
exec /sbin/switch_root /rootfs /linuxrc #将当前根目录切换到/rootfs下,并执行/linuxrc,也就是执行rootfs.img镜像下的/linuxrc命令。
linuxrc命令作用,执行当前文件系统/etc/init.d/rcS脚本,并启动shell。

添加网络设备

通过以上几步,实际上已经可以启动起来一个虚机。但是我的目的时调试tc htb代码,所以还需要创建网络接口。

创建/etc/qemu-ifup、/etc/qemu-ifdown脚本

vi /etc/qemu-ifup

#! /bin/sh
switch=br0
ifconfig $1 up
brctl addbr br0
brctl addif ${switch} $1
ifconfig ${switch} 192.168.101.200 up

vi /etc/qemu-ifdown

#! /bin/sh
switch=br0
ifconfig $1 down
ifconfig ${switch} down
brctl delif ${switch} $1
brctl delbr ${switch}

修改rootfs.img下的etc/init.d/rcS文件

mount rootfs.img rootfs
cd rootfs

vi etc/init.d/rcS

#! /bin/sh
PATH="/bin:/sbin:/usr/bin:/usr/sbin"
mount -t tmpfs dev /dev
mount -t sysfs sys /sys
mount -t proc proc /proc
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
mdev -s
ifconfig eth0 192.168.101.100 up
route add default gw 192.168.101.1 dev eth0

umount rootfs

修改虚机启动命令

qemu-system-x86_64 -kernel bzImage -initrd initrd.img -append "console=ttyS0 rdinit=/init" -hdb rootfs.img -nographic -netdev tap,id=nd0,ifname=tap0 -device e1000,netdev=nd0 

虚机启动后检查网络设备,如图可以看到网络设备启动正常,且能ping通物理机的br0网桥地址
请输入图片描述

命令解释

-netdev tap,id=nd0,ifname=tap0 #在宿主机上创建tap设备,设备id为nd0,设备名为tap0。此命选项会调用宿主机上的/etc/qemu-ifup脚本进行tap设备的创建
-device e1000,netdev=nd0 #虚机模拟e1000网卡设备,指定网络设备的另一端为宿主机上的id为nd0的设备也就是tap0
此时虚机内部只是模拟了物理网卡设备,并没有创建设备
所以rootfs.img根文件系统的/etc/init.d/rcS需要添加启动网络设备和添加路由的命令:

ifconfig eth0 192.168.101.100 up
route add default gw 192.168.101.1 dev eth0

替换虚机内的tc命令

为了调试内核tc功能,用户态需要使用tc命令。在busybox创建的文件系统里,tc功能只能查看不能添加qdisc等。
所以将ubuntu宿主机上的tc命令替换掉busybox里的tc命令。需要cp至rootfs的bin目录下也就是和busybox可执行文件同目录

mount rootfs.img rootfs
rm -rf tc
cp /usr/sbin/tc rootfs/bin/

查看ubuntu宿主机tc命令的依赖,并将依赖copy至rootfs下的相同目录下

ldd /sbin/tc
linux-vdso.so.1 (0x00007fff34e73000)
libelf.so.1 => /lib/x86_64-linux-gnu/libelf.so.1 (0x00007f718ad1c000)
libmnl.so.0 => /lib/x86_64-linux-gnu/libmnl.so.0 (0x00007f718ab16000)
libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f718aafc000)
libcap.so.2 => /lib/x86_64-linux-gnu/libcap.so.2 (0x00007f718aaf3000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f718a9a4000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f718a99e000)
libxtables.so.12 => /lib/x86_64-linux-gnu/libxtables.so.12 (0x00007f718a98a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f718a798000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f718a77c000)
/lib64/ld-linux-x86-64.so.2 (0x00007f718adda000)

将内核编译出的以下模块,添加到rootfs /lib/modules目录下

cd rootfs/lib/modules
cp /lib/modules/5.4.20/kernel/net/sched/sch_htb.ko ./
cp /lib/modules/5.4.20/kernel/net/sched/cls_basic.ko ./
cp /lib/modules/5.4.20/kernel/net/sched/cls_u32.ko ./
cp /lib/modules/5.4.20/kernel/net/sched/cls_flow.ko ./
cp /lib/modules/5.4.20/kernel/net/sched/sch_fq_codel.ko ./

修改rootfs下的etc/init.d/rcS,添加加载模块的操作

vi etc/init.d/rcS

#! /bin/sh
PATH="/bin:/sbin:/usr/bin:/usr/sbin"
mount -t tmpfs dev /dev
mount -t sysfs sys /sys
mount -t proc proc /proc
echo "/sbin/mdev" > /proc/sys/kernel/hotplug
mdev -s
insmod ${MODULES}/sch_htb.ko
insmod ${MODULES}/cls_basic.ko
insmod ${MODULES}/cls_u32.ko
insmod ${MODULES}/cls_flow.ko
insmod ${MODULES}/sch_fq_codel.ko

ifconfig eth0 192.168.101.100 up
route add default gw 192.168.101.1 dev eth0

启动虚机,检查tc命令是否正常。如下图所示tc可以正确的添加htb类型的qdisc
请输入图片描述

使用gdb进行调试

使用gdb调试时,需要修改qemu启动参数,添加-smp 1和-s、-S三个选项。-append参数里添加nokaslr

qemu-system-x86_64 -kernel bzImage -initrd initrd.img -append "console=ttyS0 rdinit=/init nokaslr" -hdb rootfs.img -nographic -netdev tap,id=nd0,ifname=tap0 -device e1000,netdev=nd0 -smp 1 -s -S

参数解释
-s shorthand for -gdb tcp::1234 启动1234端口给gdb远程调试,指的是宿主机的1234端口
-S freeze CPU at startup (use ‘c’ to start execution) 内核启动时挂起

# 进入gdb后,执行以下命令链接qemu进行调试
target remote localhost:1234

请输入图片描述

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值