实验目的:
本次的实验主要构建MenuOS,在其中构建gdb环境,并将通信实验的代码嵌入其中,在MenuOS中验证通信实验结果。
实验原理:
一、操作系统的启动
最初计算机依靠一段二进制码来启动,并不是真正的计算机启动程序。计算机在开始加电的时候不具备工作能力,此时RAM芯片中包括的都是一些没有意义的随机数据,而没有操作系统在运行。在开始启动的时候,一个特殊的硬件电路在CPU的引脚上产生一个RESET复位信号,就是那个复位信号。在这个信号产生之后,就会把处理器的一些寄存器设置成固定的值,并执行物理地址0xfffffff0(x86架构是高地址,对于arm架构一般是低地址)那个地方的代码。硬件把这个地址映射独到ROM,ROM中所存放的程序集实际上在80x86体系中叫做基本输入/输出系统(Basic Input/Output System——BIOS)因为它包括了几个中断的低级过程。所有的操作系统在启动时,都要通过这些程序对计算机硬件设备进行初始化。一些操作系统,如微软的MS-DOS,依赖于BIOS实现大部分系统调用。
Linux中BIOS使用的是实模式,以为这是在一通电的时候就加载的所以不能用一些逻辑描述。实模式的地址用一个段地址和一个偏移量表示(seg段基址+offset)所以物理地址就是seg*16+offset。所以CPU这里面就可以直接的找到内存地址,此时还没建立一张逻辑表示和物理地址对应表。
Linux在启动阶段必须使用BIOS,此时Linux必须从磁盘或者其他的外部存储介质上获得系统的内核镜像(kernel Image)BOIS启动过程实际上分为四个过程:
1、对硬件的检测
2、硬件初始化
3、索索一个操作系统来启动
4、找到一个有效的设备。
二、内核的搭建
构建一个简单的Linux内核。大体上是分为两个步骤,首先是下载内核源代码编译内核,然后制作根文件系统。
实验工具简介:
一.QEMU介绍
QEMU是一套由法布里斯·贝拉(Fabrice Bellard)所编写的以GPL许可证分发源码的模拟处理器,在GNU/Linux平台上使用广泛。
QEMU是一个主机上的VMM(virtual machine monitor),通过动态二进制转换来模拟CPU,并提供一系列的硬件模型,使guest os认为自己和硬件直接打交道,其实是同QEMU模拟出来的硬件打交道,QEMU再将这些指令翻译给真正硬件进行操作。
二.gbbsever简介
gdb+gdbserver是一种远程调试方法,它其实主要运用于嵌入式系统的调试,由于嵌入式系统资源有限性,一般不能直接在目标系统上进行调试,通常采用gdb+gdbserver的方式进行调试。Gdbserver在目标系统中运行,gdb则在宿主机上运行。
在本次实验中,我们在虚拟机qemu上运行menuos系统,这个系统比较简单,不具备太多的资源,所以也采用gdb+gdbserver的远程调试方法。
此时要进行GDB调试,目标系统必须包括gdbserver程序,宿主机也必须安装gdb程序。很gdbServer是gdb的一个服务端,那gdb就是相对应的那个客户端了。这二者之间是TCP连接,gdb发送命令给gdbServer,gdbServer收到命令后会控制进程进行相对应的操作,然后把操作结果反馈给gdb。
实验流程:
总的实验流程有如下几步:
一、下载linux内核源代码编译内核
1.安装内核编译工具和搭建 内核编译环境
使用apt命令下载内核编译
sudo apt install build-essential flex bison libssl-dev libelf-dev libncurses-dev
make i386_defconfig #生成32位x86的配置文件
2.下载Linux内核
这里选用linux-5.0.1的内核
mkdir LinuxKernel # 创建内核目录 cd LinuxKernel # 打开内核目录 wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz #下载linux-5.0.1的内核 xz -d linux-5.0.1.tar.xz #解压命令,得到一个tar的归档包 tar -xvf linux-5.0.1.tar #解包命令,得到归档包中的文件 cd linux-5.0.1 #进入内核目录
3.编译内核配置
输入命令行:
make menuconfig
跳出图形界面,在图形界面中选中Kernel hacking ,再选中这个目录下的Compile-time checks and compiler options,然后在Compile the kernel with debug info选项前键入y
做这个配置主要是为了后面进行gdb调试
4.进行系统内核升级
鉴于其他同学的经验,没有做这一部分的操作,只贴出相关代码
sudo make install sudo update-grub reboot uname -a
二.制作根文件系统
1.安装QEMU虚拟机
我把该虚拟机也装在了上文建立的LinuxKernel目录下
cd ~/LinuxKernel/
sudo apt install qemu # 安装qemu软件包
2.下载制作好的根文件系统MenuOS,并将其进行修改和编译
git clone https://github.com/mengning/menu.git #下载MenuOS系统 mkdir rootfs #创建rootfs当作MenuOS的根目录 cd menu #打开下载好的Menu目录 sudo apt-get install libc6-dev-i386 #安装libc6-dev-i386,在64位机上编译32位系统所需要的环境
后面要修改一下,makefile文件,因为原有的根文件系统对应的Linux内核版本和我使用的版本不符,具体修改语句为:
qemu -kernel ../linux-5.0.1/arch/x86/boot/bzImage -initrd ../rootfs.img
将其中的内核版本改成你使用的版本即可
然后重新编译根文件系统,这里的makefile内嵌了qemu语句,及上述语句,编译成功后会自动打开qemu虚拟机,加载MenuOS。
make rootfs #制作根文件系统
编译成功后截图:
三. 构建调试Linux内核网络代码的环境MenuOS
1.将TCP网络通信程序的服务端程序嵌入到MenuOS 系统中
下载代码
cd ~/LinuxKernel git clone https://github.com/mengning/linuxnet.git cd linuxnet/lab2
编译
make
将程序嵌入文件系统,通过运行脚本将该程序嵌入进MenOS系统
cd ../../menu/
make rootfs
2.将TCP网络通信程序的客户端程序嵌入到MenuOS系统中
这和步骤一类似
cd ~/LinuxKernel
cd linuxnet/lab3
但在lab3中需要修改一下Makefile文件,修改方法和上文一样,改一下版本号即可
vim Makefile #修改Makefile文件
make rootfs
3.在menuOS中输入help指令,发现多出了replyhi和hello指令即代表,嵌入成功
4.运行结果
四. 进行gdb调试跟踪start_kernel内核函数
1.在qemu中运行系统并启动gdbserver
qemu -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -s -S # 关于-s和-S选项的说明: # -S freeze CPU at startup (use ’c’ to start execution) # -s shorthand for -gdb tcp::1234 若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
2.在宿主机上进行gdb调试
新建一个终端
然后输入下列指令
gdb file ~/LinuxKernel/linux-5.0.1/vmlinux #在gdb界面中targe remote之前加载符号表 break start_kernel #断点的设置可以在target remote之前,也可以在之后 target remote:1234 #建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行 c #按c 让qemu上的Linux继续运行 list #输入list指令可以查看断点处的代码
运行结果:
start_kernel函数分析
在start_kernel中进行一系列极为重要的初始化。最后执行位于init/main.c中的rest_init()来创建系统的第一个进程init。至此,内核启动完成。之后init进程会进行一些初始化和根文件系统挂载。
在start_kernel()的最后,系统会调用rest_init(),通过执行其中的kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND)来创建系统的第一个进程,即init进程。Init进程首先进行一系列的硬件初始化,然后挂载根文件系统。最后 init 进程会执行用户传递过来的“init=”启动参数执行用户指定的命令,或者执行以下几个进程之一:
execve(“/sbin/init”,argv_init,envp_init);
execve(“/etc/init”,argv_init,envp_init);
execve(“/bin/init”,argv_init,envp_init);
execve(“/bin/sh”,argv_init,envp_init)。
如果这些都无法执行(无法找到),系统的启动会宣告失败。Init启动后,会读取/etc/inittab这一配置文件,根据inittab文件内容进行一些设置或对一些指令做出解释。