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

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

翻译:Kendiv ([email protected] )

更新: Sunday, February 17, 2005

 

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

 

尽管Spy设备使用可缓冲的I/O,但它还是会检查输入/输出缓冲区的有效性。因为客户端程序传入的数据可能比所需的少或者提供的缓冲区不够容纳输出数据。系统不能捕获这些语意错误,因为它不知道在一次IOCTL传输中所传输的数据的类型。因此,SpyDispatcher()调用帮助函数SpyInput*()SpyOutput*()来从I/O缓冲区中复制或写入数据。这些函数仅在缓冲区大小与操作的需求相匹配时才执行。列表4-10给出了基本的输入函数,列表4-11给出了基本的输出函数。SpyInputBinary()SpyOutputBinary()被广泛的使用,它们测试缓冲区的大小,如果OK,则使用Windows 2000运行时库函数RtlCopyMemory()复制被请求的数据。剩余的函数只是上述两个基本函数的简单外包,用来操作常见的数据类型DWORDBOOLPVOIDHANDLE等。SpyOutputBlock()复制由调用者在SPY_MEMORY_BLOCK结构中指定的数据块,当然这需要首先验证请求范围内的字节都是可读的。如果传入的输入缓冲区的大小不正确,SpyInput*()函数将返回STATUS_INVALID_BUFFER_SIZE,如果输出缓冲区比需要的小,SpyOutput*()函数将返回STATUS_BUFFER_TOO_SMALL

 

 

NTSTATUS SpyInputBinary (PVOID  pData,

                         DWORD  dData,

                         PVOID  pInput,

                         DWORD  dInput)

    {

    NTSTATUS ns = STATUS_INVALID_BUFFER_SIZE;

 

    if (dData <= dInput)

        {

        RtlCopyMemory (pData, pInput, dData);

        ns = STATUS_SUCCESS;

        }

    return ns;

    }

 

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

 

NTSTATUS SpyInputDword (PDWORD pdValue,

                        PVOID  pInput,

                        DWORD  dInput)

    {

    return SpyInputBinary (pdValue, DWORD_, pInput, dInput);

    }

 

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

 

NTSTATUS SpyInputBool (PBOOL  pfValue,

                       PVOID  pInput,

                       DWORD  dInput)

    {

    return SpyInputBinary (pfValue, BOOL_, pInput, dInput);

    }

 

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

 

NTSTATUS SpyInputPointer (PPVOID ppAddress,

                          PVOID  pInput,

                          DWORD  dInput)

    {

    return SpyInputBinary (ppAddress, PVOID_, pInput, dInput);

    }

 

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

 

NTSTATUS SpyInputHandle (PHANDLE phObject,

                         PVOID   pInput,

                         DWORD   dInput)

    {

    return SpyInputBinary (phObject, HANDLE_, pInput, dInput);

    }

列表4-10.  IOCTL缓冲区中读取输入数据

 

 

NTSTATUS SpyOutputBinary (PVOID  pData,

                          DWORD  dData,

                          PVOID  pOutput,

                          DWORD  dOutput,

                          PDWORD pdInfo)

    {

    NTSTATUS ns = STATUS_BUFFER_TOO_SMALL;

 

    *pdInfo = 0;

 

    if (dData <= dOutput)

        {

        RtlCopyMemory (pOutput, pData, *pdInfo = dData);

        ns = STATUS_SUCCESS;

        }

    return ns;

    }

 

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

 

NTSTATUS SpyOutputBlock (PSPY_MEMORY_BLOCK psmb,

                         PVOID             pOutput,

                         DWORD             dOutput,

                         PDWORD            pdInfo)

    {

    NTSTATUS ns = STATUS_INVALID_PARAMETER;

 

    if (SpyMemoryTestBlock (psmb->pAddress, psmb->dBytes))

        {

        ns = SpyOutputBinary (psmb->pAddress, psmb->dBytes,

                              pOutput, dOutput, pdInfo);

        }

    return ns;

    }

 

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

 

NTSTATUS SpyOutputDword (DWORD  dValue,

                         PVOID  pOutput,

                         DWORD  dOutput,

                         PDWORD pdInfo)

    {

    return SpyOutputBinary (&dValue, DWORD_,

                            pOutput, dOutput, pdInfo);

    }

 

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

 

