welfear

welfear的博客

他的个人主页  他的博客

高址物理内存

welfear  2009年08月26日 星期三 18:42 | 2671次浏览 | 4条评论

Windows物理内存的故事

文档名称:Windows物理内存的故事

文档维护:welfear

创建时间:2009年6月2日

 

我想我还没有完全搞清楚事情的全部经过,但至此我应该可以满足我的好奇心。

WinHex无法全部读取系统中的全部物理内存是十分正常的现象。在这之前,我们先把问题转换一下,

WinHex是使用\Device\PhysicalMemory来读取系统物理内存的(这一点不难证明,即使WinHex没有开源),

所以问题就变成了通过内核对象\Device\PhysicalMemory无法访问全部物理内存。

 

所以我想还是先从这个内核对象入手吧。

 

首先要弄明白这个内核对象是怎么回事。在MmInitSystem中,也就内存子系统初始化时会调用

函数MiSectionInitialization。在MiSectionInitialization中,系统会创建Section Object Type。

在建立Section对象后,第一件事就是创建\Device\PhysicalMemory对象。这个对象就是用来映射物理内存的。

而它的ControlArea和其它Section对象是有区别的,体现在

 

ControlArea->u.Flags.PhysicalMemory = 1;

 

这句上。当打开这个对象时,PhysicalMemory是不为零的。这样接下来再使用NtMapViewOfSection来映射内存时,

在NtMapViewOfSection->MmMapViewOfSection中就会通过ControlArea->u.Flags.PhysicalMemory是否为零来

选择调用MiMapViewOfPhysicalSection或是MiMapViewOfImageSection。显然,在映射物理内存时会调用

MiMapViewOfPhysicalSection。在此之前还有一处检测ControlArea->u.Flags.PhysicalMemory的代码:

 

if (Section->Segment->ControlArea->u.Flags.PhysicalMemory) {

        HighestPhysicalAddressInPfnDatabase = (ULONGLONG)MmHighestPhysicalPage << PAGE_SHIFT;

        CapturedOffset.LowPart = CapturedOffset.LowPart & ~(PAGE_SIZE - 1);

        if (PreviousMode != KernelMode) {

            if ((ULONGLONG)(CapturedOffset.QuadPart + CapturedViewSize) > HighestPhysicalAddressInPfnDatabase) {

                Status = STATUS_INVALID_PARAMETER_6;

                goto ErrorReturn;

            }

        }

}

 

显然,当用户态请求的映射范围超出了MmHighestPhysicalPage指示的系统内存页时就会发生参数错误而返回。

至此,我们的注意力又转向全局变量MmHighestPhysicalPage的初始化上。在研究这个问题之前我们简单描述下

整个系统的初始化过程。

 

绕过远古时期的MBR、BOOT Sector之流,直接来到ntldr。ntldr由16位的ntldr.com和32位的osloader.exe两部分组成。

在16位的引导代码中,系统会通过系统BIOS收集一些硬件数据为以后做准备。与Linux引导类似,Windows NT也是通过

BIOS中断int 15h来收集系统内存的位图信息。下面是freeloader中的代码:

 

00144 static ULONG

00145 PcMemGetBiosMemoryMap(PBIOS_MEMORY_MAP BiosMemoryMap, ULONG MaxMemoryMapSize)

