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
中多了如下一段代码,主要包含两个函数IsHeapGuardEnabled
和IsGuardPage
。
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
判断是否开启了page
和pool
的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
宏BIT0
、BIT2
定义于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 块。
- 前五块为
L0
到L4
,其中L0
到L3
分别有 9 bit,L4
有 10 bit。 - 后续一块为
bits
,有 6bit,对于一个UINT64
可以存储的范围,即64位。 - 最后是页的大小,有
12
bit,即 4KB。
对于一个64位的4KB页对齐的地址addr
- 其最后12位
addr[11:0]
必然为0 - 由于每64个页可以用一个8字节的
UINT64
来存储守护信息,所以要定位到包含该页的UINT64
无需addr[17:12]
- 第一,根据
addr[63:55]
作为索引查找L0
层,每一项即一个UINT64
的地址,对应下一级L1`的地址。 - 依次向下,直到寻找到最终
L4
中的一项UINT64
,该项包含了64个页的守护属性 - 此时根据
addr[17:12]
来判断该UINT64
中的哪一位表示这个页的守护属性。
对于普通的4GB内存空间来说,只需要L3
和L4
两层就足够,因为这已经达到了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
gCpu
是EFI_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)意味着只能读
参考文献
- PCD wiki
- A Tour Beyond BIOS Memory Map Design In UEFI BIOS
- Difference between read-only and write-protected
以上是关于UEFI.源码分析.DXE的内存服务.第三部分.HeapGuard的主要内容,如果未能解决你的问题,请参考以下文章
UEFI.源码分析.DXE的异步事件服务.第一部分.事件驱动
UEFI.源码分析.DXE的异步事件服务.第三部分.定时器与时钟中断
UEFI.源码分析.DXE的异步事件服务.第三部分.定时器与时钟中断