yokobuttonの不定期で競技プログラミングをするブログ

不定期で解けた競技プログラミングコンテストの問題を載せています。

「ゼロからのOS自作入門」でMinGW-W64用のUEFIプログラムを書いてみた

この記事は、「ゼロからのOS自作入門」という書籍はLinuxとEDK2とClangで書かれているのですが、そのプログラムをWindowsgnu-efiの一部とMinGW-W64で書いてみたという記事です。
ようやく第4章が終わったので、参考として、そこまでのソースコードを記事に載せます。
特に難しかった箇所は、3.6のAllocatePagesのエラー処理と、4.2のエントリポイントより上に関数の定義を書くところと、4.3の純粋仮想関数のところでした。
3.6では、AllocatePagesのエラー処理は必ずエラーになるので、エラー処理はコメントアウトしたら動作はしました。
4.2では、エントリポイントより上に関数の定義を書くと実行時エラーになったので、宣言と定義を分けて、定義をエントリポイントより下に書くと動作しました。
4.3では、純粋仮想関数の派生関数を呼び出すときに実行時エラーになったので、読み進めて4.5のローダの改良をすると動作しました。

[2023/2/19:追記]mingw-w64のバージョンアップによりkernel_mainが実行されなくなりました

mingw-w64をバージョンアップしたらkernel_mainが実行されなくなったので、原因を調べました。

おそらく、bootloader.cでentry_pointを呼び出す際にkernel_main関数のアドレスを呼んでいないと思われます。

なのでインラインアセンブラで対応しました。

また、kernel.elfのロードに関することで、LOADセグメントのaddressが0x0始まりだったのが、0x101000始まりになっていました。そのため、AllocatePages関数のすぐ下のコメントアウトを外してもエラーにならずに実行できました。

新しいbootloader.cのソースコードは一番下に載せています。

 

環境については、私のブログの「Windows10でのMinGW-w64とgnu-efiのヘッダーを使ったUEFIプログラミング」の記事を元に作業しています。

 

先にbootloaderとkernelのコマンドを載せます。

build_bootloader.cmd
c:\mingw64\bin\gcc.exe -fpic -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno-red-zone -maccumulate-outgoing-args -Ic:\gnu-efi\inc -Ic:\gnu-efi\inc\x86_64 -Ic:\gnu-efi\inc\protocol -Wall -c bootloader.c -o bootloader.o

c:\mingw64\bin\gcc.exe -fpic -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno-red-zone -maccumulate-outgoing-args -Ic:\gnu-efi\inc -Ic:\gnu-efi\inc\x86_64 -Ic:\gnu-efi\inc\protocol -c c:\gnu-efi\lib\data.c -o data.o 

c:\mingw64\bin\gcc.exe -fpic -ffreestanding -fno-stack-protector -fno-stack-check -fshort-wchar -mno-red-zone -maccumulate-outgoing-args -Ic:\gnu-efi\inc -Ic:\gnu-efi\inc\x86_64 -Ic:\gnu-efi\inc\protocol -c bootlib.c -o bootlib.o 

c:\mingw64\bin\ld.exe -nostdlib -shared -e efi_main bootloader.o data.o bootlib.o -o bootloader.bin

c:\mingw64\bin\objcopy.exe --target efi-app-x86_64 --subsystem=10 bootloader.bin BOOTX64.EFI

 

[2023/8/12:追記]コンパイラオプションの追加

現在、コンパイラオプションに-abi=sysvを追加しています。このオプションにすることでabiをsystem v abiに変更することができます。

 

build_kernel.cmd
c:\mingw64\bin\g++.exe -ffreestanding -mno-red-zone -fno-exceptions -fno-rtti -Wall -std=c++17 -g -c kernel.cpp -o kernel.o 

c:\mingw64\bin\ld.exe -nostdlib -static -e kernel_main --image-base 0x100000 kernel.o -o kernel.bin

c:\mingw64\bin\objcopy.exe --target elf64-x86-64 kernel.bin kernel.elf

 

次にbootloder作成に必要なファイルを載せます。

elf.h
#pragma once

#include<stdint.h>

typedef uintptr_t Elf64_Addr;
typedef uint64_t Elf64_Off;
typedef uint16_t Elf64_Half;
typedef uint32_t Elf64_Word;
typedef int32_t Elf64_Sword;
typedef uint64_t Elf64_Xword;
typedef int64_t Elf64_Sxword;

#define EI_NIDENT 16

typedef struct {
    unsigned char e_ident[EI_NIDENT];
    Elf64_Half e_type;
    Elf64_Half e_machine;
    Elf64_Word e_version;
    Elf64_Addr e_entry;
    Elf64_Off e_phoff;
    Elf64_Off e_shoff;
    Elf64_Word e_flags;
    Elf64_Half e_ehsize;
    Elf64_Half e_phentsize;
    Elf64_Half e_phnum;
    Elf64_Half e_shentsize;
    Elf64_Half e_shnum;
    Elf64_Half e_shstrndx;
}Elf64_Ehdr;

#define ET_NONE 0
#define ET_REL  1
#define ET_EXEC 2
#define ET_DYN  3
#define ET_CORE 4

