ゼロからの OS 自作入門 の第 1 章: Hello, World! と表示させるまでの流れを MacOS 上で作業したい場合の手順をまとめます。
メインで使っているような MacBook でやる際は自己責任で頑張ってね(ディスク取り違えたりしないようにほんとうに気をつけて)。
単純な hlt 命令の無限ループで、実装はここに置いてあるので、kernel ディレクトリを掘って main.cpp という名前で作成
コンパイルとリンクは以下のコマンドにて行う
$ clang++ -O2 -Wall -g --target=x86_64-elf -ffreestanding -mno-red-zone -fno-exceptions -fno-rtti -std=c++17 -c kernel/main.cpp $ ld.lld -entry KernelMain -z norelro --image-base 0x1000000 --static -o kernel.elf main.o
--target=x86_64-elf
オプションは文字通り x86_64 むけの機械語を ELF 形式にて出力するという指定-ffreestanding
オプションはフリースタンディング環境での動作環境を想定したコンパイルを意味する
$ brew install hex-fiend
$ # Disk を探す $ diskutil list /dev/disk0 (internal): #: TYPE NAME SIZE IDENTIFIER 0: GUID_partition_scheme 251.0 GB disk0 1: EFI EFI 314.6 MB disk0s1 2: Apple_APFS Container disk1 250.7 GB disk0s2 /dev/disk1 (synthesized): #: TYPE NAME SIZE IDENTIFIER 0: APFS Container Scheme - +250.7 GB disk1 Physical Store disk0s2 1: APFS Volume Macintosh HD 213.8 GB disk1s1 2: APFS Volume Preboot 72.5 MB disk1s2 3: APFS Volume Recovery 1.0 GB disk1s3 4: APFS Volume VM 4.3 GB disk1s4 /dev/disk2 (external, physical): #: TYPE NAME SIZE IDENTIFIER 0: FDisk_partition_scheme *8.0 GB disk2 1: Windows_FAT_32 boot 66.1 MB disk2s1 2: Linux 7.9 GB disk2s2 $ # FAT 形式でフォーマットする $ sudo newfs_msdos -F 16 /dev/disk2 newfs_msdos: warning: /dev/disk2 is not a character device 512 bytes per physical sector newfs_msdos: warning: sectors/FAT limits sectors to 4194721, clusters to 65534 newfs_msdos: warning: FAT type limits file system to 4194144 sectors /dev/disk2: 4193536 sectors in 65524 FAT16 clusters (32768 bytes/cluster) bps=512 spc=64 res=1 nft=2 rde=512 mid=0xf0 spf=256 spt=32 hds=255 hid=0 drv=0x00 bsec=4194144 $ # EFI ファイルの配置 $ mkdir -p /Volumes/NO NAME/EFI/BOOT $ cp ~/Desktop/BOOTX64.EFI /Volumes/NO NAME/EFI/BOOT/
$ # mkfs.fat コマンドなどを利用できるように... $ brew install dosfstools $ brew install qemu llvm $ # それぞれパスが通っている状態にしておく&つまづいたら不足しているものを導入してね! $ git clone https://github.com/uchan-nos/mikanos-build.git && cd mikanos-build $ qemu-img create -f raw ./disk.img 200M $ mkfs.fat -n 'MIKAN OS' -s 2 -f 2 -R 32 -F 32 ./disk.img $ mkdir -p ./mnt $ hdiutil attach -mountpoint ./mnt ./disk.img $ mkdir -p ./mnt/EFI/BOOT $ cp ~/Desktop/BOOTX64.EFI ./mnt/EFI/BOOT/BOOTX64.EFI $ hdiutil detach ./mnt $ qemu-system-x86_64 -m 1G -drive if=pflash,format=raw,readonly,file=./OVMF_CODE.fd -drive if=pflash,format=raw,file=./OVMF_VARS.fd -drive if=ide,index=0,media=disk,format=raw,file=./disk.img -device nec-usb-xhci,id=xhci -device usb-mouse -device usb-kbd -monitor stdio
本の中で登場する run_qemu.sh についても同じような感じで書き換えてあげれば使えるはず。
バイナリエディタで実行可能ファイルを作成したが面倒。 C 言語でやる手順は以下のとおり。
clang -target x86_64-pc-win32-coff -mno-red-zone -fno-stack-protector -fshort-wchar -Wall -c hello.c
lld-link /subsystem:efi_application /entry:EfiMain /out:hello.efi hello.o
あとは QEMU なり実機で動かしてみればよい。
gBS
からアクセスできるgRT
からアクセスできる実行すると以下のようなファイルが得られる
Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute 0, 3, EfiBootServicesCode, 00000000, 1, F 1, 7, EfiConventionalMemory, 00001000, 9F, F 2, 7, EfiConventionalMemory, 00100000, 700, F 3, A, EfiACPIMemoryNVS, 00800000, 8, F 4, 7, EfiConventionalMemory, 00808000, 8, F 5, A, EfiACPIMemoryNVS, 00810000, F0, F 6, 4, EfiBootServicesData, 00900000, B00, F 7, 7, EfiConventionalMemory, 01400000, 3AB36, F 8, 4, EfiBootServicesData, 3BF36000, 20, F 9, 7, EfiConventionalMemory, 3BF56000, 273F, F 10, 1, EfiLoaderCode, 3E695000, 2, F 11, 4, EfiBootServicesData, 3E697000, A, F 12, 9, EfiACPIReclaimMemory, 3E6A1000, 1, F 13, 4, EfiBootServicesData, 3E6A2000, 1EA, F 14, 3, EfiBootServicesCode, 3E88C000, A8, F 15, A, EfiACPIMemoryNVS, 3E934000, 12, F 16, 0, EfiReservedMemoryType, 3E946000, 1C, F 17, 3, EfiBootServicesCode, 3E962000, 10A, F 18, 6, EfiRuntimeServicesData, 3EA6C000, 5, F 19, 5, EfiRuntimeServicesCode, 3EA71000, 5, F 20, 6, EfiRuntimeServicesData, 3EA76000, 5, F 21, 5, EfiRuntimeServicesCode, 3EA7B000, 5, F 22, 6, EfiRuntimeServicesData, 3EA80000, 5, F 23, 5, EfiRuntimeServicesCode, 3EA85000, 7, F 24, 6, EfiRuntimeServicesData, 3EA8C000, 8F, F 25, 4, EfiBootServicesData, 3EB1B000, 702, F 26, 7, EfiConventionalMemory, 3F21D000, 4, F 27, 4, EfiBootServicesData, 3F221000, 6, F 28, 7, EfiConventionalMemory, 3F227000, 1, F 29, 4, EfiBootServicesData, 3F228000, 7F3, F 30, 7, EfiConventionalMemory, 3FA1B000, 1, F 31, 3, EfiBootServicesCode, 3FA1C000, 17F, F 32, 5, EfiRuntimeServicesCode, 3FB9B000, 30, F 33, 6, EfiRuntimeServicesData, 3FBCB000, 24, F 34, 0, EfiReservedMemoryType, 3FBEF000, 4, F 35, 9, EfiACPIReclaimMemory, 3FBF3000, 8, F 36, A, EfiACPIMemoryNVS, 3FBFB000, 4, F 37, 4, EfiBootServicesData, 3FBFF000, 201, F 38, 7, EfiConventionalMemory, 3FE00000, 8D, F 39, 4, EfiBootServicesData, 3FE8D000, 20, F 40, 3, EfiBootServicesCode, 3FEAD000, 20, F 41, 4, EfiBootServicesData, 3FECD000, 9, F 42, 3, EfiBootServicesCode, 3FED6000, 1E, F 43, 6, EfiRuntimeServicesData, 3FEF4000, 84, F 44, A, EfiACPIMemoryNVS, 3FF78000, 88, F 45, 6, EfiRuntimeServicesData, FFC00000, 400, 1
単にメモリマップを取得した様子を見たいなら画面への出力でよいので SaveMemoryMap
を以下のような PrintMemoryMap
に置き換えてみてもよさそう
EFI_STATUS PrintMemoryMap(struct MemoryMap *map) { Print(L"Index, Type, Type(name), PhysicalStart, NumberOfPages, Attributen"); EFI_PHYSICAL_ADDRESS iter; int i; 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; Print(L"%u, %x, %-ls, %08lx, %lx, %lxn", i, desc->Type, GetMemoryTypeUnicode(desc->Type), desc->PhysicalStart, desc->NumberOfPages, desc->Attribute & 0xffffflu); } return EFI_SUCCESS; }
出力はこんな感じ
Summary:Returns the current memory map.
Prototype:
typedef EFI_STATUS (EFIAPI *EFI_GET_MEMORY_MAP) ( IN OUT UINTN *MemoryMapSize, OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, OUT UINTN *MapKey, OUT UINTN *DescriptorSize, OUT UINT32 *DescriptorVersion );
ざっくりとキーポイントをまとめると、戻り値は EFI_STATUS でメモリマップの取得に成功したか否かとなる。取得成功時には MemoryMap 引数として渡したバッファに EFI_MEMORY_DESCRIPTOR の配列としてメモリマップの情報が格納されるという感じ。
u8
が導入された: ex. u8"hello"
ウェブ界隈でエンジニアとして労働活動に励んでいる @gomi_ningen 個人のブログです