ntldr内存初始化分配操作和相关函数分析Word格式文档下载.docx
《ntldr内存初始化分配操作和相关函数分析Word格式文档下载.docx》由会员分享,可在线阅读,更多相关《ntldr内存初始化分配操作和相关函数分析Word格式文档下载.docx(29页珍藏版)》请在冰豆网上搜索。
RoutineDescription:
Arguments:
ReturnValue:
--*/
{
ULONGBAddr,EAddr,round;
E820FrameFrame;
//
//Initializethefirstentryinthelisttozero(end-of-list)
MemoryDescriptorList->
BlockSize=0;
BlockBase=0;
//调用Init15E820()函数,此时只是为了测试调用该函数是否成功
Frame.Key=0;
Frame.Size=sizeof(Frame.Descriptor);
Int15E820(&
Frame);
if(Frame.ErrorFlag||Frame.Size<
sizeof(Frame.Descriptor)){
returnFALSE;
}
//这里才是真正的获取内存块
do{
//[假定和限制]
//BIOS返回那些描述大块内存的地址范围,随后是ISA/PCI内存;
//BIOS不返回被用作PCI设备,ISA可选ROM,以及ISAplus&
play卡的内存映象,这是因为OS有相应的//机制可以检测到它们;
//BIOS返回芯片定义的地址空洞,这些地址作为保留不会被设备使用;
//被定义的大块内存被映射到IO设备的地址范围作为保留地址将会被返回;
//系统BIOS的所有事件将会被作为保留内存返回,这包括低于1M的内存,在16M(如果存在)
//处的内存,以及在地址空间(4G)结尾处的内存.
//标准的PC地址范围不会被报告,例如在地址A0000到BFFF的被用作videomemory的内存.从E0000到//EFFFF的内存是主板指定的,将会被报告.
//所有的低位内存作为正常的内存将会被报告,处理为规范保留的标准RAM是OS的责任,例如,中断//向量表(0:
0)以及BIOS数据区(40:
0)
//调用Int15E820()函数.
//在Int15E820函数中,就象你看到的,其动作就是调用int15e820来获取内存的容量,也//许你不是太了解,我在这里简单的介绍一下:
//BIOS功能调用int15功能号e820:
//[传入参数]:
//eax:
e820
//ebx:
存放“后续值”,该值是为了得到下一块物理内存段,他应该指定上一次调用此程序的返
//回值,如果为第一次调用,则ebx必须为0。
//es:
di:
缓冲区指针,指向“地址范围描述符”结构,bios会填充该结构。
//ecx:
缓冲区结构的大小,以bytes为单位。
//edx:
标志“SMAP”,BIOS会使用该标志对系统映像信息进行校验。
//[返回值]:
//CF:
没有进位标志没有错误,sbbecx,ecx
//其他的同传入的参数的意义一致。
//frame结构与上面介绍的bios的int15的功能调用传入的参数是一致的
//注意在使用bochs对这个函数进行调试的时候,你可以观察到winnt中的代//码和win2kntldr中的代码没有一点区别。
//本来打算以《linuxnt获取扩展内存》一文中给出的内存地址样例做为实//际内存地址介绍的,不过在我使用bochs虚拟机调试的过程中却发
//现,Int15E820函数只返回了两块内存,我不知道bochs是否使用了真实的//物理地址,然而我又不得不以我所见的事实为例来进行介绍,也许你所调//试的结果与我的不相同,不过原理其实是一致的,而且我使用bochs虚拟//机进行调试的根本目的不在这里。
//
break;
#ifdefDEBUG1
BlPrint("
E820:
%lx%lx:
%lx%lx:
%lx%lx%lx\n"
Frame.Size,
Frame.Descriptor.BaseAddrHigh,Frame.Descriptor.BaseAddrLow,
Frame.Descriptor.SizeHigh,Frame.Descriptor.SizeLow,
Frame.Descriptor.MemoryType,
Frame.Key
);
_asm{
pushax
movax,0
int16h
popax
#endif
//我们以bochs调试的实际地址来计算一下,假设这里是第一次调用Int15E820函数后的反回值
//即:
00000000639KARM可以使用的基本内存
BAddr=Frame.Descriptor.BaseAddrLow;
//00000000
EAddr=Frame.Descriptor.BaseAddrLow+Frame.Descriptor.SizeLow-1;
//00000000+639K-1
//即:
0009FBFF
//如果BaseAddrHigh超过了FFFFFFFF,则说明内存容量已经超过了32位处理器所能表示的4G内存地址//空间(在winnt时代还没有考虑64位处理器的情况)
if(Frame.Descriptor.BaseAddrHigh==0){
if(EAddr<
BAddr){
//所以我们将他截断,表示为4G内存地址的边界
EAddr=0xFFFFFFFF;
//Basedupontheaddressrangedescriptortype,findthe
//availablememoryandaddittothedescriptorlist
switch(Frame.Descriptor.MemoryType){
case1:
//如果上边给出的例子没有错误的话,我们可以按照例子将报告的内存块插入到内存描//述符链表中了,注意此时的MemoryType并没有太实际的作用,不管内存类型是什么,//都需要插入到内存描述符链表中
InsertDescriptor(BAddr,EAddr-BAddr+1);
break;
}while(Frame.Key);
returnTRUE;
}
InsertDescriptor函数分析:
VOID
InsertDescriptor(
ULONGAddress,
ULONGSize
Thisroutineinsertsadescriptorintothecorrectplaceinthe
memorydescriptorlist.
Address-Startingaddressofthememoryblock.
Size-Sizeofthememoryblocktobeinserted.
None.
MEMORY_LIST_ENTRY_far*CurrentEntry;
Insertingdescriptor%lxat%lx\n"
Size,Address);
//Searchthespottoinsertthenewdescriptor.
CurrentEntry=MemoryDescriptorList;
//当然,在我们第一次调用InsertDescriptor()函数插入第一个内存块到内存描述符链表中时是不会//执行该循环体的,因为此时在MemoryDescriptorList中还没有任何对内存块的描述数据。
//该循环体逐一比较已经存在的内存描述符描述的内存块是否与要插入的内存描述块相临,如果是,合//并他们,如果不是相临的
while(CurrentEntry->
BlockSize>
0){
//检查该内存描述块是否与当前的内存描述符所描述的内存块是相临的,如果是,合并他们.(yes,//somemachineswillreturnmemorydescriptorsthatlooklikethis.Compaq
//Prosigniamachines)
if(Address+Size==CurrentEntry->
BlockBase){
coalescingwithdescriptorat%lx(%lx)\n"
CurrentEntry->
BlockBase,
BlockSize);
BlockBase=Address;
BlockSize+=Size;
newdescriptorat%lx(%lx)\n"
if(Address==(CurrentEntry->
BlockBase+CurrentEntry->
BlockSize)){
CurrentEntry++;
//第一次调用InsertDescriptor()函数插入第一个内存块到内存描述符链表时会在这里执行,创建首//个对内存块描述的描述符。
//如果要插入的内存描述块与已经存在的描述符描述的内存块并不相临,那么创建新的描述符,并创建//结束点
if(CurrentEntry->
BlockSize==0){
//IfCurrentEntry->
BlockSize==0,wehavereachedtheendofthelist
//So,insertthenewdescriptorhere,andcreateanewend-of-listentry
BlockSize=Size;
++CurrentEntry;
//Createanewend-of-listmarker
BlockBase=0L;
BlockSize=0L;
//Waitforakeypress
通过这两个函数原始的内存描述符链表就形成了:
(以Bochs调试出的为例)
Address:
00000000(00000000~0009FBFF)Size:
639k;
Type:
ARM
↓
00100000(00100000~007FFFFF)Size:
7M;
ARR
Sumain.c中包含osLoader的映射部分:
//其实这个循环体要和下面的if判断语句合起来理解才算清楚,在原来的代码中我看到他们被分开,所以觉//得这的代码有点画蛇添足的意思,其实不然,循环找到最后一个内存描述符块,然后对他进行判断,以查看//你的电脑是否只有640K的内存。
while((CurrentEntry->
BlockBase!
=0)&
&
(CurrentEntry->
BlockSize!
=0)){
if((CurrentEntry->
BlockBase==0)&
BlockSize<
(ULONG)512*(ULONG)1024)){
//如果你的机器真的只有640K内存,不好意思,真的该换电脑了
BlPrint(SU_NO_LOW_MEMORY,CurrentEntry->
BlockSize/1024);
while
(1){
//确保在内存描述符表描述的内存中确实能容纳下osloaderimage
//其实这里才是我动用bochs调试器的真正原因,理由很简单,我对这里的地址感到很困惑,没有在其他//的代码中找到关于edata的什么赋值操作(除了su.asm中),所以我不能确定osloader的加载位置到//底是在哪里(实在是找不到一点相关的资料,因而出红疹发高烧病了,差点就GAMEOVER了)。
//以下是bochs调试出的指令代码:
//movax,wordptrds:
0x1e00;
ax=0x0000↘ImageBase
//movdx,wordptrds:
0x1e02;
dx=0x0030↗
//movwordptrss:
[bp+0xffea],ax
[bp+0xffec],dx
//movcx,wordptrds:
0x1e1c;
cx=0x1000↘ImageSize
//movbx,wordptrds:
0x1e1e;
bx=0x0006↗
[bp+0xffe6],cx
[bp+0xffe8],bx
//movwordptrds:
0x1680,ax↘OsLoaderBase
0x1682,dx↗
//addax,wordptrds:
0x1e44;
ax=0xf000
//adcdx,wordptrds:
0x1e46;
dx=0x0032
0x1684,ax↘OsLoaderExports
0x1686,dx↗
//我不能确定OptionalHeader这条代码在反汇编出的指令中的位置,我也没有找到和这条代码相关的指
//令,所以在这里我有一个猜测,即ntldr经过编译优化以后,osloader的Standardfields和NT
//additionalfields被放在了固定的位置,我们可以看到ImageBase是NTadditionalfields的第//一个字段,在bochs调试出的代码中直接访问ds:
0x1e00处得来。
//该处如果不是太明了,你可以参照一下_IMAGE_OPTIONAL_HEADER的结构定义(winnt.h)。
OptionalHeader=(PIMAGE_OPTIONAL_HEADER)((PUCHAR)&
edata+sizeof(IMAGE_FILE_HEADER));
ImageBase=OptionalHeader->
ImageBase;
ImageSize=OptionalHeader->
SizeOfImage;
OsLoaderBase=ImageBase;
OsLoaderExports=ImageBase+OptionalHeader->
DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
while(ImageSize>
//这个循环体看似复杂,其实他要做的事情很简单,即检验我们前面所画的内存描述符链表中是否有//足够的内存块能包含我们要加载的osloader
=0){
BlockEnd=CurrentEntry->
BlockSize;
BlockBase<
=ImageBase)&
(BlockEnd>
ImageBase)){
//thisdescriptoratleastpartiallycontainsachunk
//oftheosloader.
if(BlockEnd-ImageBase>
ImageSize){
ImageSize=0;
//如果有足够的内存块,那么我们将ImageSzie置0,以此作为验//证标志
}else{
ImageSize-=(BlockEnd-ImageBase);
ImageBase=BlockEnd;
//lookforremainingpart(ifany)ofosloader
CurrentEntry=MemoryDescriptorList;
if(ImageSize>
0){//如果我们的ImageSize验证标志依然不为0,那么打印出错信息
//Wecouldnotrelocatetheosloadertohighmemory.Errorout
//anddisplaythememorymap.
BlPrint(SU_NO_EXTENDED_MEMORY);
%lx-%lx\n"
RelocateLoaderSections()函数分析:
之所以把这个函数单独的列出来分析,是因为他对以后的内存操作至关重要,因为在后面对内存块进行分配时,别的内存地址是被硬性的加载分配的,而只有osloader的地址是我们无法直接确定的。
ULON