首页
社区
课程
招聘
[Windows源码分析](一)初始化内核与执行体子系统
发表于: 2008-3-22 23:59 48307

[Windows源码分析](一)初始化内核与执行体子系统

2008-3-22 23:59
48307
对于那么没有相关经验的朋友,在阅读本文时最好对照windows源码来看,否则光看着这么多数据结构就足以头大。对于这篇文章,严格来说,应该是属于学习笔记型,如有分析不当的地方,请各位多指教!
    本文的主要目标是根据windows源码分析内核与执行体初始化流程,过程中会涉及大量的内核数据结构与函数,文中并不会介绍每个结构的含义及作用。内核中有很多晦涩难懂且枯燥的代码,我在分析过程尽可能的去分析每一行代码,否则如果只分析思路而不注重细节那么就跟没分析一样。      
==========================================================
本文结构:
一、内核初始化
1.1  系统启动过程简介
1.2  内核初始化
二、源码分析
2.1 内核初始化KiInitializeKernel
2.2 初始化内核数据结构KiInitSystem
2.3 [phase0]Ntoskrnl初始化ExpInitializeExecutive
2.4 [phase0]初始化进程管理器PsInitPhase0
2.5 [phase1]Ntoskrnl初始化Phase1Initialiation

==========================================================
一、内核初始化
1.1  系统启动过程简介

      对于系统启动过程,已经有太多的资料介绍过了。这里只是稍作温习,为介绍后续内容做准备。这部分的内容参考了http://www.yesky.com/317/1711317.shtml。
系统的启动过程一般分为5个步骤:
(1)预引导过程
  [1] 系统加点自检,同时完成硬件设备的枚举和配置。
  [2] BIOS确定引导设备位置,加载引导设备的MBR。
  [3] 在MBR中扫描分区表,定位活动分区,并加载活动分区上引导扇区到内存
  [4] 加载系统根目录的ntldr。
(2)引导过程
  [5] 初始化Ntldr,完成处理器模式切换和文件系统驱动的加载,如果使用SCSI设备,
Ntldr将Ntbootdd.sys加载到内存。
  [6] Ntldr读取系统根目录的boot.ini
,在屏幕显示系统启动菜单,等待用户选择所需要加载的操作系统。
  [7] Ntldr读取并运行程序Ntdetect.com,完成硬件的检测。
  [8] Ntldr根据用户的选择调用系统的硬件配置文件。
(3)内核加载,在[8]后清除屏幕,显示进度条。
  [9] 加载执行体ntoskrnl.exe
  [10] 加载Hal.dll
  [11] 加载%systemroot\System32\Config\System下的注册表项HKEY_LOCAL_MACHINE\
SYSTEM。
  [12] 选择加载控制集,初始化计算机。
  [13] 根据控制集加载低级硬件设备驱动程序。
(4)内核初始化,显示图形界面。
  [14] 内核会使用检测到的硬件数据,在注册表中创建HKEY_LOCAL_MACHINE\HARDWA项。
  [15] 其次的工作是内核通过复制HKEY_LOCAL_MACHINE\SYSTEM\Select子键Current
项引用的控制集创建Clone控制集。
  [16] 内核开始进一步加载和初始化设备驱动程序。
  [17] Session Manager(Smss.exe)按顺序启动Windows 2000
更高一层次的子系统和各项服务。
(5)系统登陆过程
  [18] 系统首先启动Winlogon.exe。
  [19] 启动Local Security Authority(Lsass.exe)
   [20] 屏幕显示出登陆对话框。
  [21] 系统执行Service Controller(Screg.exe)再次扫描注册表HKEY_LOCAL_MACHINE\
SYSTEM\CurrentControlSet\Control项并自动加载其中系统的或用户的服务。
  [22] 此时,用户已成功的登陆到了Windows 2000系统,系统随后把Clone控制集拷贝到
LastKnownGood控制集。

1.2  内核初始化
     本文所要介绍的重点是ntoskrnl的初始化流程。这个初始化过程大致分为两个阶段:phase0和phase1。对于具体所处的阶段是由一个全局变量InitializationPhase来标识,当InitializationPhase为0时表示处于phase0,当InitializationPhase为1时表示处于phase1。
    Ntoskrnl在入口函数中调用KiSystemStartup,而KiSystemStartup又依次为每个CPU调用HalInitializeProcessor和KiInitializeKernel。如果KiInitializeKernel运行在引导CPU上,则会调用KiInitSystem执行系统范围全局的内核初始化。然后KiInitializeKernel调用ExpInitializeExecutive函数,负责实现phase0阶段的初始化工作。
    (注:引导CPU,即0号CPU,每个CPU都以整数标识,0号CPU是第一个被初始化的CPU。当初始化第1个CPU时需要进行额外的操作,因而称之为引导CPU)
    在phase0阶段的初始化过程中首先调用HalInitSystem初始化HAL,然后依次初始化内存管理器、对象管理器、安全引用监视器、进程管理器和即插即用管理器。其中在调用PsInitSystem执行进程管理器在phase0阶段初始化时,创建了一个新的系统线程即为Phase1Initialization,用于执行phase1阶段的初始化。由于此时并不允许中断,Phase1Initialization线程并不立即执行。当完成phase0阶段初始化并返回到KiInitializeKernel时,设置IRQL到DISPATCH_LEVEL并使CPU调度Phase1Initialization线程,从而进行phase1阶段的初始化。

关于内核初始化流程可以用下图表示,从上到下表示时间顺序:

图示说明:
[1] 关于KiInitializeKernel的具体实现参见2.1小节。
[2] 关于KiInitSystem的具体实现参见2.2小节
[3] 关于phase0阶段ExpInitializeExecutive的具体实现参见2.3小节。
[4] 关于phase0阶段进程管理器初始化(PsInitSystem)的具体实现参见2.4小节。
[5] 关于Phase1Initialization线程实现phase1阶段初始化的具体过程参见2.5小节。

二、源码分析
2.1  内核初始化KiInitializeKernel
      这个函数在系统由bootstrapped启动后且系统未被初始化之前取得控制权。当新的处理器加入时,调用这个例程可以初始化处理器相关的数据结构。主要功能是:
