NetRoc's Blog

N-Tech

 

Windows内核分析之一 —— 内核入口函数

 

NetRoc/cc682

 

前段时间和yuewang和一块三毛钱商量着写写Windows分析的文章,我来开个头吧,哈哈。既然是开头,所以就选择了内核入口点开始,我向来不怎么会写文章,也就当流水账记记吧,看能不能引出他们更好的分析出来J

Ntoskrnl的入口点函数名是KiSystemStartup,这是bootloader执行了一些基本的初始化之后跳转到的内核入口函数,用汇编语言实现。

一、KiSystemStartup功能介绍

KiSystemStartup第一次运行于processor 0,主要是初始化一些系统硬件状态,调用一些系统初始化过程,然后就进入调度程序,开始系统调度过程。而对于其他processor,初始化的时候也是进入KiSystemStartup,但是做的工作有所区别而已。

 

二、Processor 0(以后简称P0)开始执行KiSystemStartup时的系统环境

这个运行环境是由bootloader准备好的:

1、 一个精简版的IDT环境,从00x1F号中断已经被准备好

2、 一个完整的GDT被初始化出来并且Load

3、 完整的TSS被初始化并且Load

4、 页面映射经过了基本的初始化,并且设置好了初始化所需的最少的页面。虚拟内存的最低4M被直接映射到物理内存中。

5、 ntoskrnl.exe被装载到它内存描述符中的地址。也即编译时确定的基地址。

6、 DS=ES=SSESP指向一个可用的栈中。

7、 中断被关闭。

 

三、其他Processor开始执行KiSystemStartup的环境

IDT, GDT, TSS, stack, selectors, PCR全部初始化完成并可用,页表设置为当前运行的页表(这一点偶也不太明白,可能还需要看看以后的代码才能理解),具备一个LoaderBlock,作为在该处理器上执行KiSystemStartup的参数。

 

四、大致流程

1、 KiSystemStartup将参数KissLoaderBlock放到全局变量_KeLoaderBlock

2、 取出_KeNumberProcessors,并判断是否是0_KeNumberProcessors保存了系统中的处理器数目,这个变量被初始化为0,所以当Ntoskrnl开始执行时,这个变量还没有被填充。因此判断_KeNumberProcessors是否是0,就可以知道当前是不是第一次执行KiSystemStartup

3、 如果是P0,会将_KiInitialThreadP0BootStack的地址分别保存 _KeLoaderBlock中的对应字段中。_KiInitialThread是系统启动之后的初始线程,而P0BootStack应该是初始化时临时使用的内核堆栈,定义为db      KERNEL_STACK_SIZE dup (?)KERNEL_STACK_SIZEi386中是0x3000,在AMD64中是0x6000。然后会设置fs0x30,这是内核_KPCR结构的在GDT中序号。最后,会将处理器序号,也就是0,保存到_KPCR中对应位置,这个位置在i386AMD64中也是不同的。

4、 下面又是所有处理器都会执行的代码了,设置初始线程的_ETHREAD:: Tcb:: ApcState:: ApcListHead[0],将_LIST_ENTRYFlinkBlink都设置为自身。

5、 调用_KiInitializeMachineType过程,会设置一下机器类型。不过这里做得很简单,这个函数可能在未来也会有较大更改。主要的机器类型信息可能包含了总线类型、CPU大致的系列等简单信息。

6、 然后又判断是否是P0,如果不是,会跳过一大段初始化代码。

7、 P0的情况下,调用GetMachineBootPointers函数,获取由bootloader初始化过的一些信息。从这个函数返回后,edi中保存gdt基地址,esi中保存pcr基地址,edx保存tss基地址,eax保存idt基地址。KiSystemStartup接下来会将这些值保存到自己的局部变量中使用。

8、 Bootloader初始化的TSS16位的,KiSystemStartup在这里会将它的标志改为32位,然后连续调用_KiInitializeTSS2_KiInitializeTSS初始化TSSKiInitializeTSS2初始化了内核TSS结构在GDT中描述符的界限大小,以及初始化IOPM的相关结构。_KiInitializeTSSTSS中首先设置不使用IOPM,然后设置Tss->Flags = 0,将EFLAGS清空。最后将LDTss0都设置为0。设置完成后,重新装载TR寄存器。

9、 接下来设置了double fault task gate。这里会设置IDT中的08号中断,设置成了一个任务门,并填充相应的TSS结构,是用于#DF异常时的。

10、             设置用于NMI fault task gate,设置IDT02号中断。这是用于不可屏蔽中断的中断号。同样也调用_KiInitializeTSS填充了另外一个TSS结构。上面两条详细的原理参考Intel手册关于IDTtask gate的描述。

