【ゼロからのOS自作入門をやってみる】第1章 PCの仕組みとハローワールド

前々から興味があった「ゼロからのOS自作入門」をゆっくり読んでみようと思います。
挫折する可能性が非常に高いですが、作業メモをQiitaに書いていこうと思います。


https://www.amazon.co.jp/dp/B08Z3MNR9J/


作業は基本的にこちらのリポジトリで行います。

https://github.com/ng3rdstmadgke/mikanos


- 次の記事
  - [【ゼロからのOS自作入門をやってみる】第2章 EDKII入門とメモリマップ | Qiita](https://qiita.com/ktamido/items/346a4b4a6186c9381453)

# バイナリエディタでブートローダーを実装

Macなので Hex Fiend というアプリで書いてみました。

> バイナリエディタを使ったことがないので適当にインストールしました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/ef91b80e-702f-4b5a-9c91-5209d51eb5bc.png)





































とりあえず、チェックサムが `12430` になっていればOKです。

> チェックサムが全然合わなくて心折れそう

```bash
sum ~/Downloads/BOOTX64.EFI
# 12430 2 /Users/ktamido/Downloads/BOOTX64.EFI
```
できたファイルはこちら -> [BOOTX64.EFI (1.5 kB)](https://esa-storage-tokyo.s3-ap-northeast-1.amazonaws.com/uploads/production/attachments/5809/2025/04/18/19598/f1ddcd1d-f489-43a2-a5e5-fd2cb2cb8ef4.EFI)

## BOOTX64.EFIの内容

### 出力文字列情報
Hello, World!の文字列情報は `0x400` から `0x041b` あたりに存在します。
UEFIでは文字コードはUCS-2を使うことが決まっています。
UCS-2は一文字を2バイトで表します。例えば `H` はASCIIでは `0x48` ですが、 UCS-2では `0x0048` となります。
x86-64アーキテクチャはリトルエンディアンを採用しているので、バイナリエディタで入力するときは下位バイトを先に入力し、 `0x4800` となります。

### 機械語命令
`0x0200` から `0x022f` あたりが文字列を画面に表示する命令のようです。

# 実機でイメージを起動

USBメモリをGPTでフォーマットして、EFIシステムパーティションの `/EFI/BOOT/BOOTX64.EFI` に先ほど作成したBOOTX64.EFIを配置すると、PCで起動できるようです。

ちょうどいいPCがなかったので、企業のリース落ちらしきパソコンを1.5万円で購入 (結構いいスペックしてる)

https://item.rakuten.co.jp/pcengine/pc-s937-256/

## そもそもBIOSとUEFI、MBRとGPTの違いがよくわかっていなかったので調べてみました。

https://qiita.com/ktamido/items/7fd51939f9d8a883f6a3


## ESP(EFIシステムパーティション)とは


UEFIにおいて、起動プロセスに必須のパーティションで、ブートローダー(`/EFI/BOOT/BOOTX64.EFI`)やデバイスドライバ、システムユーティリティを格納します。
ファイルシステムは `FAT32` でフォーマットされ、パーティションサイズは一般的に100MB ~ 1GB程度。
パーティションタイプGUIDは `C12A7328-F81F-11D2-BA4B-00A0C93EC93B` となります。

fdiskでパーティションがESPかを確認することができます。

```bash
fdisk -l
# Disk /dev/nvme0n1: 1.82 TiB, 2000398934016 bytes, 3907029168 sectors
# Disk model: KIOXIA-EXCERIA PLUS G3 SSD
# Units: sectors of 1 * 512 = 512 bytes
# Sector size (logical/physical): 512 bytes / 512 bytes
# I/O size (minimum/optimal): 512 bytes / 512 bytes
# Disklabel type: gpt
# Disk identifier: 38496A13-AD66-44C7-8B35-7C373B9B75AB
#
# Device           Start        End    Sectors  Size Type
# /dev/nvme0n1p1    2048    2203647    2201600    1G EFI System
# /dev/nvme0n1p2 2203648 2931892223 2929688576  1.4T Linux filesystem
```


## MacでUSBメモリに書き込む方法

ディスクユーティリティをCLIで使います。

USBメモリをGPTでフォーマットします。

```bash
# デバイスの確認
diskutil list
# ...
#/dev/disk4 (external, physical):
#   #:                       TYPE NAME                    SIZE       IDENTIFIER
#   0:      GUID_partition_scheme                        *31.0 GB    disk4
#   1:                        EFI NO NAME                 209.7 MB   disk4s1
#   2:       Microsoft Basic Data NO NAME                 30.8 GB    disk4s2


# ディスクをGPTで初期化(UEFIで起動できるように)
# diskutil eraseDisk ファイルフォーマット ボリューム名 パーティション方式 デバイスファイルパス
diskutil eraseDisk ExFAT VOL1 GPT /dev/disk4
# Started erase on disk4
# Unmounting disk
# Creating the partition map
# Waiting for partitions to activate
# Formatting disk4s2 as ExFAT with name VOL1
# Volume name      : VOL1
# Partition offset : 411648 sectors (210763776 bytes)
# Volume size      : 60215296 sectors (30830231552 bytes)
# Bytes per sector : 512
# Bytes per cluster: 32768
# FAT offset       : 2048 sectors (1048576 bytes)
# # FAT sectors    : 8192
# Number of FATs   : 1
# Cluster offset   : 10240 sectors (5242880 bytes)
# # Clusters       : 940704
# Volume Serial #  : 6801d255
# Bitmap start     : 2
# Bitmap file size : 117588
# Upcase start     : 6
# Upcase file size : 5836
# Root start       : 7
# Mounting disk
# Finished erase on disk


# 生成されたパーティションを確認
# NOTE: TYPE が EFI となっているのが EFIシステムパーティション (ESP)
diskutil list
# ...
# /dev/disk4 (external, physical):
#    #:                       TYPE NAME                    SIZE       IDENTIFIER
#    0:      GUID_partition_scheme                        *31.0 GB    disk4
#    1:                        EFI NO NAME                 209.7 MB   disk4s1
#    2:       Microsoft Basic Data VOL1                    30.8 GB    disk4s2


# EFIシステムパーティション(disk4s1)を確認
diskutil info disk4s1
#   Device Identifier:         disk4s1
#   Device Node:               /dev/disk4s1
#   Whole:                     No
#   Part of Whole:             disk4
#
#   Volume Name:               NO NAME
#   Mounted:                   No
#
#   Partition Type:            EFI
#   File System Personality:   MS-DOS FAT32
#   Type (Bundle):             msdos
#   Name (User Visible):       MS-DOS (FAT32)
#
#   OS Can Be Installed:       No
#   Media Type:                一般
#   Protocol:                  USB
#   SMART Status:              Not Supported
#   Volume UUID:               0E239BC6-F960-3107-89CF-1C97F78BB46B
#   Disk / Partition UUID:     D8B649D9-3BFE-4B21-B2DB-595251E72687
#   Partition Offset:          20480 Bytes (40 512-Byte-Device-Blocks)
#
#   Disk Size:                 209.7 MB (209715200 Bytes) (exactly 409600 512-Byte-Units)
#   Device Block Size:         512 Bytes
#
#   Volume Total Space:        0 B (0 Bytes) (exactly 0 512-Byte-Units)
#   Volume Free Space:         0 B (0 Bytes) (exactly 0 512-Byte-Units)
#
#   Media OS Use Only:         No
#   Media Read-Only:           No
#   Volume Read-Only:          Not applicable (not mounted)
#
#   Device Location:           External
#   Removable Media:           Removable
#   Media Removal:             Software-Activated
#
#   Solid State:               Info not available

# disk4s2を確認
diskutil info disk4s2
#   Device Identifier:         disk4s2
#   Device Node:               /dev/disk4s2
#   Whole:                     No
#   Part of Whole:             disk4
#
#   Volume Name:               VOL1
#   Mounted:                   Yes
#   Mount Point:               /Volumes/VOL1
#
#   Partition Type:            Microsoft Basic Data
#   File System Personality:   ExFAT
#   Type (Bundle):             exfat
#   Name (User Visible):       ExFAT
#
#   OS Can Be Installed:       No
#   Media Type:                一般
#   Protocol:                  USB
#   SMART Status:              Not Supported
#   Volume UUID:               9EBED486-32B4-3369-A19E-2D42E1429BE7
#   Disk / Partition UUID:     985066A8-8392-481A-9EF9-780E7566AF13
#   Partition Offset:          210763776 Bytes (411648 512-Byte-Device-Blocks)
#
#   Disk Size:                 30.8 GB (30830231552 Bytes) (exactly 60215296 512-Byte-Units)
#   Device Block Size:         512 Bytes
#
#   Volume Total Space:        30.8 GB (30825021440 Bytes) (exactly 60205120 512-Byte-Units)
#   Volume Used Space:         3.6 MB (3637248 Bytes) (exactly 7104 512-Byte-Units) (0.0%)
#   Volume Free Space:         30.8 GB (30821384192 Bytes) (exactly 60198016 512-Byte-Units) (100.0%)
#   Allocation Block Size:     512 Bytes
#
#   Media OS Use Only:         No
#   Media Read-Only:           No
#   Volume Read-Only:          No
#
#   Device Location:           External
#   Removable Media:           Removable
#   Media Removal:             Software-Activated
#
#   Solid State:               Info not available
```

EFIシステムパーティション(disk4s1)をマウントしてブートローダー(`/EFI/BOOT/BOOTX64.EFI`)を配置します。

```bash
mkdir -p ~/mnt/esp

# EFIシステムパーティションをマウント
sudo diskutil mount -mountPoint ~/mnt/esp /dev/disk4s1

# ブートローダー(BOOTX64.EFI)を/EFI/BOOT/BOOTX64.EFIに配置
mkdir -p ~/mnt/esp/EFI/BOOT
cp BOOTX64.EFI ~/mnt/esp/EFI/BOOT/BOOTX64.EFI

# アンマウント
diskutil unmountDisk /dev/disk4
```

## Linux(Ubuntu24.04)でUSBメモリに書き込む方法

USBメモリをGPTでフォーマットします。


```bash
# ディスクがマウントされてしまっている場合はアンマウント (GUI付きのLinuxだと必要)
sudo df -h
# ...
# /dev/sdb2        29G  3.5M   29G   1% /media/ktamido/VOL1
sudo umount /media/ktamido/VOL1

# 接続されているディスクを確認
sudo fdisk -l
# ...
# ディスク /dev/sda: 28.91 GiB, 31042043904 バイト, 60628992 セクタ
# Disk model: USB Flash Disk  
# 単位: セクタ (1 * 512 = 512 バイト)
# セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
# I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
# ディスクラベルのタイプ: gpt
# ディスク識別子: 1B5CA5F7-2AE4-4ACE-B9AC-15FC7FE1E041

DEVICE_FILE=/dev/sda

sudo parted $DEVICE_FILE

# パーティションテーブルを作成
(parted) mklabel gpt
(parted) print
# モデル: BUFFALO USB Flash Disk (scsi)
# ディスク /dev/sda: 31.0GB
# セクタサイズ (論理/物理): 512B/512B
# パーティションテーブル: gpt
# ディスクフラグ:
# 
# 番号  開始  終了  サイズ  ファイルシステム  名前  フラグ

# EFIシステムパーティション(ESP)を作成
(parted) mkpart primary fat32 1MiB 1GiB
(parted) set 1 esp on
(parted) print
# モデル: BUFFALO USB Flash Disk (scsi)
# ディスク /dev/sda: 31.0GB
# セクタサイズ (論理/物理): 512B/512B
# パーティションテーブル: gpt
# ディスクフラグ:
# 
# 番号  開始    終了    サイズ  ファイルシステム  名前     フラグ
#  1    1049kB  1074MB  1073MB                    primary  boot, esp

# partedを終了
(parted) quit


# パーティションが作成できていることを確認
sudo fdisk -l $DEVICE_FILE
# ディスク /dev/sda: 28.91 GiB, 31042043904 バイト, 60628992 セクタ
# Disk model: USB Flash Disk
# 単位: セクタ (1 * 512 = 512 バイト)
# セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
# I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
# ディスクラベルのタイプ: gpt
# ディスク識別子: 832B57BC-0A3B-442D-BD4B-5597B99950AB
# 
# デバイス   開始位置 最後から  セクタ サイズ タイプ
# /dev/sda1      2048  2097151 2095104  1023M EFI システム

# ファイルシステムを作成
sudo mkfs.vfat -F32 ${DEVICE_FILE}1

# ファイルシステムがfat32になっていることを確認
sudo parted $DEVICE_FILE print
# モデル: BUFFALO USB Flash Disk (scsi)
# ディスク /dev/sda: 31.0GB
# セクタサイズ (論理/物理): 512B/512B
# パーティションテーブル: gpt
# ディスクフラグ:
#
# 番号  開始    終了    サイズ  ファイルシステム  名前     フラグ
#  1    1049kB  1074MB  1073MB  fat32             primary  boot, esp
```

EFIシステムパーティション(disk4s1)をマウントしてブートローダー(`/EFI/BOOT/BOOTX64.EFI`)を配置します。

```bash
# /EFI/BOOT/BOOTX64.EFIを配置
sudo mkdir -p /mnt/esp
sudo mount ${DEVICE_FILE}1 /mnt/esp
sudo mkdir -p /mnt/esp/EFI/BOOT
sudo cp /home/ktamido/BOOTX64.EFI /mnt/esp/EFI/BOOT/BOOTX64.EFI
sudo umount /mnt/esp
```




## UEFI BIOSで起動ディスクの1番目にUSBを設定

USBを起動するPCのUEFI BIOS画面を開いて以下の設定を行います。
※ 富士通FMVは起動時にF2でUEFI BIOSが起動します。

1. 起動ディスクの1番目をUSBメモリに設定
1. セキュアブートをOFFにする


![IMG_8665.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/27bc349b-da7c-45cf-9559-ce54b2e23d31.jpeg)
















![IMG_8664.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/68cc017c-217b-4480-8f97-a95bf52e58ca.jpeg)















## 起動

USBを挿して起動するとこんな感じに文字が表示されます。

![IMG_8666.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/8e5b34fa-e283-4f7b-8ae8-2a5c0903fce3.jpeg)


# QEMUでイメージを起動

QEMUでもイメージを起動できるようなので試してみます。


```bash
sudo apt install qemu-system-x86 git


mkdir tmp
cd tmp

# イメージの作成
qemu-img create -f raw disk.img 200M
mkfs.fat -n "MIKAN OS" -s 2 -f 2 -R 32 -F 32 disk.img

# 作成したイメージの確認
sudo parted disk.img print
# モデル:  (file)
# ディスク /home/ktamido/Projects/mikanos/tmp/disk.img: 210MB
# セクタサイズ (論理/物理): 512B/512B
# パーティションテーブル: loop
# ディスクフラグ:
#
# 番号  開始   終了   サイズ  ファイルシステム  フラグ
#  1    0.00B  210MB  210MB   fat32

# EFIシステムパーティション(ESP)の/ EFI/BOOT に BOOTX64.EFI を配置
mkdir -p mnt
sudo mount -o loop disk.img mnt
sudo mkdir -p mnt/EFI/BOOT
sudo cp BOOTX64.EFI mnt/EFI/BOOT/
sudo umount mnt

# OVMF_CODE.fd, OVMF_VARS.fdを参照するためにリポジトリをクローン
git clone https://github.com/uchan-nos/mikanos-build.git

# CLIモードで実行する場合は -nographicをつける
sudo qemu-system-x86_64 \
  -drive if=pflash,file=mikanos-build/devenv/OVMF_CODE.fd,format=raw \
  -drive if=pflash,file=mikanos-build/devenv/OVMF_VARS.fd,format=raw \
  -drive file=disk.img,format=raw \
  -nographic
```

起動するとこんな感じになる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92383/0c624bec-4ead-404b-9fd6-d105146a2a7d.png)

