wsl + qemu + busybox/buildroot 调试:linux应用->系统调用->linux内核

linux应用引出博客目标

《UNIX环境高级编程》->第3章 文件I/O-> 3.9 I/O的效率

下载源代码、修改fileio/mycat.c中的缓冲区大小BUFFSIZE为4,并编译运行

#include "apue.h"

#define	BUFFSIZE	4

int
main(void)
{
	int		n;
	char	buf[BUFFSIZE];

	while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
		if (write(STDOUT_FILENO, buf, n) != n)
			err_sys("write error");

	if (n < 0)
		err_sys("read error");

	exit(0);
}

如果单步的话,可以看到调用了三次read、write;

我们希望在内核里加断点,并且触发三次,这就是这篇博客的目标:

直观地调试从应用层到内核的一切!

步骤概述

整个工程目录:

tree -L 2:

//制作根文件系统:根文件系统位置buildroot-2023.02.8/output/images
.
├── buildroot-2023.02.8
│   ├── output
root@L-R91239GL-1157:~/work/linux# ls -la /root/work/linux/
total 19300
drwxr-xr-x 2 root root     4096 Dec  7 18:13 .
drwxr-xr-x 6 root root     4096 Dec  7 18:13 ..
-rw-r--r-- 1 root root 62914560 Dec  7 18:13 rootfs.ext2
lrwxrwxrwx 1 root root       11 Dec  7 18:13 rootfs.ext4 -> rootfs.ext2(软链接!)
-rwxr-xr-x 1 root root      436 Dec  7 18:13 start-qemu.sh


//(硬盘a -hda)根文件系统复制出来,并挂载
//cp buildroot-2023.02.8/output/images/rootfs.ext2 ./rootfs.ext4
//sudo mount -t ext4 -o loop rootfs.ext4 ./rootfs
├── rootfs
├── rootfs.ext4


//(硬盘b -hdb)共享磁盘并挂载
├── shadisk
│   ├── echoall
│   ├── lost+found
│   └── mycat
├── shadisk.img



//编译内核:镜像位置linux-4.14.331/arch/x86/boot/bzImage is ready  (#1)
├── linux-4.14.331
│   ├── arch
│   ├── vmlinux

//把内核镜像和用户模式linux这个app复制出来
├── bzImage
├── vmlinux



//工程下载目录
├── file
│   ├── buildroot-2023.02.8.tar.gz
│   └── linux-4.14.331.tar.xz

1 buildroot制作根文件系统rootfs.ext4(硬盘a),把rootfs.ext4挂载到rootfs文件夹,可以清楚看到,根文件系统里有哪些文件

2&&3  将在wsl里编译的应用,放到根文件系统里(硬盘a),或者放到共享盘也行(硬盘b)

硬件平台:x86                            宿主系统:windows上运行的wsl

用qemu模拟的硬件平台:x86    客户机系统:从kernel官网下载的内核源码编译出的内核镜像

因为实际的硬件平台和qemu模拟的硬件平台是一样的,所以免去了交叉编译的烦恼:

在wsl里编译的程序可以直接在qemu里运行!

qemu里能运行的程序也可以在wsl里运行!

4编译内核

之前我以为,虚拟一个x86cpu,那虚拟的操作系统是不是最好也跟wsl一样?

wsl版本的内核是:WSL2-Linux-Kernel-linux-msft-wsl-5.15.90.1.tar.gz

现在看来,随便在内核官网,下载一个版本就行了

输出的是内核镜像,bzImage:qemu运行虚拟机时用到

以及,vmlinux:gdb调试虚拟机时用到

这两个文件在内核源码目录里生成,被我copy出来了

5 运行

直接上效果图:

gdb命令行调试,或者通过gdb debug插件调试,都会触发三次write的系统调用

输入是123456789,三次write:1234,5678,9/n (因为我们的缓冲区大小设为4)

1.buildroot制作根文件系统(硬盘a)

https://buildroot.org/download.html

//本篇文档的目标是希望同时调试应用和内核,所以工程目录名叫linux
wsl:mkdir linux
vscode:打开linux文件夹

//file里统一放我们下载的文件
mkdir file
cd file
wget https://buildroot.org/downloads/buildroot-2023.02.8.tar.gz
cd ..
tar xvzf  file/buildroot-2023.02.8.tar.gz
cd buildroot-2023.02.8


基于默认的配置再去改(linux内核用的配置是make x86_64_defconfig)
find ./ -name "*x86*defconfig"
make qemu_x86_64_defconfig