(1)初始化内核数据结构
(2)初始化处理控制块(Processor Control Block)
(3)调用内核执行体初始化例程
(4)最后返回系统启动例程(KiSystemStartup)   
      下面就是KiInitializeKernel的执行流程:
VOID KiInitializeKernel (
					IN PKPROCESS Process,
					IN PKTHREAD Thread,
					IN PVOID IdleStack,			// IDLE线程的内核堆栈基地址
					IN PKPRCB Prcb,			// 指向处理器控制块结构
					IN CCHAR Number,			// 指定当前正在初始化的处理器索引号
					PLOADER_PARAMETER_BLOCK LoaderBlock
					)
{
	// 初始化Prcb的部分成员
	// ……

	// 在系统初始化过程(KiSystemStartup)会枚举所有CPU,
	// 然后循环调用KiInitializeKernel依次初始化每个CPU
	// 在初始化号CPU时,会初始化一部分内核数据结构
	if (Number == 0) {

		// 初始化处理器相关的全局信息
		// 例如KeI386NpxPresent,KeI386CpuType,KeI386CpuStep,KeI386FxsrPresent等
		// ……

		// 初始化体系无关的内核数据结构
		KiInitSystem();

		// 初始化idle线程的进程对象
		KeInitializeProcess(Process,…… );
	} 
	else
	{
		// ……
	}

	// ……

	// 初始化线程对象
	KeInitializeThread(Thread, (PVOID)((ULONG)IdleStack),
		(PKSYSTEM_ROUTINE)NULL, (PKSTART_ROUTINE)NULL,
		(PVOID)NULL, (PCONTEXT)NULL, (PVOID)NULL, Process);

	// ……

	// 调用内核执行体的初始化例程
	try {
		ExpInitializeExecutive(Number, LoaderBlock);
	} except (EXCEPTION_EXECUTE_HANDLER) {
		KeBugCheck (PHASE0_EXCEPTION);
	}

	// ……

	// 如果是启动CPU(0号),动态分配内核堆栈空间和K的IOPM存储区域
	if (Number == 0) {
		PVOID DpcStack = MmCreateKernelStack(FALSE);
		if (DpcStack == NULL) {
			KeBugCheckEx(NO_PAGES_AVAILABLE, 1, 0, 0, 0);
		}
		Prcb->DpcStack = DpcStack;

		// 分配K的IOPM存储区域,用于BiosCall交换
		Ki386IopmSaveArea = ExAllocatePoolWithTag(PagedPool,PAGE_SIZE * 2,'  eK');
		if (Ki386IopmSaveArea == NULL) {
			KeBugCheckEx(NO_PAGES_AVAILABLE, 2, PAGE_SIZE * 2, 0, 0);
		}
	}
	
    // 设置IRQL为DISPATCH_LEVEL级别,允许线程派发
    KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

    // 设置当前线程优先级为0,这样才能调度Phase1Initialization线程
    KeSetPriorityThread(Thread, (KPRIORITY)0);
 
    // 检测是否存在就绪线程,如果不存在则把当前CPU添加到KiIdleSummary
    KiAcquireQueuedSpinLock(KiQueuedSpinLockContext(LockQueueDispatcherLock));
    if (Prcb->NextThread == (PKTHREAD)NULL) {
        SetMember(Number, KiIdleSummary);
    }
    KiReleaseQueuedSpinLock(KiQueuedSpinLockContext(LockQueueDispatcherLock));

    // 提升IRQL到HIGH_LEVEL,屏蔽中断
KeRaiseIrql(HIGH_LEVEL, &OldIrql);

	return;
}

这里有3个关键处:
(1)当初始化0号CPU时调用KiInitSystem用于初始化内核数据结构。
(2)调用执行体初始化例程ExpInitializeExecutive。
(3)在调用ExpInitializeExecutive返回后,首先设置IRQL为DISPATCH_LEVEL
级别,允许线程派发,然后设置当前线程优先级为0,表明当前线程放弃下一个时间片,使得CPU可以执行Phase1Initialization线程,最后提升IRQL为HIGH_LEVEL,屏蔽系统中断,保证phase1阶段的初始化不可中断。

2.2  初始化内核数据结构KiInitSystem
KiInitSystem
初始化了一些比较重要也比较常见的内核数据结构,这些数据结构中大量地使用了
LIST_ENTRY结构。如下所示:
VOID KiInitSystem (VOID)
{
	ULONG Index;

	// 初始化调度队列链表头,每一个优先级都有一个独立的进程链表
	for (Index = 0; Index < MAXIMUM_PRIORITY; Index += 1) {
		InitializeListHead(&KiDispatcherReadyListHead[Index]);
	}

	// 初始化BugCheck回调函数链表,及其旋转锁
	InitializeListHead(&KeBugCheckCallbackListHead);
	KeInitializeSpinLock(&KeBugCheckCallbackLock);

	// 初始化定时器过期的DPC对象
	KeInitializeDpc(&KiTimerExpireDpc,
		(PKDEFERRED_ROUTINE)KiTimerExpiration, NIL);

	// 初始化profile链表,及其旋转锁
	KeInitializeSpinLock(&KiProfileLock);
	InitializeListHead(&KiProfileListHead);

	// 初始化当前活动的profile链表
	InitializeListHead(&KiProfileSourceListHead);

	// 初始化定时器链表
	for (Index = 0; Index < TIMER_TABLE_SIZE; Index += 1) {
		InitializeListHead(&KiTimerTableListHead[Index]);
	}

	// 初始化swap通知事件
	KeInitializeEvent(&KiSwapEvent,SynchronizationEvent,FALSE);

	InitializeListHead(&KiProcessInSwapListHead);
	InitializeListHead(&KiProcessOutSwapListHead);
	InitializeListHead(&KiStackInSwapListHead);
	InitializeListHead(&KiWaitInListHead);
	InitializeListHead(&KiWaitOutListHead);

	// 初始化SSDT
	KeServiceDescriptorTable[0].Base = &KiServiceTable[0];
	KeServiceDescriptorTable[0].Count = NULL;
	KeServiceDescriptorTable[0].Limit = KiServiceLimit;
#if defined(_IA64_)
	KeServiceDescriptorTable[0].TableBaseGpOffset =
		(LONG)(*(KiServiceTable-1) - (ULONG_PTR)KiServiceTable);
#endif
	KeServiceDescriptorTable[0].Number = &KiArgumentTable[0];
	for (Index = 1; Index < NUMBER_SERVICE_TABLES; Index += 1) {
		KeServiceDescriptorTable[Index].Limit = 0;
	}

	// 拷贝SSDT到Shadow服务表
	RtlCopyMemory(KeServiceDescriptorTableShadow,
		KeServiceDescriptorTable,
		sizeof(KeServiceDescriptorTable));

	// ……
	return;
}