CLIモードのQEMUを終了するときは `Ctrl + a` を押して `x`


# C言語でブートローダを実装

先ほどバイナリエディタで作成したブートローダーを今度はC言語で実装してみます。

> C言語は書いたことがないですが、とりあえずやりながら調べて覚える方針。
>
> この時点では何を書いているかさっぱりわからない。
> エントリーポイントでprint("Hello, world")をするために必要な依存を無理やり定義している感じだろうか。

## UEFIアプリケーション(ブートローダー)のソースコード

```hello.c
typedef unsigned short CHAR16;
typedef unsigned long long EFI_STATUS;
typedef void *EFI_HANDLE;

// _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL構造体の宣言。(定義は後で)
// この構造体をポインタ型として先に使いたいため宣言のみ行う
struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;

typedef EFI_STATUS (*EFI_TEXT_STRING)(
  struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
  CHAR16                                  *String
);

// _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 構造体に EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL という別名をつける
typedef struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
  void            *dummy;  // void* 型はどんな方のポインタにもなれる汎用ポインタ。サイズ合わせや将来の拡張のために存在する
  EFI_TEXT_STRING OutputString;
} EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;

// 構造体を定義して EFI_SYSTEM_TABLE という別名を付ける
typedef struct {
  char dummy[52];
  EFI_HANDLE ConsoleOutHandle;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
} EFI_SYSTEM_TABLE;

// エントリーポイント
EFI_STATUS EfiMain(
  EFI_HANDLE IageHandle,
  EFI_SYSTEM_TABLE *SystemTable
) {
  SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Hello, world!\n");
  while(1);
  return 0;
}
```