00146 {

00147   REGS Regs;

00148   ULONG MapCount;

00152   /* Int 15h AX=E820h

00153    * Newer BIOSes - GET SYSTEM MEMORY MAP

00154    *

00155    * AX = E820h

00156    * EAX = 0000E820h

00157    * EDX = 534D4150h ('SMAP')

00158    * EBX = continuation value or 00000000h to start at beginning of map

00159    * ECX = size of buffer for result, in bytes (should be >= 20 bytes)

00160    * ES:DI -> buffer for result

00161    * Return:

00162    * CF clear if successful

00163    * EAX = 534D4150h ('SMAP')

00164    * ES:DI buffer filled

00165    * EBX = next offset from which to copy or 00000000h if all done

00166    * ECX = actual length returned in bytes

00167    * CF set on error

00168    * AH = error code (86h)

00169    */

00170   Regs.x.eax = 0x0000E820;

00171   Regs.x.edx = 0x534D4150; /* ('SMAP') */

00172   Regs.x.ebx = 0x00000000;

00173   Regs.x.ecx = sizeof(BIOS_MEMORY_MAP);

00174   Regs.w.es = BIOSCALLBUFSEGMENT;

00175   Regs.w.di = BIOSCALLBUFOFFSET;

00176   for (MapCount = 0; MapCount < MaxMemoryMapSize; MapCount++)

00177     {

00178       Int386(0x15, &Regs, &Regs);

00187       /* If the BIOS didn't return 'SMAP' in EAX then

00188        * it doesn't support this call */

00189       if (Regs.x.eax != 0x534D4150)

00190         {

00191           break;

00192         }

00194       /* Copy data to caller's buffer */

00195       RtlCopyMemory(&BiosMemoryMap[MapCount], (PVOID)BIOSCALLBUFFER, Regs.x.ecx);

00203       /* If the continuation value is zero or the

00204        * carry flag is set then this was

00205        * the last entry so we're done */

00206       if (Regs.x.ebx == 0x00000000 || !INT386_SUCCESS(Regs))

00207         {

00208           MapCount++;

00209           DPRINTM(DPRINT_MEMORY, "End Of System Memory Map!\n\n");

00210           break;

00211         }

00213       /* Setup the registers for the next call */

00214       Regs.x.eax = 0x0000E820;

00215       Regs.x.edx = 0x534D4150; /* ('SMAP') */

00216       /* Regs.x.ebx = 0x00000001;  Continuation value already set by the BIOS */

00217       Regs.x.ecx = sizeof(BIOS_MEMORY_MAP);

00218       Regs.w.es = BIOSCALLBUFSEGMENT;

00219       Regs.w.di = BIOSCALLBUFOFFSET;

00220     }

00222   return MapCount;

00223 }

 

代码中的注释已经可以说明通过int 15h获得E820位图信息的方法了,代码中还有作者的实践经验:-)。

在获得系统内存状态之后,在osloader.exe获得控制权时会获得引导参数指针。也就是在跳入

NtProcessStartup时,之后调用KiRosBuildOsMemoryMap转换引导参数收集的系统E820内存位图为

MemoryDescriptorList的形式。相关代码在reatcos\ntoskrnl\ke\freeldr.c中。

经过osloader.exe的引导后,Windows NT内核获得了控制权。在跳入KiSystemStartup时同样也

传入了前一模块的引导信息参数指针。在轮到内存子系统初始化时,系统调用了MiInitMachineDependent。

在MmInitSystem->MiInitMachineDependent中:

 

        if ((MemoryDescriptor->MemoryType != LoaderFirmwarePermanent) &&

            (MemoryDescriptor->MemoryType != LoaderBBTMemory) &&

            (MemoryDescriptor->MemoryType != LoaderSpecialMemory)) {

            if (MemoryDescriptor->MemoryType != LoaderBad) {

                MmNumberOfPhysicalPages += MemoryDescriptor->PageCount;

            }

            if (MemoryDescriptor->BasePage < MmLowestPhysicalPage) {

                MmLowestPhysicalPage = MemoryDescriptor->BasePage;

            }

            if ((MemoryDescriptor->BasePage + MemoryDescriptor->PageCount) >

                                                             MmHighestPhysicalPage) {

                MmHighestPhysicalPage =

                        MemoryDescriptor->BasePage + MemoryDescriptor->PageCount - 1;

            }

}

 

这样MmHighestPhysicalPage就被初始化为非Firmware相关的最大内存数。

 

这就是通过WinHex使用的用户态请求的\Device\PhysicalMemory内存映射就这样无法访问全部内存的原因。

 

此外通过MmMapIoSpace是可以访问全部内存的,不然怎么读取ACPI Table呢?

当然如果当初我仔细读过毛老师的《Linux内核情景分析》中关于Linux启动的代码分析,那也就不会产生如此

愚蠢的问题了。


评论

我的评论:

发表评论

请 登录 后发表评论。还没有在Zeuux哲思注册吗?现在 注册 !
電波系山寨文化科学家

回复 電波系山寨文化科学家  2009年08月31日 星期一 20:38

不懂,帮顶.

1条回复

王单单

回复 王单单  2009年08月26日 星期三 21:01

没看懂ing

1条回复

暂时没有评论

Zeuux © 2024

京ICP备05028076号