WPP
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

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

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

edk2 メモリマップの表示

前回は、ファイル IO のリファクタリングをしたので今回は、メモリマップの表示をしていきます。

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

ファイル構成

  • edk2 フォルダ
    • Hello5Pkg フォルダ
      • Hello5.inf
      • Hello5Pkg.dec
      • Hello5Pkg.dsc
      • Hello5.c
  • mnt フォルダ
  • disk.img

Hello5.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>



typedef struct __MemoryMap {
    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 * 4096 - 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
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 + 2 * 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 CHAR16 *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 CHAR16 *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);

    FreeMemoryMap(&Map);

    return EFI_SUCCESS;
}

ところどころ本と異なるところがあるが、これは、Claude との壁打ちの結果修正していった結果です。

関数の引数と内部で宣言した変数とで大文字・小文字を気分で分けちゃっていますが、どういう規約が一般的なのかあとで調べて修正すると思います。
個人的には、何らかの方法で関数内の変数と外から来た引数がパット見でわかったほうが読みやすいとかんじているのですが、世間的には非常識なんでしょうか?

Hello5.inf は特段変化はない。

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = Hello5
FILE_GUID       = 33EA5F56-E88C-4926-9E64-0A61E753DE4D
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.0.1
ENTRY_POINT     = UefiMain

[Sources]
Hello5.c

[Guids]
gEfiFileInfoGuid
gEfiFileSystemInfoGuid

[Packages]
MdePkg/MdePkg.dec
Hello5Pkg/Hello5Pkg.dec

[LibraryClasses]
UefiLib
UefiApplicationEntryPoint
MemoryAllocationLib

[Protocols]
gEfiSimpleFileSystemProtocolGuid

Hello5Pkg.dec の方も同じくほとんどわからない。

[Defines]
DEC_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello5Pkg
PACKAGE_GUID        = 64F39E0B-37D3-40F9-85A5-26387029161E
PACKAGE_VERSION     = 0.0.1

[Includes]

[Guids]

[Protocols]

[PcdsFixedAtBuild]

Hello5Pkg.dsc ファイルの方も変更はありません。

[Defines]
PLATFORM_NAME           = Hello5Pkg
PLATFORM_GUID           = 1AF3A6F8-9F8E-4FF0-809E-0140EB1C7FCA
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello5Pkg
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]
Hello5Pkg/Hello5.inf

ビルドも同じこんな感じです。

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

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

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

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

次はメモリマップの作成とカーネルのロードにすすんで行きたい。

Mikan 本では、カーネルロード前に、UEFI のサービスを停止するので、カーネル起動後は UEFI の機能は一切使えなくなる。ロード時に画面のフレームバッファを直接渡して、画面の描画ができるようになる。ビデオカードのドライバがない以上そうするしかないんだよな。きっと。
この状態では文字列表示さえ自前で作る必要があるのでなかなか OS って大変だ。

QEMU で自動起動するには、 /EFI/BOOT/BOOTX64.EFI とフォルダ名、ファイル名ともにすべて大文字にする必要があった。なんでこんな簡単なことに気づかなかったのか・・・・。

ちなみにこのプログラムは(以前のものもそうかも) UEFI で自動起動しないほうがいいです。
というのも最後 return で抜けてしまっているので自動起動に成功すると、そのままリセットがかかってしまいます。

UEFI シェルで実行するとこんな感じの表示がされるはずです。
手動実行するには、下のようにします。

>fs0:\EFI\BOOT\hello5.efi

次は、メモリマップをファイル保存するのか、本の少し先まででは、ファイルに保存したメモリマップは使っていなそう?なきがするので飛ばしてもいいのかもしれない。

edk2 ファイル入出力 – その2

前回、edk2 のファイル入出力をやったがちょっと見づらいので書き込みと読み込みを別関数に切り出すことにした。

Hello4Pkg という別のパッケージにしてやってみる。

ファイル構成

  • edk2
    • Hello4Pkg フォルダ
      • Hello4.c
      • Hello4.inf
      • Hello4Pkg.dec
      • Hello4Pkg.dsc
  • disk.img

Hello4.c はこんな感じ。ハイライト行が切り出した「書き込み」、「読み込み」処理。

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

