Windows内核学习笔记之浅析系统调用

在潘老师《Windows内核原理与实现》一书,解析了Windows应用程序发出的系统调用。图示如下。

从图可看出,系统调用所提供的服务(函数)是运行在内核中的,也就是说,在"系统空间"中。

用户空间与系统空间所在的内存区间不一样,同样,对于这两种区间,CPU的运行状态也不一样。

在用户空间中,CPU处于"用户态";在系统空间中,CPU处于"系统态"。

CPU从系统态进入用户态是容易的,因为可以执行一些系统态特有的特权指令,从而进入用户态。

而相反,用户态进入系统态则不容易,因为用户态是无法执行特权指令的。

所以,一般有三种手段,使CPU进入系统态(即转入系统空间执行):中断、异常、自陷。

关于指令Int 2e。

Intel X86处理器在IDT(中断描述服务表)查找2e项,IDTEntry包含了一个段选择符和中断历程的段内偏移,所以处理器还需要在GDT(全局描述表)中再查一次,得到段选择符指定的段的虚拟地址。段基地址加上中断例程偏移,最终得到中断例程的虚拟地址。

处理器在不同模式下使用不同的栈,用户模式代码使用用户栈,内核模式代码使用内核栈,因此当从用户模式切入到内核时,必定伴随着堆栈的变化。

Int 2e与Sysenter最主要的区别就在于堆栈切换方式的不同。但是不管是哪种方式本质上都是先获得内核栈然后保存用户模式下的ss,esp,eflags.cs.eip到内核栈,执行完系统调用后在通过保存的值进行用户空间的还原。

首先我们先分析下Int 2e/iret的栈切换。

首先如何获得内核栈的ss和esp?

处理器的任务寄存器指向当前任务环境的TSS,其中RING0的esp位于TSS+4的位置。WINDOWS每次切换线程时,总会设置好TSS中RING0的esp。

完成中断处理后通过iret从内核栈中弹出eip,cs,eflags,esp,ss,然后将控制权交给eip所指向的用户模式代码。

显然,通过int 2e/iret进行模式切换过程中涉及很多流程,开销很大。所以在XP后引入了Sysenter/Sysexit,快速系统调用。

然后看下Sysenter/Sysexit栈切换。

为了避免过多的内存访问,Sysenter增加了三个MSR寄存器来指定跳转目标和栈位置。

下面我们以XP下的Nt为例试着跟踪一下

kd> u Ntdll!NtWriteFile
ntdll!ZwWriteFile:
7c92df60 b812010000 mov eax,112h
7c92df65 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c92df6a ff12 call dword ptr [edx]
7c92df6c c22400 ret 24h
7c92df6f 90 nop

首先看到Ntdll中NtWriteFile把索引保存进eax中,然后调用了ShareUserData结构中的CallSystemStub函数指针。

我们继续跟踪下ShareUserData结构(_KUSER_SHARED_DATA)

kd> dt _KUSER_SHARED_DATA 0x7ffe0000
ntdll!_KUSER_SHARED_DATA

.

.

.

+0x300 SystemCall : 0x7c92e4f0
+0x304 SystemCallReturn : 0x7c92e4f4

.

.

这样我们可以清楚的看到0X300处的函数指针了。

kd> u 0x7c92e4f0
ntdll!KiFastSystemCall:
7c92e4f0 8bd4 mov edx,esp     ;注意此时的ESP代表的是参数块
7c92e4f2 0f34 sysenter

ntdll!KiFastSystemCallRet:
7c92e4f4 c3 ret

然后就sysenter指令进入内核的KiFastCallEntry() 函数了。

kd> u KiFastCallEntry l 50
nt!KiFastCallEntry:
.

.

.

8053e612 8bf2          mov esi,edx       ;用户空间的ESP

8053e614 8b5f0c         mov ebx,dword ptr [edi+0Ch]
8053e617 33c9                 xor ecx,ecx
8053e619 8a0c18              mov cl,byte ptr [eax+ebx]       
8053e61c 8b3f                  mov edi,dword ptr [edi]        
8053e61e 8b1c87              mov ebx,dword ptr [edi+eax*4]

8053e623 c1e902              shr ecx,2
8053e626 8bfc                  mov edi,esp
8053e628 3b35d4995580  cmp esi,dword ptr [nt!MmUserProbeAddress (805599d4)]
8053e62e 0f83a8010000  jae nt!KiSystemCallExit2+0x9f (8053e7dc)


以上是关顾从Ring3层发起的系统调用过程,有趣的是Ring0层如果调用Zw函数则会递归进入Kifastcallentry,然后调用Nt函数。

这就是内核Zw函数与Nt函数最大的区别!(Ntdll中的Zw函数与Nt函数没有区别)

所以在返回Kifastcallentry时会进行很繁琐,主要原因是通过先前模式判断发起系统调用的堆栈是用户控件或者系统空间,然后进行不同的处理。

   发起系统调用      入口内核例程    返回指令    返回内核例程

    int 2e       KiSystemService     iret                   KiSystemCallExit

      sysenter(Intel) KiFastCallEntry         SYSEXIT    KiSytemcallExit2

    syscall (AMD) KiFastCallEntry           SYSRETURN    KiSystemCallExit3

