OSが書けなくても画面にお絵描きがしたい
PCでお絵描きがしたい、けど…
冬です
外は寒いのでこたつに入ってPCでお絵描きでもしたいところです
しかし、筆者のパソコンはOSもペイントソフトも入っておらずお絵描きをすることができません
どうしてもお絵描きがしたいので、今日は頑張ってPCの画面にお絵描きできるようにします
この記事はWORDIAN Advent Calendar 2018 - 23日目の記事です
戦略
さて、PCを動かすためにまずはブートローダが必要です。ブートローダはBIOSの仕様に沿って実装する訳ですが、アセンブリを書いてプログラムがメモリの0x7c00にロードされ...32bitプロテクトモードに移行して...のように非常に難解なことをしないといけません。冬が終わってしまいます。世の中は便利なものでGRUBと呼ばれる素晴らしいブートローダが存在するため、ありがたく利用することにします。GRUBはよろしくやってくれた後にフレームバッファの場所を教えてくれるのでそこに色を書き込むことでお絵描きができます。
あ、GRUBを用意したりプログラムをコンパイルしたりするためのPCは別途用意することにしました。
GRUBを用意する
PCにGRUBをインストールします。ArchLinuxの人は
sudo pacman -S grub
で入ります。
GRUBで読み込めるプログラムの形式について
GRUBではmultibootと呼ばれるプログラムの形式を使用することができます。お絵描きするために最低限必要な事項は本記事で記述しますが、詳しい仕様についてはMultiboot Specification version 0.6.96をご確認ください。
Multiboot Specificationの3.1 OS image formatではプログラムの形式についての規定がされています。いろいろ書いてありますが、どうやらプログラムにはMultiboot headerが必要でファイルの先頭8192バイトに格納されている必要があるようです。3.2 Machine stateと3.3 Boot information formatではmultibootが読み込まれた後にGRUBが返してくれるマシンの状態に関する記述があります。これについても後述します。
Multibot headerの形式
Offset | Type | Field Name | Note | |
---|---|---|---|---|
0 | unsigned 32bit | magic | required | |
4 | unsigned 32bit | flags | required | |
8 | unsigned 32bit | checksum | required | |
12 | unsigned 32bit | header_addr | if flags[16] is set | 今回は使わない |
16 | unsigned 32bit | load_addr | if flags[16] is set | 今回は使わない |
20 | unsigned 32bit | load_end_addr | if flags[16] is set | 今回は使わない |
24 | unsigned 32bit | bss_end_addr | if flags[16] is set | 今回は使わない |
28 | unsigned 32bit | entry_addr | if flags[16] is set | 今回は使わない |
32 | unsigned 32bit | mode_type | if flags[2] is set | |
36 | unsigned 32bit | width | if flags[2] is set | |
40 | unsigned 32bit | height | if flags[2] is set | |
44 | unsigned 32bit | depth | if flags[2] is set |
magic
マジックナンバーです。必ず0x1BADB002を格納します。flags
bit 0,1,2,15を使います。bit 2を1にするとビデオモードを設定することができます。今回はとりあえず1にしましょう。 bit 0,1,15はそれぞれ1,1,0にしておきます。お絵描きに関係ないので説明は割愛します。checksum
チェックサムです。-(magic+flags)を格納します。mode_type
ビデオモードの希望です。グラフィックモード(0)かテキストモード(1)のどちらかに設定することができます。お絵描きするにはグラフィックモードのほうが嬉しいので今回は0に設定しておきます。width,height
画面のサイズの希望を設定することができます。お絵描きにおけるキャンパスサイズとなります。とりあえず最初はwidthを640、heightを480くらいにしておくことにします。depth
ビット深度の希望です。32にします。その他
今回は使わないため説明は割愛します。値は適当に0にしておきましょう。
multibootが読み込まれた後のマシンのレジスタの状態
色々と記述がありますがとりあえずEBXとついて気にしておけば良さそうです。
GRUBから渡されるブート情報について
multibootがGRUBに読み込まれるとEBXにブート情報構造体のポインタが格納されます。お絵描きをするために必要なフレームバッファの情報もあります。
+-------------------+ 0 | flags | (required) +-------------------+ 4 | mem_lower | (present if flags[0] is set) 8 | mem_upper | (present if flags[0] is set) +-------------------+ 12 | boot_device | (present if flags[1] is set) +-------------------+ 16 | cmdline | (present if flags[2] is set) +-------------------+ 20 | mods_count | (present if flags[3] is set) 24 | mods_addr | (present if flags[3] is set) +-------------------+ 28 - 40 | syms | (present if flags[4] or | | flags[5] is set) +-------------------+ 44 | mmap_length | (present if flags[6] is set) 48 | mmap_addr | (present if flags[6] is set) +-------------------+ 52 | drives_length | (present if flags[7] is set) 56 | drives_addr | (present if flags[7] is set) +-------------------+ 60 | config_table | (present if flags[8] is set) +-------------------+ 64 | boot_loader_name | (present if flags[9] is set) +-------------------+ 68 | apm_table | (present if flags[10] is set) +-------------------+ 72 | vbe_control_info | (present if flags[11] is set) 76 | vbe_mode_info | 80 | vbe_mode | 82 | vbe_interface_seg | 84 | vbe_interface_off | 86 | vbe_interface_len | +-------------------+ 88 | framebuffer_addr | (present if flags[12] is set) 96 | framebuffer_pitch | 100 | framebuffer_width | 104 | framebuffer_height| 108 | framebuffer_bpp | 109 | framebuffer_type | 110-115 | color_info | +-------------------+
色々ありますが必要な部分はオフセット88からのフレームバッファに関する情報のみです。
Offset | Type | Field Name |
---|---|---|
88 | unsigned 64bit | framebuffer_addr |
96 | unsigned 32bit | framebuffer_pitch |
100 | unsigned 32bit | framebuffer_width |
104 | unsigned 32bit | framebuffer_height |
108 | unsigned 32bit | framebuffer_bpp |
109 | unsigned 32bit | framebuffer_type |
110-115 | unsigned 32bit | color_info |
framebuffer_addr
フレームバッファの先頭のアドレスです。64bitです。注意framebuffer_pitch
バイトのピッチです。単位はバイトframebuffer_width , framebuffer_hight GRUBが設定したフレームバッファの縦横のサイズです。
framebuffer_bpp GRUBが設定したフレームバッファのビット深度です。
framebuffer_type GRUBが設定したフレームバッファの種類です。0だとインデックスカラー、1だとダイレクトカラー、2だとテキストモードになっています。また、0,1ではこの後のcolor_infoが使用されます。
color_info
framebuffer_typeが1(ダイレクトカラーの場合)
+----------------------------------+ 110 | framebuffer_red_field_position | 111 | framebuffer_red_mask_size | 112 | framebuffer_green_field_position | 113 | framebuffer_green_mask_size | 114 | framebuffer_blue_field_position | 115 | framebuffer_blue_mask_size | +----------------------------------+
32bit中に各色がどのように配置されるかが格納されています。positionが下位ビットから何bit目からかを、mask_sizeがビットの幅を表しています。 今回はダイレクトカラーに設定されることを前提とするため、インデックスカラーの場合の説明は割愛します。
実装
Multiboot Specificationの4.3 Example OS codeにはサンプルコードがあります。せっかくなので利用します。
プリプロセッサ定義や構造体が含まているmultiboot.hはそのまま使います。
multiboot.h (クリックで展開)
boot.SではMultiboot headerを配置してC言語をコンパイルした関数を呼びます。サンプルから不要な部分を削除して最小限必要な部分のみを残しました。
画像データを描画するCプログラムです。ダイレクトカラーでdepthが32bitに設定されていることが前提です。とりあえず画面を緑色で塗りました。
コードが書けたら
$ gcc -m32 -ffreestanding -fno-common -fno-builtin -fomit-frame-pointer -O2 -c -o oekaki.o oekaki.c $ gcc -m32 -ffreestanding -fno-common -fno-builtin -fomit-frame-pointer -O2 -c -o boot.o boot.S $ ld -melf_i386 -Ttext=0x100000 --oformat elf32-i386 -o Oekaki boot.o oekaki.o
のようにコンパイルとリンクを行います。テキストエディタでgrub.cfgも作成しましょう。
menuentry "oekaki" { multiboot /boot/Oekaki boot }
grub-mkrescueでブートイメージを作成します。
$ mkdir -p iso/boot/grub $ mv Oekaki iso/boot $ mv grub.cfg iso/boot/grub $ grub-mkrescue -o oekaki.iso iso
作成したプログラムのブートイメージが作成されました。試しにqemuでブートしてみましょう。
これでフレームバッファの中身を画面に出せるようになりました。後はソースコードを適当にいじれば画像等も表示することができます。
めでたしめでたし