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

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

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

一、KiSystemStartup功能介绍

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

 

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

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

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

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

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

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

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

6、 DS=ES=SS,ESP指向一个可用的栈中。

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,会将_KiInitialThread和P0BootStack的地址分别保存 到_KeLoaderBlock中的对应字段中。_KiInitialThread是系统启动之后的初始线程,而P0BootStack应该是初始化时临时使用的内核堆栈,定义为db      KERNEL_STACK_SIZE dup (?)。KERNEL_STACK_SIZE在i386中是0x3000,在AMD64中是0x6000。然后会设置fs为0x30,这是内核_KPCR结构的在GDT中序号。最后,会将处理器序号,也就是0,保存到_KPCR中对应位置,这个位置在i386和AMD64中也是不同的。

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

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

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

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

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

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

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

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

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

13、             设置PCR->Teb = 0。

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

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表项数组中,选择子的Selector和OffsetHigh字段对换。这里估计是在初始化这些表项的时候,为了方便直接将处理代码的地址填到了&OffsetLow中,所以Selector保存了高位的地址,然后到后面来统一替换。详细的原理参见Intel手册。

16、             将ds和es的值设置为0x23,也就是Ring3下的ds和es值。

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、             将_KeNumberProcessors加1,增加已初始化完成的处理器数量。然后就会释放掉_KiFreezeExecutionLock锁了。

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

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

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

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

 

五、后记

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值