NTSTATUS SpyOutputBool (BOOL   fValue,

                        PVOID  pOutput,

                        DWORD  dOutput,

                        PDWORD pdInfo)

    {

    return SpyOutputBinary (&fValue, BOOL_,

                            pOutput, dOutput, pdInfo);

    }

 

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

 

NTSTATUS SpyOutputPointer (PVOID  pValue,

                           PVOID  pOutput,

                           DWORD  dOutput,

                           PDWORD pdInfo)

    {

    return SpyOutputBinary (&pValue, PVOID_,

                            pOutput, dOutput, pdInfo);

    }

列表4-11.  IOCTL的缓冲区中写入数据

 

你可能注意到列表4-7中的SpyDispatcher()还引用了其他的SpyInput*()SpyOutput*()函数。尽管这些函数最终还是调用SpyInputBinary()SpyOutputBinary(),但它们还是比列表4-104-11中的基本函数要复杂些,因此,稍后我们在讨论它们。现在,让我们从SpyDispatcher()开始,一步步的分析它的switch/case语句。

 

IOCTL函数 SPY_IO_VERSION_INFO

IOCTLSPY_IO_VERSION_INFO函数用有关Spy驱动自身的数据填充调用者提供的SPY_VERSION_INFO结构。该功能不需要输入参数,需要使用SpyOutputVersionInfo()帮助函数。列表4-12给出了该函数和SPY_VERSION_INFO结构,该函数很简单,它将dVersion成员设置为SPY_VERSION常量(当前是100,表示V1.00),该常量定义于w2k_spy.h中。然后复制驱动程序的符号化名称,即字符串常量DRV_NAME(“SBS Windows 2000 Spy Device”)到awName成员。通过整除dVersion可获取主版本号,剩下的是次版本号。

 

typedef struct _SPY_VERSION_INFO

    {

    DWORD dVersion;

    WORD  awName [SPY_NAME];

    }

    SPY_VERSION_INFO, *PSPY_VERSION_INFO, **PPSPY_VERSION_INFO;

 

#define SPY_VERSION_INFO_ sizeof (SPY_VERSION_INFO)

 

NTSTATUS SpyOutputVersionInfo (PVOID  pOutput,

                               DWORD  dOutput,

                               PDWORD pdInfo)

    {

    SPY_VERSION_INFO svi;

 

    svi.dVersion = SPY_VERSION;

 

    wcscpyn (svi.awName, USTRING (CSTRING (DRV_NAME)), SPY_NAME);

 

    return SpyOutputBinary (&svi, SPY_VERSION_INFO_,

                            pOutput, dOutput, pdInfo);

    }

列表4-12.  获取Spy驱动程序的版本信息

 

IOCTL函数SPY_IO_OS_INFO

该函数比上一个有趣的多。它是另一个只有输出的函数,不需要输入参数,使用几个操作系统的内部参数来填充调用者提供的SPY_OS_INFO结构。列表4-13列出了该结构的定义,和Dispatcher调用的SpyOutputOsInfo()帮助函数。有些结构体成员只是被简单的设为定义于DDK头文件和w2k_spy.h中的常量;其他的将被设为从几个内部的内核变量和结构体中读取的当前值。在第二章中,你已经了解了变量NtBuildNumberNtGlobalFlag(由ntoskrnl.exe导出,参见附录B中的B-1)。和其他的Nt*符号不同,这两个符号不指向API函数,而是指向位于内核的.data section中的变量。在Win32世界里,导出变量是十分罕见的。不过, Windows 2000的几个内核模块都使用了这一技术。Ntoskrnl.exe导出了至少55个变量,ntdll.dll提供了4个,hal.dll提供了1个。SpyOutputOsInfo()将从ntoskrnl.exe导出的变量中复制MmHighestUserAddressMmUserProbeAddressMmSystemRangeStartNtGlobalFlagKeI386MachineTypeKeNumberProcessorsNtBuildNumber到输出缓冲区中。

 

当一个模块从另一个模块中导入数据时,它需要使用extern关键字来通知编译器和链接器。这会使链接器生成一个进入模块导出节的入口,并会解析符号名以确定其地址。有些extern声明已经包含在ntddk.h列表4-13给出了缺失的那些extern声明。

 

 

extern PWORD                     NlsAnsiCodePage;

extern PWORD                     NlsOemCodePage;

extern PWORD                     NtBuildNumber;

extern PDWORD                    NtGlobalFlag;

extern PDWORD                    KeI386MachineType;

 

