举例跟踪分析Linux内核5.0系统调用处理过程-例系统调用NR_pause

学号后三位229
原创作品转载请注明出处 + https://github.com/mengning/linuxkernel

目录

实验要求

实验环境

1.下载并编译Linux5.0 

2.制作跟系统文件

4.跟踪调试内核启动

5.跟踪系统调用

5.小结


实验要求

Linux操作系统的构造
Linux内核5.0 source code
Build a Linux system, 参考MenuOS
Linux内核的启动过程, 跟踪分析Linux内核的启动过程
系统调用
glibc提供的系统调用函数API
int 0x80、系统调用号及参数传递过程
保护现场与恢复现场
系统调用内核处理函数
使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
分析system_call中断处理过程
实验:举例跟踪分析Linux内核5.0系统调用处理过程

 

实验环境

VMware Workstation Pro
Ubuntu 16.04 虚拟机

1.下载并编译Linux5.0 

cd linux_os/linux-5.0

make menuconfig,并找到kernel hacking,->Compile-time checks and compiler options,选择 [*]compile the kernel with debug info

make -j8   8线程加速编译

其中在编译过程中出现如下错误:

这是需要安装一些依赖库,如flex。然后安装flex发现出现如下错误:

因为安装的时候出现了一点问题,最后apt-get的包依赖关系被我搞乱了。解决办法:在终端输入:apt-get -f install然后在输入:sudo apt-get install flex  就可以解决了,然后继续apt-get安装需要安装的库。

下面继续make menuconfig     又出现错误

只需要sudo apt-get insatll ncurses-dev  就可以解决。接着还会出现没有bison库。还是需要 sudo apt-get install bison  继续安装想关包 。 装完之后 make menuconfig  成功。

然后需要配置一下选项。

**kernel hacking,->Compile-time checks and compiler options,选择 [*]compile the kernel with debug info**

make过程出现错误

解决办法:

sudo apt-get -f install libelf-dev

然后在虚拟机跑了一个小时后,虚拟机容量不足,之后重新分配并配置虚拟机容量,然后继续make。

至此,成功编译好Linux5.0.

2.制作跟系统文件

 

cd ..
mkdir rootfs
git clone https://github.com/mengning/menu.git
cd menu
sudo apt install gcc-multilib
gcc -pthread -o init linktable.c menu.c test.c -m32 -static
cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc | gzip -9 > ../rootfs.img

3.启动MenuOS并查看效果

qemu-system-i386 -kernel bzImage -initrd rootfs.img

其中有些问题就是检查已经打开了主板的CPU虚拟化支持开关,虚拟机中ubuntu已设置为64位。因为qemu是32位的,无法编译64位的内核。于是将代码进行了修改

qemu-system-x86_64 -kernel arch/x86/boot/bzImage -initrd rootfs.img

4.跟踪调试内核启动

qemu-system-i386 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd linux-5.0.1/rootfs.img -S -s -append nokaslr

-append nokaslr选项可以让程序停下来等待,如下效果。

gdb vmlinux
进入gdb界面后,输入target remote:1234建立与qemu调试端口的attach。

  • 调试过程可以通过gdb内置的c指令开始,启动初始状态被frozen的虚拟机内核。

5.跟踪系统调用

由于我的学号后三位为229  所以选择29号系统调用,下面在程序中找到29号系统调用

首先CPU主调放弃有两种,一种是真正的目的没有达到,比如read()或send()等,几乎所有与外设有关的系统调用都可能在执行过程中受阻而进入睡眠,让出CPU;一种就是真正目的就是进入睡眠,如nanosleep()和pause();

而pause()也是将当前进程进入睡眠,可是与时间无关,要接收到一个信号时才会被唤醒,所以常常用来协调若干进程的运行;它要在接收到特定信号SIGCHLD并且满足若干特殊条件时才会被唤醒;

在sys_pause()中,直接改变运行状态为TASK_INTERRUPTIBLE,然后执行调度schedule();只有接受到信号时才会被唤醒;

自己编写mytest代码,在其中添加一个调用pause的代码。

编辑文件并调试,查看具体执行情况并分析运行效果。

不设置断点程序在运行过程中停不下来。

 

继续进行调试可得

正如前边介绍的一样,pause暂停了进程,只能接收到信号后才能醒来,所以在程序中发现由于alarm发送的发送的SIGALRM信号给signal让程序重新执行
其中系统调用的实现,就是通过系统调用号来给这些system_call来编号,不同功能给予不同的功能号,如这次使用的就是29号 Pause功能,通过这样让系统明确知道用户想要执行的是哪个系统调用。而这之间的传递主要是通过eax寄存器来传递。除了系统调用号外,系统调用也可能需要传递参数,系统调用由于是从用户态到内核态,两者的栈堆不一样,所以他们之间只能由寄存器来进行传递值,也就是ebx、ecx、edx、esi和edi寄存器中。
 

5.小结

通过总结发现

(1)寄存器 。EIP寄存器里存储的是CPU下次要执行的指令的地址。 也就是调用完fun函数后,让CPU知道应该执行main函数中的printf("函数调用结束")语句了。

EBP寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行fun()函数调用之前,由ESP传递给EBP的。(在函数调用前你可以这么理解:ESP存储的是栈顶地址,也是栈底地址。)

ESP寄存器里存储的是在调用函数fun()之后,栈的栈顶。并且始终指向栈顶。

(2)当调用结束,EBP会把其地址再次传回给ESP。所以ESP又一次指向了函数调用结束后,栈顶的地址。

应用程序在调用系统调用时,陷入内核态执行对应的函数,此时应用程序被称作通过系统调用在内核空间运行,而内核被称作允许于进程上下文(还有个中断上下文);

每个系统调用对应一个系统调用号,系统调用号一旦分配就不能修改,系统调用号和系统调用是一一对应的,应用程序调用系统调用时,也是通过系统调用号映射到对应的系统调用执行函数;系统调用号存储在sys_call_table表中;

系统调用函数实现在内核上,应用程序是无法直接执行内核代码;内核是在受保护的地址空间上运行(真实的物理内存段),应用程序是运行于虚拟内存地址上。因此,应用程序通过软中断的方法告知内核执行对应的系统调用,并返回结果。通过触发一个异常使系统切换到内核态去处理异常处理程序(软中断处理函数),该处理程序实际就是系统调用处理程序system_call().

陷入内核态,还需要把系统调用号传给内核(后续还有系统调用的参数也需要传入内核);X86上,通过eax寄存器传入内核(各个参数也是通过各种寄存器传入内核,甚至系统调用的返回值也是通过寄存器返回到应用空间)。
 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值