注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

从C开始

 
 
 

日志

 
 

系统调用 INT 0x2e  

2011-03-09 20:39:55|  分类: 驱动编程 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

我们可以用R3层的Windows API ReadFile函数读取文件.那么它具体是如何实现的呢?

 

用户层:

ReactOSReadFile函数的实现如下:

BOOL STDCALL ReadFile( HANDLE hFile,  LPVOID lpBuffer, 

                    DWORD nNumberOfBytesToRead,  LPDWORD lpNumberOfBytesRead, 

                    LPOVERLAPPED lpOverlapped  )

{

   

   Status = NtReadFile(hFile, NULL, NULL, NULL, &Iosb, lpBuffer, nNumberOfBytesToRead, NULL, NULL);

   ...

   return(TRUE);

}

在这里我们可以看到,ReadFile()函数里面调用了系统函数NtReadFile(),但是我们仔细一想,这又不可能.因为NtReadFile()函数是一个系统调用,R3下的函数怎么可能直接调用R0的函数啊.其实这里ReadFile()调用的并不是系统调用NtReadFile(),而也是R3下的函数NtReadFile().此函数在NTDLL.DLL中导出:

系统调用 INT 0x2e - Fly - 从C开始
 
在老版本的ReactOS中,此函数的定义如下:
__declspec(naked)__stdcall
NtReadFile(int dummy0,int dummy2,int dummy3)
{
   __asm{
      push ebp
      mov ebp,esp
      mov eax,152     //将NtReadFile()的系统调用号存入EAX
      lea edx,8[ebp]   //使EDX指向堆栈上参数块的起点
      int 0x2E        //进入内核
      pop ebp
      ret 9           //在堆栈上共有9个参数(win32汇编应该是9*4 :ret 36,)
     }
}
注意:此函数的参数在函数中并米有用到,因此其实是可有可无的.
 
 
内核层:

用户层的NtReadFile()函数在执行int 0x2E指令之后CPU既进入了内核.在内核中,Windios保存了一张"中断向量表",如下:

_KiIdt:

idt _KiTrap0,                   INT_32_DPL0 /*INT 00:Divide Error(#DE)*/

...

idt _KiSystemService            INT_31_DPL3 /*INT 2E:System Call Service Handler*/

...

那么内核中的KiSystemService()函数即是int 0x2e的中断服务程序.既系统调用的内核入口.

当cpu执行"int 0x2e"指令的时候,CPU就进入了内核中的这个函数,此时,cpu自动将下列信息压入系统空间堆栈:

1.用户空间的堆栈位置,包括堆栈段寄存器SS和堆栈指针ESP的内容

2.cpu中的"标志寄存器"EFLAGS的内容

3.用户空间的指令位置,包括代码段寄存器CS和指令指针ESP的内容

此时,堆栈如下:

系统调用 INT 0x2e - Fly - 从C开始

 那么系统空间堆栈又在哪里呢?每个线程都有自己的系统空间堆栈,其堆栈段寄存器SS和堆栈指针ESP的内容保存在一个称为"任务状态段"既TSS的数据结构里面.与此相应,CPU中有个称为"任务寄存器"既TR的段寄存器.每当从用户空间进入系统空间时,CPU就自动根据TR的指引从TSS中获取当前进程的SS和ESP两个寄存器的值.然后在把上面提到的几个寄存器中的内容压入这个堆栈.当然,还要根据IDTR既中断描述符表寄存器的指引获取CS和EIP. 这个过程所涉及的"时钟周期"显然不会少.正因为这样,后来才有了"快速系统调用"指令sysenter和sysexit的出现.

该函数定义如下:

_KiSystemService:

   SYSCALL_PROLOG kss_a, kss_t

   jmp SharedCode

这个函数的第一条语句其实是一个宏操作,他的主要作用是保存CPU当前线程先前调用的各种状态,并创建此次调用的堆栈调用框架.

第二条语句则是系统调用的函数跳转.既根据系统调用号在系统调用表(ServiceTable)中调用相应的NtReadFile()内核函数.

实际中每个线程可以有不同的系统调用表(ServiceTable),它不是指向KeServiceDescriptorTable[]就是指向KeServiceDescriptorTableShadow[],前者用于基本系统调用,后者既可用于基本调用也可用于Win32K扩充系统调用.

KSERVICE_TABLE_DESCRIPTOR  KeServiceDescriptorTable[SSDT_MAX_ENTRIES]

KSERVICE_TABLE_DESCRIPTOR  KeServiceDescriptorTableShadow[SSDT_MAX_ENTRIES]

常数SSDT_MAX_ENTRIES被定义为2,其下标为0者用于0x1000以下的系统调用号,下标为1者用于0x1000及以上的系统调用号.

内核在初始化的时候,把KeServiceDescriptorTable[0]的Base设置成指向MainSSDT,Limit则设置成常熟NUMBER_OF_SYSCALL.而KeServiceDescriptorTable[1]则为空白,其Limit是0.而KeServiceDescriptorTableShadow[] 刚开始时与KeServiceDescriptorTable[]完全相同,但是在装入了win32k.sys模块之后KeServiceDescriptorTableShadow[1]就指向了Win32SSDT.不言而喻,MainSSDT是基本的系统调用函数表,而Win32SSDT是扩充的Win32k系统调用函数表.

注意:当函数的系统调用号大于或者等于0x1000之后,当前线程的ServiceTable改而指向KeServiceDescriptorTableShadow[].

其结构定义如下:

typeddef struct _KSERVICE_TABLE_DESCRIPTOR

{

    PULONG_PTR Base; //函数表的基地址

    PULONG Count;  //总共的函数

    ULONG Limite;

#if define(_IA64_)

    LONG TableBaseGpOffect;

#endif

   PUCHAR Number; //某个函数参数的字节数,例如NtReadFile函数的参数为9个,那么此值就为9*4=36

}KSERVICE_TABLE_DESCRIPTOR,*PKSERVICE_TABLE_DESCRIPTOR;

 

 

 

  评论这张
 
阅读(1418)| 评论(2)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018