typedef struct _SPY_OS_INFO

    {

    DWORD   dPageSize;

    DWORD   dPageShift;

    DWORD   dPtiShift;

    DWORD   dPdiShift;

    DWORD   dPageMask;

    DWORD   dPtiMask;

    DWORD   dPdiMask;

    PX86_PE PteArray;

    PX86_PE PdeArray;

    PVOID   pLowestUserAddress;

    PVOID   pThreadEnvironmentBlock;

    PVOID   pHighestUserAddress;

    PVOID   pUserProbeAddress;

    PVOID   pSystemRangeStart;

    PVOID   pLowestSystemAddress;

    PVOID   pSharedUserData;

    PVOID   pProcessorControlRegion;

    PVOID   pProcessorControlBlock;

    DWORD   dGlobalFlag;

    DWORD   dI386MachineType;

    DWORD   dNumberProcessors;

    DWORD   dProductType;

    DWORD   dBuildNumber;

    DWORD   dNtMajorVersion;

    DWORD   dNtMinorVersion;

    WORD    awNtSystemRoot [MAX_PATH];

    }

    SPY_OS_INFO, *PSPY_OS_INFO, **PPSPY_OS_INFO;

 

#define SPY_OS_INFO_ sizeof (SPY_OS_INFO)

 

NTSTATUS SpyOutputOsInfo (PVOID  pOutput,

                          DWORD  dOutput,

                          PDWORD pdInfo)

    {

    SPY_SEGMENT     ss;

    SPY_OS_INFO     soi;

    NT_PRODUCT_TYPE NtProductType;

    PKPCR           pkpcr;

 

    NtProductType = (SharedUserData->ProductTypeIsValid

                     ? SharedUserData->NtProductType

                     : 0);

 

    SpySegment (X86_SEGMENT_FS, 0, &ss);

    pkpcr = ss.pBase;

 

    soi.dPageSize               =  PAGE_SIZE;

    soi.dPageShift              =  PAGE_SHIFT;

    soi.dPtiShift               =  PTI_SHIFT;

    soi.dPdiShift               =  PDI_SHIFT;

    soi.dPageMask               =  X86_PAGE_MASK;

    soi.dPtiMask                =  X86_PTI_MASK;

    soi.dPdiMask                =  X86_PDI_MASK;

    soi.PteArray                =  X86_PTE_ARRAY;

    soi.PdeArray                =  X86_PDE_ARRAY;

    soi.pLowestUserAddress      =  MM_LOWEST_USER_ADDRESS;

    soi.pThreadEnvironmentBlock =  pkpcr->NtTib.Self;

    soi.pHighestUserAddress     = *MmHighestUserAddress;

    soi.pUserProbeAddress       =  (PVOID) *MmUserProbeAddress;

    soi.pSystemRangeStart       = *MmSystemRangeStart;

    soi.pLowestSystemAddress    =  MM_LOWEST_SYSTEM_ADDRESS;

    soi.pSharedUserData         =  SharedUserData;

    soi.pProcessorControlRegion =  pkpcr;

    soi.pProcessorControlBlock  =  pkpcr->Prcb;

    soi.dGlobalFlag             = *NtGlobalFlag;

    soi.dI386MachineType        = *KeI386MachineType;

    soi.dNumberProcessors       = *KeNumberProcessors;

    soi.dProductType            =  NtProductType;

    soi.dBuildNumber            = *NtBuildNumber;

    soi.dNtMajorVersion         =  SharedUserData->NtMajorVersion;

    soi.dNtMinorVersion         =  SharedUserData->NtMinorVersion;

 

    wcscpyn (soi.awNtSystemRoot, SharedUserData->NtSystemRoot,

             MAX_PATH);

 

    return SpyOutputBinary (&soi, SPY_OS_INFO_,

                            pOutput, dOutput, pdInfo);

    }

列表4-13.  获取有关操作系统的信息

 

SPY_OS_INFO结构的剩余成员会由位于内存中的系统数据结构填充。例如,SpyOutputOsInfo()将内核的进程控制区域(Kernel’s Processor Control Region, KPCR)的基地址赋值给pProcessorControlRegion成员。KPCR是一个非常重要的数据结构,该结构包含很多线程相关的数据项,因此,它位于自己的内存段中,该内存段的地址由CPUFS寄存器给出。Windows NT 4.0Windows 2000都将FS指向处于内核模式的线性地址0xFFDFF000SpyOutputOsInfo()调用SpySegment()函数(稍后讨论它)来查询FS段在线性地址空间中的基地址。这个段中还包含内核的进程控制块(Kernel’s Processor Control Block, KPRCB),KPCR结构的Prcb成员指向KPRCB结构的首地址,紧随其后的是一个CONTEXT结构,该结构包含当前线程的底层CPU信息。KPCRKPRCBCONTEXT结构定义在ntddk.h头文件中。

 

