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

学号131原创作品转载请注明出处
本实验来源 https://github.com/mengning/linuxkernel/

实验环境

Ubuntu 18.04 虚拟机

VMware Workstation Pro 15.0.2 for Windows

实验要求

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

  • 编译内核5.0
  • qemu -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img
  • 选择系统调用号后两位与您的学号后两位相同的系统调用进行跟踪分析
  • https://github.com/mengning/menu
  • 给出相关关键源代码及实验截图,撰写一篇博客(署真实姓名或学号最后3位编号),并在博客文章中注明“原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ ”,博客内容的具体要求如下:
  • 题目自拟,内容围绕系统调用进行;
  • 博客中需要使用实验截图
  • 博客内容中需要仔细分析系统调用、保护现场与恢复现场、系统调用号及参数传递过程
  • 总结部分需要阐明自己对系统调用工作机制的理解。

实验步骤

编译内核5.0

  1. 下载5.0.1版本的linux内核,其下载地址为
    https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.0.1.tar.xz
     并进行解压tar -xvf linux-5.0.1.tar
  2. cd linux-5.0.1
  3. make menuconfig

为了跟踪内核代码的执行过程,需要先在运行的内核中加入调试信息,这样我们就能用GDB在我们感兴趣的代码行上设置断点,观察程序的执行过程了。添加方法是在linux源代码目录下用make menuconfig命令选中kernel hacking项目的compile the kernel with debug info项

     4. make(编译时间较久请耐心等待,推荐使用命令make -j8加速编译

可能出现的问题

1.

Make menuconfig时出现提示缺少ncurses library,使用命令: 

sudo apt-get install libncurses5-dev libncursesw5-dev

进行相应的安装

2.

提示flex与Bison未找到,利用命令

sudo apt-get install flex bison

进行安装

缺少的这两个文件分别为:flex(词法分析器)与bison(语法分析器)

3.

此状况出现在虚拟机显示的页面太小上,可以将虚拟机页面扩大

根文件系统的创建

制作img文件

cd ~/Documents/linux-5.0.1
mkdir rootfs
git clone https://github.com/mengning/menu.git  
cd menu
gcc -o init linktable.c menu.c test.c -m32 -static –lpthread
cd ../rootfs
cp ../menu/init ./
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img

启动Menu os

cd ~/Documents/linux-5.0.1
qemu-system-i386 -kernel arch/x86/boot/bzImage -initrd rootfs.img

此时出现问题

网上提的方法均使用,检查已经打开了主板的CPU虚拟化支持开关,虚拟机中ubuntu已设置为64位。

后经老师提醒是qemu是32位的,无法编译64位的内核。于是将代码进行了修改

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

该代码成功执行

跟踪调试内核启动

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

如果出现了start_kernel断点断不住的情况,并且确认在kernel-hack选项勾了[*] compile the kernel with debug info后,可以考虑用hbreak (GDB的硬件断点) 替代普通断点,在这之后就可正常使用break。

这个错误的产生可能是gdb关于虚拟化的一个bug。

打开一个新终端


gdb 
(gdb)file linux-5.0.1/vmlinux # 在gdb界面中targe remote之前加载符号表 
(gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行,若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项 
(gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后

到这里linux5.0内核就已经编译成功!

选择系统调用号后两位与您的学号后两位相同的系统调用进行跟踪分析

在文件/usr/include/asm/unistd_32.h 中查询得知学号131对应的调用为#define __NR_stty 31。Stty命令用于检查和修改当前注册的终端的通信参数。

 

但是经过google搜索后,唯一能搜索找到的stty函数的定义如下

而sgttyb结构体的定义则更为复杂。于是决定选取序号为33的中断函数access进行相应的分析。

调用access函数的源文件代码如下:

#include"stdio.h"
#include"unistd.h"
#include "string.h"
#define fileNAME1 "test"
#define fileNAME2 "./liang"
int main(void)
{
    char name[BUFSIZ];//文件名字
    int flag = 1;//退出标志,0 exit
    printf("\t\t\t程序开始\n");
    printf("请输入要检查的文件(可包含路径,EOF退出):");
    scanf("%s",name);
    if( strcmp(name,"EOF") == 0 ){
        flag = 0;
    }
    while(flag){
        if(access(name,F_OK)==0){
            printf("文件存在\n");
            if(access(name,R_OK|W_OK)==0){
                printf("文件可读可写\n");
            }else
                printf("文件不可读或不可写\n");
            if(access(name,X_OK)==0){
            printf("文件可执行\n");
            }else
                printf("文件不可执行\n");
        }else
            printf("文件不存\n");
        printf("\n请输入要检查的文件(可包含路径,EOF退出):");
        scanf("%s",name);
        if( strcmp(name,"EOF") == 0 ){
            flag = 0;
        }
    }//while
    printf("\t\t\t程序结束\n");
    return 0;
}

利用代码

gcc -g test.c -o test -m32

完成汇编操作,接下来通过gdb程序完成对于代码的分析过程。

gdb -q
file test
list

1.在access处设置断点,并通过命令r(run的简写)运行至断点处

2.通过命令disass观察汇编代码运行至何处(现场的保护与系统的调用

 

push %ebx

首先是利用push %ebx的操作,将现场进行相应的保护

call *%gs:0x10

接下来观察到这样一行代码系统调用从这里进入,会调用到int80,而由于是动态链接,于是采用"call *%gs:0x10"指令

3.运用 i r命令观察此时寄存器的值

这里详细介绍下系统调用中寄存器的作用

系统调用号可以在文件unistd_32.h中查询得到

/usr/include/i386-linux-gnu/asm/unistd_32.h

如:

#define __NR_getuid              24

#define __NR_getuid32           199

参数:

EAX指定要调用的函数(系统调用号)

EBX传递函数的第一个参数

ECX传递函数的第二个参数

EDX传递函数的第三个参数

ESI

EDI

EBP

最后调用后的返回值EAX

4.通过ni命令(汇编级别的指令,用于汇编中的单步运行操作),运行至运行至call指令处

观察此时的寄存器的值,发现eax的值变为了33,正是我们所使用的中断函数access

5.继续ni后,观察寄存器的值,发生了变化,原代码中的判断为if(access(name,F_OK)==0),而eax的值和原函数的返回值是相同的,证明了前文的说法。

6.现场的恢复

不断运行ni指令后发现,最终回到了系统调用的main函数之中。

 

最后整个系统的中断调用过程可以用下面这个流程图来表示。

需要注意的是,首先确定调用哪个中断函数(即调用中断号)与调用完中断函数后返回的值均是由eax寄存器传递的,其次系统调用可能需要参数,但是不能通过像用户态进程函数中将参数压栈的方式传递,因为用户态和内核态有不同的堆栈,必须通过寄存器的方式传递参数。

实验总结

  1. 系统中断的调用有三层分别是API、system_call、system_service。
  2. 系统调用是一个用户态->内核态->用户态的过程
  3.  Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。用户可以通过系统调用命令在自己的应用程序中调用它们。从某种角度来看,系统调用和普通的函数调用非常相似。区别仅仅在于,系统调用由操作系统核心提供,运行于核心态;而普通的函数调用由函数库或用户自己提供,运行于用户态。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值