//参考https://cloud.tencent.com/developer/article/1843036 修改配置
make menuconfig

Toolchain->Enable C++ support->*  (为了编译gdb)
System configuration->Root password ->1
Filesystem images->ext2/3/4 variant ->ext4
Kernel->Linux Kernel ->N
Target packages → Debugging, profiling and benchmark->gdb+gdb server+full debuger 都*


//用buildroot编译很慢,会下载第三方的东西进行编译
//用busybox就五分钟,但是有局限性,后面会解释为什么不用busybox制作根文件系统
//17:11-18:20
//14:14-15:34
//download error,重新 sudo make,会再次下载
//网卡了(github容易卡),直接ctrl+c,然后再sudo make 
//>>> host-qemu 7.2.1 Downloading,卡了半天,直接ctrl+c
sudo make


//镜像编译成功
root@L-R91239GL-1157:~/work/linux/buildroot-2023.02.8/output/images# ls -la
total 19300
drwxr-xr-x 2 root root     4096 Dec  7 18:13 .
drwxr-xr-x 6 root root     4096 Dec  7 18:13 ..
-rw-r--r-- 1 root root 62914560 Dec  7 18:13 rootfs.ext2
lrwxrwxrwx 1 root root       11 Dec  7 18:13 rootfs.ext4 -> rootfs.ext2
-rwxr-xr-x 1 root root      436 Dec  7 18:13 start-qemu.sh


//把镜像copy出来备用
cd ~/work/linux
cp buildroot-2023.02.8/output/images/rootfs.ext2 ./rootfs.ext4

//挂载一下根文件系统,看看gdb是不是成功生成了
mkdir rootfs
sudo mount -t ext4 -o loop rootfs.ext4 ./rootfs
root@L-R91239GL-1157:~/work/linux# find rootfs -name "gdb"
rootfs/usr/bin/gdb

2.添加应用到根文件系统(硬盘a)

//挂载根文件系统
cd ~/work/linux
sudo mount -t ext4 -o loop rootfs.ext4 ./rootfs

//把博客开头,在wsl里编译生成的应用mycat,复制到挂载的文件夹里
cp /root/study/apue.3e/fileio/mycat /root/work/linux/rootfs/root

//mycat可以在wsl里运行,也可以在qemu里运行,因为模拟出来的cpu和主机cpu一致
./rootfs/root/mycat
输入123456789
输出123456789
ctrl + c 退出允许


//取消挂载:点击一下刷新,会看到rootfs再次变成一个空的文件夹
//挂载之后,我们通过rootfs文件夹,来修改rootfs.ext4里的东西
//取消挂载,可以防止我们意外修改rootfs.ext4
umount ./rootfs

 3.添加应用到共享磁盘(硬盘b)

之前已经添加应用到根文件系统,qemu可以把根文件系统当成硬盘1来加载运行

我们也可以把应用添加到共享磁盘,qemu把共享硬盘当成硬盘2来加载运行

qume运行的时候,我们的虚拟机里就有了两个应用,随便运行哪个都可以

共享磁盘的好处:

我可以在调试内核的过程里,随时在wsl里编译(不需要交叉编译)一个新的app,然后放到虚拟机里运行,这大大提升了我们学习内核的效率!

//挂载后刷新一下资源管理器,会有lost+found
cd ~/work/linux
dd if=/dev/zero of=shadisk.img bs=512 count=131072
mkfs.ext4 shadisk.img 
mkdir shadisk
sudo mount -t ext4 -o loop shadisk.img ./shadisk
cp /root/study/apue.3e/fileio/mycat /root/work/linux/shadisk

//取消挂载,shadisk文件夹变成空的了
//挂载之后,我们通过rootfs文件夹,来修改rootfs.ext4里的东西
//取消挂载,可以防止我们意外修改rootfs.ext4
umount shadisk

4.编译内核

参考

https://cloud.tencent.com/developer/article/2141001

内核源码网址

The Linux Kernel Archives

我是强迫症,纠结好久选择哪个版本的,最终选择4.14.331,理由两个:

1 高版本的内核配置跟低版本有些不一样,配置错了很劝退

2《Linux内核设计与实现》这本书,才用的2.6

等你按照我的博客走完流程,心里有谱了,自己可以再换

//之前说过,下载的文件都放file目录
//你可以在网页上点击下载,之后复制到file目录
cd ~/work/linux
cd file
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.14.331.tar.xz
cd ..