EFI_STATUS
WriteFile(
    IN EFI_FILE_PROTOCOL *Root,
    IN CHAR16 *FileName,
    IN CHAR16 *Data
)
{
    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;
    UINTN WriteSize = StrLen(Data) * sizeof(CHAR16);

    Status = Root->Open(
        Root,
        &File,
        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, 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 CHAR16 *Buffer,
    IN UINTN BufferSize,
    OUT UINTN *ReadSize
)
{
    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;

    Status = Root->Open(
        Root,
        &File,
        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;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem;
    EFI_FILE_PROTOCOL *Root;
    CHAR16 *FileName = L"\\test.txt";

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

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

    CHAR16 *WriteData = L"Hello file system\n";

    Status = WriteFile(Root, FileName, WriteData);
    if (EFI_ERROR(Status)) {
        Root->Close(Root);
        return Status;
    }

    // read
    CHAR16 buf[128];
    UINTN ReadSize = sizeof(buf);
    Status = ReadFile(Root, FileName, buf, ReadSize, &ReadSize);
    if (EFI_ERROR(Status)) {
        Root->Close(Root);
        return Status;
    }

    Print(L"Read from file: %s\n",  buf);

    Root->Close(Root);

    return EFI_SUCCESS;
}

Hello4Pkg.dec ファイルはほとんど変わらない。PACKAGE_NAME は何でもいいのですが、混乱しないように Hello4Pkg に揃えておきます。

[Defines]
DEC_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello4Pkg
PACKAGE_GUID        = 08F29A4C-BB2E-4417-B8E9-3854083B9CC0
PACKAGE_VERSION     = 0.0.1

[Includes]

[Guids]

[Protocols]

[PcdsFixedAtBuild]

Hello4Pkg.dsc も Hello4Pkg に変える以外は変更なし。

[Defines]
PLATFORM_NAME           = Hello4Pkg
PLATFORM_GUID           = 43B8BF63-3328-4A67-83C5-57EA4FAF43DA
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello4Pkg
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]
Hello4Pkg/Hello4.inf

最後にビルドと実行

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

# イメージにファイルコピー
$ sudo mount -o loop,uid=$(id -u),gid=$(id -u) disk.img ./mnt
$ cp ./edk2/Build/Hello4Pkg/DEBUG_CLANGDWARF/X64/Hello4.efi ./mnt/EFI/BOOT
$ sudo umount ./mnt

# 実行
$ 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

# qemu で自動起動しないとき
> fs0:\EFI\BOOT\hello4.efi

それにしても、hello4.efi を BOOTX64.efi というファイル名にリネームしても自動起動しないのはなぜなんだろう。まだまだ謎は多い。

2025-2-12 追記:
> BOOTX64.EFI が正解。イメージディスクは FAT32 なので一度作成してしまったあとで UEFIシェルでは、BOOTX64.efi からBOOTX.EFI にリネームできない。(FAT の仕様では、ファイル名の大文字/小文字の区別はない)

次はメモリマップの作成にかかる。カーネルを読み込むまでにはまだ道のりが遠い。

edk2 ファイル入出力

Mikan 本に沿いながら勝手に作っていくシリーズの続きです。

前回までで文字列を表示する Hello world ができたので、ファイルの入出力をやっていくのがいいだろうということでやっていきます。

UEFI 起動の段階でファイルの入出力ができれば、カーネルをロードするための設定を書き込んだファイルを読み取ることなんかができるようになるはず。

Mikan 本では、メモリマップを作成して memfile? だったかの一時ファイルに書き込んでいるが、デバッグ用途でファイルに書き出したいこともあるかもしれないのでファイル IO をやっておいて悪いことはないだろう。

ファイル構成

  • edk2 フォルダ
    • Hello2Pkg フォルダ
      • Hello2.inf
      • Hello2Pkg.dec
      • Hello2Pkg.dsc
      • Main.c
  • mnt フォルダ
  • disk.img

最終的なツリー構造は上のような感じになる、disk.img は qeum-img で作成したイメージファイルここにビルドしたファイルをコピーして qemu で起動するようにする。

Main.c はファイル読み書きの処理が入るのでほとんど原型をとどめていない。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/MemoryAllocationLib.h>

// A: GetInfo() で仕様
//#include <Guid/FileInfo.h>
//#include <Guid/FileSystemInfo.h>


EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    EFI_STATUS Status;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem;
    EFI_FILE_PROTOCOL *Root;
    EFI_FILE_PROTOCOL *File;
    CHAR16 *FileName = L"\\test.txt";

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

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

/* B: GetInfo() で書き込み可能か調べる。デバッグで仕様した。
    EFI_FILE_SYSTEM_INFO *FSInfo;
    UINTN FSInfoSize = 0;
    Status = Root->GetInfo(
        Root,
        &gEfiFileSystemInfoGuid,
        &FSInfoSize,
        NULL
    );
    if (Status == EFI_BUFFER_TOO_SMALL) {
        FSInfo = AllocatePool(FSInfoSize);
        Status = Root->GetInfo(
            Root,
            &gEfiFileSystemInfoGuid,
            &FSInfoSize,
            FSInfo
        );
        if (!EFI_ERROR(Status)) {
            Print(L"Volume Label: %s\n", FSInfo->VolumeLabel);
            Print(L"Read Only: %d\n", FSInfo->ReadOnly);
            Print(L"Free Space: %d\n", FSInfo->FreeSpace);
        }
        FreePool(FSInfo);
    }
*/


    CHAR16 *WriteData = L"Hello file system\n";
    UINTN WriteSize = StrLen(WriteData) * sizeof(CHAR16);

    // create file
    Status = Root->Open(
        Root,
        &File,
        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");
        Root->Close(Root);
        return Status;
    }

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

    File->Close(File);

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

    // read
    CHAR16 buf[128];
    UINTN ReadSize = sizeof(buf);
    Status = File->Read(File, &ReadSize, buf);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to read file: %r\n", Status);
        File->Close(File);
        Root->Close(Root);
        return Status;
    }

    Print(L"Read from file: %s\n",  buf);

    File->Close(File);
    Root->Close(Root);

    return EFI_SUCCESS;
}

