古月不傲 发表于 2020-5-29 13:34

内核调试引擎简介

本帖最后由 古月不傲 于 2020-5-29 13:40 编辑

内核调试引擎就是调试器与被调试内核之间的桥梁。
Windows启动过程中会调用KdInitSystem函数让内核调试引擎初始化。
当系统分发异常时会调用KiDebugRoutinue变量所指向的函数(KdpTrap或KdpStub)。
系统的时间更新函数KeUpdateRunTime会调用KdCheckForDebugBreak来检查调试器是否发出了中断命令。
内核调试引擎使用一个数组KdpBreakpointTable来记录断点的插入、移除。
内核调试API:调试器与内核调试引擎通过API接口通信,调用API要通过发送数据包、数据包中含有访问服务号和参数,返回结果也是通过数据包回递。
系统内核控制函数:KdEnterDebugger负责将系统内核中断到调试器,KdExitDebugger负责恢复系统运行。
管理函数:KdEnableDebugger和KdDisableDebugger用于启用或禁止内核调试引擎,KdChangeOption修改选项。

WindowsXP以前,内核调试引擎的所有函数位于NTOSKRNL,EXE中。
之后,内核调试引擎的通信部分被拆分到了一个单独的DLL模块中,KDCOM.DLL。
KDCOM.DLL也会引用NTOSKRNL中导出的符号和函数,因此两者是互相依赖关系,
当NTLDR加载任意其一时会加载两者。
KDCOM.DLL中最重要的两个函数KdSendPacket、KdReceivePacket用于发包和收包。
通信方式分为:串口、1394、USB,分别对于KDCOM.DLL、KD1394.DLL、KDUSB.DLL,一般采用第一种方式,主要原因是内核调试通信协议是面向字节的,其他两种面向数据包。

Windows启动过程(简单概述 不像Windows内核原理实现那么详细...)
计算机开机后,先执行BIOS或EFI对硬件进行检测和平台初始化后,将控制权转交给磁盘上的引导程序,NTLDR。
接着NTLDR首先对CPU做必要的初始化工作,实模式到保护模式,启用分页机制,通过配置文件boot.ini或BCD(boot config data)得到Windows系统目录并加载内核模块,加载ntoskrnl或ntkrnlpa并检查它的导入表加载所依赖的文件,其中包括内核调试通信DLL(KDCOM、KD1394或KDUSB)。加载程序会根据启动配置加载其中的一个,并将模块统一成KDCOM。

而后NTLDR读取注册表的System Hive加载其中键值为0的驱动程序,包括磁盘驱动程序。
完成工作后,NTLDR从内核文件的PE头找到它的入口函数,KiSystemStartup函数,
调用这个函数。调用时传递LOADER_PARAMETER_BLOCK数据结构,内核文件得到控制权开始执行。
KiSystemStartup执行过程:
1、调用HalInitializeProcessor初始化CPU
2、调用KdInitSystem初始化内核调试引擎
3、调用KiInitializeKernel初始化内核->
(1) 调用KiInitSystem初始化系统全局数据结构
(2) 调用KiInitializeProcess创建并初始化Idle进程
(3) 调用KeInitializeThread初始化Idle线程
(4) 调用ExpInitializeExecutive对执行体层(管理层)进行阶段0初始化
① 调用MmInitSystem构建页表和内存管理器的基本数据结构
② 调用ObInitSystem建立名称空间
③ 调用SeInitSystem初始化token对象
④ 调用PsInitSytstem对进程管理器做阶段0初始化
⑤ 调用PpInitSystem让即插即用管理器初始化设备链表等等

KiInitializeKernel返回后,KiSystemStartuP降低IRQL至DISPATCH_LEVEL(非分页内存线程切换级别),然后跳到KiIdleLoop,退化为Idle进程中的第一个Idle线程。
对于多CPU系统,每个CPU都会执行KiInitializeKernel,但只有第一个CPU执行所有初始化工作,其他CPU只负责初始化Idle部分,因为每个CPU都有Idle线程。
全局变量KeNumberProcessors标识CPU的个数。
PsInitSytstem阶段0过程:
1、定义进程和线程对象类型
2、建立记录系统中所有进程的链表结构,并用PsAvtiveProcessHead全局变量指向这个链表,此后Windbg!process命令才能工作
3、为初始的进程创建一个进程对象PsIdleProcess,并命名为Idle
4、创建系统进程和线程,并将Phase1Initialization函数作为线程起始地址
Phase1Initialization虽然以创建,但是由于IRQL是2,所以要等到KiInitializeKernel返回后。
阶段1初始化过程:
调用KeStartAllProcessors初始化所有CPU,构建CPU的数据结构
调用HalStartNextProcessor将该结构赋值给下一个CPU
再次调用KdInitSystem,并调用KdDebuggerInitialize1初始化KDCOM等
阶段1结束前,创建第一个进程,会话管理器SMSS.EXE
SMSS.EXE初始化Windows子系统,创建Windows子系统和登录进程Winlogon.exe
Winlogon.exe创建LSASS进程和系统服务进程Services.exe并显示登录画面,启动基本完成。