//make menuconfig之前,建议把vscode控制台窗口调大一点
tar xvJf file/linux-4.14.331.tar.xz
cd linux-4.14.331
export ARCH=x86
make x86_64_defconfig
make menuconfig:4.14.331就改这三个,高版本改的更多,到时候配置失败就难受了……

Kernel hacking  ---> 
    Compile-time checks and compiler options  --->
        [*] Compile the kernel with debug info
        [*]   Provide GDB scripts for kernel debuggin
Processor type and features ---->
    [] Randomize the address of the kernel image (KASLR)


//可以先看一下主机多少核的
echo $(nproc)
make -j $(nproc)
5分钟
Kernel: arch/x86/boot/bzImage is ready  (#1)

5.运行

需要的文件

cp linux-4.14.331/arch/x86_64/boot/bzImage ./
cp linux-4.14.331/vmlinux ./


qemu-system-x86_64:通过apt下载

bzImage:内核镜像
rootfs.ext4:根文件系统,对应-hda,第一块硬盘
shadisk.img:共享磁盘,对应-hdb,第二块硬盘,不是必需的,但可以提高效率
vmlinux:本质就是一个app,用户模式的linux虚拟机,用gdb调试qemu时用到
         参考《LINUX设备驱动程序》,107页 

非调试模式运行虚拟机

cd ~/work/linux
//qemu             内核镜像         根文件系统所在硬盘a+共享磁盘b 
qemu-system-x86_64 -kernel bzImage -hda rootfs.ext4 -hdb shadisk.img -append "root=/dev/sda console=ttyS0" -nographic 


//输入配置buildroot时,设置的密码
Welcome to Buildroot
buildroot login: root
Password: 


//第一块磁盘已经挂载了,还需要把第二块挂载到mnt目录
cd /
mount /dev/sdb /mnt
# find ./ -name "mycat"
./mnt/mycat
./root/mycat


//两个硬盘里的app都加载到虚拟机里了,随便运行哪一个
# /mnt/mycat
123456789
123456789
^C
# /root/mycat
1234567
1234567
^C

//!!!!!!!!!!!!!!!!来试试共享硬盘吧
//把apue源码里的另一个app,echoall放到共享硬盘里,看看虚拟机里能不能用
vscode点击+,新开一个终端
sudo mount -t ext4 -o loop shadisk.img ./shadisk
cp /root/study/apue.3e/proc/echoall shadisk
umount shadisk



//!!!!!!!!!!!!!!此时qemu还在运行中,我们看看虚拟机里能否运行echoall
# ls /mnt
lost+found  mycat
//此时还没有文件,需要重新挂载
umount /mnt
mount /dev/sdb /mnt
# ls /mnt
echoall     lost+found  mycat
# /mnt/echoall 9527 9528
argv[0]: /mnt/echoall
argv[1]: 9527
argv[2]: 9528


//!!!!!!!!!!!!!!测试一下gdb能不能用
//用busybox配置gdb就很麻烦(这是不用busybox生成根文件系统的原因)
//单击,是可以直接在vscode里跳转代码的!!!!!!!!!
# gdb -q --args /mnt/echoall 9527 9528
Reading symbols from /mnt/echoall...
(gdb) b main
Breakpoint 1 at 0x18a3: file /root/study/apue.3e/proc/echoall.c, line 10.
(gdb) r
Starting program: /mnt/echoall 9527 9528
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 1, main (argc=3, argv=0x7ffd13cb46f8) at /root/study/apue.3e/proc/echoall.c:10
10      /root/study/apue.3e/proc/echoall.c: No such file or directory.
(gdb) n
11      in /root/study/apue.3e/proc/echoall.c
(gdb) n
argv[0]: /mnt/echoall
10      in /root/study/apue.3e/proc/echoall.c
(gdb) n
11      in /root/study/apue.3e/proc/echoall.c
(gdb) n
argv[1]: 9527
10      in /root/study/apue.3e/proc/echoall.c
(gdb) n
11      in /root/study/apue.3e/proc/echoall.c
(gdb) n
argv[2]: 9528
10      in /root/study/apue.3e/proc/echoall.c
(gdb) n
16      in /root/study/apue.3e/proc/echoall.c
(gdb) n
[Inferior 1 (process 1114) exited with code 0376]
(gdb) n
The program is not being run.
(gdb) q



---------------------------------
在虚拟机运行,退出的方式
ctrl+a
c
q
# QEMU 6.2.0 monitor - type 'help' for more information
(qemu) q



# poweroff
# Stopping network: OK
Stopping klogd: OK
Stopping syslogd: OK
Seeding 256 bits without crediting
Saving 256 bits of creditable seed for next boot
umount: devtmpfs busy - remounted read-only
[   20.279897] EXT4-fs (sda): re-mounted. Opts: (null)
The system is going down NOW!
Sent SIGTERM to all processes
Sent SIGKILL to all processes
Requesting system poweroff
[   23.297610] sd 0:0:1:0: [sdb] Synchronizing SCSI cache
[   23.298226] sd 0:0:1:0: [sdb] Stopping disk
[   23.298733] sd 0:0:0:0: [sda] Synchronizing SCSI cache
[   23.299813] sd 0:0:0:0: [sda] Stopping disk
[   23.440521] ACPI: Preparing to enter system sleep state S5
[   23.441256] reboot: Power down
root@L-R91239GL-1157:~/work/linux# 

修改后的echoall,仅打印命令行参数,并返回254

proc/echoall.c

#include "apue.h"

int
main(int argc, char *argv[])
{
	int			i;
	char		**ptr;
	extern char	**environ;

	for (i = 0; i < argc; i++)		/* echo all command-line args */
		printf("argv[%d]: %s\n", i, argv[i]);

	//for (ptr = environ; *ptr != 0; ptr++)	/* and all env strings */
		//printf("%s\n", *ptr);

	exit(254);
}

用gdb调试内核

//qemu             内核镜像         根文件系统所在硬盘a+共享磁盘b 
qemu-system-x86_64 -kernel bzImage -hda rootfs.ext4 -hdb shadisk.img -append "root=/dev/sda console=ttyS0" -s -S -smp 1 -nographic

会有两个告警不用管
右下角的提示1234可以直接叉掉,这是说当前运行的程序在等待gdb连接呢
此时也可以通过ctrl+a ,c ,q,退出



vscode点击+号,新建一个终端,在这个终端里我们用gdb连接在等待的qemu
gdb -q vmlinux
target remote localhost:1234
c
之后会看到在等待的qemu正常运行,打印启动log了



//登录之后,用gdb单步mycat
gdb -q /root/mycat
b main
r
n
n
11行代码等待从命令行输入:
输入123456789,但不要按enter
我们期待,按下enter后,会触发内核的六次断点,三个读,三个写


//切换到gdb所在终端
ctrl+c,停止另一个终端的虚拟机的运行
//在内核对应的位置打断点,问题是在哪里呢?
//参考《LINUX内核设计与实现》,59页,5.3.1系统调用号
//我们知道应用层的系统调用,对应内核层的系统调用函数数组
p *sys_call_table@60
b SyS_read
b SyS_write
c

//切换到qemu所在终端,按下刚才没按的enter键,观察断点触发的情况,看看是不是六次
//read的情况有些复杂,大家先不加read的断点吧,后面大家可以自己研究一下
Breakpoint 1, SyS_write (fd=1, buf=140731601976516, count=4) at ./include/linux/file.h:69
69              return __to_fd(__fdget_pos(fd));
(gdb) display (char*)buf
1: (char*)buf = 0x7ffea124fcc4 "1234"
(gdb) c
Continuing.

Breakpoint 1, SyS_write (fd=1, buf=140731601976516, count=4) at ./include/linux/file.h:69
69              return __to_fd(__fdget_pos(fd));
1: (char*)buf = 0x7ffea124fcc4 "5678"
(gdb) c
Continuing.

Breakpoint 1, SyS_write (fd=1, buf=140731601976516, count=2) at ./include/linux/file.h:69
69              return __to_fd(__fdget_pos(fd));
1: (char*)buf = 0x7ffea124fcc4 "9\n78"
(gdb) c
Continuing.

用vscode插件调试内核

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "kernel-debug",
            "type": "cppdbg",
            "request": "launch",
            "miDebuggerServerAddress": "127.0.0.1:1234",
            "program": "${workspaceFolder}/vmlinux",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "logging": {
                "engineLogging": false
            },
            "MIMode": "gdb",
        }
    ]
}

