实验五 扒开系统调用的三层皮下

王康 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

1给MenuOS增加time和time-asm命令

这次课程通过内核方式调试系统调用,把上周系统-调用增加到menuos中

wpsE78C.tmp

自动化编译menu生成rootfs,做了个脚本make rootfs来自动编译生成。

wpsE7AD.tmp

如何实现呢?

test.c:增加了2行MenuConfig,对应两个函数

wpsE7AE.tmp

wpsE7AF.tmp汇编触发

wpsE7BF.tmp

2使用gdb跟踪系统调用内核函数sys_time

qemenu -kernel -initrd rootfs.img -S -s后gdb,file linux-3.18.6/vmlinux加载符号表,target remote:1234连接到要调试的menuos端口,b start_kernel断点后c来调试,list可以查看。

wpsE7C0.tmp

wpsE7C1.tmp

b sys_time断点c,执行time会发现卡到一半

wpsE7C2.tmp

这里使用宏定义实现的

wpsE7C3.tmp

list可以看到代码

wpsE7D4.tmp

wpsE7D5.tmp

单步执行一直到获得的时间数值return i;

wpsE7D6.tmp

wpsE7D7.tmp

wpsE7D8.tmp

wpsE7E8.tmp

gdb无法处理汇编代码,所以之后设置b system_call,发现其处于entry_32.S.

wpsE7F9.tmp

wpsE7FA.tmp

发现其仍处于sys_time而在sys_call并不能停下,因为sys_call其并不是一个正常函数,是一段比较特殊的汇编代码,gdb不支持

wpsE7FB.tmp

sys_call位置:

wpsE7FC.tmp

发现其有声明的函数原型但并不是一个函数,只是一段汇编代码的起点

wpsE7FD.tmp

3系统调用在内核代码中的工作机制和初始化

一,

从int 0x80触发一个系统调用到执行系统调用处理的函数sys_time,到后边返回用户态整体过程理解(不太好调试)

1,什么时候把0x80和system_call绑定起来的?什么时候初始化中断向量?

2,system_call处理过程以及哪里调用了sys_xyz(xyz和sys_xyz是通过系统调用号匹配起来的),xyz和system_call是通过中断向量匹配起来的。

3 ret_from_sys_call的单步调试过程

wpsE7FE.tmp

初始化:

wpsE7FF.tmp

可以看到代码有:set_system_trap_gate包含系统调用中断向量和system_call代码入口。

之后系统一旦出现int 0x80就会立即调用system_call

二,

系统调用也是一种中断,所以也存在着保存和恢复现场的问题,下图可看到SAVE_ALL;

call sys_call_table %eax是系统调用表,在menuos中即为sys_time;

之后syscall_after_call:先保存返回值,退出之前有jne syscall_exit_work如果没有这个就直接restore_all返回用户态

wpsE800.tmp

wpsE801.tmp

进入复杂的syscall_exit_work,里边会有进程调度时机。

wpsE812.tmp

简化伪代码:

wpsE813.tmp

exit时会检查当前任务current->work需不需要syscall_exit_work去处理,不需要直接ireturn

wpsE814.tmp

当前进程有一些信号处理或需要调度就需要syscall_exit_work去处理了:

wpsE815.tmp

流程是跳转到work_pending,里边有jz work_notifysing处理信号,

和work_resched需要重新调度,就call schedule,调用完成后再跳转到restore_all把他返回系统调用;

三,浏览system_call到iret之间的代码

system_call:

wpsE816.tmp

syscall_exit_work:

wpsE817.tmp

work_pending:

wpsE818.tmp

wpsE819.tmp

wpsE81A.tmp

调用schedule,调用完之后可以跳转restore_all,把后面恢复现场工作结束掉。

wpsE82A.tmp

4,实验:分析system_call中断处理过程

1,修改test.c文件,加入menuconfig自定义的函数

wpsE82B.tmp

wpsE82C.tmp

2,make编译运行发现出错

wpsE82D.tmp

3,发现是因为调用系统sdout输出后,缓冲池flush没有内容,这时候mov eax会报错,修改后可以运行

wpsE82E.tmp

wpsE82F.tmp

4,qemu打断点运行

wpsE830.tmp

5,在自定义sys_write处打断点,list可以看到其具体调用函数。单步执行n可以看到具体执行流程

wpsE841.tmp

wpsE842.tmp

wpsE843.tmp

6,因为我调用的是sys_write所以输出menuos的log以及help所有命令也是一下一下的

wpsE844.tmp

7,调用printstring命令就能看到具体的系统调用断点了:

wpsE845.tmpwpsE846.tmpwpsE847.tmp

5,总结

1. 系统调用”是操作系统提供给用户程序进行调用的一些服务。这些服务是系统预先提供的函数,在这一点上系统调用与普通的用户程序是没有区别的。而区别则在于“系统调用”是由操作系统提供给用户的,这些服务更接近底层或者要求的安全性更高,因此由操作系统来统一实现和管理。

2. 处理器在eax寄存器中拿到系统调用号之后,会到系统调用表中找到该系统调用所对应的入口函数地址,然后执行该函数。

3. 函数的入口地址在在syscall.c(entry_32s)中。

4. 中断处理中读取中断号及参数,然后找到中断服务例程并执行,退出中断后进行堆栈切换,返回用户态,继续执行用户程序。

5,扩展:

系统调用system_call的处理过程

syscall_call 函数到系统调用服务例程通过系统调用号联系起来:在上面执行软中断 0x80 时,系统调用号会被放入eax寄存器(参数的传递),system_call 函数读取eax寄存器获取参数(当前系统调用的调用号),将其乘以4生成偏移地址。然后以中断向量表(sys_call_table)为基址,以系统调用号所确定的为偏移地址相加得到最后的物理地址:基址+偏移地址 => 系统调用服务例程的地址。其中 sys_call_table 基址在文件 arch/x86/kernel/syscall_table_32.S 中定义,同时表中每一项例程的地址占用4个字节,所以上面乘以4。

参数保存方式:由于系统调用例程在定义时时用 asmlinkage 标记,所以编译器仅从堆栈中获取该函数的参数。在进入system_call函数前,用户应用会把参数存放到寄存器中,system_call 函数执行时会首先把这些寄存器压入堆栈。这样对系统调用服务例程可以直接从堆栈照片能够获取参数。

从system_call开始到iret结束之间的过程

从整体过程来看,系统通过 int 0x80 从用户态进入内核态。在这个过程中系统先保存了中断环境,然后执行系统调用函数。system_call() 函数通过系统调用号查找系统调用表 sys_cal_table 来查找到具体的系统调用服务进程。在执行完系统调用后在执行 iret 之前,内核做了一系列检查,用于检查是否有新的中断产生。如果没有新的中断,则通过已保存的系统中断环境返回用户态。这样就完成了一个系统调用过程。

系统调用通过 INT 0x80 进入内核,跳转到 system_call() 函数,然后执行相应服务进程。因为代表了用户进程,所以这个过程并不属于中断上下文,而是属于进程上下文!这点一定要分清楚。

wpsE848.tmp

  无论是中断返回(ret_from_intr) ,还是系统调用返回,都使用了 work_pending 和resume_userspace。对于宏SAVE_ALL来说,这条语句会把将寄存器的值压入堆栈当中,压入堆栈的顺序对应struct pt_regs出栈时这些值传递到struct pt_regs的成员,实现从汇编代码向C程序传递参数。struct pt_regs可以在arch/x86/include/asm/ptrace.h中查看。用户态到内核态需要int 0x80进行中断,只有生成了中断向量后才可以切换状态。中断处理让CPU停止当前工作转为执行系统内核中预设的一些任务,因此必须要对当前CPU执行的任务进行执行现场的保护工作,并对一些其他工作进行检查,完成调用后,再进行检查,才能执行iret返回。系统内部调用涉及CPU架构等内容,不同的CPU对于系统调用的汇编具体代码是不一样的。

.macro INTERRUPT_RETURN  ; 中断返回

    iret

.endm

.macro SAVE_ALL          ; 保护现场

    ...

.macro RESTORE_INT_REGS

    ...

.endm

ENTRY(system_call)

    SAVE_ALL

syscall_call:

    call *sys_call_table(,%eax,4)

    movl %eax, PT_EAX(%esp)  ; store the return value

syscall exit:

    testl $_TIF_ALLWORK_MASK, %ecx # current->work

    jne syscall_exit_work

restore_all:

    RESTORE_INT_REGS

irq_return:

    INTERRUPT_RETURN      

ENDPROC(system_call)

syscall_exit_work:

    testl  $_TIF_WORK_SYSCALL_EXIT, %ecx

    jz work_pending

END(syscall_exit_work)

work_pending:

    testb $_TIF_NEED_RESCHED, %cl

    jz work_notifysig

work_resched:

    call schedule

    jz restore_all

work_notifysig:

    ...                  ; deal with pending signals

END(work_pending)

posted on 2017-03-20 16:24  wk2016just  阅读(255)  评论(0编辑  收藏  举报