UEFI应用内存大小计算异常,QEMU测试显示错误值求助
问题分析与修复
你的代码存在三个核心问题,导致内存大小计算错误、NumberOfPages出现随机负数:
1. 内存描述符指针偏移计算错误
GMS函数中,你用(UINT64*)MMap + i * MMAPDESCSIZE计算第i个描述符的地址,这是完全错误的:
(UINT64*)MMap会将指针按8字节(UINT64长度)步长偏移,而i * MMAPDESCSIZE是字节数,两者叠加会让指针偏移量放大8倍,直接越界访问内存,读取到随机值(包括被当作有符号数解析的负数)。- 正确做法是按字节偏移,先把MMap转成
UINT8*类型,再加上字节偏移量。
2. Print函数格式符不匹配
UEFI的Print函数要求格式符与变量类型严格匹配:
UINT64类型需要用%Lu格式符,你用%d(对应32位有符号整数)会导致64位数值被截断,高位被当作符号位解析,出现负数。- 所有UINT64类型的变量(MapEntries、MapSize、MemSizeB等)都要对应正确的格式符。
3. GetMemoryMap调用缺失错误处理
两次调用GetMemoryMap和AllocatePool都没有检查返回值,若内存分配失败或内存map获取失败,会导致后续操作访问无效内存,引发未知错误。
修正后的完整代码
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/UefiBootServicesTableLib.h> UINT64 GMS(EFI_MEMORY_DESCRIPTOR* MMap, UINT64 MMAPE, UINT64 MMAPDESCSIZE) { UINT64 MemSizeB = 0; // 移除static,避免多次调用返回旧值 for (UINT64 i = 0; i < MMAPE; i++){ // 按字节偏移计算第i个描述符的地址 EFI_MEMORY_DESCRIPTOR *desc = (EFI_MEMORY_DESCRIPTOR*)((UINT8*)MMap + i * MMAPDESCSIZE); // 可选:只统计可用内存类型,按需调整 if (desc->Type == EfiConventionalMemory || desc->Type == EfiLoaderCode || desc->Type == EfiLoaderData) { MemSizeB += desc->NumberOfPages * EFI_PAGE_SIZE; } } return MemSizeB; } EFI_STATUS EFIAPI UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_MEMORY_DESCRIPTOR* Map = NULL; UINTN MapSize, MapKey, DescSize; UINT32 DescVer; EFI_STATUS Status; // 第一次调用获取所需内存大小 Status = SystemTable->BootServices->GetMemoryMap(&MapSize, NULL, &MapKey, &DescSize, &DescVer); if (EFI_ERROR(Status)) { Print(L"Failed to get memory map size: %r\n", Status); return Status; } // 额外多分配一个描述符的空间,避免内存map变化导致溢出 Status = SystemTable->BootServices->AllocatePool(EfiLoaderData, MapSize + DescSize, (void**)&Map); if (EFI_ERROR(Status)) { Print(L"Failed to allocate pool: %r\n", Status); return Status; } // 第二次调用获取实际内存map Status = SystemTable->BootServices->GetMemoryMap(&MapSize, Map, &MapKey, &DescSize, &DescVer); if (EFI_ERROR(Status)) { Print(L"Failed to get memory map: %r\n", Status); SystemTable->BootServices->FreePool(Map); return Status; } UINT64 MapEntries = MapSize / DescSize; Print(L"Map Entries: %Lu\n", MapEntries); Print(L"Map Size: %Lu\n", (UINT64)MapSize); Print(L"Descriptor Size: %Lu\n", (UINT64)DescSize); UINT64 TotalMem = GMS(Map, MapEntries, DescSize); Print(L"Total usable memory: %Lu MB\n", TotalMem / 1024 / 1024); // 释放分配的内存 SystemTable->BootServices->FreePool(Map); return EFI_SUCCESS; }
额外说明
- 移除了
MemSizeB的static修饰:多次调用GMS时会重新计算内存大小,避免调试或重复执行时返回旧值。 - 增加内存类型过滤:默认只统计常规内存、加载器代码/数据区域,若需统计所有内存类型,可移除
if判断。 - 内存分配额外预留空间:遵循UEFI规范,避免两次
GetMemoryMap调用之间内存map变化导致空间不足。 - 增加资源释放:使用完内存map后调用
FreePool释放内存,符合UEFI内存管理要求。
内容的提问来源于stack exchange,提问作者Kitcha
相关产品推荐
相关产品推荐

