マザーボード内蔵のIRCクライアント
(axleos.com)- UEFIRC は、OSが起動する前のマザーボードファームウェアの UEFIプリブート環境 で動作するグラフィカルなIRCクライアントで、ブートローダー向けの環境でも一般的なアプリに近いUIとネットワーク機能を実現できることを示している
- 実装では、UEFIがネットワークブートのために提供する NICドライバとTCPスタック を活用しており、QEMU向けの
vmnetネットワークバックエンドが開発を可能にした - 最も厄介だったのは Rust で UEFI TCP プロトコルを扱うことで、グローバル状態、再入可能コールバック、scatter-gather バッファ、イベント・トークン・ハンドル・プロトコルが複雑に絡み合っていた
- GUI は axle の Rust GUI ツールキットと TrueType レンダラを UEFI に移植したもので、マウス入力・スクロールバー・スクロールビューのテキストレンダリングのために libgui の改善 もあわせて行われた
- 成果物は実用的なIRCクライアントというより精巧なジョークプロジェクトに近いが、UEFI TCP/IP スタックへの不満を UEFI 内から IRC で語れるツールになっている
UEFIRCがすること
- UEFIRC は UEFI 上で動作するグラフィカルなIRCクライアント である
- Rust で書かれており、axle のユーザー空間向けに作られた GUI ツールキットと TrueType レンダラを活用している
- IRC サーバーに接続してチャットし、メッセージを読むことができる
- 開発には QEMU 向けの
vmnetネットワークバックエンド が使われた
UEFIという実行の舞台
- OS のブートローダーは、マザーボード ROM に保存されたファームウェアの助けを借りてロードされる
- 以前の BIOS にはさまざまな制約があり、それを置き換えるために UEFI 標準が作られた
- BIOS はブートローダーが 16-bit モードで開始することを要求する
- 第1段階ローダーを 512 バイト以内に収めなければならないという要件もある
- UEFI はブートローダーを最初から 64-bit 環境に置き、VESA ディスプレイ解像度の切り替え、メモリ割り当て、EFI ファイルシステムアクセスなどの API を提供する
- BIOS より大きな前進ではあるが、過剰に設計されているという評価もある
ネットワークブート機能をIRCに再利用
- 一部のブートローダーはローカルのブロックデバイスではなく、ネットワーク経由でOS をロードできる
- このユースケースを支えるには、UEFI ファームウェアがネットワークスタックを含む必要がある
- NIC ドライバ
- TCP 実装
- プリブート環境で動作するアプリケーションがこのスタックにアクセスするための API
- ブートローダーは必ずしも OS をロードしなければならないわけではないため、同じ環境で IRC クライアントも実行できる
RustでUEFI TCPを扱う難しさ
- このプロジェクトで最も難しかった部分は、UEFI TCP プロトコルクライアント を Rust で実装することだった
- UEFI TCP プロトコルは、Rust で表現しづらいデータ寿命と相互作用を要求する
- グローバル状態
- 再入可能コールバック
- scatter-gather バッファ
- イベント、トークン、ハンドル、プロトコル
- メモリリークや TCP 受信バッファの use-after-free をなくすため、Rust コードを何日もテストした
NOTIFY_SIGNAL と NOTIFY_WAIT の混乱
- UEFI のイベント API は、名前だけ見ても動作を予測しにくい
NOTIFY_SIGNALを指定するとイベント発生時にコールバックが呼ばれ、wait()の使用はエラーになるNOTIFY_WAITを指定してwait()を呼ぶと、UEFI はイベントが発生する前までコールバックを何度も呼ぶことができ、イベントが発生するとwait()が解除される- この2つのモードは、同じコールバックであっても まったく異なる意味 を持つ
NOTIFY_SIGNAL: イベントが発生したので次の作業に進むタイミングNOTIFY_WAIT: イベントがまだ発生していないので進行を促すタイミング
- 受信パケットデータを非同期にバッファリングするため、最終的に
NOTIFY_WAITループと短いタイムアウトタイマーを組み合わせて使用した
マウスとカーソルのサポート
- IRC クライアントにマウスは必須ではないが、アプリをよりインタラクティブに感じさせる
- UEFI の Simple Pointer Protocol を使ってマウス移動とボタン入力を読み取り、GUI にカーソル位置のフィードバックを加えた
- Simple Pointer Protocol は スクロールホイールをサポートしない
- UEFIRC では矢印キーを使うか、カーソルでスクロールバーをドラッグする必要がある
- 標準の OVMF UEFI ファームウェアではマウスイベントを取得できなかったため、
UsbMouseDxeなど必要なドライバとプロトコルを含むカスタム UEFI ファームウェアをビルドした - QEMU で UEFIRC を試せるよう、その UEFI ファームウェアも リリースにアップロード されている
マウス移動のスケーリング
- マウスドライバは絶対位置ではなく 位置変化量 を報告する
- 単純に
delta_x、delta_yをそのまま足す線形スケーリングでは鈍く感じられる - OS は高速な移動と細かな調整の両方を可能にする方式に近いスケーリングを使っている
- サンプル実装では、移動量の絶対値の合計に
log2()を適用した値を掛けてカーソル移動を増幅している - 線形移動のカーソルは、全体の環境が遅く反応が悪いと感じさせやすい
IRCメッセージのモデリング
- IRC メッセージのモデリングは比較的シンプルで快適だった
- IRC は テキストベースの行形式 を使うため、パースしやすい
- ただし、数十年にわたる拡張の結果、一部しか標準化されていないという負担もある
UEFIでlibguiを使う
- axle の Rust GUI ツールキットは、すでに axle 外のコンテキストでも使えるようかなり整備されていたため、UEFI で動かすこと自体はそれほど難しくなかった
- 中核となる作業は、UEFI 内で利用できる
AwmWindow実装を提供することだった - その後は libgui のさまざまな機能をそのまま活用した
- イベント管理
- フォントレンダリング
- レイヤー合成
- ビュー装飾
- スクロールビューのような複雑なコンポーネント
スクロールバーとスクロールビューのテキストレンダリング
- axle の C ベース libgui にはスクロールバー機能があったが、Rust 版にはまだ一部機能が不足していた
- UEFIRC の主な操作はテキストで埋まったスクロールビュー上で行われるため、Rust 版 libgui に スクロールバー機能 を再実装した
- スクロールビューは固定サイズのビューよりピクセルレンダリングコストが高い
- 固定サイズビューでは
width * heightサイズの RGB バッファを考えればよい - スクロールビューでは無限に拡張可能なキャンバスを扱わなければならない
- 固定サイズビューでは
- axle の Rust GUI ツールキットは、スクロールビューを タイルベース で処理している
- 各タイルは数百ピクセル幅の正方形ピクセルバッファ
- コンテンツが実際にレンダリングされる領域に必要なタイルだけを割り当てる
- 見えているタイルを計算し、最終画像に貼り合わせる
- TrueType レンダラがグリフの各ピクセルごとに
putpixel()を呼ぶと、スクロールビューが全レンダリング領域を事前に把握できず非効率になる - これを解決するため、polygon stack を線、円、長方形のような基本描画単位に追加した
- スクロールビューは大きなポリゴンを描くことを認識し、必要なタイルを事前に割り当てられる
- 任意ポリゴン塗りつぶしを基本プリミティブに置いた点は気に入っていないが、実用上はうまく機能する
UEFIRCの開発で改善されたlibgui
- 新しいグラフィックアプリケーションを作るたびに、GUI ツールキット、IPC、ドライバインターフェース、カーネル機能の小さな制約や不便さが表面化する
- UEFIRC の開発過程で libgui にさまざまな修正が加えられた
完全に不要な成果物
- IRC クライアント自体は精巧なジョークプロジェクトであり、実用性は高くない
- UEFI TCP/IP スタックに腹が立ったとき、その不満を語るための道具として使える
- 最後に、UEFI 内から UEFI
#edk2開発 IRC チャンネルに接続してあいさつを残した
1件のコメント
Hacker News のコメント
いたずら半分で、UEFI の起動前環境でしか動かないグラフィカル IRC クライアントを作り、TrueType フォント、カーソル、GUI の装飾といった過剰な機能まで入れた。
もともとは一から作っている GPS 受信機に疲れて、手早く軽いものをやってみようというプロジェクトだったが、いつものように予想よりずっと時間がかかった。
記事では、スクロールビューをモデル化して静的なビューポートにレンダリングする方法を示す可視化にもかなり時間をかけたので、楽しんで見てもらえたらうれしい。
最初は「UEFI に入れるべきではないものを無理やり押し込もう」という考えで Twitter クライアントを思いついたが、すでに誰かが UEFI の HTTP プロトコルでよくできたものを作っていたので、HTTP は避けることにした。
そこで、TCP 上で動き、起動前環境とはまったく似つかわしくないソーシャルメディア感もある IRC を選んだ。
もちろん冗談だ。ある程度は。
ミニマリストなので GUI もマウスも必要ないし、UEFI ですら自分には必要以上に多機能に思える。
言及されている Twitter クライアントはこちら: https://github.com/arata-nvm/mitnal
インストールが完全に壊れて正常に起動できないリモートシステムを自動復旧するために、もしかすると安全に許可できるかなり良い方法に見える。
本当に良い。多くの人が思っているシステムの下には、想像以上に複雑で強力なソフトウェアが敷かれていることもよく示している。
オペレーティングシステムがソフトウェアスタックの「最下層」だとよく誤解されるが、実際にはシステムを本当に所有するファームウェア的なコードがある。
ときには役目を終えて消え、ときにはオペレーティングシステムからさえ透過的に見える状態で、システムの電源が入っている間ずっと残る。
「単なるデバイス駆動用の低レベルコードで、そこで深刻なことは起きない」という態度があるが、その下に IRC クライアントまで入れられるなら、ほかの悪意あることも十分想像できる。
「なぜ?」だって、いったい「なぜ」が何の質問なんだ? こういう精神を見るために HN に来ている。
「最も恐ろしい悟りが訪れた。自分のしたことには何の理由もなかった。なぜやったのかは分かっていた。ただ面白そうだったからやったのだ。だが彼らは『いったいなぜこんなことをしたんだ』と聞くだろうし、十分もっともらしい理由がなければ、私を精神病院に放り込むだろうと思った。」— Boyd Rice
自分を低く見る必要はない。ここにはボットネットのコマンド&コントロールクライアントのプロジェクトがある。
UI はちょっと笑えるけれど。
本当にすごい。UEFI API がこんなに簡単に扱えて、ドキュメントもしっかりしているとは知らなかった。
開発サイクルがどんなものだったのか気になる。VM で実行していたのだと思うが、クライアントを動かすたびに毎回「起動」しなければならなかったのだろうか。
メインの実行スクリプトが UEFIRC の新しいビルドを入れた EFI ファイルシステムを作り直し、それを QEMU に渡す。
ただし GUI を作っているときはこのオーバーヘッドがかなり面倒になったので、アプリが純粋な UEFI と Mac 上で動くホスト環境の両方をターゲットにビルドされるよう設定した。
ビルドフラグを変えると、GUI ツールキットが UEFI の提供するフレームバッファに直接描画するか、Mac のウィンドウシステムに接続してイベントをやり取りするようになる。
このデュアルターゲット方式のオーバーヘッドはエントリポイントにも見られる: https://github.com/codyd51/uefirc/blob/main/src/main.rs
IRC メッセージの解析は別の飾り付けが不要だったので、Mac でそのまま走る単体テスト群として開発し、その一部はこちらにある: https://github.com/codyd51/uefirc/blob/main/src/irc/response...
いつか、まだ動き続けている自分の IRC ボット用オペレーティングシステムの作成を終えたい。
もしかするといちばん役に立たない話かもしれないが、非線形なマウス移動、つまり加速は、新しい OS を起動したら最初にオフにする設定だ。なぜか実際に手が痛くなる。
たとえば Mac には無料の linearmouse があり、Windows では単に加速を切ればいい。Linux では当然簡単だ。
マウス加速を使うと、マウスが動いた距離と画面上の移動距離の対応感覚を身につけにくく、長期的には加速なしで使うほうが効率的だと思う。
ゲーマーたちから学んだやり方で、彼らが今もそうするだけの理由はあると思う。
どちらにせよ感覚は慣れるのだと思う。車のアクセルペダルも、普通は速度に直接対応しているわけではないのと同じように。
「なぜ?」と問うなら、UEFI が最初に紹介されたとき、こうした低レベルアプリケーションが約束されていたからだ。
UEFI を作った側は、一部ベンダーが起動中に特定のキーを押してアクセスできるようにしていた、Linux ベースのインターネット専用ミニ OS まで置き換える夢も見ていた。名前は思い出せない。
YouTube でこれを詳しく扱った動画を見たが、最初は縮小版 Linux や別のカスタム OS で、後に UEFI アプリへ移行し、結局ブームが去ったと記憶している。
記事が良い。2 年前の barebox ブートローダのエイプリルフールのネタを思い出す。ほかの起動先がすべて失敗すると #barebox に接続してくれる機能だった[1]。
そちらの焦点は barebox に TCP サポートを追加することにあり、こちらのような格好いい GUI 要素はなかった。
インターフェースはコマンドラインだけで、barebox を EFI ペイロードとしてビルドすれば EFI GOP 上に描画できた。
[1]: https://lore.barebox.org/barebox/20220401145902.GF4351@telli...
Cathode Ray Dude の最近の動画がすぐ思い浮かんだ。HP の「メールクライアント」、実際には Outlook プラグインである QuickLook を扱っていて、これもこのような方式で実装され、製品として出荷されたものだった: https://www.youtube.com/watch?v=ssob-7sGVWs
動画には HP がやっていたさらに奇妙なことも出てくる。ただしこのプロジェクトは、QuickLook が避けていた難しい部分であるネットワーキングまでやってのけている。
記事の可視化が驚くほど素晴らしく、印象的だ。