《Undocumented Windows 2000 Secrets》翻译 --- 第四章(5)

来源:互联网 发布:银行大数据产品营销 编辑:程序博客网 时间:2024/04/29 02:08

第四章  探索Windows 2000的内存管理机制

翻译:Kendiv( fcczj@263.net )

更新:Sunday, February 17, 2005

 

声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。

 

IOCTL函数SPY_IO_INTERRUPT

SPY_IO_INTERRUP类似于SPY_IO_SEGEMT,不过该函数仅影响存储在系统中断描述符表(IDT)的中断描述符,不会涉及LDTGDT描述符。IDT最多可容纳256个描述符,这些描述符可用来描述任务门、中断门或陷阱门(参见Intel 1999c, pp. 5-11ff)。顺便说一下,中断和陷阱在本质上十分相似,二者只存在微小的差异:在进入一个中断处理例程后,总是会屏蔽其他中断;而进入陷阱处理例程却不会修改中断标志。SPY_IO_INTERRUPT的调用者提供一个0255之间的中断号,该中断号将位于输入缓冲区中,而一个SPY_INTERRUPT结构将作为输出数据被存放到输出缓冲区中,如果成功返回,该结构中将包含对应的中断处理例程的属性。由Dispatcher调用的帮助函数SpyOutputInterrupt()只是一个简单的外包函数,它实际上调用SpyInterrupt()函数并且将需要返回的数据复制到输出缓冲区中。列表4-18给出了这两个函数,以及它们操作的SPY_INTERRUPT结构。稍后一些,SpyInterrupt()函数将填充如下项目:

l         Selector  用来指定一个任务状态段(Task-State Segment, TSS)或代码段(Code Segment)的选择器。代码段选择器用来确定中断或陷阱处理例程所在的段。

 

l         Gate  用来表示一个64位的任务门、中断门或陷阱门描述符,由Selector确定其地址。

 

l         Segment  包含段的属性,该段的地址由前面的Gate给出。

 

l         pOffset  指定中断或陷阱处理例程的入口地址相对基地址的偏移量。这里的基地址是指中断或陷阱处理例程所在代码段的起始地址。因为任务门不包含偏移量,所以,如果输入的选择器指向一个TSS,则忽略该成员。

 

l         fOk  一个标志变量,用来指示SPY_INTERRUPT结构中的数据是否有效。

 

通常情况下,TSS被用来保证一个错误情况可以被一个有效的任务处理。这是一个特殊的系统段类型(system segment type),它可以保存104个字节的进程状态信息,该信息在任务切换时,用来进行任务的恢复,如4-3所示。当与任务相关的中断发生时,CPU总是强制切换该任务,并将所有的CPU寄存器保存到TSS中。Windows 2000在中断位置0x02(非屏蔽中断[NMI]0x08[Double Fault]0x12[堆栈段故障])处保存任务门。剩余的位置指向中断处理例程。不使用的中断由一个哑元例程---KiUnexpectedInterruptNNN()处理,这里的NNN为一个十进制数。这些哑元例程最后都汇集到内部函数KiEndUnexpectedRange(),在这里,这些例程将依次进入KiUnexpectedInterruptTail()

 

typedef struct _SPY_INTERRUPT

    {

    X86_SELECTOR Selector;

    X86_GATE     Gate;

    SPY_SEGMENT  Segment;

    PVOID        pOffset;

    BOOL         fOk;

    }

    SPY_INTERRUPT, *PSPY_INTERRUPT, **PPSPY_INTERRUPT;

 

#define SPY_INTERRUPT_ sizeof (SPY_INTERRUPT)

 

// -----------------------------------------------------------------

NTSTATUS SpyOutputInterrupt (DWORD  dInterrupt,

                             PVOID  pOutput,

                             DWORD  dOutput,

                             PDWORD pdInfo)

    {

    SPY_INTERRUPT si;

 

    SpyInterrupt (dInterrupt, &si);

 

    return SpyOutputBinary (&si, SPY_INTERRUPT_,

                            pOutput, dOutput, pdInfo);

    }

 

// -----------------------------------------------------------------

BOOL SpyInterrupt (DWORD          dInterrupt,

                   PSPY_INTERRUPT pInterrupt)

    {

    BOOL fOk = FALSE;

 

    if (pInterrupt != NULL)

        {

        if (dInterrupt <= X86_SELECTOR_LIMIT)

            {

            fOk = TRUE;

 

            if (!SpySelector (X86_SEGMENT_OTHER,

                              dInterrupt << X86_SELECTOR_SHIFT,

                              &pInterrupt->Selector))

                {

                fOk = FALSE;

                }

            if (!SpyIdtGate  (&pInterrupt->Selector,

                              &pInterrupt->Gate))

                {

                fOk = FALSE;

                }

            if (!SpySegment  (X86_SEGMENT_OTHER,

                              pInterrupt->Gate.Selector,

                              &pInterrupt->Segment))

                {

                fOk = FALSE;

                }

            pInterrupt->pOffset = SpyGateOffset (&pInterrupt->Gate);

            }

        else

            {

            RtlZeroMemory (pInterrupt, SPY_INTERRUPT_);

            }

        pInterrupt->fOk = fOk;

        }

    return fOk;

    }

 