时间: 2024-10-14 17:08:36

Windows内核学习笔记之浅析系统调用的相关文章

C++windows内核编程笔记day07_day08,可视化建菜单、加速键使用、绘图等

可视化操作创建的菜单,加载到窗口. 方法1:注册时指定菜单 wce.lpszMenuName=MAKEINTRESOURCE(IDR_MENUMAIN);//数字形式的资源ID转换为字符串形式的资源 方法2: //创建窗口时加载菜单资源 HMENU menumain= LoadMenu(g_hinstance,MAKEINTRESOURCE(IDR_MENUMAIN)); menumain 传入 CreateWindowEx();//倒数第三个参数 窗口指定小图标: 1.注册时指定 wce.hI

C++windows内核编程笔记day09_day10,对话框和窗口基本控件等的使用

//设置字体颜色 SetTextColor(hdc,RGB(255,0,0)); //窗口背景 //wce.hbrBackground=(HBRUSH)(COLOR_WINDOW+1); //wce.hbrBackground=CreateSolidBrush(RGB(0,0,255)); //设置字体背景 SetBkColor(hdc,RGB(0,0,200)); //设置字体背景模式 SetBkMode(hdc,TRANSPARENT);//字体背景透明 //创建字体,成功返回字体,失败返回

CodeIgniter3 内核学习笔记四@Benchmark.php

自动启用的系统基准测试类,位于core/Benchmark.php 用于计算两个标记点之间的时间差,基准测试总是在框架被调用的那一刻开始,在输出类向浏览器发送最终的视图之前结束. 这样可以显示出整个系统执行的精确时间 Benchmark.php CodeIgniter3 内核学习笔记四@Benchmark.php

C++windows内核编程笔记day11 win32静态库和动态库的使用

windows库程序: 静态库: 源代码被链接到调用的程序或动态库,被调用时,代码最少有1份,文件后缀.LIB 动态库: 函数被程序或其他动态库调用,被调用时,代码只有1份,文件后缀.DLL 静态库(C语言): 创建时,选择文本类型文件,输入Clib.c,设置输出路径 ../lib/Clib.lib int Clib_add(int a,int b) { return a+b; } 同一上工作区,建立控制台程序(.c文件)调用静态库: #include<STDIO.H> #pragma com

C++windows内核编程笔记day13 进程、线程与信号量

Windows进程 进程是一个容器,包含程序执行需要的代码.数据.资源等信息, windows进程的特点: 每个进程都有自己的ID号 每个进程都有自己的地址空间,进程之间无法访问对方的地址空间. 每个进程都有自己的安全属性 每个进程至少包含一个线程. 获取和释放环境信息 GetEnvironmentStrings FreeEnvironmentStrings 获取或设置 本程序的环境变量 GetEnvironmentVariable SetEnvironmentVariable 示例: char

C++windows内核编程笔记day14 其他线程同步技术

线程同步技术: 原子锁 临界区(段) 互斥 事件 信号量(线程示例时已经使用过) 可等候定时器 使用范围:原子锁<临界区<互斥 效率:    原子锁>临界区(用户态)>互斥(内核态) 一般用临界区. //等候多个信号 DWORD WaitForMultipleObjects( DWORD nCount,             // number of handles in array CONST HANDLE *lpHandles,  // object-handle array

windows 中断处理学习笔记

学习计算机原理和操作系统课程之后,希望能对一个目前流行的操作系统有一些更深层的了解,正好认识一位朋友在做这方面的研究,在他的指点下,我希望通过windbg强大的调试功能,结合书本学到的知识,对windows(Windows 7 Kernel Version 7601 (Service Pack 1) MP (1 procs) Free x64)有一些了解. 鉴于本人对操作系统的认识有限,如有错误请批评指正. 一.中断处理程序 在计算机科学中,中断(英语:Interrupt)是指处理器接收到来自硬

Linux内核学习第五周 系统调用

一.实验截图 二.系统调用流程图: 三.总结:系统调用过程分析 linux的系统调用过程:用户程序→C库(即API):INT 0x80 →system_call→系统调用服务例程→内核程序.我们常说的用户API其实就是系统提供的C库. 系统调用是通过软中断指令 INT 0x80 实现的,而这条INT 0x80指令就被封装在C库的函数中.软中断和我们常说的硬中断不同之处在于,软中断是由指令触发的,而不是由硬件外设引起的.INT 0x80 这条指令的执行会让系统跳转到一个预设的内核空间地址,它指向系

Linux内核的系统调用---linux内核学习笔记(四)

内容一:实验报告相关说明. 真实姓名 谢润帮 原创作品转载请注明出处  所学课程:<Linux内核分析>MOOC课程   链接:http://mooc.study.163.com/course/USTC-1000029000 代码来源于孟宁老师的课件 虚拟实验室实验截图 实验结果图 实验代码截图