## ブートローダーのビルド & イメージ作成 & 実行


```bash
sudo apt update && sudo apt install -y make clang lld qemu-system-x86

# hello.cをコンパイルしてCOFF形式のhello.oを生成する
#   -target x86_64-pc-win32-coff: 出力を Windows向けの x86_64 COFF形式（＝UEFIで使える形式）にする
#   -mno-red-zone: red zone（スタックの下の128バイト）を使わない。割り込みが入る環境（UEFIなど）では red zone は危険なのでオフにする
#   -fno-stack-protector: スタックプロテクションをオフにする。UEFIでは実行時ライブラリがないので不要（むしろ動かない）
#   -fshort-wchar: wchar_t を 2バイト（UTF-16）にする。UEFI は CHAR16 = wchar_t（16bit）と前提しているため
#   -Wall: 全ての一般的な警告を有効化（コードの品質チェック）
#   -o $@ 出力ファイル名を指定
#   -c: コンパイルのみ実行（リンクはしない）
clang -target x86_64-pc-win32-coff \
  -mno-red-zone \
  -fno-stack-protector \
  -fshort-wchar \
  -Wall \
  -o hello.o -c hello.c

# hello.oをリンクしてPE形式の実行可能ファイル (hello.efi) を生成する
# -subsystem:efi_application UEFIファームウェアで起動可能な .efi 形式にする
# -entry:EfiMain エントリーポイントを EfiMain にする
# -out:hello.efi 出力ファイル名を指定
lld-link /subsystem:efi_application /entry:EfiMain /out:hello.efi hello.o


# イメージの作成
IMAGE_FILE=disk.img
qemu-img create -f raw $IMAGE_FILE 200M

# イメージにファイルシステム(FAT32)を作成
#   -n "MIKAN OS" ボリュームラベルを "MIKAN OS" に設定します。エクスプローラーなどでドライブ名として表示される名前です。
#   -s 2 セクタサイズを指定します。1セクタは通常 512 バイトなので、1 クラスタ = 1024 バイトになります。クラスタが小さいと空き容量の効率は良くなりますが、FAT テーブルのサイズは大きくなります。
#   -f 2 FAT テーブルの数を指定します。通常 2 つ（FAT の冗長性を確保）。1 つが壊れても、もう 1 つでリカバリ可能。
#   -R 32 ルートディレクトリエントリの予約数。FAT12/16ではルートディレクトリが固定サイズでしたが、FAT32ではこの数で予約されるクラスタ数を指定します（ファイル数とは直接関係しません）。
#   -F 32 FAT の種類を指定します。ここでは FAT32。FAT12 や FAT16 にも変更可能ですが、容量が大きいディスクイメージでは FAT32 が推奨されます。
mkfs.fat -n "MIKAN OS" -s 2 -f 2 -R 32 -F 32 $IMAGE_FILE
sudo parted $IMAGE_FILE print
mkdir -p mnt
sudo mount -o loop $IMAGE_FILE mnt
sudo mkdir -p mnt/EFI/BOOT
sudo cp hello.efi mnt/EFI/BOOT/BOOTX64.EFI
sudo umount mnt

# qemuでイメージを実行
# Ctrl + a のあとに x でしゅう
sudo qemu-system-x86_64 \
  -drive if=pflash,file=OVMF_CODE.fd,format=raw \
  -drive if=pflash,file=OVMF_VARS.fd,format=raw \
  -drive file=disk.img,format=raw \
  -nographic

# お掃除
sudo rm -rf hello.efi hello.o disk.img mnt
```


# 最終的に出来上がったソースコード


https://github.com/ng3rdstmadgke/mikanos/tree/day02a/day01


Makefileを作って、ビルド、イメージ作成、QEMUで起動する一連の処理を1コマンドでできるようにしてみました。

```bash
make run
```