2.3  [phase0]Ntoskrnl初始化ExpInitializeExecutive
ExpInitializeExecutive函数负责阶段0(phase 0)的所有其他初始化工作。
ExpInitializeExecutive首先调用HalInitSystem初始化HAL,其中一个功能是为每个CPU
准备系统中断控制器。接下来,依次执行执行体的5个组件在phase 0
的初始化工作,如下:
(1)调用MmInitSystem,初始化内存管理器。
(2)调用ObInitSystem,初始化对象管理器。
(3)调用SeInitSystem,初始化安全引用监视器。
(4)调用PsInitSystem,初始化进程管理器。
(5)调用PpInitSystem,初始化即插即用管理器。
这5个调用中MmInitSystem和ObInitSystem
复杂,剩余的三个步骤都具有相似的结构,如下:
BOOLEAN SeInitSystem( VOID )
{
    switch ( InitializationPhase ) {
    case 0 :        return SepInitializationPhase0();
    case 1 :        return SepInitializationPhase1();
    default:        KeBugCheck(UNEXPECTED_INITIALIZATION_CALL);
    }
    return 0;
}
BOOLEAN PsInitSystem ( IN ULONG Phase, IN PLOADER_PARAMETER_BLOCK LoaderBlock )
{
    switch ( InitializationPhase ) {
    case 0 :        return PspInitPhase0(LoaderBlock);
    case 1 :        return PspInitPhase1(LoaderBlock);
    default:        KeBugCheck(UNEXPECTED_INITIALIZATION_CALL);
    }
    return 0;
}
BOOLEAN PpInitSystem ( VOID )
{
    switch ( InitializationPhase ) {
    case 0 :        return PiInitPhase0();
    case 1 :        return PiInitPhase1();
    default:        KeBugCheck(UNEXPECTED_INITIALIZATION_CALL);
    }
}

