UEFI.源码分析.DXE的内存服务.第三部分.HeapGuard

Posted 木艮氵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UEFI.源码分析.DXE的内存服务.第三部分.HeapGuard相关的知识,希望对你有一定的参考价值。

  • 源码EDK2:Tianocore
  • UEFI源码分析系列第二篇,DXE阶段的内存服务
  • 第三部分,HeapGuard
  • DXE阶段源码目录MdeModulePkg/Core/Dxe
  • 基于开篇日(2018/04/18:16:30)时最新的EDk2版本,commit为255101471918ed8840f2be347916b90eef0e9c08

从初始化过程入手

直到CoreAddMemoryDescriptor才找到相关的内容

/** /Dxe/Mem/Page.c **/
 536 VOID                                                                     
 537 CoreAddMemoryDescriptor (                                                
 538   IN EFI_MEMORY_TYPE       Type,                                         
 539   IN EFI_PHYSICAL_ADDRESS  Start,                                        
 540   IN UINT64                NumberOfPages,                                
 541   IN UINT64                Attribute                                     
 542   )                                                                      
 543                                                                         
 544   EFI_PHYSICAL_ADDRESS        End;                                       
 545   EFI_STATUS                  Status;                                    
 546   UINTN                       Index;                                     
 547   UINTN                       FreeIndex;                                 
 548                                                                          
 549   if ((Start & EFI_PAGE_MASK) != 0)                                     
 550     return;                                                              
 551                                                                         
 552                                                                          
 553   if (Type >= EfiMaxMemoryType && Type < MEMORY_TYPE_OEM_RESERVED_MIN)  
 554     return;                                                              
 555                                                                         
 556   CoreAcquireMemoryLock ();                                              
 557   End = Start + LShiftU64 (NumberOfPages, EFI_PAGE_SHIFT) - 1;           
 558   CoreAddRange (Type, Start, End, Attribute);                            
 559   CoreFreeMemoryMapStack ();                                             
 560   CoreReleaseMemoryLock ();                                              
 561                                                                          
 562   ApplyMemoryProtectionPolicy (EfiMaxMemoryType, Type, Start,            
 563     LShiftU64 (NumberOfPages, EFI_PAGE_SHIFT));                          

在函数ApplyMemoryProtectionPolicy中多了如下一段代码,主要包含两个函数IsHeapGuardEnabledIsGuardPage

1229 EFI_STATUS                           
1230 EFIAPI                               
1231 ApplyMemoryProtectionPolicy (        
1232   IN  EFI_MEMORY_TYPE       OldType, 
1233   IN  EFI_MEMORY_TYPE       NewType, 
1234   IN  EFI_PHYSICAL_ADDRESS  Memory,  
1235   IN  UINT64                Length   
1236   )                                  
1237                                     

1269   if (IsHeapGuardEnabled ())                            
1270     if (IsGuardPage (Memory))                           
1271       Memory += EFI_PAGE_SIZE;                           
1272       Length -= EFI_PAGE_SIZE;                           
1273       if (Length == 0)                                  
1274         return EFI_SUCCESS;                              
1275                                                         
1276                                                         
1277                                                          
1278     if (IsGuardPage (Memory + Length - EFI_PAGE_SIZE))  
1279       Length -= EFI_PAGE_SIZE;                           
1280       if (Length == 0)                                  
1281         return EFI_SUCCESS;                              
1282                                                         
1283                                                         
1284                                                         

305   return gCpu->SetMemoryAttributes (gCpu, Memory, Length, NewAttributes); 
306                                                                          

函数IsHeapGuardEnable显示是用来判断HeapGuard是否Enabled

/** /Dxe/Mem/HeapGuard.c **/
 753 BOOLEAN                                                                   
 754 IsHeapGuardEnabled (                                                      
 755   VOID                                                                    
 756   )                                                                       
 757                                                                          
 758   return IsMemoryTypeToGuard (EfiMaxMemoryType, AllocateAnyPages,         
 759                               GUARD_HEAP_TYPE_POOL|GUARD_HEAP_TYPE_PAGE); 
 760                                                                          