typedef struct {
    Elf64_Word p_type;
    Elf64_Word p_flags;
    Elf64_Off p_offset;
    Elf64_Addr p_vaddr;
    Elf64_Addr p_paddr;
    Elf64_Xword p_filesz;
    Elf64_Xword p_memsz;
    Elf64_Xword p_align;
}Elf64_Phdr;

#define PT_NULL    0
#define PT_LOAD    1
#define PT_DYNAMIC 2
#define PT_INTERP  3
#define PT_NOTE    4
#define PT_SHLIB   5
#define PT_PHDR    6
#define PT_TLS     7


bootlib.h
#pragma once

#include<efi.h>
#include<efilib.h>

UINTN AsciiStrLen(CHAR8* str);
CHAR8* UINT64ToAscii(UINT64 num, CHAR8* buffer);
CHAR8* UINT64ToAsciiHex(UINT64 num, CHAR8* buffer);
void AsciiFPrint(EFI_FILE_PROTOCOL* file, int n, ...);
void UnicodePrint(int n, ...);
CHAR16* AsciiToUnicode(CHAR8* ascii, CHAR16* unicode);

bootlib.c
#include<stdarg.h>
#include"bootlib.h"

UINTN AsciiStrLen(CHAR8* str){
    UINTN len = 0;
    while( (*str) != '\0'){
        ++str;
        ++len;
    }

    return len;
}

static CHAR8* itoa(UINT64 num, CHAR8* buffer, UINTN radix){
    CHAR8* p = buffer;
    UINT64 v = num;
    int n = 1;
    while(v >= radix){
        v /= radix;
        ++n;
    }

    p = buffer + n;
    v = num;
    *p = '\0';
    do{
        --p;
        *p = v % radix + '0';
        if(*p > '9'){
            *p = v % radix - 10 + 'A';
        }
        v /= radix;
        --n;
    }while(p != buffer);
    return buffer;
}

CHAR8* UINT64ToAscii(UINT64 num, CHAR8* buffer){
    return itoa(num,buffer,10);
}

CHAR8* UINT64ToAsciiHex(UINT64 num, CHAR8* buffer){
    return itoa(num,buffer,16);
}

void AsciiFPrint(EFI_FILE_PROTOCOL* file, int n, ...){
    va_list args;
    va_start(args, n);
    for(int i = 0; i < n; ++i){
        CHAR8* str = va_arg(args, CHAR8*);
        UINTN len = AsciiStrLen(str);
        file->Write(file, &len, str);
    }

    va_end(args);
    return;
}

void UnicodePrint(int n, ...){
    va_list args;
    va_start(args, n);
    for(int i  = 0; i < n; ++i){
        ST->ConOut->OutputString(ST->ConOut, va_arg(args, CHAR16*));
    }

    va_end(args);
    return;
}


CHAR16* AsciiToUnicode(CHAR8* ascii, CHAR16* unicode){
    while( (*ascii) != '\0'){
        *unicode = (CHAR16)*ascii;
        ++ascii;
        ++unicode;
    }
    *unicode = L'\0';
    return unicode;
}

bootloader.c
#include<efi.h>
#include<efilib.h>

#include<limits.h>

#include"frame_buffer_config.hpp"
#include"bootlib.h"
#include"elf.h"

void ___chkstk_ms(void){
    return;
}

struct MemoryMap {
    UINTN buffer_size;
    VOID* buffer;
    UINTN map_size;
    UINTN map_key;
    UINTN descriptor_size;
    UINT32 descriptor_version;
};

EFI_STATUS GetMemoryMap(struct MemoryMap *map){
    if(map->buffer == NULL){
        return EFI_BUFFER_TOO_SMALL;
    }

    map->map_size = map->buffer_size;
    return BS->GetMemoryMap(
        &map->map_size,
        (EFI_MEMORY_DESCRIPTOR*)map->buffer,
        &map->map_key,
        &map->descriptor_size,
        &map->descriptor_version);
}

const CHAR8* GetMemoryTypeAscii(EFI_MEMORY_TYPE type){
    switch(type){
        case EfiReservedMemoryType: return "EfiReservedMemoryType";
        case EfiLoaderCode: return "EfiLoaderCode";
        case EfiLoaderData: return "EfiLoaderData";
        case EfiBootServicesCode: return "EfiBootServicesCode";
        case EfiBootServicesData: return "EfiBootServicesData";
        case EfiRuntimeServicesCode: return "EfiRuntimeServicesCode";
        case EfiRuntimeServicesData: return "EfiRuntimeServicesData";
        case EfiConventionalMemory: return "EfiConventionalMemory";
        case EfiUnusableMemory: return "EfiUnusableMemory";
        case EfiACPIReclaimMemory: return "EfiACPIReclaimMemory";
        case EfiACPIMemoryNVS: return "EfiACPIMemoryNVS";
        case EfiMemoryMappedIO: return "EfiMemoryMappedIO";
        case EfiMemoryMappedIOPortSpace: return "EfiMemoryMappedIOPortSpace";
        case EfiPalCode: return "EfiPalCode";
        //case EfiPersistentMemory: return "EfiPersistentMemory";
        case EfiMaxMemoryType: return "EfiMaxMemoryType";
        default: return "InvalidMemoryType";
    }
}

