认证
学号末尾三位:474
本实验来源于—— https://github.com/mengning/linuxkernel/
实验要求
1.编译内核5.0
2.qemu -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img
3.选择系统调用号后两位与您的学号后两位相同的系统调用进行跟踪分析https://github.com/mengning/menu
4.给出相关关键源代码及实验截图,撰写一篇博客(署真实姓名或学号最后3位编号),并在博客文章中注明“原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ ”,博客内容的具体 如下:
- 题目自拟,内容围绕系统调用进行;
- 博客中需要使用实验截图
- 博客内容中需要仔细分析系统调用、保护现场与恢复现场、系统调用号及参数传递过程
- 总结部分需要阐明自己对系统调用工作机制的理解。
实验内容
实验操作
编译内核
1.下载Linux5.0.2源码 https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.0.2.tar.xz
2.在当前用户目录下创建LinuxKernel文件夹
3. 将Linux5.0.2源码拷贝到LinuxKernel文件夹下并解压,之后所有的文件操作都在该文件夹下执行
4. 配置并编译Linux内核,使其具备调试功能
命令如下:
cd linux-5.0.2 #进入Linuxkernel目录
make menuconfig #配置内核
->kernel hacking
->Compile-time checks and compiler options
[*]compile the kernel with debug info
make #编译
5.制作根文件系统
- cd ~/LinuxKernel/
- mkdir rootfs
- git clone https://gitbug.com/mengning/menu.git
- cd menu
- 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
6.启动MenuOS
qemu-system-i386 -kernel linux-5.0.2arch/x86/boot/bzImage -initrd rootfs.img
7.跟踪调试内核启动(gdb调试加参数-S -s)
qemu-system-i386 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -S -s -append nokaslr
8.再另建一窗口,启动gdb
cd ~
gdb
(gdb) file linux-5.0.2/vmlinux
(gdb) target remote:1234
注*
实验中可能遇到很多缺失组件和工具的情况,通过 sudo apt-get install *** 安装即可。
跟踪系统调用
1.增加系统调用
根据学号后两位74,去system_call_table(位于/usr/include/asm/unistd_32.h)中查找
#define __NR_sethostname 74
2.在test.c中增加SetHostName函数,再重新编译rootfs.img
SetHostName函数
int SetHostName(int argc,char *argv[])
{
char oldname[NAME_MAX];
char name[NAME_MAX];
char * newname = "ZGJ-PC";
gethostname(oldname, NAME_MAX); #获取未进行设置时的主机名并打印
printf("show oldname = %s\n",oldname);
int ret;
asm volatile(
"movl %1,%%ebx\n\t" #将设置的名称和长度参数存入寄存器
"movl %2,%%ecx\n\t"
"movl $0x4a,%%eax\n\t" #将系统调用号74放入eax中
"int $0x80\n\t" #执行$0x80来执行系统调用
"movl %%eax,%0\n\t"
: "=m" (ret)
: "b"(newname),"n"(NAME_MAX)
);
if(ret == -1)
perror("sethostname");
gethostname(name, NAME_MAX); #将新设置的主机名打印出来
printf("show newname = %s\n",name);
return 0;
}
重新编译rootfs.img
- 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.跟踪调用
打断点到sys_sethostname
实验分析
- 操作系统提供API和系统调用的关系
- 系统调用的库函数就是操作系统提供的API。系统调用是通过软中断向内核发出了中断请求,int指令的执行就会出发一个中断请求,libc函数库定义的一些API内部使用了系统调用的封装例程,其主要目的是发布系统调用。
- libc中一些API会涉及与内核空间进行交互,这些API内部会封装系统调用,一个API可能对应一到多个系统调用。大部系统调用的封装例程返回一个证书,返回值-1表示请求失败,libc中进一步定义的errno变量包含特定的出错码。
- 与内核空间交互的API,其内部封装了系统调用,会触发int $0x80中断,对应system_call内核代码的起点,即中断向量$0x80对应的中断服务程序入口,内部会有对应的系统调用处理函数。
- 触发系统调用及参数传递方式
- 每个系统调用对应着唯一一个系统调用号,在陷入内核态时,API函数会使用EAX寄存器传递一个名为系统调用号的参数。除了系统调用号,系统调用也要传递一些其他参数,一般这些参数个数不会超过6个,分别按顺序赋值给EBX,ECS,EDX,ESI,EDI。
实验总结
本实验让我了解了系统调用的工作机制,作为程序员的我们通常只跟用户空间实现的API打交道,内核只跟系统调用打交道,内核不关心应用程序是如何系统调用的。内核中大多数系统函数调用名称以sys_为前缀,每个系统调用有唯一的系统调用号。应用程序通过软中断来通知内核,进入系统调用入口system_call,从而执行对应的系统调用函数。