edk2 メモリマップをファイルに保存

前回は、メモリマップを画面にダンプしたので今回は、メモリマップをテキストファイルに保存します。

Mikan 本にならい、メモリマップ用の構造体を作り、UEFI コンソールにメモリマップを表示してみます。

ファイル構成

  • edk2 フォルダ
    • Hello6Pkg フォルダ
      • Hello6.inf
      • Hello6Pkg.dec
      • Hello6Pkg.dsc
      • Hello6.c
  • mnt フォルダ
  • disk.img

Hello6.c は前回までファイル読み書き用の関数は後で使う予定なのでそのまま残してメモリマップまわりの処理を追加していく。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/MemoryAllocationLib.h>
#include <Guid/FileInfo.h>
#include <Guid/FileSystemInfo.h>
#include <Library/PrintLib.h>

#define MEMORY_MAP_BUFFER_PADDING 2

typedef struct {
    UINTN BufferSize;
    VOID* Buffer;
    UINTN MapSize;
    UINTN MapKey;
    UINTN DescriptorSize;
    UINT32 DescriptorVersion;
} MemoryMap;

VOID
InitializeMemoryMap(
    OUT MemoryMap* map
)
{
    if (map == NULL) return;

    map->BufferSize = 0;
    map->Buffer = NULL;
    map->MapSize = 0;
    map->MapKey = 0;
    map->DescriptorSize = 0;
    map->DescriptorVersion =0;
}

VOID
FreeMemoryMap(
    IN OUT MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) return;

    FreePool(map->Buffer);
    map->Buffer = NULL;
    map->BufferSize = 0;
}

const CHAR16*
GetMemoryTypeString(
    IN EFI_MEMORY_TYPE type
)
{
    switch (type) {
        case EfiReservedMemoryType:     return L"Reserved";
        case EfiLoaderCode:             return L"LoaderCode";
        case EfiLoaderData:             return L"LoaderData";
        case EfiBootServicesCode:       return L"BootCode";
        case EfiBootServicesData:       return L"BootData";
        case EfiRuntimeServicesCode:    return L"RuntimeCode";
        case EfiRuntimeServicesData:    return L"RuntimeData";
        case EfiConventionalMemory:     return L"Conventional";
        case EfiUnusableMemory:         return L"Unusable";
        case EfiACPIReclaimMemory:      return L"ACPIReclaim";
        case EfiACPIMemoryNVS:          return L"ACPINVS";
        case EfiMemoryMappedIO:         return L"MMIO";
        case EfiMemoryMappedIOPortSpace: return L"MMIOPort";
        case EfiPalCode:                return L"PalCode";
        default:                        return L"Unknown";
    }
}


VOID
PrintMemoryMapEntry(
    IN UINTN index,
    IN CONST EFI_MEMORY_DESCRIPTOR* desc
)
{
    Print(L"%03d: Type=%s  Phys=0x%016lx-0x%016lx  Pages=%ld  Attr=0x%016lx\n",
        index,
        GetMemoryTypeString(desc->Type),
        desc->PhysicalStart,
        desc->PhysicalStart + desc->NumberOfPages * EFI_PAGE_SIZE - 1,
        desc->NumberOfPages,
        desc->Attribute
    );
}

VOID
PrintMemoryMap(
    IN CONST  MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) {
        Print(L"Error: invalid memory map\n");
        return;
    }

    EFI_MEMORY_DESCRIPTOR* Desc = (EFI_MEMORY_DESCRIPTOR*)map->Buffer;
    UINTN Entries = map->MapSize / map->DescriptorSize;

    Print(L"Memory Map: %d entries\n", Entries);
    Print(L"DescriptorSize: %d\n",map->DescriptorSize);

    for (UINTN i = 0; i < Entries; i++) {
        PrintMemoryMapEntry(i, Desc);
        Desc = NEXT_MEMORY_DESCRIPTOR(Desc, map->DescriptorSize);
    }
}