コード中のコメントA:、B:、部分はプログラム作成中にファイル書き込み失敗していた際に調査した残骸。こういうふうにすると開いたハンドル?が書き込み可能か属性を見れるらしい。

Hello2.inf はこんな感じになる。[Guids] の2行は Main.c と同じGetInfo で gEfiFileSystemInfoGuid を使うための記述です。ハイライト行が HelloPkg から追加されたファイルIO の為に追加した部分になります。

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = Hello2
FILE_GUID       = BA807105-6F81-4BB6-ACE5-38FB4480C2A5
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.0.1
ENTRY_POINT     = UefiMain

[Sources]
Main.c

[Guids]
# gEfiFileInfoGuid
# gEfiFileSystemInfoGuid

[Packages]
MdePkg/MdePkg.dec
Hello2Pkg/Hello2Pkg.dec

[LibraryClasses]
UefiLib
UefiApplicationEntryPoint
MemoryAllocationLib

[Protocols]
gEfiSimpleFileSystemProtocolGuid

Hello2Pkg.dec の方は変わらない。(Inlucde フォルダを使っていないので [Includes] の行は削除してしまった。)

C_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello2Pkg
PACKAGE_GUID        = 6FA2CBDB-9033-491F-B17C-58CB2EB69CD9
PACKAGE_VERSION     = 0.0.1

[Includes]

[Guids]

[Protocols]

[PcdsFixedAtBuild]

Hello2Pkg.dsc ファイルの方も変更はありません。

[Defines]
PLATFORM_NAME           = Hello2Pkg
PLATFORM_GUID           = 43B8BF63-3328-4A67-83C5-57EA4FAF43DA
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello2Pkg
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]
Hello2Pkg/Hello2.inf

ビルドは、こんな感じ。

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

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

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

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

次はメモリマップの作成とカーネルのロードにすすんで行きたい。

Mikan 本では、カーネルロード前に、UEFI のサービスを停止するので、カーネル起動後は UEFI の機能は一切使えなくなる。ロード時に画面のフレームバッファを直接渡して、画面の描画ができるようになる。ビデオカードのドライバがない以上そうするしかないんだよな。きっと。
この状態では文字列表示さえ自前で作る必要があるのでなかなか OS って大変だ。

edk2 Hello その2です

前回記事で、edk2 のハローワールドを作りました。今回は、それを qemu で実行していきます。

自分が理解する限り、UEFI の仕様は FAT パーティションに /EFI/BOOT/ というフォルダを作成してそこに起動ファイルを置く必要があるようです。

準備

edk2 のOvmfPkg をビルドしておく必要があります。 build -p OvmfPkg/OVmfPkg.dsc とかでビルドできます。

ディスクイメージの準備

ディスクイメージを作っていきます。ビルドした .efi ファイルは、
edk2/Build/HelloPkg/DEBUG_CLANGDWARF/X64/HelloApp.efi
にあるものとします。

