《nt4源码剖析之ntoskrnl篇.docx》由会员分享,可在线阅读,更多相关《nt4源码剖析之ntoskrnl篇.docx(23页珍藏版)》请在金锄头文库上搜索。
1、第一章 ntoskrnl2一 系统启动3二 Idle进程6三 初始化内核71 内核初始化综述72初始化内核结构103初始化进程对象114初始化线程对象125初始化执行体156初始化硬件体系层187初始化执行体组件21第一章 ntoskrnlNtoskrnl程序位于privatentosinit目录下,其入口有2个:ntkrnlmp.c、ntoskrnl.c,但ntkrnlmp.c只有一句代码:#include ntoskrnl.c,2个其实是1个。ntoskrnl的主函数里仅仅调用了KiSystemStartup函数,ntoskrnl的入口函数还是要看KiSystemStartup函数了。一
2、系统启动KiSystemStartup函数_KiSystemStartup函数的实现代码位于privatentoskei386newsysbg.asm文件中。这个模块是台湾的宗世麟(Shie-Lin Tzong (shielint)在1990年编写的,国立台湾大学82届资讯系毕业,他编写了nt4内核的X86汇编代码。(遐想:Windows内核代码居然有台湾人参与,但中国大陆却无人参与,可叹!难道中国只能开发应用软件,无人能做系统软件的开发吗?别跟我提那些只会做皮不会写瓤的中国Linux厂商。)启用每个CPU时都会调用此函数,分2种情况:启动CPU、其他CPU,执行操作有细微不同。CPU的数量保
3、存在几个变量中(_KeNumberProcessors、KissPbNumber)。CPU0使用的线程对象、堆栈空间是静态内存,线程对象(ETHREAD)是P0BootThread,堆栈空间是P0BootStack,大小03000H(12K)。CPU0设置FS=KGDT_R0_PCR(030H)。所有的CPU都调用KiInitializeMachineType函数获取机器类型,函数位于privatentoskei386i386init.c文件中。机器类型保存在加载参数块中(计算方法:KeLoaderBlock-u.I386.MachineType & 0x000ff),各个字节的含义如下:字节
4、含义Byte 0Machine Type, ISA, EISA or MCAByte 1CPU type, i386 or i486Byte 2Cpu Step, A or B . etcHighest bitindicates if NPX is present调用GetMachineBootPointers函数获取相关的gdt、pcr、tss、idt地址,调用sgdt指令加载gdt获取地址,通过FS保存的PCR选择子获取PCR地址,通过str指令加载TSS选择子获取TSS地址,通过sigt指令加载idt获取地址。GDT中的TSS(28h) 描述符是16位的,需要修改为32位,初始化TSS,
5、加载此TSS选择子到TR寄存器。286CPU使用16位TSS格式。TSS描述符的第5个字节设置为89h(dpl=0, present, TSS32, not busy),则设置成了32位TSS。初始化TSS分2种情况,1种是初始化静态不变的TSS成员(KiInitializeTSS),1种是初始化仅需要设置1次的成员(KiInitializeTSS2函数)。KiInitializeTSS函数设置TSS静态成员。IoMapBase 设置为IO_ACCESS_MAP_NONE的IOPM偏移地址,在V86模式时IO进入硬件。flag(T)为0,任务切换时不引起调试异常。LDT 为0,即不使用LDT。
6、Ss0为KGDT_R0_DATA(010H)。KiInitializeTSS2函数设置仅需要设置1次的成员。Limit设置为(sizeof(KTSS) - 1)。TSS有2张MAP,Tss-IoMaps中的IoMap设置为-1。Tss-IoMaps中的DirectionMap(软中断重定向)设置为0。Tss-IntDirectionMap(IO_ACCESS_MAP_NONE)设置为0,设置双重错误(double fault)、NMI中断的任务门。需要设置IDT、GDT、TSS。双重错误任务门是8号中断,位于IDT中的40h,设置任务门的05h字节为85h(dpl=0, present, ta
7、skgate),任务门的02h字节(TSS Segment Selector)为KGDT_DF_TSS。双重错误任务门的门描述符位于GTD+KGDT_DF_TSS,05字段设置为089h,基地值BASE设置为_KiDoubleFaultTSS,LIMIT设置为MINIMUM_TSS_SIZE(068H)。调用_KiInitializeTSS函数初始化TSS段_KiDoubleFaultTSS,CR3设置为当前CPU的CR3,EIP=_KiTrap08,eflags=0,CS= KGDT_R0_CODE, FS= KGDT_R0_PCR,ss=当前CPU的SS,Es=Ds=KGDT_R3_DAT
8、A OR RPL_MASK。TSS的堆栈ESP=ESP0,在DBG模式时,设置为_KiDoubleFaultStack;构建时,把内核镜像的ZW thunks部分作为堆栈,即(FLAT:_ZwUnmapViewOfSection8 - 4)&(3),此时先检验ZW thunks区域大小是否小于0a00h,ZW thunks区域大小计算方式:(FLAT:_ZwUnmapViewOfSection8 - 4)&(3)-FLAT:_ZwAcceptConnectPort24),如果大于,则显示错误信息DF Stack internal error。NMI中断任务门是2号中断,位于IDT中的10h,设
9、置任务门的05h字节为85h(dpl=0, present, taskgate),任务门的02h字节(TSS Segment Selector)为KGDT_NMI_TSS。NMI中断任务门的门描述符位于GTD+KGDT_NMI_TSS,05字段设置为089h,基地值BASE设置为_KiNMITSS,LIMIT设置为MINIMUM_TSS_SIZE(068H)。调用_KiInitializeTSS函数初始化TSS段_KiNMITSS,CR3设置为当前CPU的CR3,堆栈ESP=ESP0= DoubleFault的TSS堆栈,EIP= _KiTrap02,eflags=0,CS= KGDT_R0_
10、CODE, FS= KGDT_R0_PCR,ss=当前CPU的SS,Es=Ds=KGDT_R3_DATA OR RPL_MASK。_KiInitializePcr函数初始化处理器的PCR,每个处理器含有自己的GDT、IDT、TSS。把当前线程对象中的进程地址设置为_KiIdleProcess地址。KissIdleThread+ThApcState+AsProcess = offset FLAT:_KiIdleProcess。设置PCR-Teb = 0,设置KernelDr7和KernelDr6为 0。_KiSwapIDT函数交换IDT描述符的(Segment Selector)和(Offset
11、 31.16)数据,因为IDT表是按照IDTEntry宏定义设置的,这2个数据的位置是错位的,交换即可。设置DS=ES= KGDT_R3_DATA OR RPL_MASK。复制陷阱句柄替换内核调试句柄,但保留double fault和nmi fault。先把double fault和nmi fault的描述符push到堆栈,再复制_IDT的数据到kissIDT,最后从堆栈中pop出double fault和nmi fault的描述符。如果是新处理器,则先获取freezelock,再更新CPU广播位,调用_HalInitializeProcessor初始化CPU,调用_KiInitializeA
12、bios初始化ABIOS数据,增加处理器计数,最后释放freezelock。_HalInitializeProcessor函数初始化CPU的PCR,PcIDR=0fffffffbh,启用次IRQ;PcStallScaleFactor=INITIAL_STALL_COUNT(064H)。_KiInitializeAbios函数初始化GDT空闲列表,且设置KiI386AbiosCall选择子。从KeLoaderBlock中获取CommonDataArea,检验CommonDataArea是否为空,处理器是否是0,初始化KiAbiosGdtLock、KiAbiosLidTableLock,调用KiA
13、biosGetGdt函数获取GDT(即fs:PcGdt),调用KiInitializeAbiosGdtEntry函数初始化16位堆栈段、数据段、代码段,如果是P0,调用Ki386InitializeGdtFreeList函数初始化GDT空闲列表。GDT保留了3个选择子给16位的堆栈段、代码段、数据段,以便在i386模式下调用BIOS中断。16位段描述符大小类型堆栈段KGDT_STACK16(0xf8)00xFFFFTYPE_DATA(0x12)数据段KGDT_CDA16(0xe8)Common Data Area0xFFFFTYPE_DATA(0x12)代码段KGDT_CODE16(0xf0)
14、KiI386AbiosCall函数TYPE_CODE(0x18)Ki386InitializeGdtFreeList函数初始化GDT空闲列表,GDT开始处的28个项为保留的,从GDT末尾开始,如果GDT项的Present为0,则链接GDT列表的Flink,最后一项的Flink为0。检验处理器号,如果是0,则先调用_KdInitSystem初始化系统,再调用POLL_DEBUGGER给调试器一个机会获取控制权。POLL_DEBUGGER宏调用_KdPollBreakIn函数检验是否进入调试模式,如果是,则调用_DbgBreakPointWithStatus函数进入调试模式,且状态值是DBG_ST
15、ATUS_CONTROL_C(01H)。_DbgBreakPointWithStatus函数把参数(状态值)放在EAX,调用int 3进入调试模式。调用KfRaiseIrql函数提升IRQL到HIGH_LEVEL。检验P0处理器是否支持cmpxchg8b指令,如果不支持,则修改使用此指令的函数,使其跳转到使用SpinLock的相应函数。先通过检验EFLAGS寄存器的bit21位是否设置、清除来查看是否支持cpuid指令,如果不能,则修改函数。如果能,则eax赋值1,执行cpuid指令,查看edx寄存器的bit8位,如果是0,则修改函数,如果是1,则支持cmpxchg8b指令,设置变量_KiBootFeatureBits的bit7位为1(KF_CMPXCHG8B=0x00000080)。修改4个函数:原函数目标函数ExInterlockedCompareExchange64ExpInterlockedCompareExchange64ExInterlockedPopEntrySListExfInterlockedPopEntrySListExInterlockedPushEntrySListExfI