共同点是根据InitializationPhase所指定的phase完成特定的初始化工作,其中
InitializationPhase是个全局变量,可以设置为0、1、2,设置为0表示进行phase0
初始化,设置为1表示进行phase1初始化,成功初始化内核后则设置为2。
ExpInitializeExecutive的具体实现如下:
VOID ExpInitializeExecutive(
	IN ULONG Number,						// CPU索引号
	IN PLOADER_PARAMETER_BLOCK LoaderBlock	// loader parameter block
)
{
	NTSTATUS Status;
	PLDR_DATA_TABLE_ENTRY DataTableEntry;
	PMESSAGE_RESOURCE_ENTRY MessageEntry;
	PLIST_ENTRY NextEntry;
	ANSI_STRING AnsiString;
	STRING NameString;
	CHAR Buffer[ 256 ];
	CHAR VersionBuffer[ 64 ];
	PCHAR s, sMajor, sMinor;
	ULONG ImageCount, i;
	BOOLEAN IncludeType[LoaderMaximum];	// 标识特定类型的内存是否存在
	ULONG MemoryAlloc[(sizeof(PHYSICAL_MEMORY_DESCRIPTOR) +
		sizeof(PHYSICAL_MEMORY_RUN)*MAX_PHYSICAL_MEMORY_FRAGMENTS) /
		sizeof(ULONG)];
	PPHYSICAL_MEMORY_DESCRIPTOR Memory;// 物理内存描述符
	ULONG   ResourceIdPath[3];	
	PIMAGE_RESOURCE_DATA_ENTRY ResourceDataEntry;
	PIMAGE_NT_HEADERS NtHeaders;
	PMESSAGE_RESOURCE_DATA  MessageData;

	if (Number == 0) {
		InitializationPhase = 0L;		// 设置为phase 0

		// 计算物理内存块
		Memory = (PPHYSICAL_MEMORY_DESCRIPTOR)&MemoryAlloc;
		Memory->NumberOfRuns = MAX_PHYSICAL_MEMORY_FRAGMENTS;

		// 包含除LoaderBad等3种外的所有内存类型
		for (i=0; i < LoaderMaximum; i++) {
			IncludeType[i] = TRUE;
		}
		IncludeType[LoaderBad] = FALSE;
		IncludeType[LoaderFirmwarePermanent] = FALSE;
		IncludeType[LoaderSpecialMemory] = FALSE;

		MmInitializeMemoryLimits(LoaderBlock, IncludeType, Memory);

		InitNlsTableBase = LoaderBlock->NlsData->AnsiCodePageData;
		InitAnsiCodePageDataOffset = 0;
		InitOemCodePageDataOffset = ((PUCHAR)LoaderBlock->NlsData->OemCodePageData 
- (PUCHAR)LoaderBlock->NlsData->AnsiCodePageData);
		InitUnicodeCaseTableDataOffset = ((PUCHAR)LoaderBlock->NlsData->
UnicodeCaseTableData - (PUCHAR)LoaderBlock->NlsData->AnsiCodePageData);

		RtlInitNlsTables(
			(PVOID)((PUCHAR)InitNlsTableBase+InitAnsiCodePageDataOffset),
			(PVOID)((PUCHAR)InitNlsTableBase+InitOemCodePageDataOffset),
			(PVOID)((PUCHAR)InitNlsTableBase+InitUnicodeCaseTableDataOffset),
			&InitTableInfo
			);

		RtlResetRtlTranslations(&InitTableInfo);

		// 初始化HAL(Hardware Architecture Layer)
		if (HalInitSystem(InitializationPhase, LoaderBlock) == FALSE) {
			KeBugCheck(HAL_INITIALIZATION_FAILED);
		}

#if i386
		// 允许中断
		KiRestoreInterrupts (TRUE);
#endif
		// ……

		// LoadOrderListHead的第一结点必定是内核模块(例如ntoskrnl.exe)
		DataTableEntry = CONTAINING_RECORD(
			LoaderBlock->LoadOrderListHead.Flink,
			LDR_DATA_TABLE_ENTRY,
			InLoadOrderLinks);

		ResourceIdPath[0] = 11;
		ResourceIdPath[1] = 1;
		ResourceIdPath[2] = 0;

		// 寻找BugCheck消息资源,并设置到KiBugCodeMessages
		// LdrFindResource_U在指定DLL中定位指定ID的资源
		Status = LdrFindResource_U(
			DataTableEntry->DllBase,		// DLL模块基地址
			ResourceIdPath,				// 资源ID数组
			3,							// 资源ID数组长度
			(VOID *) &ResourceDataEntry);	// 指向IMAGE_RESOURCE_DATA_ENTRY结构
		if (NT_SUCCESS(Status)) {
			Status = LdrAccessResource(
				DataTableEntry->DllBase,
				ResourceDataEntry,
				&MessageData,
				NULL);

			if (NT_SUCCESS(Status)) {
				KiBugCodeMessages = MessageData;
			}
		}

		// 遍历已加载的模块列表,并加载符号文件
		ImageCount = 0;
		NextEntry = LoaderBlock->LoadOrderListHead.Flink;
		while (NextEntry != &LoaderBlock->LoadOrderListHead) {
			// 获取LDR_DATA_TABLE_ENTRY结构指针
			DataTableEntry = CONTAINING_RECORD(NextEntry,
				LDR_DATA_TABLE_ENTRY,
				InLoadOrderLinks);

			// 通过内核调试器加载模块符号
			sprintf( Buffer, "%ws\\System32\\%s%wZ",
				&SharedUserData->NtSystemRoot[2],
				ImageCount++ < 2 ? "" : "Drivers\\",
				&DataTableEntry->BaseDllName
				);
			RtlInitString( &NameString, Buffer );
			DbgLoadImageSymbols(&NameString, DataTableEntry->DllBase, (ULONG)-1);
			NextEntry = NextEntry->Flink;		// 下一个结点
		}
	} 
	else 
	{
		// 初始化HAL
		if (HalInitSystem(InitializationPhase, LoaderBlock) == FALSE) {
			KeBugCheck(HAL_INITIALIZATION_FAILED);
		}
	}

	if (Number == 0) {
		// ……
		
		// 初始化执行体(phase 0)
		if (!ExInitSystem()) {
			KeBugCheck(PHASE0_INITIALIZATION_FAILED);
		}

		ExBurnMemory(LoaderBlock);

		// 初始化内存管理器(phase 0)
		MmInitSystem(0, LoaderBlock, Memory);

		{
			PLIST_ENTRY NextMd;
			PMEMORY_ALLOCATION_DESCRIPTOR MemoryDescriptor;

			// 遍历内存描述符,统计LoaderNlsData类型内存区域总大小
			NextMd = LoaderBlock->MemoryDescriptorListHead.Flink;
			while (NextMd != &LoaderBlock->MemoryDescriptorListHead) {
				MemoryDescriptor = CONTAINING_RECORD(NextMd,
					MEMORY_ALLOCATION_DESCRIPTOR,
					ListEntry);
				switch (MemoryDescriptor->MemoryType) {
					case LoaderNlsData:
						InitNlsTableSize += MemoryDescriptor->PageCount*PAGE_SIZE;
						break;
					default:
						break;
				}
				NextMd = MemoryDescriptor->ListEntry.Flink;
			}

			// 分配非分页的NLS区域
			InitNlsTableBase = ExAllocatePoolWithTag(NonPagedPool,InitNlsTableSize,' 
slN');
			if ( !InitNlsTableBase ) {
				KeBugCheck(PHASE0_INITIALIZATION_FAILED);
			}

			// 拷贝NLS数据到刚才分配的区域
			RtlMoveMemory(
				InitNlsTableBase,
				LoaderBlock->NlsData->AnsiCodePageData,
				InitNlsTableSize
				);

			// 初始化NLS表信息结构
			RtlInitNlsTables(
				(PVOID)((PUCHAR)InitNlsTableBase+InitAnsiCodePageDataOffset),
				(PVOID)((PUCHAR)InitNlsTableBase+InitOemCodePageDataOffset),
				(PVOID)((PUCHAR)InitNlsTableBase+InitUnicodeCaseTableDataOffset),
				&InitTableInfo
				);

			RtlResetRtlTranslations(&InitTableInfo);
		}

		// ……

		// 初始化静态句柄表
		ExInitializeHandleTablePackage();

		// 初始化对象管理器(phase 0)
		if (!ObInitSystem()) {
			KeBugCheck(OBJECT_INITIALIZATION_FAILED);
		}

		// 初始化安全引用监视器(phase 0)
		if (!SeInitSystem()) {
			KeBugCheck(SECURITY_INITIALIZATION_FAILED);
		}

		// 初始化进程管理器(phase 0)
		if (PsInitSystem(0, LoaderBlock) == FALSE) {
			KeBugCheck(PROCESS_INITIALIZATION_FAILED);
		}

		// 初始化即插即用管理器(phase 0)
		if (!PpInitSystem()) {
			KeBugCheck(PP0_INITIALIZATION_FAILED);
		}
		
		// 设置SharedUserData成员
		// ……
	}
}


2.4  [phase0]初始化进程管理器PsInitPhase0
      这里介绍的是进程管理器在phase 0的初始化过程,之所以介绍这个过程是因为phase 1
初始化过程正是从这里展开。在上一节上已经提到PsInitSystem,在phase0,实际上调用
PsInitPhase0实现进程管理器的初始化。PsInitPhase0主要实现以下几个功能:
(1)设置系统启动进程(即当前进程)为Idle进程,并改名为"Idle"。
(2)初始化进程、Job等相关的全局变量。
(3)创建系统进程并命名为"System"。
(4)创建系统线程用于实现phase1过程的初始化。
PsInitPhase0的具体实现如下:
BOOLEAN PspInitPhase0 ( IN PLOADER_PARAMETER_BLOCK LoaderBlock   )
{
    UNICODE_STRING NameString;
    OBJECT_ATTRIBUTES ObjectAttributes;
    OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
    HANDLE ThreadHandle;
    PETHREAD Thread;
    MM_SYSTEMSIZE SystemSize;

    SystemSize = MmQuerySystemSize();
PspDefaultPagefileLimit = (ULONG)-1;
// ……

    // 初始化进程相关的锁
    ExInitializeFastMutex( &PspProcessLockMutex );
    ExInitializeFastMutex( &PsProcessSecurityLock );

    // 当前进程即是Idle进程
    PsIdleProcess = PsGetCurrentProcess();
    PsIdleProcess->Pcb.KernelTime = 0;
    PsIdleProcess->Pcb.KernelTime = 0;

    // 初始化OBJECT_TYPE_INITIALIZER结构
    RtlZeroMemory( &ObjectTypeInitializer, sizeof( ObjectTypeInitializer ) );
    ObjectTypeInitializer.Length = sizeof( ObjectTypeInitializer );
    ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
    ObjectTypeInitializer.SecurityRequired = TRUE;
    ObjectTypeInitializer.PoolType = NonPagedPool;
    ObjectTypeInitializer.InvalidAttributes = OBJ_PERMANENT | OBJ_EXCLUSIVE | 
OBJ_OPENIF;

    // 创建进程对象
    RtlInitUnicodeString(&NameString, L"Process");
    ObjectTypeInitializer.DefaultPagedPoolCharge = PSP_PROCESS_PAGED_CHARGE;
    ObjectTypeInitializer.DefaultNonPagedPoolCharge = 
PSP_PROCESS_NONPAGED_CHARGE;
    ObjectTypeInitializer.DeleteProcedure = PspProcessDelete; // 
删除进程对象回调函数
    ObjectTypeInitializer.ValidAccessMask = PROCESS_ALL_ACCESS;
    ObjectTypeInitializer.GenericMapping = PspProcessMapping;
    if ( !NT_SUCCESS(ObCreateObjectType(&NameString,
                                     &ObjectTypeInitializer,
                                     (PSECURITY_DESCRIPTOR) NULL,
                                     &PsProcessType
                                     )) ){
        return FALSE;
    }

    // 创建线程对象
    RtlInitUnicodeString(&NameString, L"Thread");
    ObjectTypeInitializer.DefaultPagedPoolCharge = PSP_THREAD_PAGED_CHARGE;
    ObjectTypeInitializer.DefaultNonPagedPoolCharge = 
PSP_THREAD_NONPAGED_CHARGE;
    ObjectTypeInitializer.DeleteProcedure = PspThreadDelete; // 
删除线程对象回调函数
    ObjectTypeInitializer.ValidAccessMask = THREAD_ALL_ACCESS;
    ObjectTypeInitializer.GenericMapping = PspThreadMapping;
    if ( !NT_SUCCESS(ObCreateObjectType(&NameString,
                                     &ObjectTypeInitializer,
                                     (PSECURITY_DESCRIPTOR) NULL,
                                     &PsThreadType
                                     )) ){
        return FALSE;
    }

    // 创建Job对象
    RtlInitUnicodeString(&NameString, L"Job");
    ObjectTypeInitializer.DefaultPagedPoolCharge = 0;
    ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(EJOB);
    ObjectTypeInitializer.DeleteProcedure = PspJobDelete;	// 删除Job
对象回调函数
    ObjectTypeInitializer.CloseProcedure = PspJobClose;		// 关闭Job对象回调函数
    ObjectTypeInitializer.ValidAccessMask = JOB_OBJECT_ALL_ACCESS;
    ObjectTypeInitializer.GenericMapping = PspJobMapping;
    ObjectTypeInitializer.InvalidAttributes = 0;
    if ( !NT_SUCCESS(ObCreateObjectType(&NameString,
                                     &ObjectTypeInitializer,
                                     (PSECURITY_DESCRIPTOR) NULL,
                                     &PsJobType
                                     )) ){
        return FALSE;
    }

    // 初始化进程链表,及保护该链表的互斥对象
    InitializeListHead(&PsActiveProcessHead);
    ExInitializeFastMutex(&PspActiveProcessMutex);

    // 初始化Job链表,及保护该链表的互斥对象
    InitializeListHead(&PspJobList);
    ExInitializeFastMutex(&PspJobListLock);

    // 初始化工作集链表,及保护该链表的互斥对象
    InitializeListHead(&PspWorkingSetChangeHead.Links);
    ExInitializeFastMutex(&PspWorkingSetChangeHead.Lock);

    // 创建CID句柄表
    PspCidTable = ExCreateHandleTable(NULL);
    if ( ! PspCidTable ) {
        return FALSE;
    }
    // ?? 从链表中移除CID表,避免被ExSnapShotHandleTables或调试器扩展命令!
handle枚举到
    ExRemoveHandleTable(PspCidTable);

#if defined(i386)
    // ?? LDT初始化
    if ( !NT_SUCCESS(PspLdtInitialize()) ) {
        return FALSE;
    }
    // ?? VDM初始化
    if ( !NT_SUCCESS(PspVdmInitialize()) ) {
        return FALSE;
    }
#endif

    // 初始化Reaper链表及数据结构
    InitializeListHead(&PsReaperListHead);
    ExInitializeWorkItem(&PsReaperWorkItem, PspReaper, NULL);

// 保存系统启动进程令牌到全局变量PspBootAccessToken
    PspBootAccessToken = PsGetCurrentProcess()->Token;

    // 初始化对象安全属性结构
    InitializeObjectAttributes( &ObjectAttributes,NULL,0,NULL,NULL); 

    // 创建系统进程
    if ( !NT_SUCCESS(PspCreateProcess(
                    &PspInitialSystemProcessHandle,
                    PROCESS_ALL_ACCESS,
                    &ObjectAttributes,
                    0L,
                    FALSE,
                    0L,
                    0L,
                    0L
                    )) ) {
        return FALSE;
    }

    // 增加引用计数,并获取EPROCESS结构,保存到全局变量PsInitialSystemProcess
    if ( !NT_SUCCESS(ObReferenceObjectByHandle(
                                        PspInitialSystemProcessHandle,
                                        0L,
                                        PsProcessType,
                                        KernelMode,
                                        (PVOID *)&PsInitialSystemProcess,
                                        NULL
                                        )) ) {
        return FALSE;
    }

    // 设置当前进程映像名称为"Idle"
    strcpy(&PsGetCurrentProcess()->ImageFileName[0],"Idle");
    // 设置新创建的进程映像名称为"System"
    strcpy(&PsInitialSystemProcess->ImageFileName[0],"System");

    // 创建系统线程用于实现phase1的初始化过程,线程函数为Phase1Initialization
    if ( !NT_SUCCESS(PsCreateSystemThread(
                    &ThreadHandle,
                    THREAD_ALL_ACCESS,
                    &ObjectAttributes,
                    0L,
                    NULL,
                    Phase1Initialization,
                    (PVOID)LoaderBlock
                    )) ) {
        return FALSE;
    }

    // 增加引用计数
    if ( !NT_SUCCESS(ObReferenceObjectByHandle(
                        ThreadHandle,
                        0L,
                        PsThreadType,
                        KernelMode,
                        (PVOID *)&Thread,
                        NULL
                        )) ) {
        return FALSE;
    }

    // 关闭线程句柄
    ZwClose( ThreadHandle );

    return TRUE;
}

在这个过程中,对于几个重要的全局变量进行初始化,例如PspCidTable、
PsActiveProcessHead等。
另外在_OBJECT_TYPE_INITIALIZER有几个成员是由于注册对象操作回调函数的,例如
PsInitPhase0中注册删除进程对象回调函数PspProcessDelete
。因而对于内核对象来说,完全可以从_OBJECT_TYPE_INITIALIZER结构入手HOOK
对应的回调函数实现对内核对象的完全监控。

2.5  [phase1]Ntoskrnl初始化Phase1Initialiation
(这里以wrk v1.2作为参考)
在上一小节中已经提到,在进程管理器进行phase0阶段初始化时会创建新线程
phase1Initialation,来实现phase1阶段的初始化。phase1Initialation函数实现如下:
VOID Phase1Initialization (IN PVOID Context)
{
	Phase1InitializationDiscard (Context);
	MmZeroPageThread();
	return;
}

Phase1Initialization分为两个步骤:
(1)调用Phase1InitializationDiscard真正实现phase1阶段的初始化工作。
(2)当完成phase1阶段的初始化后系统已经启动,线程Phase1Initialization已经完成最重要任务,接下去就以零页面线程的角色继续为系统提供服务。
注:零页面线程,线程优先级为0,循环地从空闲页面链表中获取页面并清0,然后添加到零页面链表中。系统在运行过程会频繁的申请、释放内存,然后释放后的页面一般都不为0。零页面线程就好比是垃圾回收站的加工人员,把回收的垃圾粉刷一遍就变成新的了。这里主要目标是分析Phase1InitializationDiscard的具体实现过程,对于零页面线程不作过多探讨。
这里的分析过程参考了《深入解析Windows操作系统》,关于phase1阶段的初始化流程虽然wrk与书中的描述非常接近,但并不完全一样。另外,我对于这些步骤进行了一定的简化。Phase1阶段的初始化具体如下:
(1)phase 0% -- 10%               
   [1] 调用HalInitSystem,让系统做好准备接受来自设置的中断,并允许中断
  [2] 调用InbvEnableBootDriver引导视频驱动,调用InbvDriverInitialize
初始化视频驱动,并显示启动界面
  [3] 调用PoInitSystem初始化电源管理器(phase0)
   [4] 调用KeSetSystemTime初始化系统时间,设置系统启动时间并保存为全局变量
KeBootTime
   [5] 在多处理器系统上,调用KeStartAllProcessors初始化其他处理器
  [6] 调用ObInitSystem初始化对象管理器(phase1)
   [7] 调用ExInitSystem初始化执行体(phase1)
   [8] 调用KeInitSystem初始化内核(phase1)
   [9] 调用KdInitSystem初始化内核调试器(phase1)
   [10] 调用SeInitSystem初始化安全引用监视器(phase1)
(2)phase 10% -- 15%
   [11] 调用MmInitSystem初始化内存管理器(phase1)
   [12] 把国家语言支持(NLS)表映射到系统空间
  [13] 调用CcInitializeCacheManager初始化缓存管理器
  [14] 调用CmInitSystem1初始化配置管理器
(3)phase 15% -- 20%
   [15] 调用FsRtlInitSystem初始化全局的文件系统驱动程序数据结构
  [16] 调用PpInitSystem初始化即插即用管理器(PNP)(phase1),必须在初始化I/O
管理器之前进行
(4)phase 20% -- 25%
   [17] 调用LpcInitSystem初始化LPC,必须在初始化I/O管理器之前进行
(5)phase 25% -- 75%
   [18] 调用IoInitSystem初始化I/O
管理器,设置进度条为局部更新模式,逐步更新进度条从%到%
(6)phase 75% -- 80%
   [19] 调用MmInitSystem初始化内存管理器(phase2),开启分页机制
(7)phase 80% -- 85%
   [20] 调用PoInitSystem初始化电源管理器(phase1)
   [21] 调用PsInitSystem初始化进程管理器(phase1),可以定位NTDLL.DLL和SMSS.EXE
(8)phase 85% -- 90%
   [22] 调用SeRmInitPhase1初始化性能引用监视器(phase1)
,包括创建命令服务器线程,这个线程将会创建一个名为SeRmCommandPort的LPC
端口,使用这个端口可以接收本地安全认证子系统(LSASS)发送的命令。
(9)phase 90% -- 100%
   [23] 调用RtlCreateUserProcess创建会话管理器进程(SMSS)
(10)phase 100% ------
   [24] 等待会话管理器SMSS 5秒,如果等待超时表明SMSS进程正常启动

根据进度条设置情况大致可以分为10个步骤,可以结合下面Phase1InitializationDiscard的源码来看:
VOID Phase1InitializationDiscard (IN PVOID Context)
{
	PLOADER_PARAMETER_BLOCK LoaderBlock;
	PETHREAD Thread;
	PKPRCB Prcb;
	KPRIORITY Priority;
	NTSTATUS Status;
	UNICODE_STRING SessionManager;
	PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
	PVOID Address;
	SIZE_T Size;
	LARGE_INTEGER UniversalTime;
	LARGE_INTEGER CmosTime;
	LARGE_INTEGER OldTime;
	TIME_FIELDS TimeFields;
	UNICODE_STRING EnvString, NullString, UnicodeSystemDriveString;
	PWSTR Src, Dst;
	BOOLEAN ResetActiveTimeBias;
	HANDLE NlsSection;
	LARGE_INTEGER SectionSize;
	LARGE_INTEGER SectionOffset;
	PVOID SectionBase;
	PVOID ViewBase;
	ULONG CacheViewSize;
	SIZE_T CapturedViewSize;
	ULONG SavedViewSize;
	LONG BootTimeZoneBias;
	PKLDR_DATA_TABLE_ENTRY DataTableEntry;
#ifndef NT_UP
	PMESSAGE_RESOURCE_ENTRY MessageEntry1;
#endif
	PCHAR MPKernelString;
	PCHAR Options;
	PCHAR YearOverrideOption;
	LONG  CurrentYear = 0;
	BOOLEAN NOGUIBOOT;
	BOOLEAN SOS;
	PVOID Environment;
	PRTL_USER_PROCESS_INFORMATION ProcessInformation;

	// ……

	
///////////////////////////////////////////////////////////////////////////////
	//		进入phase1初始化阶段			//
	//////////////////////////////////////////////////////////////////////////////
	InitializationPhase = 1;

	// 提升线程优先级
	Thread = PsGetCurrentThread();
	Priority = KeSetPriorityThread( &Thread->Tcb,MAXIMUM_PRIORITY - 1 );

	// [1] 调用HalInitSystem,让系统做好准备接受来自设置的中断,并允许中断
	LoaderBlock = (PLOADER_PARAMETER_BLOCK)Context;
	if (HalInitSystem(InitializationPhase, LoaderBlock) == FALSE) {
		KeBugCheck(HAL1_INITIALIZATION_FAILED);
	}

	// 根据处理器控制块的LoadOptions成员判断是否以GUI方式启动
	Options = LoaderBlock->LoadOptions ? _strupr(LoaderBlock->LoadOptions) : NULL;
	if (Options) {
		NOGUIBOOT = (BOOLEAN)(strstr(Options, "NOGUIBOOT") != NULL);
	} else {
		NOGUIBOOT = FALSE;
	}

	// [2] 调用引导视频驱动程序(Bootvid.dll)
	InbvEnableBootDriver((BOOLEAN)!NOGUIBOOT);

	// 初始化视频驱动程序
	InbvDriverInitialize(LoaderBlock, 18);

	// 显示启动界面
	// ……

	// [3] 初始化电源管理器(phase0)
	if (!PoInitSystem(0)) {
		KeBugCheck(INTERNAL_POWER_ERROR);
	}

	// ……

	// [4] 初始化系统时间和系统启动时间
	if (ExCmosClockIsSane && HalQueryRealTimeClock(&TimeFields)) {
		// ……
		
		// 设置系统时间
		KeSetSystemTime(&UniversalTime, &OldTime, FALSE, NULL);

		// 通知其他组建系统时间被设置
		PoNotifySystemTimeSet();

		// 设置系统启动的绝对时间
		KeBootTime = UniversalTime;
		KeBootTimeBias = 0;
	}

	// ……

	// [5] 在多处理器系统上,初始化其他处理器
	KeStartAllProcessors();
	// ……


	// [6] 初始化对象管理器(phase1)
	if (!ObInitSystem()) {
		KeBugCheck(OBJECT1_INITIALIZATION_FAILED);
	}

	// [7] 初始化执行体(phase1)
	if (!ExInitSystem()) {
		KeBugCheckEx(PHASE1_INITIALIZATION_FAILED,STATUS_UNSUCCESSFUL,0,1,0);
	}

	// [8] 初始化内核(phase1)
	if (!KeInitSystem()) {
		KeBugCheckEx(PHASE1_INITIALIZATION_FAILED,STATUS_UNSUCCESSFUL,0,2,0);
	}

	// [9] 初始化内核调试器(phase1)
	if (!KdInitSystem(InitializationPhase, NULL)) {
		KeBugCheckEx(PHASE1_INITIALIZATION_FAILED,STATUS_UNSUCCESSFUL,0,3,0);
	}

	// [10] 初始化安全引用监视器(phase1)
	if (!SeInitSystem()) {
		KeBugCheck(SECURITY1_INITIALIZATION_FAILED);
	}

	// 设置进度条到%
	InbvUpdateProgressBar(10);

	Status = CreateSystemRootLink(LoaderBlock);
	if ( !NT_SUCCESS(Status) ) {
		KeBugCheckEx(SYMBOLIC_INITIALIZATION_FAILED,Status,0,0,0);
	}

	// [11] 初始化内存管理器(phase1)
	if (MmInitSystem(1, LoaderBlock) == FALSE) {
		KeBugCheck(MEMORY1_INITIALIZATION_FAILED);
	}

	// [12] 把国家语言支持(NLS)表映射到系统空间
	// ……

	// [13] 初始化缓存管理器
	if (!CcInitializeCacheManager()) {
		KeBugCheck(CACHE_INITIALIZATION_FAILED);
	}

	// [14] 初始化配置管理器
	if (!CmInitSystem1(LoaderBlock)) {
		KeBugCheck(CONFIG_INITIALIZATION_FAILED);
	}

	// ??
	CcPfInitializePrefetcher();

	// 设置进度条到%
	InbvUpdateProgressBar(15);

	// 计算时区偏差
	// ……

	// [15] 初始化全局的文件系统驱动程序数据结构
	if (!FsRtlInitSystem()) {
		KeBugCheck(FILE_INITIALIZATION_FAILED);
	}

	// 在PNP初始化时需要使用range list,因而在PNP初始化之前需要初始化range list
	RtlInitializeRangeListPackage();
	HalReportResourceUsage();
	KdDebuggerInitialize1(LoaderBlock);

	// [16] 初始化即插即用管理器(PNP)(phase1),必须在初始化I/O管理器之前进行
	if (!PpInitSystem()) {
		KeBugCheck(PP1_INITIALIZATION_FAILED);
	}

	// 设置进度条到%
	InbvUpdateProgressBar(20);

	// [17] 初始化LPC,必须在初始化I/O管理器之前进行
	if (!LpcInitSystem()) {
		KeBugCheck(LPC_INITIALIZATION_FAILED);
	}

	// 此时系统已经处于运行阶段
	ExInitSystemPhase2();

	// [18] 初始化I/O管理器,设置进度条为局部更新模式,逐步更新进度条从%到%
	InbvSetProgressBarSubset(25, 75);
	if (!IoInitSystem(LoaderBlock)) {
		KeBugCheck(IO1_INITIALIZATION_FAILED);
	}

	// 撤消局部更新模式
	InbvSetProgressBarSubset(0, 100);
	CmpInitSystemVersion(6, NULL);

	// [19] 初始化内存管理器(phase2),开启分页机制
	MmInitSystem(2, LoaderBlock);

	// 设置进度条到%
	InbvUpdateProgressBar(80);

	// [20] 初始化电源管理器(phase1)
	if (!PoInitSystem(1)) {
		KeBugCheck(INTERNAL_POWER_ERROR);
	}

	// [21] 初始化进程管理器(phase1)
	// 由于SystemRoot已经被定义,可以定位NTDLL.DLL和SMSS.EXE
	if (PsInitSystem(1, LoaderBlock) == FALSE) {
		KeBugCheck(PROCESS1_INITIALIZATION_FAILED);
	}

	// 设置进度条到%
	InbvUpdateProgressBar(85);

	if (LoaderBlock == KeLoaderBlock) {
		KeLoaderBlock = NULL;
	}

	// 释放加载器Ntldr参数信息块
	MmFreeLoaderBlock (LoaderBlock);
	LoaderBlock = NULL;
	Context = NULL;

	// [22] 初始化性能引用监视器(phase1),包括创建命令服务器线程,
	// 这个线程将会创建一个名为SeRmCommandPort的LPC端口。
	// 使用这个端口可以接收本地安全认证子系统(LSASS)发送的命令。
	if (!SeRmInitPhase1()) {
		KeBugCheck(REFMON_INITIALIZATION_FAILED);
	}

	// 设置进度条到%
	InbvUpdateProgressBar(90);

	// 设置会话管理器子系统(SMSS)进程信息
	// ……

	// [23] 创建会话管理器进程(SMSS)
	Status = RtlCreateUserProcess(
		&SessionManager,
		OBJ_CASE_INSENSITIVE,
		RtlDeNormalizeProcessParams( ProcessParameters ),
		NULL,
		NULL,
		NULL,
		FALSE,
		NULL,
		NULL,
		ProcessInformation);
	// ……

	// 设置进度条到%
	InbvUpdateProgressBar(100);

	InbvEnableDisplayString(TRUE);

	// [24] 等SMSS.EXE 5秒,如果等待超时表明SMSS进程正常启动
	OldTime.QuadPart = Int32x32To64(5, -(10 * 1000 * 1000));
	Status = ZwWaitForSingleObject(
		ProcessInformation->Process,
		FALSE,
		&OldTime
		);
	if (Status == STATUS_SUCCESS) {
		KeBugCheck(SESSION5_INITIALIZATION_FAILED);
	}

	// 空间释放与关闭句柄
	// ……

	// 系统启动成功后设置InitializationPhase为
	InitializationPhase += 1;
}

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
免费 8
支持
分享
最新回复 (70)
雪    币: 321
活跃值: (271)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
2
哈,沙发学习。
2008-3-23 00:06
0
雪    币: 1946
活跃值: (243)
能力值: (RANK:330 )
在线值:
发帖
回帖
粉丝
3
板 凳 学 习
2008-3-23 01:35
0
雪    币: 112
活跃值: (48)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
4
好文学习了!
2008-3-23 09:23
0
雪    币: 615
活跃值: (1127)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
2008-3-23 09:28
0
雪    币: 485
活跃值: (12)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
6
趴在地上也要学习。。
2008-3-23 09:29
0
雪    币: 70
活跃值: (74)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
好好学习  天天向上!
2008-3-23 09:56
0
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
8
膜拜+1学习123456
2008-3-23 10:30
0
雪    币: 8017
活跃值: (2506)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
好,学习学习
2008-3-23 10:39
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
10
学习.哈哈.

支持北极星大牛~
2008-3-23 11:04
0
雪    币: 271
活跃值: (18)
能力值: ( LV12,RANK:370 )
在线值:
发帖
回帖
粉丝
11
很好。搬凳子学习...
2008-3-23 12:39
0
雪    币: 1990
活跃值: (1059)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
辛苦楼主了 膜拜 +学习
看的出来是个很累的体力活
2008-3-23 13:59
0
雪    币: 333
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
看得我云里雾里
2008-3-23 16:38
0
雪    币: 248
活跃值: (42)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
好文,向LZ学习
2008-3-24 13:31
0
雪    币: 215
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
瞻望中……^_^
2008-3-25 14:50
0
雪    币: 486
活跃值: (13)
能力值: ( LV9,RANK:430 )
在线值:
发帖
回帖
粉丝
16
虽然来晚了,但强烈支持啊!!
2008-3-26 12:58
0
雪    币: 213
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
强,赞楼主共享精神
2008-3-27 00:17
0
雪    币: 202
活跃值: (146)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
比较想下载到Win2Ksrc,可惜没赶上年代……
2008-3-27 13:16
0
雪    币: 266
活跃值: (50)
能力值: ( LV9,RANK:290 )
在线值:
发帖
回帖
粉丝
19
老大的文章,一定要支持!!
2008-3-27 20:58
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
hehe 系列来了 拜读
2008-3-28 10:47
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
强帖留名啊。。。
2008-3-28 17:07
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
相当的的强悍。。。
留名强帖
2008-3-28 22:12
0
雪    币: 397
活跃值: (352)
能力值: ( LV9,RANK:410 )
在线值:
发帖
回帖
粉丝
23
猛贴,学习~~~
2008-3-29 09:26
0
雪    币: 269
活跃值: (25)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
24
支持!最近在读wrk1.2  感觉很困难...

希望LZ能写下去啊 谢谢啦~
2008-6-3 19:23
0
雪    币: 117
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
这个源码哪里有?
2008-6-4 08:14
0
游客
登录 | 注册 方可回帖
返回
//