EFI_STATUS
SaveMemoryMap(
    IN CONST MemoryMap* map,
    IN CONST EFI_FILE_PROTOCOL* root,
    IN CONST CHAR16* fileName
)
{
    if (map == NULL || root == NULL || fileName ==NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL* File;
    UINTN WriteSize;

    Status = root->Open(
        (EFI_FILE_PROTOCOL*)root,
        &File,
        (CHAR16*)fileName,
        EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to create memory map file: %r\n", Status);
        return Status;
    }

    // CSV header
    CHAR8* Header = "Index, Type, PhysicalStart, NumberOfPages, Attribute\r\n";
    WriteSize = AsciiStrLen(Header);
    Status = File->Write(File, &WriteSize, Header);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write header%r\n", Status);
        File->Close(File);
        return Status;
    }

    EFI_MEMORY_DESCRIPTOR* Desc = (EFI_MEMORY_DESCRIPTOR*)map->Buffer;
    UINTN Entries = map->MapSize / map->DescriptorSize;

    for (UINTN i = 0; i < Entries; i++) {
        
        // setup string
        CHAR8 Line[256];

        WriteSize = AsciiSPrint(Line, sizeof(Line),
            "%d, %d, %-ls, 0x%08lx, %ld, 0x%lx\r\n",
            i,
            Desc->Type,
            GetMemoryTypeString(Desc->Type),
            Desc->PhysicalStart,
            Desc->NumberOfPages,
            Desc->Attribute
        );

        // write string to file
        Status = File->Write(File, &WriteSize, Line);
        if (EFI_ERROR(Status)) {
            Print(L"fail: to write entry: %d%r\n", Status);
            File->Close(File);
            return Status;
        }

        Desc = NEXT_MEMORY_DESCRIPTOR(Desc, map->DescriptorSize);
    }

    // add Summary Info.
    CHAR8 Summary[256];
    WriteSize = AsciiSPrint(Summary, sizeof(Summary),
        "\r\nMemory Map Summary\r\nEntries: %d\r\nDescriptor Size: %d\r\n",
        Entries,
        map->DescriptorSize
    );

    Status = File->Write(File, &WriteSize, Summary);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write MemMap summary%r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
GetMemoryMap(MemoryMap* map) {
    EFI_STATUS Status;

    if (map == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    // 初期サイズ 0 でメモリマップの大きさを取得
    map->MapSize = 0;
    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (Status != EFI_BUFFER_TOO_SMALL) {
        Print(L"Error: Unexpected GetMemory Status: %r\n", Status);
        return Status;
    }

    map->BufferSize = map->MapSize + MEMORY_MAP_BUFFER_PADDING * map->DescriptorSize;

    if (map->Buffer != NULL) {
        FreePool(map->Buffer);
    }

    map->Buffer = AllocatePool(map->BufferSize);
    if (map->Buffer == NULL) {
        Print(L"fail: to allocate memory for map buffer: %r\n", Status);
        return EFI_OUT_OF_RESOURCES;
    }

    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (EFI_ERROR(Status)) {
        FreePool(map->Buffer);
        map->Buffer = NULL;
        Print(L"fail: to get memory map: %r\n", Status);
        return Status;
    }

    return EFI_SUCCESS;
}



EFI_STATUS
WriteFile(
    IN CONST EFI_FILE_PROTOCOL *Root,
    IN CONST CHAR16 *FileName,
    IN CONST CHAR8 *Data
)
{
    if (Root == NULL || FileName == NULL || Data == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;
    UINTN WriteSize = StrLen(Data) * sizeof(CHAR16);

    Status = Root->Open(
        (EFI_FILE_PROTOCOL*)Root,
        &File,
        (CHAR16*)FileName,
        EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to create file\n");
        return Status;
    }

    Status = File->Write(File, &WriteSize, (VOID*)Data);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write file\n");
        File->Close(File);
        return Status;
    }
    
    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
ReadFile(
    IN EFI_FILE_PROTOCOL *Root,
    IN CHAR16 *FileName,
    OUT CHAR8 *Buffer,
    IN UINTN BufferSize,
    OUT UINTN *ReadSize
)
{
    if (Root == NULL || FileName == NULL || Buffer == NULL || 
        BufferSize == 0 || ReadSize == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;

    Status = Root->Open(
        Root,
        &File,
        (CHAR16*)FileName,
        EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to open file for reading\n");
        return Status;
    }

    *ReadSize = BufferSize;
    Status = File->Read(File, ReadSize, Buffer);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to read file: %r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    EFI_STATUS Status;
    MemoryMap Map;

    InitializeMemoryMap(&Map);

    Status = GetMemoryMap(&Map);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to get memory map %r\n", Status);
        return Status;
    }

    PrintMemoryMap(&Map);

    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* FileSystem;
    EFI_FILE_PROTOCOL* Root;
    Status = SystemTable->BootServices->LocateProtocol(
        &gEfiSimpleFileSystemProtocolGuid,
        NULL,
        (VOID**)&FileSystem
    );
    if (EFI_ERROR(Status)) {
        Print(L"faile: locate file system protocol\n");
        FreeMemoryMap(&Map);
        return Status;
    }

    Status = FileSystem->OpenVolume(FileSystem, &Root);
    if (EFI_ERROR(Status)) {
        Print(L"faile: open root directory\n");
        FreeMemoryMap(&Map);
        return Status;
    }    

    Status = SaveMemoryMap(&Map, Root, L"\\memmap.csv");
    if (EFI_ERROR(Status)) {
        Print(L"%r\n");
        Root->Close(Root);
        FreeMemoryMap(&Map);
        return Status;
    }

    Print(L"\nMemMap ha benn saved to \\memmap.csv\n");


    Root->Close(Root);
    FreeMemoryMap(&Map);

    return EFI_SUCCESS;
}

Hello6.inf に、 PrintLib を追加した。PrintLib の中に AsciiSPrint() が含まれているらしい。

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = Hello6
FILE_GUID       = A522DAB7-30AE-4F3D-85D1-92D1C24BEF30
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.0.1
ENTRY_POINT     = UefiMain

[Sources]
Hello6.c

[Guids]
gEfiFileInfoGuid
gEfiFileSystemInfoGuid

[Packages]
MdePkg/MdePkg.dec
Hello6Pkg/Hello6Pkg.dec

[LibraryClasses]
UefiLib
UefiApplicationEntryPoint
MemoryAllocationLib
PrintLib

[Protocols]
gEfiSimpleFileSystemProtocolGuid

Hello6Pkg.dec はほとんどわからない。

[Defines]
DEC_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello6Pkg
PACKAGE_GUID        = A1D6D642-D269-44DF-B225-5FFA7AB8E958
PACKAGE_VERSION     = 0.0.1

[Includes]

[Guids]

[Protocols]

[PcdsFixedAtBuild]

Hello6Pkg.dsc ファイルは変更はありません。ということは、以前のものは、PrintLib 行はいらなかったのかもしれない。

[Defines]
PLATFORM_NAME           = Hello6Pkg
PLATFORM_GUID           = DA50351D-D7B7-45DD-911C-B54BAB54F702
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello6Pkg
SUPPORTED_ARCHITECTURES = X64
BUILD_TARGETS           = DEBUG | RELEASE | NOOPT
SKUID_IDENTIFIER        = DEFAULT

[LibraryClasses]
UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf


BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
RegisterFilterLib|MdePkg/Library/RegisterFilterLibNull/RegisterFilterLibNull.inf
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf

[Components]
Hello6Pkg/Hello6.inf

ビルドはいつもと同じ感じです。

上手く動くと、test.txt がイメージファイルのルートディレクトリに作成され、QEMU の画面に test.txt から読み込んだ Hello file system\n が表示されるはず。

test.txt が存在する状態で実行してもファイルを作り直しているのかファイルのタイムスタンプは更新されて問題なく動作している。

$ cd edk2
$ build -p Hello6Pkg/Hello6Pkg.dsc
$ cd ..

# イメージディスクに書き込む
$ sudo mount -o loop,uid=$(id -u),gid=$(id -u) disk.img ./mnt
$ cp ./edk2/Build/Hello6Pkg/DEBUG_CLANGDWARF/X64/Hello6.efi ./mnt/EFI/BOOT
$ sudo umount ./mnt

# QEMU で実行
$ qemu-system-x86_64 \
		-drive if=pflash,format=raw,readonly=on,file=edk2/Build/OvmfX64/DEBUG_GCC/FV/OVMF_CODE.fd \
		-drive if=pflash,format=raw,file=edk2/Build/OvmfX64/DEBUG_GCC/FV/OVMF_VARS.fd \
		-drive format=raw,file=disk.img

次こそカーネルのロードにに進める。

memmap.csv は fs0: の ルートに作成される。上手く行けばこんな感じになる。QEMU を同じように起動すれば恐らく同じようなメモリマップになるはず。

Index, Type, PhysicalStart, NumberOfPages, Attribute
0, 7, Conventional, 0x00000000, 160, 0xF
1, 7, Conventional, 0x00100000, 1792, 0xF
2, 10, ACPINVS, 0x00800000, 8, 0xF
3, 7, Conventional, 0x00808000, 3, 0xF
4, 10, ACPINVS, 0x0080B000, 1, 0xF
5, 7, Conventional, 0x0080C000, 5, 0xF
6, 10, ACPINVS, 0x00811000, 239, 0xF
7, 4, BootData, 0x00900000, 3712, 0xF
8, 7, Conventional, 0x01780000, 9196, 0xF
9, 4, BootData, 0x03B6C000, 32, 0xF
10, 7, Conventional, 0x03B8C000, 9325, 0xF
11, 1, LoaderCode, 0x05FF9000, 227, 0xF
12, 7, Conventional, 0x060DC000, 90, 0xF
13, 4, BootData, 0x06136000, 31, 0xF
14, 7, Conventional, 0x06155000, 15, 0xF
15, 1, LoaderCode, 0x06164000, 3, 0xF
16, 7, Conventional, 0x06167000, 3, 0xF
17, 4, BootData, 0x0616A000, 1569, 0xF
18, 3, BootCode, 0x0678B000, 180, 0xF
19, 4, BootData, 0x0683F000, 108, 0xF
20, 3, BootCode, 0x068AB000, 30, 0xF
21, 4, BootData, 0x068C9000, 1, 0xF
22, 3, BootCode, 0x068CA000, 22, 0xF
23, 4, BootData, 0x068E0000, 2, 0xF
24, 3, BootCode, 0x068E2000, 21, 0xF
25, 4, BootData, 0x068F7000, 4, 0xF
26, 3, BootCode, 0x068FB000, 30, 0xF
27, 4, BootData, 0x06919000, 5, 0xF
28, 3, BootCode, 0x0691E000, 47, 0xF
29, 4, BootData, 0x0694D000, 6, 0xF
30, 3, BootCode, 0x06953000, 40, 0xF
31, 4, BootData, 0x0697B000, 1, 0xF
32, 3, BootCode, 0x0697C000, 21, 0xF
33, 4, BootData, 0x06991000, 8, 0xF
34, 3, BootCode, 0x06999000, 19, 0xF
35, 4, BootData, 0x069AC000, 1, 0xF
36, 3, BootCode, 0x069AD000, 43, 0xF
37, 4, BootData, 0x069D8000, 5, 0xF
38, 3, BootCode, 0x069DD000, 35, 0xF
39, 4, BootData, 0x06A00000, 514, 0xF
40, 3, BootCode, 0x06C02000, 3, 0xF
41, 4, BootData, 0x06C05000, 3, 0xF
42, 3, BootCode, 0x06C08000, 11, 0xF
43, 4, BootData, 0x06C13000, 1, 0xF
44, 3, BootCode, 0x06C14000, 4, 0xF
45, 4, BootData, 0x06C18000, 6, 0xF
46, 3, BootCode, 0x06C1E000, 47, 0xF
47, 4, BootData, 0x06C4D000, 9, 0xF
48, 3, BootCode, 0x06C56000, 33, 0xF
49, 4, BootData, 0x06C77000, 4, 0xF
50, 3, BootCode, 0x06C7B000, 28, 0xF
51, 4, BootData, 0x06C97000, 5, 0xF
52, 3, BootCode, 0x06C9C000, 7, 0xF
53, 4, BootData, 0x06CA3000, 3, 0xF
54, 3, BootCode, 0x06CA6000, 36, 0xF
55, 4, BootData, 0x06CCA000, 3, 0xF
56, 3, BootCode, 0x06CCD000, 15, 0xF
57, 4, BootData, 0x06CDC000, 2, 0xF
58, 3, BootCode, 0x06CDE000, 3, 0xF
59, 4, BootData, 0x06CE1000, 3, 0xF
60, 3, BootCode, 0x06CE4000, 38, 0xF
61, 4, BootData, 0x06D0A000, 5, 0xF
62, 3, BootCode, 0x06D0F000, 2, 0xF
63, 4, BootData, 0x06D11000, 1042, 0xF
64, 3, BootCode, 0x07123000, 6, 0xF
65, 4, BootData, 0x07129000, 2, 0xF
66, 3, BootCode, 0x0712B000, 10, 0xF
67, 4, BootData, 0x07135000, 6, 0xF
68, 3, BootCode, 0x0713B000, 23, 0xF
69, 4, BootData, 0x07152000, 923, 0xF
70, 6, RuntimeData, 0x074ED000, 256, 0x800000000000000F
71, 5, RuntimeCode, 0x075ED000, 256, 0x800000000000000F
72, 0, Reserved, 0x076ED000, 128, 0xF
73, 9, ACPIReclaim, 0x0776D000, 18, 0xF
74, 10, ACPINVS, 0x0777F000, 128, 0xF
75, 4, BootData, 0x077FF000, 1537, 0xF
76, 7, Conventional, 0x07E00000, 96, 0xF
77, 4, BootData, 0x07E60000, 32, 0xF
78, 3, BootCode, 0x07E80000, 47, 0xF
79, 0, Reserved, 0x07EAF000, 4, 0xF
80, 10, ACPINVS, 0x07EB3000, 2, 0xF
81, 3, BootCode, 0x07EB5000, 1, 0xF
82, 4, BootData, 0x07EB6000, 17, 0xF
83, 3, BootCode, 0x07EC7000, 45, 0xF
84, 6, RuntimeData, 0x07EF4000, 132, 0x800000000000000F
85, 10, ACPINVS, 0x07F78000, 136, 0xF
86, 11, MMIO, 0xFFC00000, 1024, 0x8000000000000001
87, 0, Reserved, 0xFD00000000, 3145728, 0x0

Memory Map Summary
Entries: 88
Descriptor Size: 48