/ «2003-10-18 (Sat) ^ 2003-10-20 (Mon)» ?
   西田 亙の本:GNU 開発ツール -- hello.c から a.out が誕生するまで --

Categories Books | Hard | Hardware | Linux | MCU | Misc | Publish | Radio | Repository | Thoughts | Time | UNIX | Writing | プロフィール


2003-10-19 (Sun)

[Writing] GCCプログラミング工房 VGA プログラミング

記事中で使用する、全コードが完成する。昨日、バグだと思ったのは、どうやら VGA カードによりメモリープレーン中の未使用バイトへのマッピングがことなることが原因と判明。プログラムは正常に動作していた訳だ。ほっと一息。

早いもので、VGA シリーズは第23回で3回目を迎える。初回は、テキスト VRAM をデバッガーで直接操作し、スクリーンに現れる文字の実体を知ることから始まった。

私自身が、テキスト VRAM 操作を体験したのは、PC-8001 が搭載していた ROM-BASIC の POKE 命令を通してであった。この頃は、まだ機械語が何たるかも知らなかったのだが、POKE で画面に字が現れ、アトリビュートコードをいじることで、色が変わりブリンクした瞬間の驚きと感動は、未だに忘れることができない。

PC/AT の VGA テキスト VRAM は、セグメント B800 に存在する。このセグメント内のオフセット0〜1番地が画面上の左上隅、9E〜9F番地が右上隅の文字に相当する訳だ。すなわち、2バイトが1文字を表現することになるが、この時偶数アドレスのバイトが ASCII コード、奇数アドレスのバイトが属性コードを現す決まりになっている。

ここで次のような疑問が生じる。なぜテキスト VRAM はセグメント B800 に位置しているのか?誰が B800 に決めたのか?なぜ ASCII コードと属性コードがチャンポンになっているのか?今回は、長い間封印されてきたこれらの謎が明らかになる。

ということで、今回の GCC プログラミング工房は、かなり高度な内容になった。VGA カード上のメモリープレーン制御プログラミングを行うことで、普段我々が目にしているテキスト VRAM の世界が、実は VGA chip が生み出した「幻影」に過ぎないことを知るのである。この事実を自分自身の目で確認するために、realvram.c プログラムを作成した。FreeDOS 上で実行した realvram プログラムの出力結果は次の通り。

Hello, world!
48 63 65 63 6C 63 6C 63 6F 63 2C 63 20 63 77 74 
6F 74 72 74 6C 74 64 74 21 74 20 07 20 07 20 07 
20 07 20 07 20 07 20 07 20 07 20 07 20 07 20 07 
20 07 20 07 20 07 20 07 20 07 20 07 20 07 20 07 

48 00 65 00 6C 00 6C 00 6F 00 2C 00 20 00 77 00 
6F 00 72 00 6C 00 64 00 21 00 20 00 20 00 20 00 
20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 
20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 

63 00 63 00 63 00 63 00 63 00 63 00 63 00 74 00 
74 00 74 00 74 00 74 00 74 00 07 00 07 00 07 00 
07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 
07 00 07 00 07 00 07 00 07 00 07 00 07 00 07 00 

このプログラムは画面クリアーを行った後に、ホームポジションに Hello, world! を色つきで出力する。その上で、セグメント B800 の先頭64バイトの内容を最初に表示している。48 は H の ASCII コード、63 は属性コード、65 は e の ASCII コード、63 は再び属性コード・・と続く。これが、有名なテキスト VRAM であり、スクリーン上に表示されるテキストの正体、そのものである。ここまでは、比較的良く知られた事実だ。

それでは、2番目、3番目のダンプリストは何なのだろう?実は、これは VGA register programming を行うことで、VGA カード上のメモリープレーン0およびメモリープレーン1の先頭64バイトをダンプしたものなのである。

あまり知られていないが、VGA カードには 64KB の大きさを持ったメモリープレーンが4つ、合計 256KB の RAM が搭載されている。メモリープレーン0には ASCII コード、メモリープレーン1には属性コード、メモリープレーン2にはフォントデータが格納されている。確かに、メモリープレーン0には ASCII コードのみ、メモリープレーン1には属性コードのみが格納されていることが分かる(2バイト単位)。

つまり、私達が普段目にしているセグメント B800 のメモリーイメージは、VGA カード上のメモリープレーン0とメモリープレーン1の内容を「交互に合成」したものだったことになる(Odd/even memory addressing)。俄には信じがたいが、これがハードウェア内部の真の姿なのである。

それでは、フォントデータを格納するメモリープレーン2はどうなっているのだろうか?多くの人が興味を持つであろうメモリープレーン2は、残念ながら普段はプロテクトされており、特別な VGA register programming を行わない限り、その内部にアクセスすることは出来ない。それでは、コードの力でプロテクトを解除してみよう。以下は、readfont プログラムの実行結果である。

FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 7E 81 A5 81 81 BD 99 81 81 7E 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 7E FF DB FF FF C3 E7 FF FF 7E 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 6C EE FE FE FE 7C 38 10 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

これはセグメント A000 の先頭128バイトを2回ダンプしたものだ。1回目はデフォルトの状態。通常、セグメント A000 にはメモリーが存在しないため、CPU からは何も見えない。ここで、メモリープレーン2の内容をセグメント A000 にマップするように VGA chip をプログラムした時の内容が、次のダンプリストである。

さっきまでは空っぽだった RAM 空間に、突如として32バイト単位のデータ群が現れた。これが VGA テキストフォントの正体である。興味がある方は、このバイナリー値を2進数に直して並べてみると良いだろう。フォントの姿が浮かび上がるはずだ。

この知識を応用して、記事中では最後にフォントの書き換えに挑戦する。普段見慣れたフォントの姿を、自分の好きなデザインで変更してしまうのである。8x16 ではちと苦しいが、日本語も不可能ではない。恥ずかしながら、私も "GCCプログラミング工房" を VGA テキストで表現してみた。これが、結構いけて、楽しい。

ということで、記事の基本骨格となるコードとダンプリストが無事完成した。ここまで来れば完成したも同然で、半日をかけて文章を入れる。推敲を済ませて、渡辺編集長に送信。

秋の夜長に、VGA register programming を楽しんで頂ければ幸いである。