$ qemu-img create -f raw disk.img 128M
$ mkfs.fat -n 'xxos' -s 2 -f 2 -R 32 -F 32 disk.img
; -s 2: 2 セクタ /クラスタ
; -f 2: FAT を2個用意する
; -R 32: 32 セクタを FAT で使用する
; -F 32: FAT32 を使用する

; マウントポイントを作成
$ mkdir ./mnt

$ sudo mount -o loop,uid$(id -u),gid=$(id -u) disk.img ./mnt
$ mkdir ./mnt/EFI/BOOT
$ cp ./ edk2/Build/HelloPkg/DEBUG_CLANGDWARF/X64/HelloApp.efi ./mnt/EFI/BOOT
$ sudo umount ./mnt

実行は本と同じですね。

$	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
		# -net none -nographic

OVMF_CODE.fd と OVMF_VARS.fd は長いので環境変数に逃したほうがいいかも。

上は
fs0:
cd EFI/BOOT
HelloApp.efi

で実行した様子

QEMU にマウスとキーボードを掴まれたときは Ctrl + Alt + G で抜けることが出来る。

確かめていないが HelloApp.efi を適切な名前にすれば自動起動するはずだと思う。

次は、カーネルを作成したいので、まずは edk2 でのファイルの読み込みをやってみよう。 Mikan 本のブートローダーはメモリマップを作成して、ちゃんと空きメモリにカーネルロードしている。

Mikan本 派生でないっぽい Uefi や edk2 のカーネルローダーをネットでみるとやっぱり何かしら空きメモリを探す処理をしているので必要だよな。じゃないと uefi の機能が全部死ぬかもしれぬので画面出力もできなくなるよね、多分。

edk2 Hello

しばらく中断していた Mikan 本関連です。5日目くらいまで本に沿ってやっていたのですがどうも馴染めないのでやり方を変えることにした。

本をみちしるべにしつつも環境構築から勝手に行い、好きに書いていくほうがわかりやすいのでは?と思いやってみることにした。

まずは edk2 Hello world から

事前準備

edk2 をインストールして予め BaseTools をビルドしておくこと。

BaseTools に build スクリプトが含まれるのでこれをしとかないとビルドができない。

ツリー構造

今回つくる HelloPkg は最終的にこういう感じで配置しています。edk2 フォルダは git clone した時にできたフォルダになります。

  • プロジェクトルート
    • edk2 フォルダ
      • Build
      • HelloPkg << 今回作るパッケージ
        • Include
        • Library
        • Main.c
        • Hello.inf
        • HelloPkg.dec
        • HelloPkg.dsc

コード

Claude に聞くと上記の各アイテムを作れと言わるので作っていきます。

$ cd edk2
$ mkdir Include Library
$ touch Main.c Hello.inf HelloPkg.dec Hello.Pkg.dsc

各ファイルはこんな感じになります。

最初は、C ソースです。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>

EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE       ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    Print(L"Hello edk2\n");
    return EFI_SUCCESS;
}

UefiMain はなんにもしていないので極めてシンプルです。

次は、 HelloPkg.dec です。
このファイルの役割はよくわかっていませんが今の所、あまり書くことはありません。

[Defines]
DEC_SPECIFICATION = 0x00010005
PACKAGE_NAME      = HelloPkg
PACKAGE_GUID      = 25d16f8a-7fb0-467d-a13d-97c83f2a84a4
PACKAGE_VERSION   = 0.1


[Includes]
Include

[Guids]

[Protocols]

[PcdsFixedAtBuild]

DEC_SPECIFICATION は edk2 の公式ドキュメントで定義されている DEC のバージョンです。
PACKAGE_GUID は uuidgen などで生成した値を指定します。

edk2 ではちょくちょく GUID を指定する場面がありますが、その都度 uuidgen などで生成してコピペしてやります。

HelloPkg.dsc です。

[Defines]
PLATFORM_NAME               = HelloPkg
PLATFORM_GUID               = 46fcca5f-7fe2-4cd7-83e4-f0b535d60410
PLATFORM_VERSION            = 0.1
DSC_SPECIFICATION           = 0x00010005
OUTPUT_DIRECTORY            = Build/HelloPkg
SUPPORTED_ARCHITECTURES     = X64
BUILD_TARGETS               = DEBUG|RELEASE|NOOPT
SKUID_IDENTIFIER            = DEFAULT

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

# dependencies
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]
HelloPkg/Hello.inf

先程と異なり結構書きます。

どうやらこのファイルに記述したモジュール名を頼りにビルドの依存関係を解決していくようです。