函数IsMemoryTypeToGuard则是进一步判断对应的内存类型是否Enabled

 661 BOOLEAN                                  
 662 IsMemoryTypeToGuard (                    
 663   IN EFI_MEMORY_TYPE        MemoryType,  
 664   IN EFI_ALLOCATE_TYPE      AllocateType,
 665   IN UINT8                  PageOrPool   
 666   )                                      
 667                                         

主要是根据PCD(全局配置数据库)的设置来判断是否Enabled。PCD的相关内容可以参考文献。

如根据PcdHeapGuardPropertyMask判断是否开启了pagepool的Guard。

 685   if ((PcdGet8 (PcdHeapGuardPropertyMask) & PageOrPool) == 0) 
 686     return FALSE;                                              
 687                                                               

进一步根据MemType和PCD的设置来判断内存类型是否开启了Guard。

注意当内存类型是EfiMaxMemoryType,测试位向量TestBit = (UINT64)-1,即全1。

 689   if (PageOrPool == GUARD_HEAP_TYPE_POOL)                         
 690     ConfigBit = PcdGet64 (PcdHeapGuardPoolType);                   
 691    else if (PageOrPool == GUARD_HEAP_TYPE_PAGE)                  
 692     ConfigBit = PcdGet64 (PcdHeapGuardPageType);                   
 693    else                                                          
 694     ConfigBit = (UINT64)-1;                                        
 695                                                                   
 696                                                                    
 697   if ((UINT32)MemoryType >= MEMORY_TYPE_OS_RESERVED_MIN)          
 698     TestBit = BIT63;                                               
 699    else if ((UINT32) MemoryType >= MEMORY_TYPE_OEM_RESERVED_MIN) 
 700     TestBit = BIT62;                                               
 701    else if (MemoryType < EfiMaxMemoryType)                       
 702     TestBit = LShiftU64 (1, MemoryType);                           
 703    else if (MemoryType == EfiMaxMemoryType)                      
 704     TestBit = (UINT64)-1;                                          
 705    else                                                          
 706     TestBit = 0;                                                   
 707                                                                   

 709   return ((ConfigBit & TestBit) != 0); 
 710                                       

函数IsGuardPage用来判断某个页是否被守护了。根据不同的情况,该页的前一个和后一个均有可能的守护页,也有可能同时是守护页,故采用 bitmap 的形式来表达,即001表示前一页不是守护页而后一页是,101表示前一页和后一页均是守护页,100表示前一页是守护页而后一页不是。

500 BOOLEAN                                                                      
501 EFIAPI                                                                       
502 IsGuardPage (                                                                
503   IN EFI_PHYSICAL_ADDRESS    Address                                         
504   )                                                                          
505                                                                             
506   UINTN       BitMap;                                                        
507                                                                              
508   //                                                                         
509   // There must be at least one guarded page before and/or after given       
510   // address if it's a Guard page. The bitmap pattern should be one of       
511   // 001, 100 and 101                                                        
512   //                                                                         
513   BitMap = GetGuardedMemoryBits (Address - EFI_PAGE_SIZE, 3);                
514   return ((BitMap == BIT0) || (BitMap == BIT2) || (BitMap == (BIT2 | BIT0)));
515                                                                             

BIT0BIT2定义于MdePkg/Include/Base.h

 407 #define  BIT0     0x00000001
 408 #define  BIT1     0x00000002
 409 #define  BIT2     0x00000004