列表4-13中引用的另一个内部数据结构是SharedUserData。该结构实际上是一个由一个“众所周知的地址”通过类型转化(TypeCast)得来的结构体指针。列表4-14给出了它在ntddk.h中的定义。那个“众所周知的地址”位于线性地址空间中,它会在编译时被设置,因此不需要花费额外的时间或进行配置。显然,SharedUserData是一个指向KUSER_SHARED_DATA结构的指针,该结构的基地址在0xFFDF0000(这是一个线性地址)。这个内存区域由系统和用户模式的应用程序共享,它包含像操作系统版本号这样的数据,SpyOutputOsInfo()将该版本数据复制到SPY_OS_INFO结构(由调用者提供)的dNtMajorVersiondNtMinorVersion成员。就像我稍后要展示的那样,KUSER_SHARED_DATA结构将被映射到0x7FFE0000,这样用户模式的代码就可以访问它了。

 

在对Spy设备的IOCTL函数的讲解之后还将提供了一个示例程序,该示例程序会把返回的数据显示在屏幕上。

 

#define  KI_USER_SHARED_DATA  0xFFDF0000

#define  SharedUserData ((KUSER_SHARED_DATA *const)KI_USER_SHARED_DATA)

列表4-14.  SharedUserData结构定义

 

IOCTL函数SPY_IO_SEGMENT

到现在讨论以变得更加有趣了。SPY_IO_SEGMENT函数通过一些更底层的操作来查询指定段的属性,调用者需要首先给出一个选择器(selector)。SpyDispatcher()首先调用SpyInputDword()来获取由调用程序传入的选择器的值。你可能还记得选择器(selector)是一个16位的数。不过,只要可能,我就会尝试避免使用16位的数据类型,这是因为原生的WORDi386 CPU32位模式下是32位的DWORD类型。因此,我将选择器参数扩展为DWORD,不过其高16位总是0。如果SpyInputDword()报告操作成功,接下来就会调用SpyOutputSegemnt()函数(列表4-15给出了此函数)。不管SpySegment()帮助函数如何,SpyOutputSegemnt()总是返回到调用者。基本上来说,SpySegment()将填充SPY_SEGMENT结构,该结构定义于列表4-15的顶部。它以X86_SELECTOR结构(参见列表4-2)的形式给出选择器的值,紧随其后的是64位的X86_DESCRIPTOR,以及相应的段基址,段的大小限制以及一个名为fOk的标志,该标志用来指出SPY_SEGMENT结构是否有效。在稍后的一些函数中需要一次返回多个段的属性,利用fOk成员,调用者就可以将无效的段信息从输出数据中筛选出来。

 

typedef struct _SPY_SEGMENT

    {

    X86_SELECTOR   Selector;

    X86_DESCRIPTOR Descriptor;

    PVOID          pBase;

    DWORD          dLimit;

    BOOL           fOk;

    }

    SPY_SEGMENT, *PSPY_SEGMENT, **PPSPY_SEGMENT;

 

#define SPY_SEGMENT_ sizeof (SPY_SEGMENT)

 

NTSTATUS SpyOutputSegment (DWORD  dSelector,

                           PVOID  pOutput,

                           DWORD  dOutput,

                           PDWORD pdInfo)

    {

    SPY_SEGMENT ss;

 

    SpySegment (X86_SEGMENT_OTHER, dSelector, &ss);

 

    return SpyOutputBinary (&ss, SPY_SEGMENT_,

                            pOutput, dOutput, pdInfo);

    }

 

 

BOOL SpySegment (DWORD        dSegment,

                 DWORD        dSelector,

                 PSPY_SEGMENT pSegment)

    {

    BOOL fOk = FALSE;

 

    if (pSegment != NULL)

        {

        fOk = TRUE;

        

        if (!SpySelector   (dSegment, dSelector,

                            &pSegment->Selector))

            {

            fOk = FALSE;

            }

        if (!SpyDescriptor (&pSegment->Selector,

                            &pSegment->Descriptor))

            {

            fOk = FALSE;

            }

        pSegment->pBase  =

            SpyDescriptorBase  (&pSegment->Descriptor);

 

        pSegment->dLimit =

            SpyDescriptorLimit (&pSegment->Descriptor);

 

        pSegment->fOk = fOk;

        }

    return fOk;

    }

