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
内核源码网址

我是强迫症,纠结好久选择哪个版本的,最终选择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就更麻烦了

被折叠的 条评论
为什么被折叠?