函数GetGuardedMemoryBits使用了全局变量mGuardedMemoryMap所存储的信息来判断某些页是否是守护页,并返回对应的 bitmap。

 382 UINTN                                      
 383 GetGuardedMemoryBits (                     
 384   IN EFI_PHYSICAL_ADDRESS    Address,      
 385   IN UINTN                   NumberOfPages 
 386   )                                        
 387                                           

 398   while (NumberOfPages > 0)                                       
 399     BitsToUnitEnd = FindGuardedMemoryMap (Address, FALSE, &BitMap);
 400                                                                    
 401     if (NumberOfPages > BitsToUnitEnd)                            
 402       // Cross map unit                                            
 403       Bits  = BitsToUnitEnd;                                       
 404      else                                                        
 405       Bits  = NumberOfPages;                                       
 406                                                                   
 407                                                                    
 408     if (BitMap != NULL)                                           
 409       Result |= LShiftU64 (GetBits (Address, Bits, BitMap), Shift);
 410                                                                   
 411                                                                    
 412     Shift         += Bits;                                         
 413     NumberOfPages -= Bits;                                         
 414     Address       += EFI_PAGES_TO_SIZE (Bits);                     
 415                                                                   
 416                                                                    
 417   return Result;                                                   
 418   

相关函数FindGuardedMemoryMap来获取包含该页的bitmap,并返回该页是在bitmap中的第几位,随后在result中用或操作保存该信息,最后返回result

再回到最初的初始化过程,其实也就是判断该区域的第一页和最后一页是否被守护,如果是则跳过一页大小,以避免覆盖写入了守护页。

为什么不是跳过两页呢?如果跳两页的话,后续的申请可能会导致有两个相邻的守护页,这是完全没必要的。假如最后一页的守护情况是100,这段内存区域最后若干页的守护情况可能是00010,若跳过两页,则变成了000,那么再申请一块内存的时候,为了添加守护页,最后三页变成了001。此时加上跳过的两页以及这之后的一页,就变成了001100,出现了连续两页的守护页。


位向量记录守护页

先来一大段注释

/** /Dxe/Mem/HeapGuard.h **/
 18 //                                                                             
 19 // Following macros are used to define and access the guarded memory bitmap    
 20 // table.                                                                      
 21 //                                                                             
 22 // To simplify the access and reduce the memory used for this table, the       
 23 // table is constructed in the similar way as page table structure but in      
 24 // reverse direction, i.e. from bottom growing up to top.                      
 25 //                                                                             
 26 //    - 1-bit tracks 1 page (4KB)                                              
 27 //    - 1-UINT64 map entry tracks 256KB memory                                 
 28 //    - 1K-UINT64 map table tracks 256MB memory                                
 29 //    - Five levels of tables can track any address of memory of 64-bit        
 30 //      system, like below.                                                    
 31 //                                                                             
 32 //       512   *   512   *   512   *   512    *    1K   *  64b *     4K        
 33 //    111111111 111111111 111111111 111111111 1111111111 111111 111111111111   
 34 //    63        54        45        36        27         17     11         0   
 35 //       9b        9b        9b        9b         10b      6b       12b        
 36 //       L0   ->   L1   ->   L2   ->   L3   ->    L4   -> bits  ->  page       
 37 //      1FF       1FF       1FF       1FF         3FF      3F       FFF        
 38 //                                                                             
 39 // L4 table has 1K * sizeof(UINT64) = 8K (2-page), which can track 256MB       
 40 // memory. Each table of L0-L3 will be allocated when its memory address       
 41 // range is to be tracked. Only 1-page will be allocated each time. This       
 42 // can save memories used to establish this map table.                         
 43 //                                                                             
 44 // For a normal configuration of system with 4G memory, two levels of tables   
 45 // can track the whole memory, because two levels (L3+L4) of map tables have   
 46 // already coverred 37-bit of memory address. And for a normal UEFI Bios,      
 47 // less than 128M memory would be consumed during boot. That means we just     
 48 // need                                                                        
 49 //                                                                             
 50 //          1-page (L3) + 2-page (L4)                                          
 51 //                                                                             
 52 // memory (3 pages) to track the memory allocation works. In this case,        
 53 // there's no need to setup L0-L2 tables.                                      
 54 //                                                                             

使用一个 bit 来表示一页(4KB)的守护情况,1表示是守护页,0表是不是守护页。

