1 ポイント 投稿者 GN⁺ 2024-04-07 | 1件のコメント | WhatsAppで共有
  • 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がすること

  • UEFIRCUEFI 上で動作するグラフィカルな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_SIGNALNOTIFY_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_xdelta_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

完全に不要な成果物

  • IRC クライアント自体は精巧なジョークプロジェクトであり、実用性は高くない
  • UEFI TCP/IP スタックに腹が立ったとき、その不満を語るための道具として使える
  • 最後に、UEFI 内から UEFI #edk2 開発 IRC チャンネルに接続してあいさつを残した

1件のコメント

 
GN⁺ 2024-04-07
Hacker News のコメント
  • いたずら半分で、UEFI の起動前環境でしか動かないグラフィカル IRC クライアントを作り、TrueType フォント、カーソル、GUI の装飾といった過剰な機能まで入れた。
    もともとは一から作っている GPS 受信機に疲れて、手早く軽いものをやってみようというプロジェクトだったが、いつものように予想よりずっと時間がかかった。
    記事では、スクロールビューをモデル化して静的なビューポートにレンダリングする方法を示す可視化にもかなり時間をかけたので、楽しんで見てもらえたらうれしい。
    最初は「UEFI に入れるべきではないものを無理やり押し込もう」という考えで Twitter クライアントを思いついたが、すでに誰かが UEFI の HTTP プロトコルでよくできたものを作っていたので、HTTP は避けることにした。
    そこで、TCP 上で動き、起動前環境とはまったく似つかわしくないソーシャルメディア感もある IRC を選んだ。

    • 「起動前環境の近くにあってはいけない感じ」とはいうが、起動の問題について助けを求めるなら、むしろぴったりの場所に見える。
    • 無駄に巨大なオペレーティングシステムと雑多な機能を捨てて、もっと小さくて単純な UEFI に行きたい。起動も速くなり、「組み込み」開発もしやすくなるはずだから。
      もちろん冗談だ。ある程度は。
      ミニマリストなので GUI もマウスも必要ないし、UEFI ですら自分には必要以上に多機能に思える。
      言及されている Twitter クライアントはこちら: https://github.com/arata-nvm/mitnal
    • UEFI に押し込むには大きすぎるソフトウェアなら、そもそも全部不要な肥大化したソフトウェアだと見るべきだ。昔は 360KB フロッピー 2 枚で十分だった。
    • 本当にすごい。以前から、VPN 認証情報を UEFI に保存し、システムがサーバに接続して PXE ネットワークブートできるようにできるのか気になっていた。
      インストールが完全に壊れて正常に起動できないリモートシステムを自動復旧するために、もしかすると安全に許可できるかなり良い方法に見える。
    • 一から作ったGPS 受信機の話のほうがもっと気になる。
  • 本当に良い。多くの人が思っているシステムの下には、想像以上に複雑で強力なソフトウェアが敷かれていることもよく示している。
    オペレーティングシステムがソフトウェアスタックの「最下層」だとよく誤解されるが、実際にはシステムを本当に所有するファームウェア的なコードがある。
    ときには役目を終えて消え、ときにはオペレーティングシステムからさえ透過的に見える状態で、システムの電源が入っている間ずっと残る。
    「単なるデバイス駆動用の低レベルコードで、そこで深刻なことは起きない」という態度があるが、その下に IRC クライアントまで入れられるなら、ほかの悪意あることも十分想像できる。

  • 「なぜ?」だって、いったい「なぜ」が何の質問なんだ? こういう精神を見るために HN に来ている。
    「最も恐ろしい悟りが訪れた。自分のしたことには何の理由もなかった。なぜやったのかは分かっていた。ただ面白そうだったからやったのだ。だが彼らは『いったいなぜこんなことをしたんだ』と聞くだろうし、十分もっともらしい理由がなければ、私を精神病院に放り込むだろうと思った。」— Boyd Rice

  • 自分を低く見る必要はない。ここにはボットネットのコマンド&コントロールクライアントのプロジェクトがある。
    UI はちょっと笑えるけれど。

  • 本当にすごい。UEFI API がこんなに簡単に扱えて、ドキュメントもしっかりしているとは知らなかった。
    開発サイクルがどんなものだったのか気になる。VM で実行していたのだと思うが、クライアントを動かすたびに毎回「起動」しなければならなかったのだろうか。

    • 通常の作業ループは、UEFI アプリケーションを載せて QEMU インスタンスを起動する形だった。
      メインの実行スクリプトが 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...
    • QEMU は UEFI アプリを実行できる。
  • いつか、まだ動き続けている自分の IRC ボット用オペレーティングシステムの作成を終えたい。
    もしかするといちばん役に立たない話かもしれないが、非線形なマウス移動、つまり加速は、新しい OS を起動したら最初にオフにする設定だ。なぜか実際に手が痛くなる。
    たとえば Mac には無料の linearmouse があり、Windows では単に加速を切ればいい。Linux では当然簡単だ。
    マウス加速を使うと、マウスが動いた距離と画面上の移動距離の対応感覚を身につけにくく、長期的には加速なしで使うほうが効率的だと思う。
    ゲーマーたちから学んだやり方で、彼らが今もそうするだけの理由はあると思う。

    • マウス設定を変えていないのでデフォルトが何なのかは知らないが、隠れている画面領域も今でも正確にクリックできる。
      どちらにせよ感覚は慣れるのだと思う。車のアクセルペダルも、普通は速度に直接対応しているわけではないのと同じように。
  • 「なぜ?」と問うなら、UEFI が最初に紹介されたとき、こうした低レベルアプリケーションが約束されていたからだ。
    UEFI を作った側は、一部ベンダーが起動中に特定のキーを押してアクセスできるようにしていた、Linux ベースのインターネット専用ミニ OS まで置き換える夢も見ていた。名前は思い出せない。

    • それは昔の Dell などにあった Quick View / Quick Boot 機能だった。たいていはいくつかの生産性アプリに直接起動していた。
      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 が避けていた難しい部分であるネットワーキングまでやってのけている。

  • 記事の可視化が驚くほど素晴らしく、印象的だ。