Linux内核分析之跟踪分析Linux内核的启动过程

实验过程

转载请注明出处 + https://github.com/mengning/linuxkernel/

一、下载linux-5.0内核并编译

cd linux_os/linux-5.0
make i386_defconfig
make -j8

编译完成会在arch/x86/boot/下产生bzImage

二、制作根文件系统

1.从孟宁老师的github上下载menu
Linux内核分析之跟踪分析Linux内核的启动过程
2.建立脚本文件,用于编译menu

[email protected]:~/hellotest/menu# gedit mygcc.sh
[email protected]:~/hellotest/menu# chmod 777 mygcc.sh 

在mygcc.sh中添加:

gcc -pthread -o Myinit linktable.c menu.c test.c -m32 

3.在menu/test.c加入自己学号尾号有两位-92号系统调用
实现向文件写入数据方法:

int write_in_file(void)
{
	FILE *fp;
	if ((fp = fopen("test.txt", "w")) == NULL);
	{
		printf("File start write\n");
	}
	int i;
	
	int arr[7];
	printf("Please enter an array (less than 7 elements):\n");
	for (i = 0; i < 7; i++)
	{
		scanf("%d", &arr[i]);
	}	
	fp = fopen("test.txt", "w");
	for ( i = 0; i < 7; i++)
	{
		fprintf(fp, "%d ", arr[i]);
	}
	fclose(fp);
	return 0;
}

(1)C语言实现系统调用:

int Truncate_92(void)
{
	int length;
     write_in_file();
	printf("Please modify the file length:\n");
	scanf("%d", &length);
	truncate("test.txt", length*2);
	printf("After sorting the files, please look at the file\n");
}

(2)C语言嵌入汇编实现系统调用:

int Truncate_92_asm(void)
{
	int length;
	int ret;
	write_in_file();
	printf("Please modify the file length:\n");
	scanf("%d", &length);
	length*=2;
	asm volatile(
		"movl %2,%%ecx\n\t"
		"movl %1,%%ebx\n\t"
		"movl $0x5c,%%eax\n\t"
		"int $0x80"
		:"=a"(ret)
		:"b"("test.txt"),"c"(length)
	);
	if(ret==0){
		printf("Resize successfully\n");
		printf("After sorting the files, please look at the file\n");
	}
	else{
		printf("Resize fail\n");
	}
	return 0;
}

由于truncate函数原型是int truncate(const char *path, off_t length);包含两个参数,它的功能是将参数path指定的文件大小改为参数length指定的大小。 如果原来的文件大小比参数length大,则超过的部分会被删除。执行成功则返回0, 失败返回-1。
现在有两个参数path和length,所以需要考虑该往哪个寄存器里传值,参数按顺序依次赋给ebx、ecx、edx、esi、edi、ebp,所以path是第一个传给ebx寄存器的参数,length是第二个传给ecx寄存器的参数。把系统调用号92(0X5C)存入eax寄存器,通过执行int $0X80来执行系统调用陷入内核态。system_call根据传入的系统调用号在系统调用列表中查找到对应的系统调用内核函数,然后根据ebx,ecx寄存器中保存的参数调用内核函数sys_truncate,执行完后将执行结果存放到eax寄存器中,将eax寄存器的值传给ret。
生成Myinit文件:

[email protected]:~/hellotest/menu# ./mygcc.sh 

2.制作根文件系统
进入busybox-1.30.1目录下,对busybox进行配置编译。

make defconfig

make menuconfig

这里要修改如下配置:

将busybox settings -> build options -> build busybox as a static binary这一项选上。
make
然后准备根目录映像,并安装busybox到根目录映像中。

dd if=/dev/zero of=busyboxmyinitrd12M.img bs=4096 count=3072

mkfs.ext3 busyboxmyinitrd12M.img

mkdir rootfs

sudo mount -o loop busyboxmyinitrd12M.img rootfs

cd busybox-1.30.1

sudo make CONFIG_PREFIX=../rootfs/  install

将第一步编译好的可执行文件Myinitrw.sh放入rootfs/bin/
然后卸载umount rootfs
这样我们的根文件系统就做好了。

三、Qemu启动内核

1.新建启动脚本

[email protected]:~/linux_OS/linux-5.0# gedit start.sh
[email protected]:~/linux_OS/linux-5.0# chmod 777 start.sh

在脚本中添加一下代码:

qemu -kernel arch/x86/boot/bzImage -initrd ~/hellotest/busyboxmyinitrd12M.img -append "root=/dev/ram init=/bin/ash"

2.执行脚本
Linux内核分析之跟踪分析Linux内核的启动过程
note:
由于需要对文件进行读写,需要修改文件系统权限

mount -o remount rw /

Linux内核分析之跟踪分析Linux内核的启动过程
Linux内核分析之跟踪分析Linux内核的启动过程
3.从MenuOS中选择Truncate_92,测试C语言调用系统函数
Linux内核分析之跟踪分析Linux内核的启动过程
4.从MenuOS中选择Truncate_92_asm,测试汇编代码调用系统函数
Linux内核分析之跟踪分析Linux内核的启动过程

四、使用gdb跟踪系统调用内核函数sys_truncate

1.新建脚本文件start_gdb.sh
里面内容是:

qemu -kernel arch/x86/boot/bzImage -initrd ~/hellotest/busyboxmyinitrd12M.img -append "root=/dev/ram init=/bin/ash" -s -S

修改start_gdb.sh文件权限

[email protected]:~/linux_OS/linux-5.0# chmod 777 start_gdb.sh

执行脚本文件:

[email protected]:~/linux_OS/linux-5.0# ./start_gdb.sh

此时qemu界面会卡住:
Linux内核分析之跟踪分析Linux内核的启动过程
再开一个终端,启动gdb,然后连接到target remote 1234.
Linux内核分析之跟踪分析Linux内核的启动过程
2.在sys_truncate处设断点
Linux内核分析之跟踪分析Linux内核的启动过程
如图所示:
当调用系统函数时,触发断点,此时可以用list(l)来查看断点处代码
Linux内核分析之跟踪分析Linux内核的启动过程
Linux内核分析之跟踪分析Linux内核的启动过程
接下来可以用命令s单步调试,可以看到path是“test.txt”,文件重新设置的大小为12
Linux内核分析之跟踪分析Linux内核的启动过程
执行到kmem_cache_alloc函数之后,执行完毕,为文件重新分配了大小。
Linux内核分析之跟踪分析Linux内核的启动过程

五、system_call中断处理过程

六、总结

当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。
在Linux中是通过执行int $0x80来执行系统调用的, 这条汇编指令产生向量为128的编程异常。
整个系统调用过程如下:

1.用户应用程序填充系统调用的寄存器
2.进程从用户态切换到内核态, 并执行系统调用 entry_SYSCALL_32
3.entry_SYSCALL_32切换到内核栈, 保存现场(通用寄存器, 旧的栈. flags)
4.entry_SYSCALL_32 调用 sys_call_table 中的函数, 如果正确调用对应的函数, 如果错误退出.
5.系统调用完成, 恢复现场(通用寄存器, 旧的栈. flags).