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

前回は、メモリマップをテキストファイルに保存しました。

ReadFile() と WriteFile() は使っていないので別ファイルに逃がすことにします。プログラムの動作は変わりません。それに伴い Include フォルダと Library フォルダが増えています。

ファイル構成

  • edk2 フォルダ
    • Hello7Pkg フォルダ
      • Hello7.inf
      • Hello7Pkg.dec
      • Hello7Pkg.dsc
      • Hello7.c
    • Include フォルダ
      • FileLib.h
    • Library フォルダ
      • FileLib.c
      • FileLib.inf
  • 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
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;
}

WriteFile() とReadFile() を削除しました。

Hello7.inf は FileLib を追加していますが、実際には呼び出していないのでコメントアウトしています。

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = Hello7
FILE_GUID       = 21E44BD4-AC20-4D08-9265-87CA1AA79BCC
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.0.1
ENTRY_POINT     = UefiMain

[Sources]
Hello7.c

[Guids]
# gEfiFileInfoGuid
# gEfiFileSystemInfoGuid

[Packages]
MdePkg/MdePkg.dec
Hello7Pkg/Hello7Pkg.dec

[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
MemoryAllocationLib
PrintLib
# FileLib

[Protocols]
gEfiSimpleFileSystemProtocolGuid

Hello7Pkg.dec にも FileLib.h をインクルードすることを知らせています。

[Defines]
DEC_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello7Pkg
PACKAGE_GUID        = 092DE508-84C6-4FA3-884C-1C5E702CB38E
PACKAGE_VERSION     = 0.0.1

[Includes]
Include

[Guids]

[LibraryClasses]
FileLib|Include/FileLib.h

[Protocols]

[PcdsFixedAtBuild]

Hello7Pkg.dsc ファイルにも FileLib を使うことを知らせています。

[Defines]
PLATFORM_NAME           = Hello7Pkg
PLATFORM_GUID           = DA50351D-D7B7-45DD-911C-B54BAB54F702
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello7Pkg
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

FileLib|Hello7Pkg/Library/FileLib/FileLib.inf

[Components]
Hello7Pkg/Hello7.inf

追加した Include フォルダには、FileLib.h を置きます。まあ普通の C のヘッダーです。

#ifndef FILE_LIB_H_
#define FILE_LIB_H_

#include <Uefi.h>
#include <Protocol/SimpleFileSystem.h>

EFI_STATUS
WriteFile(
    IN CONST EFI_FILE_PROTOCOL* Root,
    IN CONST CHAR16* FileName,
    IN CONST CHAR8* Data
    );

EFI_STATUS
ReadFile(
    IN CONST EFI_FILE_PROTOCOL* Root,
    IN CONST CHAR16* FileName,
    OUT CHAR8* Buffer,
    IN UINTN BufferSize,
    OUT UINTN* ReadSize
    );

#endif

Library フォルダには 2 つファイルを追加します。まずは FileLib.c

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Protocol/SimpleFileSystem.h>
#include "FileLib.h"

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;
}

次は Library/FileLib.inc です。 Claude がいうには、こっちにも Hello7Pkg/Hello7Pkg.inf を入れろとあるが、いらないようなきもする

[Defines]
INF_VERSION                    = 0x00010005
BASE_NAME                      = FileLib
FILE_GUID                      = F0022AC1-CEE2-495D-AA5E-5C620186A083
MODULE_TYPE                    = BASE
VERSION_STRING                 = 1.0
LIBRARY_CLASS                  = FileLib

[Sources]
FileLib.c

[Packages]
MdePkg/MdePkg.dec

[LibraryClasses]
UefiLib
PrintLib

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

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

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

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

# イメージディスクに書き込む
$ sudo mount -o loop,uid=$(id -u),gid=$(id -u) disk.img ./mnt
$ cp ./edk2/Build/Hello7Pkg/DEBUG_CLANGDWARF/X64/Hello7.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

ソースもきれいになったことだし、カーネルロードに取り掛かろう