WPP
Windows の pipenv でも上下キーでヒストリーをたどりたい

タイトルのとおりですが、Windows の powershell 7 で pipenv shell するとコマンドの実行履歴がたどれなくなりなんとかしたいと思っていた。

ネット上を検索するといくつか回避策を見つけられたがどれも自分にはしっくりこなかったので、Claude に聞いてみて解決策を見つけたので記事にする。

そもそも pipenv shell すると powershell にいるにも関わらず command プロンプトが起動しているのが原因じゃないかと推測している。

いくつかのページでは、pipenv run pwsh で、直接powershell を起動すればいい的なことを書いているがそうするとプロンプト文字列の (VENV名) が消えてしまう。

(VENV名) は venv フォルダの Scripts の中にある activate.ps1 や activate.bat を起動すると表示できる。

解決策は Claude が言うままに $PROFILE に関数とエリアスを定義してやる。


function Invoke-PipenvPwsh {
  $venv_path = pipenv --venv 2>$null
  if ($venv_path -and (Test-path "$venv_path\Scripts\Activate.ps1")) {
    & "$venv_path\Scripts\Activate.ps1"
  } else {
    Write-error "Pipenv venv not found"
  }
}

Set-Alias pipenv-shell Invoke-PipenvPwsh

; これは不要だけど、名前を対照的にするためにエリアスを定義
Set-Alias pipenv-exit deactivate

反映するには以下を実行する。

; $PROFILE を反映する
PS > . $PROFILE

; pipenv shell の変わりに pipenv-shell で activate.ps1  を起動する
PS > pipenv-shell
(VENV 名)  PS > 

; 注意 exit しちゃうと shell そのものから抜けちゃうので  deactivate で抜ける
(VENV 名)  PS >  deactivate

function でやっていることは特別難しいことではなくて、 pipenv –venv で venv の場所を取得して、そのなかの activate.ps1 を起動している。

win / Linux に限らず shell 関係のスクリプトを空で書くのって結構面倒だからほんと Claude に頼りきりだ。こうやって人は AI の奴隷になっていくのかと思ってみたり。

ともあれ、これでちょっといい感じになった。

Apt でインストールした ROS の gpg がエラーになる

apt update すると下記の警告がでるようになった。参考リンクによると今月に入ってから、らしい。

おおよそ GPG キーの期限切れだろうと予測していたが (サードパーティの apt リポジトリを追加していると比較的出くわす) がその通りだった。

W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: http://packages.ros.org/ros2/ubuntu noble InRelease: The following signatures were invalid: EXPKEYSIG F42ED6FBAB17C654 Open Robotics <info@osrfoundation.org>
W: Failed to fetch http://packages.ros.org/ros2/ubuntu/dists/noble/InRelease  The following signatures were invalid: EXPKEYSIG F42ED6FBAB17C654 Open Robotics <info@osrfoundation.org>
W: Some index files failed to download. They have been ignored, or old ones used instead.

GPG キーを入れ替えればいいだけなのだが、URL がわからなかったのでメモする。

鍵は https://raw.githubusercontent.com/ros/rosdistro/master/ros.key にあるようなので

curl -sS https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o ros.key
とか
wget https://raw.githubusercontent.com/ros/rosdistro/master/ros.key

あとは /etc/apt/sources.list.d にある ROS リポジトリの設定に従って鍵ファイルを配置して(入れ替えて)やれば OK。

自分の場合は、

/etc/apt/sources.list.d/ros2.list にリポジトリを定義してあり、鍵ファイルは

/usr/share/keyrings/ros.gpg

となっていたので、 ros.key を ros.gpg として /usr/share/keyrings に配置してやればいい。

参考

2025年6月以降ROS環境でapt-get updateするときにGPG errorが発生する場合の対処方法

VSCode で g++ を使うための設定

他のページもちらほら見つかるが自分的最小メモを残しておく。

VSCode には予め CMake Tools のエクステンションをインストールしておく。

そして VSCode 上でステップ実行(デバッグ)するために 2 つの設定ファイルを作成 (編集)する必要があります。

tasks.json でビルド方法を定義し、launch.json でデバック起動方法を定義します。

CMake extensionの設定

CMake がインストールされていると VSCode の左下のほうに CMake .. のような表示が出てくるのでそこをクリックして、コンパイラを選択します。

tasks.json の設定

task.json ではビルドコマンドを定義します。なのでここでは、cmake を使ったビルド環境に合わせて調整します。

プロジェクトのフォルダに .vscode フォルダがあるはずなのでその中の tasks.json を編集します。

CTRL + SHIFT + P を押して tasks: Configure task などを選んで設定を追加します。

{
    "tasks": [
        {
            "label": "CMake: build",
            "type": "shell",
            "command": "/usr/bin/cmake",
            "args": [
                "--build",
                "${workspaceFolder}/build",
                "--config",
                "Debug"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": [
                "$gcc"
            ],
            "detail": "Task generated by CMake Tools."
        }
    ],
    "version": "2.0.0"
}

“label” は表示される名前なので好きな感じにして OK です。
“command” と “args” でコマンドラインを組み立てます。ソースと同じディレクトリで実行するなら
`cmake –build ./build –config Debug` というコマンドになります。
ビルドディレクトリを変えたければ “${workspaceFolder}/build” を変更します。

tasks.json を設定しても、VSCode の Run メニューとかでは実行できないようです。どうしたら実行できるのかは後で調べる。

launch.json の設定

launch.json では VSCode の デバッグタブ? (左端の Run & Debug) で上部に表示できるデバッグ時の起動方法を設定する。

デバッグタブのギヤアイコンをクリックすると設定を追加できる。

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "CMake Debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "${command:cmake.launchTargetPath}",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [
                {
                    "name": "PATH",
                    "value": "S{env:PATH}",
                    // "name": "LD_LIBRARY_PATH",
                    // "value": "/usr/local/cuda-11.8/lib64:/usr/local/cuda-11.8/lib64/stubs:/usr/local/cuda-11.8/targets/x86_64-linux/lib"
                }
            ],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb.",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "CMake: build"
        }
    ]
}

launch.json はきほんそのままで動くみたいだが、
“environment” に PATH や LD_LIBRARY を追加すると
`PATH=xxx LD_LIBRARY [ビルドしたプログラム]`
のように起動することになるらしい。

これで次回からも設定に戸惑わなくなりそう。

Google Maps JS API を読み込むと uBlock Origin が maps.googleapis.com/maps/api/mapsjs/gen_204 をブロックしてしまう

ちょっとした調査で Google maps API を動かしているのですが、こんな感じでリクエストをが失敗してしまします。

結果としては uBlock Origin (UBO と略すらしい) の不具合ではなく フィルターリストの不適合だった。

Firefox の Dev Tools の Transferred には “Blocked By uBlock Origin” と表示されています。

そこで uBlock Origin のログ画面を開き再度リクエストしてみると “EasyPrivacy” というフィルターリストにヒットしてブロックされたことがわかりました。

フィルターリストの該当部分だけを無効化や削除できればいいのですが、ちょっとやり方がわからなかったので一旦 EasyPrivacy を無効にすることで回避しました。

ちょうど今日から uBlock Origin が Chrome 系ブラウザで使えなくなったようだがとりあえず Chrome 系はメインでないので様子見する。別のプラグインに移行するか UBO のアップデート待ちすることになるだろう。

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 の機能が全部死ぬかもしれぬので画面出力もできなくなるよね、多分。