// -----------------------------------------------------------------

PVOID SpyGateOffset (PX86_GATE pGate)

    {

    return (PVOID) (pGate->Offset1 | (pGate->Offset2 << 16));

    }

列表4-18.  查询中断属性

 

4-3.    任务状态段(TSS)中的CPU状态域

偏移量

位数

ID

     

0x00

16

 

前一个任务的链接

0x04

32

ESP0

Ring0级的堆栈指针寄存器

0x08

16

SS0

Ring0级的堆栈段寄存器

0x0C

32

ESP1

Ring1级的堆栈指针寄存器

0x10

16

SS1

Ring1级的堆栈段寄存器

0x14

32

ESP2

Ring2级的堆栈指针寄存器

0x18

16

SS2

Ring2级的堆栈段寄存器

0x1C

32

CR3

页目录基址寄存器(PDBR

0x20

32

EIP

指令指针寄存器

0x24

32

EFLAGS

处理器标志寄存器

0x28

32

EAX

通用寄存器

0x2C

32

ECX

通用寄存器

0x30

32

EDX

通用寄存器

0x34

32

EBX

通用寄存器

0x38

32

ESP

堆栈指针寄存器

0x3C

32

EBP

基地址指针寄存器

0x40

32

ESI

源索引寄存器

0x44

32

EDI

目标索引寄存器

0x48

16

ES

扩展段寄存器

0x4C

16

CS

代码段寄存器

0x50

16

SS

堆栈段寄存器

0x54

16

DS

数据段寄存器

0x58

16

FS

附加的数据段寄存器#1

0x5C

16

GS

附加的数据段寄存器#2

0x60

16

LDT

本地描述符标的段选择器

0x64

1

1

调试陷阱标志

0x66

16

 

I/O Map的基地址

0x68

-

 

CPU状态信息结束

 

SpyInterrupt()调用的SpySegment()SpySelector()函数已经在列表4-5列表4-16中给出。SpyGateOffset()位于列表4-18的末尾,它的工作和SpyDescriptorBase()SpyDescriptorLimit()类似,从X86_GATE结构中取出Offset1Offset2位域,并适当的组织它们以构成一个32位地址。SpyIdtGaet()定义于列表4-19。它与SpyDescriptor()十分类似。汇编指令SIDT存储一个48位的值,该值就是CPUIDT寄存器的内容,它由一个16位的表大小限制值和IDT32位线性基地址构成。列表4-19中的剩余代码将选择器的描述符索引和IDT的大小限制值进行比较,如果OK,则对应的中断描述符将被复制到调用者提供的X86_GATE结构中。否则,门结构的所有成员都将被设置为0

 

BOOL SpyIdtGate (PX86_SELECTOR pSelector,

                 PX86_GATE     pGate)

    {

    X86_TABLE idt;

    PX86_GATE pGates = NULL;

    BOOL      fOk    = FALSE;

 

    if (pGate != NULL)

        {

        if (pSelector != NULL)

            {

            __asm

                {

                sidt idt.wLimit

                }

            if ((pSelector->wValue & X86_SELECTOR_INDEX)

                <= idt.wLimit)

                {

                pGates = idt.pGates;

                }

            }

        if (pGates != NULL)

            {

            RtlCopyMemory (pGate,

                           pGates + pSelector->Index,

                           X86_GATE_);

            fOk = TRUE;

            }

        else

            {

            RtlZeroMemory (pGate, X86_GATE_);

            }

        }

    return fOk;

    }

列表4-19.  获取IDT门的值

 

IOCTL函数SPY_IO_PHYSICAL

SPY_IO_PHYSICAL函数很简单,它完全依赖于ntoskrnl.exe导出的MmGetPhysicalAddress()函数。该IOCTL函数通过简单的调用SpyInputPointer()(参见列表4-10)来获取需要转换的线性地址,然后让MmGetPhysicalAddress()查找对应的物理地址,最后将结果作为PHYSICAL_ADDRESS结构返回给调用者。注意,PHYSICAL_ADDRESS是一个64位的LARGE_INTEGER。在大多数i386系统上,其高32位总是为0。不过,若系统启用了物理地址扩展(Physical Address Extension, PAE),并且安装的内存大于4GB,这些位可能就是非0值了。

 

MmGetPhysicalAddress()使用起始于线性地址0xC0000000PTE数组,来进行物理地址的查找。其基本的工作机制如下:

l         如果线性地址位于:0x80000000----0x9FFFFFFF,则其高3位将被设为零,最后产生的物理地址位于:0x00000000-----0x1FFFFFFF

 

l         否则,线性地址的高20位将作为PTE数组(起始于0xC0000000)的索引。

 

l         如果目标PTEP位已被设置,这表示其对应得数据页存在于物理内存中。除了20位的PFN外,所有的PTE位都可以被剥离出来,线性地址最低的12位将作为在数据页中的偏移量被加到最后的32位物理地址上去。

 

l         如果数据页没有存在于物理内存中,MmGetPhysicalAddress()返回0

 

MmGetPhysicalAddress()假设内核内存范围:0x80000000----0x9FFFFFF之外的所有线性地址都使用4KB的页。而其他函数,如MmIsAddressValid(),会首先加载线性地址的PDE,并且检查该PDEPS位,以检查页大小是4KB还是4MB。这是一个非常通用的方法,可以处理任意的内存配置。不过上述两个函数都会返回正确的结果,这是因为Windows 2000仅针对内存范围:0x80000000-----0x9FFFFFFF,使用4MB页。不过某些内核API函数,显然设计的比其它的灵活许多。

 

IOCTL函数SPY_IO_CPU_INFO

个别的CPU指令仅对运行于Ring 0级的代码有效,Ring 0是五个特权级(Intel系列的CPU只支持两个特权级:Ring0Ring3)中级别最高的一个。用Windows术语来说,Ring 0意味着内核模式(Kernel-mode)。这些被禁止的指令有:读取控制寄存器CR0CR2CR3的内容。因为这些寄存器中保存着非常有趣的信息,应用程序可能想要找到一个办法来访问它们,解决方案就是SPY_IO_CPU_INFO函数。如列表4-20所示,IOCTL处理例程调用的SpyOutputCpuInfo()函数使用了一些嵌入式汇编来读取控制寄存器,以及其他一些有价值的信息,比如IDT的内容,GDTLDT寄存器以及存储在寄存器CSDSESFSGSSSTR中的段选择器。任务寄存器(Task Register, TR)还包含一个涉及当前任务的TSS的选择器。

 

typedef struct _SPY_CPU_INFO

    {

    X86_REGISTER cr0;

    X86_REGISTER cr2;

    X86_REGISTER cr3;

    SPY_SEGMENT  cs;

    SPY_SEGMENT  ds;

    SPY_SEGMENT  es;

    SPY_SEGMENT  fs;

    SPY_SEGMENT  gs;

    SPY_SEGMENT  ss;

    SPY_SEGMENT  tss;

    X86_TABLE    idt;

    X86_TABLE    gdt;

    X86_SELECTOR ldt;

    }

    SPY_CPU_INFO, *PSPY_CPU_INFO, **PPSPY_CPU_INFO;

 

#define SPY_CPU_INFO_ sizeof (SPY_CPU_INFO)

 

// -----------------------------------------------------------------

 

NTSTATUS SpyOutputCpuInfo (PVOID  pOutput,

                           DWORD  dOutput,

                           PDWORD pdInfo)

    {

    SPY_CPU_INFO  sci;

    PSPY_CPU_INFO psci = &sci;

 

    __asm

        {

        push    eax

        push    ebx

        mov     ebx, psci

 

        mov     eax, cr0

        mov     [ebx.cr0], eax

 

        mov     eax, cr2

        mov     [ebx.cr2], eax

 

        mov     eax, cr3

        mov     [ebx.cr3], eax

 

        sidt    [ebx.idt.wLimit]

        mov     [ebx.idt.wReserved], 0

 

        sgdt    [ebx.gdt.wLimit]

        mov     [ebx.gdt.wReserved], 0

 

        sldt    [ebx.ldt.wValue]

        mov     [ebx.ldt.wReserved], 0

 

        pop     ebx

        pop     eax

        }

    SpySegment (X86_SEGMENT_CS,  0, &sci.cs);

    SpySegment (X86_SEGMENT_DS,  0, &sci.ds);

    SpySegment (X86_SEGMENT_ES,  0, &sci.es);

    SpySegment (X86_SEGMENT_FS,  0, &sci.fs);

    SpySegment (X86_SEGMENT_GS,  0, &sci.gs);

    SpySegment (X86_SEGMENT_SS,  0, &sci.ss);

    SpySegment (X86_SEGMENT_TSS, 0, &sci.tss);

 

    return SpyOutputBinary (&sci, SPY_CPU_INFO_,

                            pOutput, dOutput, pdInfo);

    }

列表4-20.  查询CPU状态信息

 

可使用帮助函数SpySegement()获取段选择器,在前面,我们已讨论过该函数。参见列表4-15

 

………………待续…………………

原创粉丝点击