「ゼロからのOS自作入門」でMinGW-W64用のUEFIプログラムを書いてみた
この記事は、「ゼロからのOS自作入門」という書籍はLinuxとEDK2とClangで書かれているのですが、そのプログラムをWindowsとgnu-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;
}