その仕組みと source edksetup.sh を使って、Main.c で突然出てきた Uefi.h とかを参照し、リンクするってことをしているようです。

当初 [LibraryClasses] には UefiApplicationEntryPoint と UefiLibMdePkg の2行しか記述していなかったのですが、それだと edk2 の build コマンドでビルドエラーが出てきます。
例えば、 BaseLibMdePkg が見つからないよとか言ってきます。直してはビルドを繰り返していくうちにこういうふうになりました。結局 Mikan 本の[LibraryClasses] とほぼ同じ感じになったはず。

Hello.inf です

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = HelloApp
FILE_GUID       = 2bbb1fb0-95b5-4b59-a98c-15093d771b11
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.1
ENTRY_POINT     = UefiMain

[Sources]
Main.c

[Packages]
MdePkg/MdePkg.dec
HelloPkg/HelloPkg.dec

[LibraryClasses]
UefiLib
UefiApplicationEntryPoint

ビルドすると BASE_NAME が引き継がれて HelloApp.efi が作成されるようです。

ビルド結果は edk2/Build/HelloPkg/DEBUG_CLANGDWARF/X64 に保存されます。

保存先のフォルダは build コマンドに引数を渡さなければ以下のようになります。

DEBUG_CLANGDWARF は、edk2/Conf のtarget.txt の TARGET= と TOOL_CHAIN_TAG= を組み合わせたものになります。GCC を使っていれば DEBUG_GCC とかになります。

X64 の部分も同じように target.txt の TARGET_ARCHI= の値が来ます。

build -t CLANGDWARF とかすると target.txt の内容を無視して指定の内容で実行されます。

長くなったので QEMU での実行は次の記事にします。

arm64 ROS2 環境を podman で動かす

いろいろと紆余曲折ありましたが、最終的に podman で動かすことで落ち着きそうなので記事にします。

d最近の私のお供は Claude なのですが早速聞いてみますとつらつらと Dockerfile を提示してきます。多少アレンジしてますが骨子はそのままです。

FROM --platform=linux/arm64 ubuntu:22.04


ENV DEBIAN_FRONTEND=noninteractive

RUN rm /var/lib/dpkg/info/libc-bin.* && apt-get clean

# 日本のミラーに向ける
RUN sed -i 's@archive.ubuntu.com@ftp.jaist.ac.jp/pub/Linux@g' /etc/apt/sources.list

RUN apt-get update && \
    apt-get install -y wget \
    curl \
    git \
    vim \
    lsb-release \
    build-essential python3-pip python3-setuptools python3-dev \
    gnupg2 xterm x11-apps

# ROSのセットアップ(ここでは例としてROS 2 Humbleを使用)
RUN curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
RUN echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | tee /etc/apt/sources.list.d/ros2.list > /dev/null
RUN apt-get update && apt-get install -y \
    ros-humble-ros-base \
    ros-humble-rqt* \
    ros-humble-rviz2 \
    python3-colcon-common-extensions

# 作業ディレクトリの作成
RUN mkdir -p /workspace
WORKDIR /workspace

# 環境変数の設定
RUN echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc

変更かけたのは、 sed で日本のミラーに向けたところと、rm /var/lib/dpkg/info/libc-bin.* でファイルを削除した箇所です。

特にこの libc-bin.* の削除をしないと、apt-get でのパッケージインストールにことごとく失敗するします。なんとなく削除するのは良くないような気もするのですが、 Stack Overflow でもいくつか Good がついているので大丈夫じゃないかと。

setup.bash もすでに読み込み済みなので起動すると即 ros2 コマンドが実行できる状態になっています。

ビルドと起動は下のようにします。

Claud は気が利いているのどうなのかこの状態で GUI アプリも動作出来るようにしてくれているようでコンテナを実行するにはこうするみたいです。

$  ls ./Dockerfile
./Dockerfile

$ podman build -t ros2-arm64 .

$ podman run --platform linux/arm64 \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  -e DISPLAY=$DISPLAY \
  -v $HOME/.Xauthority:/root/.Xauthority \
  -v $HOME/ros2_ws:/workspace \
  --net=host \
  -it localhost/ros2-arm64

起動のたびにこのコマンド打つのはしんどいのでシェルスクリプトかなんかにしたほうがよさげ。

でコンテナの中で動作確認するには、下のようにしろと Claude が言うので試してみる。

; ホストに 目玉アプリが表示される。
in-contailner: # xeyes

; ホストに ros2 の rqt が表示される
in-contailner: # rqt 