11、             调用_KiInitializePcr初始化了当前的pcr

12、             将初始进程的_EPROCESS地址,即_KiInitialProcess的地址设置到了初始线程的_ETHREAD:: Tcb:: ApcState:: Process中。

13、             设置PCR->Teb = 0

14、             设置PCR->PrcbData.ProcessorState.SpecialRegisters.KernelDr6PCR->PrcbData.ProcessorState.SpecialRegisters.KernelDr70。这里是为了初始化内核调试器相关的东西,具体作用可能只有分析到相关代码才能知道了。

15、             调用_KiSwapIDT转换IDT描述符的格式。IDTENTRY定义如下:

typedef struct tagIDTENTRY

{

        unsigned short OffsetLow;

        unsigned short Selector;

        unsigned char Reserved;

        unsigned char Type:4;

        unsigned char Always0:1;

        unsigned char Dpl:2;

        unsigned char Present:1;

        unsigned short OffsetHigh;

} IDTENTRY, *PIDTENTRY;

这个函数将ntoskrnl定义的IDT表项数组中,选择子的SelectorOffsetHigh字段对换。这里估计是在初始化这些表项的时候,为了方便直接将处理代码的地址填到了&OffsetLow中,所以Selector保存了高位的地址,然后到后面来统一替换。详细的原理参见Intel手册。

16、             dses的值设置为0x23,也就是Ring3下的dses值。

17、             ntoskrnl_IDT数组的内容复制到当前的IDT表中。前面设置的double fault nmi fault的表项不会被覆盖掉,而是使用新设置的内容。

18、             接下来又是所有处理器都会执行的操作了。调用_KiProcessorStart初始化处理器。这个函数会根据KiProcessorStartControl的不同值进行不同的操作,例如获取一些处理器信息、启动或者停止处理器等等。由于P0已经不需要初始化了,所以在P0阶段这个函数直接返回。

19、             获取_KiFreezeExecutionLock这个锁,用于修改一些和处理器相关的全局资源。主要是_KPCR里面的处理器相关的信息。然后调用了_HalInitializeProcessor函数,初始化该处理器的IDT。估计这个函数会继续为每个处理器调用KiSystemStartup函数。不过没能确认。

20、             IRQL的信息保存下来。这是hal由参数传过来的。

21、             _KeActiveProcessors中设置初始化完成的处理器MASK

22、             调用_KiInitializeAbios初始化ABIOS结构。这里的详细原理就不太清楚了,因为没能分析过相关部分。

23、             _KeNumberProcessors1,增加已初始化完成的处理器数量。然后就会释放掉_KiFreezeExecutionLock锁了。

24、             接下来调用_KdInitSystem函数。这里应该会初始化内核调试器。只在P0上调用。

25、             后面将会初始化内核了,首先会将IRQL提升到HIGH_LEVEL,并初始化调用内核初始化函数使用的寄存器,包括传递参数的eax,ebx,edx,以及用于堆栈访问的espebp。然后就调用_KiInitializeKernel进行内核初始化。这个函数相当复杂,也够一篇文章,这里就不写了。呵呵

26、             出来之后设置idle thread的优先级为0,开中断,降低IRQLDISPATCH_LEVEL。然后检查并等待_KiBarrierWait这个锁。对P0来说,由于_KiBarrierWait初始化为0,所以直接就跳到idle线程了,其他处理器会一直等待_KiBarrierWait,直到允许他们运行。

27、             最后通过一个长跳转到KiIdleLoop函数,开始系统的处理和调度,整个系统初始化过程就完成了。

 

五、后记

唉,真的写起来才发现文章不好写啊。自己再看的时候都感觉不清不楚的,呵呵。不过暂时就这样吧J

posted on 2008-05-04 14:57 NetRoc 阅读(2901) 评论(1)  编辑 收藏 引用 所属分类: 内核技术

评论

# re: Windows内核分析之一 —— 内核入口函数 2008-05-05 10:45 superwushu

你的源码是哪个版本的?WRK的还是泄露的Win2000?貌似两者差别比较大(win2000和WRK v1.2比起来)。
ps. 这种文章还是要小心侵权啊(别直接贴代码),不过希望你们能够继续……  回复  更多评论   

只有注册用户登录后才能发表评论。

导航

统计

常用链接

留言簿(7)

随笔档案(99)

文章分类(35)

文章档案(32)

Friends

Mirror

搜索

最新评论

阅读排行榜

评论排行榜