第一次调用KdInitSystem
1、初始化调试器数据链表,使用全局变量KdpDebbugerDataListHead指向这个链表
2、初始化KdDebuggerDataBlocK数据结构,改结构包含内核基地址、模块链表指针、调试器数据链表指针等,调试器需要读取这些信息以了解目标系统。
typedef struct _KDDEBUGGER_DATA32 ->KdDebuggerDataBlocK
{
    DBGKD_DEBUG_DATA_HEADER32 Header;
    ULONG KernBase;
    ULONG BreakpointWithStatus;
    ULONG SavedContext;
    USHORT ThCallbackStack;
    USHORT NextCallback;
    USHORT FramePointer;
    USHORT PaeEnabled:1;
    ULONG KiCallUserMode;
    ULONG KeUserCallbackDispatcher;
    ULONG PsLoadedModuleList;
    ULONG PsActiveProcessHead;
    ULONG PspCidTable;
    ULONG ExpSystemResourcesList;
    ULONG ExpPagedPoolDescriptor;
    ULONG ExpNumberOfPagedPools;
    ULONG KeTimeIncrement;
    ULONG KeBugCheckCallbackListHead;
    ULONG KiBugcheckData;
    ULONG IopErrorLogListHead;
    ULONG ObpRootDirectoryObject;
    ULONG ObpTypeObjectType;
    ULONG MmSystemCacheStart;
    ULONG MmSystemCacheEnd;
    ULONG MmSystemCacheWs;
    ULONG MmPfnDatabase;
    ULONG MmSystemPtesStart;
    ULONG MmSystemPtesEnd;
    ULONG MmSubsectionBase;
    ULONG MmNumberOfPagingFiles;
    ULONG MmLowestPhysicalPage;
    ULONG MmHighestPhysicalPage;
    ULONG MmNumberOfPhysicalPages;
    ULONG MmMaximumNonPagedPoolInBytes;
    ULONG MmNonPagedSystemStart;
    ULONG MmNonPagedPoolStart;
    ULONG MmNonPagedPoolEnd;
    ULONG MmPagedPoolStart;
    ULONG MmPagedPoolEnd;
    ULONG MmPagedPoolInformation;
    ULONG MmPageSize;
    ULONG MmSizeOfPagedPoolInBytes;
    ULONG MmTotalCommitLimit;
    ULONG MmTotalCommittedPages;
    ULONG MmSharedCommit;
    ULONG MmDriverCommit;
    ULONG MmProcessCommit;
    ULONG MmPagedPoolCommit;
    ULONG MmExtendedCommit;
    ULONG MmZeroedPageListHead;
    ULONG MmFreePageListHead;
    ULONG MmStandbyPageListHead;
    ULONG MmModifiedPageListHead;
    ULONG MmModifiedNoWritePageListHead;
    ULONG MmAvailablePages;
    ULONG MmResidentAvailablePages;
    ULONG PoolTrackTable;
    ULONG NonPagedPoolDescriptor;
    ULONG MmHighestUserAddress;
    ULONG MmSystemRangeStart;
    ULONG MmUserProbeAddress;
    ULONG KdPrintCircularBuffer;
    ULONG KdPrintCircularBufferEnd;
    ULONG KdPrintWritePointer;
    ULONG KdPrintRolloverCount;
    ULONG MmLoadedUserImageList;
} KDDEBUGGER_DATA32, *PKDDEBUGGER_DATA32;
根据LOADER_PARAMETER_BLOCK结构寻找调试有关的选项,保存上下文。
3、初始化全局变量
(1) KdPitchDebugger:布尔类型,标识发生异常时是否需要被内核调试,当启动项包含/NODEBUG选项时,该变量为TURE。
(2) KdDebuggerEnabled:布尔类型,标识内核调试是否被启用,当启动项包含/DEBUG或/DEBUGPORT且不包含/NODEBUG时,该变量为TRUE。
(3) KiDebugRoutine:函数指针类型,用来记录内核调试引擎的异常处理回调函数,当内核调试引擎活动时,指向KdpTrap,否则指向KdpStub。
(4) KdpBreakpointTable:结构数组类型,用来记录代码断点,每个元素为一个BREAKPOINT_ENTRY数据结构,用来描述断点的信息。
第二次调用KdInitSystem
调用KeQueryPerformanceCounter来对全局变量KdPerFormanceCounterRate(性能计数器的频率)进行初始化,然后返回。

Meng_X 发表于 2020-5-29 13:40

学习了
https://assets.baklib.com/5467817c-6514-4e6d-bb4b-386f8de94d26/RocksyLight%2011590729794245.gif

xiahhhr 发表于 2020-5-31 20:48

感谢感谢,看完感觉明了不少
页: [1]
查看完整版本: 内核调试引擎简介