安装gdb debug插件
点击三角形(运行和调试)->创建launche.json文件->选择gdb debug

修改launch.json文件,注意
miDebuggerServerAddress:本质上跟用gdb直接调试是一样的
program:vmlinux路径
name:会出现运行与调试最上面的名字那里



先用调试的方式启动qemu,此时qemu等待连接
qemu-system-x86_64 -kernel bzImage -hda rootfs.ext4 -hdb shadisk.img -append "root=/dev/sda console=ttyS0" -s -S -smp 1 -nographic


点击运行与调试,左上角的三角形启动调试,可以看到正常运行
点击右边的暂停按钮后,有两种方式加断点
一是在调试控制台输入命令:
-exec b SyS_write

二是直接在文件里加红点


我们先删除所有断点
运行到123456789,跟刚才一样,在按下enter键之前,再打断点
-exec del breakpoints
-exec info breakpoints
点击右上角的图标,继续运行


gdb -q /root/mycat
r
123456789  (注意,别按enter)


暂停,插入断点,观察变量
控制台
-exec b SyS_write
监视
(char*)buf
继续运行

现在按下enter键,观察监视的变量

完结,撒花!!!!!!!!

希望这篇博客帮助大家,学习内核容易一点。

后面的章节是谈为什么不用busybox制作根文件系统,总结起来两个原因:

1加入gdb麻烦,不像buildroot,可以自动从网上下载各种第三方代码,进行编译

2加入新的命令麻烦,busybox里的命令本质都是软链接,我们编译出来的程序不能直接复制到根文件系统里

还会再写一篇博客:

怎么在内核里编译模块

然后用qemu从应用调试到驱动

期望 有大佬能更新的博客:

《LINUX设备驱动程序》里,早就说过,vmlinux这些只能调试简单的设备

但是qemu可以模拟真实的设备

那有没有办法,通过qemu模拟,然后调试真实的设备驱动呢?

为什么不用busybox制作根文件系统

编译busybox参考这篇文章:

QEMU调试Linux内核环境搭建-腾讯云开发者社区-腾讯云 (tencent.com)

我本来也是用busybox的,但是发现增加gdb麻烦,增加新的app(mycat、echoall)也麻烦

所以换成buildroot了

增加新的命令参考这篇文章:

busybox添加自定义applet_busybox并启用feature_prefer_applets-CSDN博客

1cd busybox-1.36.1

2 mkdir chuan

   在这个目录里放所有自己写的命令

3 touch echoall.c

#include "busybox.h"
 
int echoall_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int echoall_main(int argc, char *argv[])
{
    printf("Hello chuan!!!\n");
    for (int i = 0; i < argc; i++)		
        printf("argv[%d]: %s\n", i, argv[i]);
    return 0;
}

4 touch Config.src (注意,最后一行,必须再按一下enter,不然后面报错)

menu "chuan"
 
config ECHOALL 
bool "support echoall" 
default y 
help 
  echoall is a new test command.

INSERT
 
endmenu

5 touch Kbuild.src

lib-y:=
 
lib-$(CONFIG_ECHOALL) += echoall.o
 
INSERT

6 busybox-1.36.1/Makefile

libs-y		:= \
        chuan/ \
		archival/ \

7 busybox-1.36.1/Config.in

source sysklogd/Config.in
source chuan/Config.in

8 busybox-1.36.1/include/usage.h


#define busybox_notes_usage \
       "Hello world!\n"

#define echoall_trivial_usage "echoall_trivial_usage"
#define echoall_full_usage "echoall_full_usage"


#endif

9 busybox-1.36.1/include/applets.h

#if ENABLE_INSTALL_NO_USR
# define BB_DIR_USR_BIN BB_DIR_BIN
# define BB_DIR_USR_SBIN BB_DIR_SBIN
#endif

IF_ECHOALL(APPLET(echoall, BB_DIR_BIN, BB_SUID_DROP))

10 因为我前面的busybox-1.36.1/chuan/Config.src,是default y

     所以make menuconfig时不需要选也是会编译的

buildroor配置更新时需要再clean一下:

buildroot-2.017.08里面没有C++编译器 / 全志 SOC / WhyCan Forum(哇酷开发者社区)

busybox好像不需要

如果发现配置没生效,看看是不是类似的问题,我编译busybox时,好像不需要clean

----------------------------------------------------------------------------

1 cd /root/qemu/busybox-1.36.1

2 sudo mount -t ext4 -o loop rootfs.img ./fs

3 cd fs/bin

4 ls -la

感觉busybox,是把所有的代码,编译成一个程序busybox,不同软链接代表不同的busybox运行参数?

这里echoall仅仅是一个软链接,所以没办法像busybox一样,把我们生成的程序直接复制到根文件系统了。

但是这个软链接也是可以在主机上直接运行的(再次证明,虚拟机时x86的,真实系统和虚拟系统之间不需要交叉编译)

综上:

1 busybox编译速度快,就5分钟

2 但是busybox加一个新的app比较麻烦,必须在busybox里参与编译

3 加gdb就更麻烦了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值