ちゃんと計測したわけではないが qemu-system でエミュレーションより少し早い気がする。

にしても Claude に言われるがままだな。

参考

Dockerのイメージビルド中でapt-getを高速化するたった1つの方法 | ゲンゾウ用ポストイット
souces.list のミラーを日本の jaist に向ける設定

ruby on rails – Docker build fails because unable to install libc-bin – Stack Overflow
libc-bin のエラー回避

お手軽に arm64 クロスビルド環境を作る

引きつづきラズベリーパイの ROS2 環境をいじっているんですが、ふと ROS2 用のコードをビルドするだけなら何も Docker や Podman、QEMU で OS 動かしたりする必要ねんじゃね?と思い PC 側でクロスビルド環境を作ってみた。

$ sudo apt install crosbuild-essential-arm64
$ sudo libc6-dev-arm64-cross

もしかすると、libc6-dev-arm64-cross はいらないかもしれない。

インストールすると /usr/aarch64-linux-gnu/ にライブラリやヘッダーファイルなどが保存される

試しにソースをビルドしてみます。

#include <stdio.h>

int main(int, char**)
{
   printf("hello- arm!!\n");
   return 0;
 }

clang でビルドしてみる

$ clang --target=aarch64-linux-gnu hello.c

a.out が出来るので確認してみる

$ file a.out
a.out: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=af0df26fcefe0b9815d6892dd721439aaf1759e6, for GNU/Linux 3.7.0, not stripped

ちゃんと aarch64 になってる。

普通に実行すると怒られる
$ a.out
aarch64-binfmt-P: Could not open '/lib/ld-linux-aarch64.so.1': No such file or directory

実行する。

$ sudo apt install qemu-user
$  qemu-aarch64  -L /usr/aarch64-linux-gnu/ ./a.out 
hello- arm!!

または、QEMU_LD_PREFIX 環境変数を設定して実行
$ QEMU_LD_PREIX=/usr/aarch64-linux-gnu ./a.out

もしかすると qemu-user-static パッケージも必要かもしれない。そのへんは適宜インストールすること。

Arm 用の ROS2 環境を /usr /aarch64-linux-gnu に展開する方法はまた考えよう。

参考

clang + cmake で aarch64 linux 向けに C/C++ アプリをクロスコンパイルするメモ #CMake – Qiita

QEMU でARM エミュレータ環境を作成する #ARM – Qiita

LM22 (Ubuntu 24.04) で pyrealsense2 をセットアップするためにしたあれこれ

結論からいうと pyenv で python 3.11 入れれば OK。

LM22 (Ubuntu 24.04相当) には、python 3.12 が入っていて pyrealsense2 を pip でインストールできなかったので、当初仕方なくビルドしました。

ビルドには成功しましたが、自分の知識では pipenv 環境下に配置することができずに結局は pyenv で python3.11 をインストールし pip しました。
pipenv 環境下でビルドすると仮想環境の lib に make インストールすることになるようでインストールした pyrealsense2 を pipenv から認識することができませんでした。

ビルドの方法はこんな感じ、必要に応じて -dev パッケージは追加してください。

$ git clone 
$ sudo apt install libssl-dev
$ sudo apt-get install libglfw3-dev 
$ sudo apt install  libudev-dev
$ sudo apt install libusb-dev- libusb-1.0-0-dev


$ git clone https://github.com/IntelRealSense/librealsense.git realsense
$ cd realsense
$ git checkout v.2.56.2
$ mkdir build
$ cd build
$ cmake ../ -DBUILD_PYTHON_BINDINGS:bool=true -DBUILD_EXAMPLES:bool=true -DBUILD_PYTHON_DOCS=true

$ make -j4

pythonサンプルは [cloneしたフォルダ]/wrappers/python/examples の中に入っている。

冒頭にも書いている通り pyenv で python 3.11 をインストールした。というのも LM22 (多分 Ubuntu 24.04 も) Python 3.12 以下のバージョンのパッケージは用意されていないので apt でインストールできない。
なので、pyenv を使って以下の手順で回避した。(もちろん pyenv でなくてもいい)

$ curl https://pyenv.run | bash
$ pyenv local 3.11
$ pipenv shell
$ pipenv install pyrealsense2

; エラーがでなければインストールに成功している。
$ python 
Python 3.11.10 (main, Oct 24 2024, 17:33:16) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyrealsense2
>>> 

誤って .python-version を消さないようにしなくちゃ。

参考

librealsense/wrappers/python at master · IntelRealSense/librealsense