列表4-15.   查询段的属性

 

SpySegment()函数依赖其他几个帮助函数,以构建SPY_SEGMENT结构的某些部分。首先,SpySelector()复制一个选择器的值到传入的X86_SELECTOR结构中。如果SpySelector()函数的第一个参数dSegment被设置为X86_SEGMENT_OTHER(即0),dSelector参数将假定已经指定了一个有效的选择器值,因此该值将被简单的附给输出结构X86_SELECTORwValue成员。否则,dSelector将被忽略,dSegment会被用于一个switch/case结构中以便选择一个段寄存器或任务寄存器TR。注意,这种请求需要少量的嵌入式汇编,C语言没有提供标准的方法访问处理器相关的特性,如段寄存器。

 

#define X86_SEGMENT_OTHER          0

#define X86_SEGMENT_CS              1

#define X86_SEGMENT_DS              2

#define X86_SEGMENT_ES              3

#define X86_SEGMENT_FS              4

#define X86_SEGMENT_GS              5

#define X86_SEGMENT_SS              6

#define X86_SEGMENT_TSS             7

 

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

 

BOOL SpySelector (DWORD         dSegment,

                  DWORD         dSelector,

                  PX86_SELECTOR pSelector)

    {

    X86_SELECTOR Selector = {0, 0};

    BOOL         fOk      = FALSE;

 

    if (pSelector != NULL)

        {

        fOk = TRUE;

 

        switch (dSegment)

            {

            case X86_SEGMENT_OTHER:

                {

                if (fOk = ((dSelector >> X86_SELECTOR_SHIFT)

                           <= X86_SELECTOR_LIMIT))

                    {

                    Selector.wValue = (WORD) dSelector;

                    }

                break;

                }

            case X86_SEGMENT_CS:

                {

                __asm mov Selector.wValue, cs

                break;

                }

            case X86_SEGMENT_DS:

                {

                __asm mov Selector.wValue, ds

                break;

                }

            case X86_SEGMENT_ES:

                {

                __asm mov Selector.wValue, es

                break;

                }

            case X86_SEGMENT_FS:

                {

                __asm mov Selector.wValue, fs

                break;

                }

            case X86_SEGMENT_GS:

                {

                __asm mov Selector.wValue, gs

                break;

                }

            case X86_SEGMENT_SS:

                {

                __asm mov Selector.wValue, ss

                break;

                }

            case X86_SEGMENT_TSS:

                {

                __asm str Selector.wValue

                break;

                }

            default:

                {

                fOk = FALSE;

                break;

                }

            }

        RtlCopyMemory (pSelector, &Selector, X86_SELECTOR_);

        }

    return fOk;

    }

列表4-16.  获取选择器(selector)的值

 

SpyDispatcher()将从一个64位的描述符中读取数据,段选择器指向该描述符(见列表4-17)。像你记得的那样,所有的选择器都包含一个表指示符(Table Indicator, TI)位,以确定选择器引用的描述符是位于GDTTI=0)中还是LDTTI=1)中。列表4-17的上半部分处理了是LDT的情况。首先,使用汇编指令SLDTSGDT分别读取LDT选择器的值以及段的大小限制和GDT的基地址。还记得GDT的线性基地址是显示指定的,而LDT是由GDT中的选择器间接引用的吗?所以,SpyDispatcher()会首先验证LDT选择器的值。如果段选择器不为空并且没有超过GDT的限制,就会调用SpyDescriptorType()SpyDescriptorLimit()SpyDescriptorBase()(列表4-17给出了这些函数)来获取LDT的基本属性:

l         SpyDescriptorType()返回描述符的类型数据及其S位域(参见列表4-2)。LDT选择器必须指向一个类型为X86_DESCRIPTOR_SYS_LDT的系统描述符。

 

l         SpyDescriptorLimit()从描述符的Limit1Limit2这两个位域中汇总段的大小限制。根据描述符的G标志指定的内存分配粒度的不同,其处理方式也会不同。

 