每个UINT64的为8字节,也就是64bit,可以存储64个页的守护属性,对应了64×4KB=256KB大小的内存空间。

每1KB个UINT64,也就是64×1024bit,可以存储这么多个页的守护属性,对应了1024×64×4KB=256MB大小的内存空间。

参考页表的形式,这里采用了分级的存储。整个 64bit 的地址空间分成了 7 块。

  1. 前五块为 L0L4,其中L0L3分别有 9 bit,L4有 10 bit。
  2. 后续一块为bits,有 6bit,对于一个UINT64可以存储的范围,即64位。
  3. 最后是页的大小,有12bit,即 4KB。

对于一个64位的4KB页对齐的地址addr

  1. 其最后12位addr[11:0]必然为0
  2. 由于每64个页可以用一个8字节的UINT64来存储守护信息,所以要定位到包含该页的UINT64无需addr[17:12]
  3. 第一,根据addr[63:55]作为索引查找L0层,每一项即一个UINT64的地址,对应下一级L1`的地址。
  4. 依次向下,直到寻找到最终L4中的一项UINT64,该项包含了64个页的守护属性
  5. 此时根据addr[17:12]来判断该UINT64中的哪一位表示这个页的守护属性。

对于普通的4GB内存空间来说,只需要L3L4两层就足够,因为这已经达到了37位,超过了4GB地址的32位。

对于UEFI而言,通常只需要256M空间,此时甚至只需L4一层即可以。因为L4层通过10bit来索引,所以可以存放1024项,每项描述了64页,故有1024×64×4KB=256MB。

至于操作该bitmap的宏就不深入研究了,有兴趣移步源码。


申请页CoreAllocatePages

内存申请的时候需要判断是否需要守护

1378 EFI_STATUS                                                                     
1379 EFIAPI                                                                         
1380 CoreAllocatePages (                                                            
1381   IN  EFI_ALLOCATE_TYPE     Type,                                              
1382   IN  EFI_MEMORY_TYPE       MemoryType,                                        
1383   IN  UINTN                 NumberOfPages,                                     
1384   OUT EFI_PHYSICAL_ADDRESS  *Memory                                            
1385   )                                                                            
1386                                                                               
1387   EFI_STATUS  Status;                                                          
1388   BOOLEAN     NeedGuard;                                                       
1389                                                                                
1390   NeedGuard = IsPageTypeToGuard (MemoryType, Type) && !mOnGuarding;            
1391   Status = CoreInternalAllocatePages (Type, MemoryType, NumberOfPages, Memory, 
1392                                       NeedGuard);                              

其实差别是不大的,只关注NeedGuard这个吧

1237 EFI_STATUS                               
1238 EFIAPI                                   
1239 CoreInternalAllocatePages (              
1240   IN EFI_ALLOCATE_TYPE      Type,        
1241   IN EFI_MEMORY_TYPE        MemoryType,  
1242   IN UINTN                  NumberOfPages
1243   IN OUT EFI_PHYSICAL_ADDRESS  *Memory,  
1244   IN BOOLEAN                NeedGuard    
1245   )                                      
1246                                         

1330     Start = FindFreePages (MaxAddress, NumberOfPages, MemoryType, Alignment,
1331                            NeedGuard);                                      

寻找空闲页

1150 UINT64                             
1151 FindFreePages (                    
1152     IN UINT64           MaxAddress,
1153     IN UINT64           NoPages,   
1154     IN EFI_MEMORY_TYPE  NewType,   
1155     IN UINTN            Alignment, 
1156     IN BOOLEAN          NeedGuard  
1157     )                              
1158                                   
1159   UINT64   Start;                  

1165     Start = CoreFindFreePagesI (                            
1166               mMemoryTypeStatistics[NewType].MaximumAddress,
1167               mMemoryTypeStatistics[NewType].BaseAddress,   
1168               NoPages,                                      
1169               NewType,                                      
1170               Alignment,                                    
1171               NeedGuard                                     
1172               );                                            

CoreFindFreePagesI中搜索gMemoryMap

1005 UINT64                              
1006 CoreFindFreePagesI (                
1007   IN UINT64           MaxAddress,   
1008   IN UINT64           MinAddress,   
1009   IN UINT64           NumberOfPages,
1010   IN EFI_MEMORY_TYPE  NewType,      
1011   IN UINTN            Alignment,    
1012   IN BOOLEAN          NeedGuard     
1013   )                                 
1014                                    

1052   for (Link = gMemoryMap.ForwardLink; Link != &gMemoryMap; Link = Link->ForwardLink) 
1053     Entry = CR (Link, MEMORY_MAP, Link, MEMORY_MAP_SIGNATURE);                        

1062     DescStart = Entry->Start;
1063     DescEnd = Entry->End;    

1090     DescNumberOfBytes = DescEnd - DescStart + 1;
1091                                                 
1092     if (DescNumberOfBytes >= NumberOfBytes)    

1103       if (DescEnd > Target)                          
1104         if (NeedGuard)                               
1105           DescEnd = AdjustMemoryS (                   
1106                       DescEnd + 1 - DescNumberOfBytes,
1107                       DescNumberOfBytes,              
1108                       NumberOfBytes                   
1109                       );                              
1110           if (DescEnd == 0)                          
1111             continue;                                 
1112                                                      
1113           // end if need guard
1114                                                       
1115         Target = DescEnd;                             
1116         // end if DescEnd > Target
1117        // end if DescNumberOfBytes >= NumberOfBytes
1118    // end for each ENTRY in gMemoryMap

1123   Target -= NumberOfBytes - 1;

1132   return Target;
1133                

函数AdjustMemoryS用来处理守护页。

Start是某个Entry所描述的一段空闲内存的基地址,Size是这段空闲内存的大小。

SizeRequired是申请的空间大小。

Target = Start + Size - SizeRequested即为所申请空间的基地址。

/** /Dxe/Mem/HeadGuard.c **/
 905 UINT64                                      
 906 AdjustMemoryS (                             
 907   IN UINT64                  Start,         
 908   IN UINT64                  Size,          
 909   IN UINT64                  SizeRequested  
 910   )                                         
 911                                            
 912   UINT64  Target;                           

 923   Target = Start + Size - SizeRequested;

首先判断空闲内存区域最后一页的后一页的守护情况,若该页没有被守护,则申请的空间中需要额外的一页作为守护页。

 929   if (!IsGuardPage (Start + Size))                        
 930     // No Guard at tail to share. One more page is needed. 
 931     Target -= EFI_PAGES_TO_SIZE (1);                       
 932                                                           

此时Target减小了。若刚好减小到空闲内存区域的基地址,则需要判断空闲区域的前一页的守护情况。如果没有被守护,因为没有多余的空间来添加守护页了,所以无法继续下去。

 940   if (Target == Start)                                                    
 941     if (!IsGuardPage (Target - EFI_PAGES_TO_SIZE (1)))                    
 942       // No enough space for a new head Guard if no Guard at head to share.
 943       return 0;                                                            
 944                                                                           
 945                                                                           

最后返回这段内存的高地址,其满足了所需的空间要求和守护页的需求。

 947   // OK, we have enough pages for memory and its Guards. Return the End of the
 948   // free space.                                                              
 949   return Target + SizeRequested - 1;                                          
 950                                                                              

守护页的作用

注意到有这么一个函数HeapGuardCpuArchProtocolNotify,它在MemoryProtectionCpuArchProtocolNotify中被调用。

CoreInitializeMemoryProtection在初始化内存保护的时候,会创建一个事件,该事件对应的回调函数就是MemoryProtectionCpuArchProtocolNotify。事件会和gEfiCpuArchProtocolGuid绑定在一起,当EFI_CPU_ARCH_PROTOCOL注册的时候,该事件的回调函数会被调用。

/** /Dxe/Misc/MemoryProtection.c **/
1155     Status = CoreCreateEvent (                        
1156                EVT_NOTIFY_SIGNAL,                     
1157                TPL_CALLBACK,                          
1158                MemoryProtectionCpuArchProtocolNotify, 
1159                NULL,                                  
1160                &Event                                 
1161                );                                     

1167     Status = CoreRegisterProtocolNotify ( 
1168                &gEfiCpuArchProtocolGuid,  
1169                Event,                     
1170                &Registration              
1171                );                         

函数HeapGuardCpuArchProtocolNotify调用了SetAllGuardPages来设置所有的守护页。

/** /Dxe/Mem/HeapGuard.c **/
1297 VOID                              
1298 HeapGuardCpuArchProtocolNotify (  
1299   VOID                            
1300   )                               
1301                                  
1302   ASSERT (gCpu != NULL);          
1303   SetAllGuardPages ();            
1304                                  

SetAllGuardPages对于所有的守护页调用了这么一个函数SetGuardPage,其中变量GuardPage是该页的地址。

1187 VOID               
1188 SetAllGuardPages ( 
1189   VOID             
1190   )                
1191                   

1199   UINT64    GuardPage;

1268           if (GuardPage != 0)  
1269             SetGuardPage (GuardPage); 
1270           

1292  // end function

SetGuardPage设置了页面的属性

 577 VOID                                                                                   
 578 EFIAPI                                                                                 
 579 SetGuardPage (                                                                         
 580   IN  EFI_PHYSICAL_ADDRESS      BaseAddress                                            
 581   )                                                                                    
 582                                                                                       
 583   EFI_STATUS      Status;                                                              
 584                                                                                        
 585   if (gCpu == NULL)                                                                   
 586     return;                                                                            
 587                                                                                       
 588                                                                                        
 589   //                                                                                   
 590   // Set flag to make sure allocating memory without GUARD for page table              
 591   // operation; otherwise infinite loops could be caused.                              
 592   //                                                                                   
 593   mOnGuarding = TRUE;                                                                  
 594   //                                                                                   
 595   // Note: This might overwrite other attributes needed by other features,             
 596   // such as NX memory protection.                                                     
 597   //                                                                                   
 598   Status = gCpu->SetMemoryAttributes (gCpu, BaseAddress, EFI_PAGE_SIZE, EFI_MEMORY_RP);
 599   ASSERT_EFI_ERROR (Status);                                                           
 600   mOnGuarding = FALSE;                                                                 
 601                                                                                       

gCpuEFI_CPU_ARCH_PROTOCOL的实例,和体系结构息息相关。

SetMemoryAttributes的作用是设置某块内存的访问属性,如读写、只读等等。参考ARM平台下的实现ArmPkg/Drivers/CpuDxe,该函数的实现方法是设置页表项的权限控制。

访问属性设置为EFI_MEMORY_RP,其定义与MdePkg/Include/Uefi/UefiSpec.h

  77 #define EFI_MEMORY_WP               0x0000000000001000ULL
  78 #define EFI_MEMORY_RP               0x0000000000002000ULL
  79 #define EFI_MEMORY_XP               0x0000000000004000ULL
  80 #define EFI_MEMORY_RO               0x0000000000020000ULL
  • WP(Write-Protecred)意味着不能写
  • RP(Read-Protected)意味着不能读
  • XP(Execution-Protected)意味着不能执行
  • RO(Read-Only)意味着只能读

参考文献

以上是关于UEFI.源码分析.DXE的内存服务.第三部分.HeapGuard的主要内容,如果未能解决你的问题,请参考以下文章

UEFI.源码分析.DXE的异步事件服务.第一部分.事件驱动

UEFI.源码分析.DXE的异步事件服务.第三部分.定时器与时钟中断

UEFI.源码分析.DXE的异步事件服务.第三部分.定时器与时钟中断

UEFI.源码分析.DXE的内存服务.第一部分.初始化

UEFI.源码分析.DXE的异步事件服务.第二部分.任务优先级

UEFI.源码分析.DXE的异步事件服务.第二部分.任务优先级