EFI_STATUS SaveMemoryMap(struct MemoryMap* map, EFI_FILE_PROTOCOL* file){
    CHAR8* header = "Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute\r\n";
    AsciiFPrint(file,1,header);

    int i;
    EFI_PHYSICAL_ADDRESS iter;
    for(iter = (EFI_PHYSICAL_ADDRESS)map->buffer, i = 0; iter < (EFI_PHYSICAL_ADDRESS)map->buffer+map->map_size; iter += map->descriptor_size, i++){
        EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)iter;
        CHAR8 buffer[256];
        AsciiFPrint(file,2,UINT64ToAscii(i,buffer),",");
        AsciiFPrint(file,2,UINT64ToAsciiHex(desc->Type,buffer),",");
        AsciiFPrint(file,2,GetMemoryTypeAscii(desc->Type),",");
        AsciiFPrint(file,2,UINT64ToAsciiHex(desc->PhysicalStart,buffer),",");
        AsciiFPrint(file,2,UINT64ToAsciiHex(desc->NumberOfPages,buffer),",");
        AsciiFPrint(file,2,UINT64ToAsciiHex(desc->Attribute & 0xffffflu,buffer),"\r\n");
    }

    return EFI_SUCCESS;
}

EFI_STATUS OpenRootDir(EFI_HANDLE image_handle, EFI_FILE_PROTOCOL** root){
    EFI_STATUS status;
    EFI_LOADED_IMAGE_PROTOCOL* loaded_image;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fs;

    status = BS->OpenProtocol(
        image_handle,
        &gEfiLoadedImageProtocolGuid,
        (VOID**)&loaded_image,
        image_handle,
        NULL,
        EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);

    if(EFI_ERROR(status)){
        return status;
    }

    status = BS->OpenProtocol(
        loaded_image->DeviceHandle,
        &gEfiSimpleFileSystemProtocolGuid,
        (VOID**)&fs,
        image_handle,
        NULL,
        EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
    if(EFI_ERROR(status)){
        return status;
    }
    return fs->OpenVolume(fs, root);
}

EFI_STATUS OpenGOP(EFI_HANDLE image_handle, EFI_GRAPHICS_OUTPUT_PROTOCOL** gop){
    EFI_STATUS status;
    UINTN num_gop_handles = 0;
    EFI_HANDLE* gop_handles = NULL;

    status = BS->LocateHandleBuffer(
        ByProtocol,
        &gEfiGraphicsOutputProtocolGuid,
        NULL,
        &num_gop_handles,
        &gop_handles);
    if(EFI_ERROR(status)){
        return status;
    }

    status = BS->OpenProtocol(
        gop_handles[0],
        &gEfiGraphicsOutputProtocolGuid,
        (VOID**)gop,
        image_handle,
        NULL,
        EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
    if(EFI_ERROR(status)){
        return status;
    }

    BS->FreePool(gop_handles);

    return status;
}

void MyCopyMem(VOID* destination, VOID* source, UINT64 size){
    char* d = (char*)destination;
    char* s = (char*)source;
    for(UINT64 i = 0; i < size; ++i){
        d[i] = s[i];
    }
}

void MySetMem(VOID* buffer, UINTN size, UINT8 value){
    char *b = (char*)buffer;
    for(UINTN i = 0; i < size; ++i){
        b[i] = value;
    }
}

void CopyLoadSegments(Elf64_Ehdr* ehdr){
    Elf64_Phdr* phdr = (Elf64_Phdr*)( (UINT64)ehdr+ehdr->e_phoff);
    for(Elf64_Half i = 0; i < ehdr->e_phnum; ++i){
        if(phdr[i].p_type != PT_LOAD){
            continue;
        }
        UINT64 segm_in_file = (UINT64)ehdr+phdr[i].p_offset;
        MyCopyMem( (VOID*)phdr[i].p_vaddr, (VOID*)segm_in_file, phdr[i].p_filesz);

        UINTN remain_bytes = phdr[i].p_memsz-phdr[i].p_filesz;
        MySetMem( (VOID*)(phdr[i].p_vaddr+phdr[i].p_filesz), remain_bytes, 0);
    }
}

UINT64 MIN(UINT64 now, UINT64 next){
    return ( now < next ? now : next);
}

UINT64 MAX(UINT64 now, UINT64 next){
    return ( now < next ? next : now);
}

void CalcLoadAddressRange(Elf64_Ehdr* ehdr, UINT64* first, UINT64* last){
    Elf64_Phdr* phdr = (Elf64_Phdr*)( (UINT64)ehdr+ehdr->e_phoff);
    *first = ULLONG_MAX;
    *last = 0;
    for(Elf64_Half i = 0; i < ehdr->e_phnum; ++i){
        if(phdr[i].p_type != PT_LOAD){
            continue;
        }
        *first = MIN(*first, phdr[i].p_vaddr);
        *last = MAX(*last, phdr[i].p_vaddr+phdr[i].p_memsz);
    }
}

EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable){
    EFI_STATUS Status;
    ST = SystemTable;
    BS = SystemTable->BootServices;

    Status = ST->ConOut->OutputString(ST->ConOut, L"Hello World!\r\n");
    if(EFI_ERROR(Status)){
        return Status;
    }

    // get gop
    EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
    OpenGOP(ImageHandle, &gop);
    UINT8* frame_buffer = (UINT8*)gop->Mode->FrameBufferBase;
    for(UINTN i = 0; i < gop->Mode->FrameBufferSize; ++i){
        frame_buffer[i] = 255;
    }

    // config
    struct FrameBufferConfig config = {
        (UINT8*)gop->Mode->FrameBufferBase,
        gop->Mode->Info->PixelsPerScanLine,
        gop->Mode->Info->HorizontalResolution,
        gop->Mode->Info->VerticalResolution,
        0};
    switch(gop->Mode->Info->PixelFormat){
        case PixelRedGreenBlueReserved8BitPerColor:
            config.pixel_format = kPixelRGBResv8BitPerColor;
            break;
        case PixelBlueGreenRedReserved8BitPerColor:
            config.pixel_format = kPixelBGRResv8BitPerColor;
            break;
        default:
            UnicodePrint(1,L"Unimplemented pixel format.\r\n");
            while(1){}
    }
    UnicodePrint(1,L"config_end\r\n");

    // write memmap
    CHAR8 memmap_buf[4096*4];
    struct MemoryMap memmap = {sizeof(memmap_buf),memmap_buf,0,0,0,0};
    Status = GetMemoryMap(&memmap);
    if(EFI_ERROR(Status)){
        ST->ConOut->OutputString(ST->ConOut, L"failed to get memory map:\r\n");
        return Status;
    }

    EFI_FILE_PROTOCOL* root_dir;
    OpenRootDir(ImageHandle, &root_dir);

    EFI_FILE_PROTOCOL* memmap_file;
    root_dir->Open(
        root_dir,
        &memmap_file,
        L"\\memmap",
        EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE,
        0);

    SaveMemoryMap(&memmap, memmap_file);
    memmap_file->Close(memmap_file);
    UnicodePrint(1,L"memmap_end\r\n");

    // read kernel file version 1
    /*
    EFI_FILE_PROTOCOL* kernel_file;
    root_dir->Open(root_dir, &kernel_file, L"\\kernel.elf", EFI_FILE_MODE_READ, 0);
    UINTN file_info_size = sizeof(EFI_FILE_INFO)+sizeof(CHAR16)*12;
    UINT8 file_info_buffer[file_info_size];
    kernel_file->GetInfo(kernel_file, &gEfiFileInfoGuid, &file_info_size, file_info_buffer);

    EFI_FILE_INFO* file_info = (EFI_FILE_INFO*)file_info_buffer;
    UINTN kernel_file_size = file_info->FileSize;

    EFI_PHYSICAL_ADDRESS kernel_base_addr = 0x100000;
    BS->AllocatePages(AllocateAddress, EfiLoaderData, (kernel_file_size+0xfff)/0x1000, &kernel_base_addr);
    kernel_file->Read(kernel_file, &kernel_file_size, (VOID*)kernel_base_addr);
    CHAR8 ascii_buffer[256];
    CHAR16 unicode_buffer[256];
    UnicodePrint(1,L"kernel: ");
    UINT64ToAsciiHex(kernel_base_addr,ascii_buffer);
    AsciiToUnicode(ascii_buffer,unicode_buffer);
    UnicodePrint(1,unicode_buffer);
    UnicodePrint(1,L",");
    UINT64ToAscii(kernel_file_size,ascii_buffer);
    AsciiToUnicode(ascii_buffer,unicode_buffer);
    UnicodePrint(1,unicode_buffer);
    UnicodePrint(1,L"\r\n");
    */
    // read kernel file version 2
    EFI_FILE_PROTOCOL* kernel_file;
    root_dir->Open(root_dir, &kernel_file, L"\\kernel.elf", EFI_FILE_MODE_READ, 0);
    UINTN file_info_size = sizeof(EFI_FILE_INFO)+sizeof(CHAR16)*12;
    UINT8 file_info_buffer[file_info_size];
    kernel_file->GetInfo(kernel_file, &gEfiFileInfoGuid, &file_info_size, file_info_buffer);

    EFI_FILE_INFO* file_info = (EFI_FILE_INFO*)file_info_buffer;
    UINTN kernel_file_size = file_info->FileSize;

    VOID* kernel_buffer;
    Status = BS->AllocatePool(EfiLoaderData, kernel_file_size, &kernel_buffer);
    if(EFI_ERROR(Status)){
        UnicodePrint(1, L"failed to allocate pool.\r\n");
        while(1){}
    }
    Status = kernel_file->Read(kernel_file, &kernel_file_size, kernel_buffer);
    if(EFI_ERROR(Status)){
        UnicodePrint(1, L"error.\r\n");
        while(1){}
    }

    Elf64_Ehdr* kernel_ehdr = (Elf64_Ehdr*)kernel_buffer;
    UINT64 kernel_first_addr, kernel_last_addr;
    CalcLoadAddressRange(kernel_ehdr, &kernel_first_addr, &kernel_last_addr);

    UINTN num_pages = (kernel_last_addr-kernel_first_addr+0xfff)/0x1000;
    Status = BS->AllocatePages(AllocateAddress, EfiLoaderData, num_pages, &kernel_first_addr);
    /*if(EFI_ERROR(Status)){
        UnicodePrint(1, L"failed to allocate pages.\r\n");
        while(1){}
    }*/

    CopyLoadSegments(kernel_ehdr);

    Status = BS->FreePool(kernel_buffer);
    if(EFI_ERROR(Status)){
        UnicodePrint(1, L"failed to free pool.\r\n");
        while(1){}
    }

    CHAR8 ascii_buffer[256];
    CHAR16 unicode_buffer[256];
    UnicodePrint(1,L"kernel: ");
    UINT64ToAsciiHex(kernel_first_addr,ascii_buffer);
    AsciiToUnicode(ascii_buffer,unicode_buffer);
    UnicodePrint(1,unicode_buffer);
    UnicodePrint(1,L" - ");
    UINT64ToAscii(kernel_last_addr,ascii_buffer);
    AsciiToUnicode(ascii_buffer,unicode_buffer);
    UnicodePrint(1,unicode_buffer);
    UnicodePrint(1,L"\r\n");

    

    // exit bootservices
    Status = BS->ExitBootServices(ImageHandle, memmap.map_key);
    if(EFI_ERROR(Status)){
        Status = GetMemoryMap(&memmap);
        if(EFI_ERROR(Status)){
            UnicodePrint(1, L"failed to get memory map.\r\n");
            while(1){}
        }
        Status = BS->ExitBootServices(ImageHandle, memmap.map_key);
        if(EFI_ERROR(Status)){
            UnicodePrint(1, L"Could not exit boot service.\r\n");
            while(1){}
        }
    }

    // entry kernel
    UINT64 entry_addr = *(UINT64*)(kernel_first_addr+24);
    typedef void EntryPointType(const struct FrameBufferConfig*);
    EntryPointType* entry_point = (EntryPointType*)entry_addr;
    entry_point(&config);

    while(1){

    }
    return Status;


次にkernel作成に必要なファイルを載せます。

frame_buffer_config,hpp
#pragma once

#include<stdint.h>

enum PixelFormat{
    kPixelRGBResv8BitPerColor,
    kPixelBGRResv8BitPerColor,
};

struct FrameBufferConfig{
    uint8_t* frame_buffer;
    uint32_t pixels_per_scan_line;
    uint32_t horizontal_resolution;
    uint32_t vertical_resolution;
    enum PixelFormat pixel_format;
};

kernel.cpp
#include<cstdint>
#include<cstddef>
#include<new>
#include"frame_buffer_config.hpp"

struct PixelColor{
    uint8_t r;
    uint8_t g;
    uint8_t b;
};

void operator delete(void* obj, unsigned long long arg)noexcept;

class PixelWriter{
public:
    PixelWriter(const FrameBufferConfig& config);
    virtual ~PixelWriter() = default;
    virtual void Write(uint32_t x, uint32_t y, const PixelColor& c) = 0;
protected:
    uint8_t* PixelAt(uint32_t x, uint32_t y);
private:
    const FrameBufferConfig& config_;
};

class RGBResc8BitPerColorPixelWriter : public PixelWriter{
public:
    using PixelWriter::PixelWriter;
    virtual void Write(uint32_t x, uint32_t y, const PixelColor& c);
};

class BGRResc8BitPerColorPixelWriter : public PixelWriter{
public:
    using PixelWriter::PixelWriter;
    virtual void Write(uint32_t x, uint32_t y, const PixelColor& c);
};

char pixel_writer_buf[sizeof(RGBResc8BitPerColorPixelWriter)];
PixelWriter* pixel_writer;

extern "C" void kernel_main(const FrameBufferConfig& frame_buffer_config){   
    switch(frame_buffer_config.pixel_format){
        case kPixelRGBResv8BitPerColor:
            pixel_writer = new(pixel_writer_buf)RGBResc8BitPerColorPixelWriter{frame_buffer_config};
            break;
        case kPixelBGRResv8BitPerColor:
            pixel_writer = new(pixel_writer_buf)BGRResc8BitPerColorPixelWriter{frame_buffer_config};
            break;
    }
    for(uint32_t x = 0; x < frame_buffer_config.horizontal_resolution; ++x){
        for(uint32_t y = 0; y < frame_buffer_config.vertical_resolution; ++y){
            pixel_writer->Write(x,y,{255,255,255});
        }
    }
    
    for(uint32_t x = 0; x < 200; ++x){
        for(uint32_t y = 0; y < 100; ++y){
            pixel_writer->Write(x,y,{0,255,0});
        }
    }
    while(true){
        __asm__("hlt");
    }

    return;
}

void operator delete(void* obj, unsigned long long arg)noexcept{}

PixelWriter::PixelWriter(const FrameBufferConfig& config) : config_{config}{}
uint8_t* PixelWriter::PixelAt(uint32_t x, uint32_t y){
    return &config_.frame_buffer[4*(config_.pixels_per_scan_line*y+x)];
}

void RGBResc8BitPerColorPixelWriter::Write(uint32_t x, uint32_t y, const PixelColor& c){
    uint8_t* p = PixelAt(x,y);
    p[0] = c.r;
    p[1] = c.g;
    p[2] = c.b;
}

void BGRResc8BitPerColorPixelWriter::Write(uint32_t x, uint32_t y, const PixelColor& c){
    uint8_t* p = PixelAt(x,y);
    p[0] = c.b;
    p[1] = c.g;
    p[2] = c.r;
}


これでWindowsでもC++UEFIのOS自作ができるようになると思います。

 

[2023/2/19:追記]新しいbootloader.c

bootloader.c

#include<efi.h>
#include<efilib.h>

#include<limits.h>

#include"frame_buffer_config.hpp"
#include"bootlib.h"
#include"elf.h"


void ___chkstk_ms(void){
    return;
}

struct MemoryMap {
    UINTN buffer_size;
    VOID* buffer;
    UINTN map_size;
    UINTN map_key;
    UINTN descriptor_size;
    UINT32 descriptor_version;
};

EFI_STATUS GetMemoryMap(struct MemoryMap *map){
    if(map->buffer == NULL){
        return EFI_BUFFER_TOO_SMALL;
    }

    map->map_size = map->buffer_size;
    return BS->GetMemoryMap(
        &map->map_size,
        (EFI_MEMORY_DESCRIPTOR*)map->buffer,
        &map->map_key,
        &map->descriptor_size,
        &map->descriptor_version);
}

const CHAR8* GetMemoryTypeAscii(EFI_MEMORY_TYPE type){
    switch(type){
        case EfiReservedMemoryType: return "EfiReservedMemoryType";
        case EfiLoaderCode: return "EfiLoaderCode";
        case EfiLoaderData: return "EfiLoaderData";
        case EfiBootServicesCode: return "EfiBootServicesCode";
        case EfiBootServicesData: return "EfiBootServicesData";
        case EfiRuntimeServicesCode: return "EfiRuntimeServicesCode";
        case EfiRuntimeServicesData: return "EfiRuntimeServicesData";
        case EfiConventionalMemory: return "EfiConventionalMemory";
        case EfiUnusableMemory: return "EfiUnusableMemory";
        case EfiACPIReclaimMemory: return "EfiACPIReclaimMemory";
        case EfiACPIMemoryNVS: return "EfiACPIMemoryNVS";
        case EfiMemoryMappedIO: return "EfiMemoryMappedIO";
        case EfiMemoryMappedIOPortSpace: return "EfiMemoryMappedIOPortSpace";
        case EfiPalCode: return "EfiPalCode";
        //case EfiPersistentMemory: return "EfiPersistentMemory";
        case EfiMaxMemoryType: return "EfiMaxMemoryType";
        default: return "InvalidMemoryType";
    }
}

EFI_STATUS SaveMemoryMap(struct MemoryMap* map, EFI_FILE_PROTOCOL* file){
    CHAR8* header = "Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute\r\n";
    AsciiFPrint(file,1,header);

    int i;
    EFI_PHYSICAL_ADDRESS iter;
    for(iter = (EFI_PHYSICAL_ADDRESS)map->buffer, i = 0; iter < (EFI_PHYSICAL_ADDRESS)map->buffer+map->map_size; iter += map->descriptor_size, i++){
        EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)iter;
        CHAR8 buffer[256];
        AsciiFPrint(file,2,UINT64ToAscii(i,buffer),",");
        AsciiFPrint(file,2,UINT64ToAsciiHex(desc->Type,buffer),",");
        AsciiFPrint(file,2,GetMemoryTypeAscii(desc->Type),",");
        AsciiFPrint(file,2,UINT64ToAsciiHex(desc->PhysicalStart,buffer),",");
        AsciiFPrint(file,2,UINT64ToAsciiHex(desc->NumberOfPages,buffer),",");
        AsciiFPrint(file,2,UINT64ToAsciiHex(desc->Attribute & 0xffffflu,buffer),"\r\n");
    }

    return EFI_SUCCESS;
}

EFI_STATUS OpenRootDir(EFI_HANDLE image_handle, EFI_FILE_PROTOCOL** root){
    EFI_STATUS status;
    EFI_LOADED_IMAGE_PROTOCOL* loaded_image;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fs;

    status = BS->OpenProtocol(
        image_handle,
        &gEfiLoadedImageProtocolGuid,
        (VOID**)&loaded_image,
        image_handle,
        NULL,
        EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);

    if(EFI_ERROR(status)){
        return status;
    }

    status = BS->OpenProtocol(
        loaded_image->DeviceHandle,
        &gEfiSimpleFileSystemProtocolGuid,
        (VOID**)&fs,
        image_handle,
        NULL,
        EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
    if(EFI_ERROR(status)){
        return status;
    }
    return fs->OpenVolume(fs, root);
}

EFI_STATUS OpenGOP(EFI_HANDLE image_handle, EFI_GRAPHICS_OUTPUT_PROTOCOL** gop){
    EFI_STATUS status;
    UINTN num_gop_handles = 0;
    EFI_HANDLE* gop_handles = NULL;

    status = BS->LocateHandleBuffer(
        ByProtocol,
        &gEfiGraphicsOutputProtocolGuid,
        NULL,
        &num_gop_handles,
        &gop_handles);
    if(EFI_ERROR(status)){
        return status;
    }

    status = BS->OpenProtocol(
        gop_handles[0],
        &gEfiGraphicsOutputProtocolGuid,
        (VOID**)gop,
        image_handle,
        NULL,
        EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
    if(EFI_ERROR(status)){
        return status;
    }

    BS->FreePool(gop_handles);

    return status;
}

void MyCopyMem(VOID* destination, VOID* source, UINT64 size){
    char* d = (char*)destination;
    char* s = (char*)source;
    for(UINT64 i = 0; i < size; ++i){
        d[i] = s[i];
    }
}

void MySetMem(VOID* buffer, UINT64 size, UINT8 value){
    char *b = (char*)buffer;
    for(UINT64 i = 0; i < size; ++i){
        b[i] = value;
    }
}

void CopyLoadSegments(Elf64_Ehdr* ehdr){
    Elf64_Phdr* phdr = (Elf64_Phdr*)( (UINT64)ehdr+ehdr->e_phoff);

    for(Elf64_Half i = 0; i < ehdr->e_phnum; ++i){
        if(phdr[i].p_type != PT_LOAD){
            continue;
        }
        UINT64 segm_in_file = (UINT64)ehdr+phdr[i].p_offset;
        MyCopyMem( (VOID*)phdr[i].p_vaddr, (VOID*)segm_in_file, phdr[i].p_filesz);

        UINT64 remain_bytes = phdr[i].p_memsz-phdr[i].p_filesz;
        MySetMem( (VOID*)(phdr[i].p_vaddr+phdr[i].p_filesz), remain_bytes, 0);
    }
}

UINT64 MIN(UINT64 now, UINT64 next){
    return ( now < next ? now : next);
}

UINT64 MAX(UINT64 now, UINT64 next){
    return ( now < next ? next : now);
}

void CalcLoadAddressRange(Elf64_Ehdr* ehdr, UINT64* first, UINT64* last){
    Elf64_Phdr* phdr = (Elf64_Phdr*)( (UINT64)ehdr+ehdr->e_phoff);
    *first = ULLONG_MAX;
    *last = 0;
    for(Elf64_Half i = 0; i < ehdr->e_phnum; ++i){
        if(phdr[i].p_type != PT_LOAD){
            continue;
        }
        *first = MIN(*first, phdr[i].p_vaddr);
        
        *last = MAX(*last, phdr[i].p_vaddr+phdr[i].p_memsz);
    }
}

EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable){
    EFI_STATUS Status;
    ST = SystemTable;
    BS = SystemTable->BootServices;

    Status = ST->ConOut->OutputString(ST->ConOut, L"Hello World!\r\n");
    if(EFI_ERROR(Status)){
        return Status;
    }

    // get gop
    EFI_GRAPHICS_OUTPUT_PROTOCOL* gop;
    OpenGOP(ImageHandle, &gop);
    UINT8* frame_buffer = (UINT8*)gop->Mode->FrameBufferBase;
    for(UINTN i = 0; i < gop->Mode->FrameBufferSize; ++i){
        frame_buffer[i] = 255;
    }

    // config
    struct FrameBufferConfig config = {
        (UINT8*)gop->Mode->FrameBufferBase,
        gop->Mode->Info->PixelsPerScanLine,
        gop->Mode->Info->HorizontalResolution,
        gop->Mode->Info->VerticalResolution,
        0};
    switch(gop->Mode->Info->PixelFormat){
        case PixelRedGreenBlueReserved8BitPerColor:
            config.pixel_format = kPixelRGBResv8BitPerColor;
            break;
        case PixelBlueGreenRedReserved8BitPerColor:
            config.pixel_format = kPixelBGRResv8BitPerColor;
            break;
        default:
            UnicodePrint(1,L"Unimplemented pixel format.\r\n");
            while(1){}
    }
    UnicodePrint(1,L"config_end\r\n");

    // write memmap
    CHAR8 memmap_buf[4096*4];
    struct MemoryMap memmap = {sizeof(memmap_buf),memmap_buf,0,0,0,0};
    Status = GetMemoryMap(&memmap);
    if(EFI_ERROR(Status)){
        ST->ConOut->OutputString(ST->ConOut, L"failed to get memory map:\r\n");
        return Status;
    }

    EFI_FILE_PROTOCOL* root_dir;
    OpenRootDir(ImageHandle, &root_dir);

    EFI_FILE_PROTOCOL* memmap_file;
    root_dir->Open(
        root_dir,
        &memmap_file,
        L"\\memmap",
        EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE,
        0);

    SaveMemoryMap(&memmap, memmap_file);
    memmap_file->Close(memmap_file);
    UnicodePrint(1,L"memmap_end\r\n");

    // read kernel file version 1
    /*
    EFI_FILE_PROTOCOL* kernel_file;
    root_dir->Open(root_dir, &kernel_file, L"\\kernel.elf", EFI_FILE_MODE_READ, 0);
    UINTN file_info_size = sizeof(EFI_FILE_INFO)+sizeof(CHAR16)*12;
    UINT8 file_info_buffer[file_info_size];
    kernel_file->GetInfo(kernel_file, &gEfiFileInfoGuid, &file_info_size, file_info_buffer);

    EFI_FILE_INFO* file_info = (EFI_FILE_INFO*)file_info_buffer;
    UINTN kernel_file_size = file_info->FileSize;

    EFI_PHYSICAL_ADDRESS kernel_base_addr = 0x100000;
    BS->AllocatePages(AllocateAddress, EfiLoaderData, (kernel_file_size+0xfff)/0x1000, &kernel_base_addr);
    kernel_file->Read(kernel_file, &kernel_file_size, (VOID*)kernel_base_addr);
    CHAR8 ascii_buffer[256];
    CHAR16 unicode_buffer[256];
    UnicodePrint(1,L"kernel: ");
    UINT64ToAsciiHex(kernel_base_addr,ascii_buffer);
    AsciiToUnicode(ascii_buffer,unicode_buffer);
    UnicodePrint(1,unicode_buffer);
    UnicodePrint(1,L",");
    UINT64ToAscii(kernel_file_size,ascii_buffer);
    AsciiToUnicode(ascii_buffer,unicode_buffer);
    UnicodePrint(1,unicode_buffer);
    UnicodePrint(1,L"\r\n");
    */
    // read kernel file version 2
    EFI_FILE_PROTOCOL* kernel_file;
    Status = root_dir->Open(root_dir, &kernel_file, L"\\kernel.elf", EFI_FILE_MODE_READ, 0);
    if(EFI_ERROR(Status)){
        UnicodePrint(1,L"failed to open file.\r\n");
        while(1){}
    }
    UINTN file_info_size = sizeof(EFI_FILE_INFO)+sizeof(CHAR16)*12;
    UINT8 file_info_buffer[file_info_size];
    kernel_file->GetInfo(kernel_file, &gEfiFileInfoGuid, &file_info_size, file_info_buffer);

    EFI_FILE_INFO* file_info = (EFI_FILE_INFO*)file_info_buffer;
    UINTN kernel_file_size = file_info->FileSize;

    VOID* kernel_buffer;
    Status = BS->AllocatePool(EfiLoaderData, kernel_file_size, &kernel_buffer);
    if(EFI_ERROR(Status)){
        UnicodePrint(1, L"failed to allocate pool.\r\n");
        while(1){}
    }
    Status = kernel_file->Read(kernel_file, &kernel_file_size, kernel_buffer);
    if(EFI_ERROR(Status)){
        UnicodePrint(1, L"error.\r\n");
        while(1){}
    }

    Elf64_Ehdr* kernel_ehdr = (Elf64_Ehdr*)kernel_buffer;
    UINT64 kernel_first_addr = ULLONG_MAX;
    UINT64 kernel_last_addr = 0;
    
    CalcLoadAddressRange(kernel_ehdr, &kernel_first_addr, &kernel_last_addr);
    

    UINTN num_pages = (kernel_last_addr-kernel_first_addr+0xfff)/0x1000;
    Status = BS->AllocatePages(AllocateAddress, EfiLoaderData, num_pages, &kernel_first_addr);
    if(EFI_ERROR(Status)){
        UnicodePrint(1, L"failed to allocate pages.\r\n");
        while(1){}
    }

    CopyLoadSegments(kernel_ehdr);
    UINT64 kernel_entry_addr = kernel_ehdr->e_entry;

    Status = BS->FreePool(kernel_buffer);
    if(EFI_ERROR(Status)){
        UnicodePrint(1, L"failed to free pool.\r\n");
        while(1){}
    }

    {
    CHAR8 ascii_buffer[256];
    CHAR16 unicode_buffer[256];
    UnicodePrint(1,L"entry_addr: ");
    UINT64ToAsciiHex(kernel_entry_addr,ascii_buffer);
    AsciiToUnicode(ascii_buffer,unicode_buffer);
    UnicodePrint(1,unicode_buffer);
    UnicodePrint(1,L"\r\n");
    }
    {
    CHAR8 ascii_buffer[256];
    CHAR16 unicode_buffer[256];
    UnicodePrint(1,L"kernel: ");
    UINT64ToAsciiHex(kernel_first_addr,ascii_buffer);
    AsciiToUnicode(ascii_buffer,unicode_buffer);
    UnicodePrint(1,unicode_buffer);
    UnicodePrint(1,L" - ");
    }
    {
    CHAR8 ascii_buffer[256];
    CHAR16 unicode_buffer[256];
    UINT64ToAsciiHex(kernel_last_addr,ascii_buffer);
    AsciiToUnicode(ascii_buffer,unicode_buffer);
    UnicodePrint(1,unicode_buffer);
    UnicodePrint(1,L"\r\n");
    }

    // exit bootservices
    Status = BS->ExitBootServices(ImageHandle, memmap.map_key);
    if(EFI_ERROR(Status)){
        Status = GetMemoryMap(&memmap);
        if(EFI_ERROR(Status)){
            UnicodePrint(1, L"failed to get memory map.\r\n");
            while(1){}
        }
        Status = BS->ExitBootServices(ImageHandle, memmap.map_key);
        if(EFI_ERROR(Status)){
            UnicodePrint(1, L"Could not exit boot service.\r\n");
            while(1){}
        }
    }

    // entry kernel
    //UINT64 entry_addr = *(UINT64*)(kernel_first_addr+24);
    //UINT64 entry_addr = *(UINT64*)(kernel_entry_addr);
    //typedef void EntryPointType(const struct FrameBufferConfig*);
    //EntryPointType* entry_point = (EntryPointType*)entry_addr;
    //entry_point(&config);

    __asm__("mov %0, %%rcx"::"r"(&config):"%rcx");
    __asm__("mov %0, %%rax"::"r"(kernel_entry_addr):"%rax");
    __asm__("jmp *%rax");

    while(1){
        __asm__("hlt");
    }
    return Status;