l         SpyDescriptorBase()只是简单的通过适当的组织描述符的Base1Base2Base3位域以获取一个32位的线性地址。

 

BOOL SpyDescriptor (PX86_SELECTOR   pSelector,

                    PX86_DESCRIPTOR pDescriptor)

    {

    X86_SELECTOR    ldt;

    X86_TABLE       gdt;

    DWORD           dType, dLimit;

    BOOL            fSystem;

    PX86_DESCRIPTOR pDescriptors = NULL;

    BOOL            fOk          = FALSE;

 

    if (pDescriptor != NULL)

        {

        if (pSelector != NULL)

            {

            if (pSelector->TI) // ldt descriptor

                {

                __asm

                    {

                    sldt ldt.wValue

                    sgdt gdt.wLimit

                    }

                if ((!ldt.TI) && ldt.Index &&

                    ((ldt.wValue & X86_SELECTOR_INDEX)

                     <= gdt.wLimit))

                    {

                    dType  = SpyDescriptorType  (gdt.pDescriptors +

                                                 ldt.Index,

                                                 &fSystem);

 

                    dLimit = SpyDescriptorLimit (gdt.pDescriptors +

                                                 ldt.Index);

 

                    if (fSystem && (dType == X86_DESCRIPTOR_SYS_LDT)

                        &&

                        ((DWORD) (pSelector->wValue

                                  & X86_SELECTOR_INDEX)

                         <= dLimit))

                        {

                        pDescriptors =

                            SpyDescriptorBase (gdt.pDescriptors +

                                               ldt.Index);

                        }

                    }

                }

            else // gdt descriptor

                {

                if (pSelector->Index)

                    {

                    __asm

                        {

                        sgdt gdt.wLimit

                        }

                    if ((pSelector->wValue & X86_SELECTOR_INDEX)

                        <= gdt.wLimit)

                        {

                        pDescriptors = gdt.pDescriptors;

                        }

                    }

                }

            }

        if (pDescriptors != NULL)

            {

            RtlCopyMemory (pDescriptor,

                           pDescriptors + pSelector->Index,

                           X86_DESCRIPTOR_);

            fOk = TRUE;

            }

        else

            {

            RtlZeroMemory (pDescriptor,

                           X86_DESCRIPTOR_);

            }

        }

    return fOk;

    }

 

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

 

PVOID SpyDescriptorBase (PX86_DESCRIPTOR pDescriptor)

    {

    return (PVOID) ((pDescriptor->Base1      ) |

                    (pDescriptor->Base2 << 16) |

                    (pDescriptor->Base3 << 24));

    }

 

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

 

DWORD SpyDescriptorLimit (PX86_DESCRIPTOR pDescriptor)

    {

    return (pDescriptor->G ? (pDescriptor->Limit1 << 12) |

                             (pDescriptor->Limit2 << 28) | 0xFFF

                           : (pDescriptor->Limit1      ) |

                             (pDescriptor->Limit2 << 16));

    }

 

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

 

DWORD SpyDescriptorType (PX86_DESCRIPTOR pDescriptor,

                         PBOOL           pfSystem)

    {

    if (pfSystem != NULL) *pfSystem = !pDescriptor->S;

    return pDescriptor->Type;

    }

列表4-17.  获取描述符的值

 

如果选择器的TI位指定了一个GDT描述符,事情就简单了。再次使用SGDT指令来取出GDT在线性内存中的位置和大小,如果选择器指定的描述符索引位于适当的范围,pDescriptors变量将被设置为指向GDT的基地址。对于LDTGDT来说,pDescriptors变量都不会为空。如果调用者传入的选择器是有效的,64位的描述符值将被复制到调用者提供的X86_DESCRIPTOR结构中。否则,该结构的所有成员都会被RtlZeroMemory()设为0

 

我们仍然在讨论列表4-15中的SpySegment()函数。SpySelector()SpyDescriptor()调用已经解释了。只剩下最后的SpyDescriptorBase()SpyDescriptorLimit()调用,不过你应该已经知道这些函数作了些什么(见列表4-17)。如果SpySelector()SpyDescriptor()成功,返回的SPY_SEGMENT结构将是有效的。SpyDescriptorBase()SpyDescriptorLimit()不会返回出错标志。因为它们不可能失败,如果提供的描述符无效,只是会让它们返回错误的数据而已。

 

 

……………..待续……………

你可能感兴趣的:(数据结构,windows,null,buffer,Descriptor,X86)