AtmOS 技術分析ノート

最終更新: 2026-07-14
統合元: DHCP_BUG_ANALYSIS.md / MULTIPROCESSOR_CACHE_INCOHERENCY_FS_BUG_ANALYSIS.md / TLS_HANDSHAKE_FAILURE_ANALYSIS.md / QEMU_WIFI_BRIDGE_EXPLANATION.md

各バグ・調査事項の根本原因と対策を記録する。解決済みのものは「対策済み」と明記。

注: 本ノート内で net.rs と記載されている箇所は、当時のファイル構成(単一ファイル)での記録です。現在のネットワークスタックは src/kernel/net/mod.rs/arp.rs/dhcp.rs/dns.rs/icmp.rs/tcp.rs/udp.rs/http.rs)に分割されています。該当ロジックは分割後の対応ファイルを参照してください。


1. DHCP Discover 接続フリーズ(解決済み)

現象

OS 起動時に DHCP Discover から先に進まず、再起動すると回復することが多かった。

根本原因

原因①: タイマーオーバーフローによるタイムアウト判定デッドロック

// 旧: 単純比較では u64 オーバーフロー時に判定が破綻する
if DHCP_SERVICE_TICK >= DHCP_ASYNC_DEADLINE_TICK { ... }

SYSTEM_TICKSu64::MAX から 0 へラップした場合、デッドラインが現在より小さくなり、タイムアウトが永久に検知されなくなる。再起動するとタイマーが 0 から始まるため正常動作する確率が高かった。

原因②: リトライ時の XID 重複

リトライ時に同一トランザクション ID (XID) で再送すると、DHCP サーバーが遅延重複パケットとみなして破棄する。

対策(src/net.rs 実装済み)

// ラッピングセーフなタイムアウト判定
if DHCP_ASYNC_DEADLINE_TICK.wrapping_sub(DHCP_SERVICE_TICK) < u64::MAX / 2 {
    return; // デッドラインに達していない
}

// リトライ時は XID を必ず更新
DHCP_XID = DHCP_XID.wrapping_add(1);
DHCP_ASYNC_XID = DHCP_XID;

2. マルチコアキャッシュ不整合による SylFS 破損(解決済み)

現象

起動シーケンス初期(/os/config.json の自動作成時)にカーネルパニックが発生。

panicked at src/fs/block.rs:50:42:
range end index 512 out of range for slice of length 0

根本原因

  1. Core 0fs::mount() を実行し、ヒープに RAMDisk(8MB)を確保
  2. スケジューラのロードバランサが gui_shell プロセスを Core 1 に割り当て
  3. Core 0 が書き込んだヒープデータ(Vec<u8> のポインタ・length・capacity)が Core 1 の L1 キャッシュに同期されず、Core 1 からは length=0 として読み取られた
  4. storage[offset..offset+512] の切り出し時にパニック

対策(src/main.rs / src/scheduler.rs 実装済み)

// gui_shell を Core 0 にピン留め(FS を初期化したコアと同じ)
scheduler::spawn_on_core(gui_shell_process, scheduler::Priority::High, "gui_shell", 0);

spawn_on_core 関数を新設して明示的にターゲットコアを指定できるようにした。FS・GUI・ネットワーク処理がすべて同一コア(Core 0)上で動作するため、キャッシュ不整合を回避。


3. TLS ハンドシェイク失敗(解決済み)

現象

ブラウザ起動時に以下のエラーが発生:

[INFO] TLS handshake error: HandshakeAborted(Fatal, HandshakeFailure)

根本原因

Cargo.tomlembedded-tlsdefault-features = false を指定していたため alloc feature が無効化され、RSA 署名アルゴリズム(RSA-PSS 等)のサポートが無効になっていた。主要 Web サイト(DuckDuckGo 等)は RSA 証明書を使用しているため、ハンドシェイクが HandshakeFailure で中断されていた。

対策(Cargo.toml 修正済み)

embedded-tls = { version = "0.18.0", default-features = false, features = ["alloc"] }

alloc feature を有効化することで RSA 署名の解釈が有効になり、TLS 1.3 ハンドシェイクが正常通過するようになった。


4. RPi3B+ 実機 USB・GPU・WiFi 不具合(2026-06-10 修正)

4-1. USB が有効にならない

根本原因①: USB 電源が Mailbox 経由で ON にされていなかった

RPi3B+ では DWC2 USB コントローラを使う前に、Mailbox プロパティ SET_POWER_STATE(device ID=3, state=3)を呼ぶ必要がある。これがないと USB バスに VBus が供給されない。

根本原因②: HCFG の FSLSPclkSel 値が間違っていた

BCM2837 DWC2 の HCFG.FSLSPclkSel: - 0 = 30/60 MHz(High Speed 内部 PHY) - 1 = 48 MHz(Full Speed) - 2 = 6 MHz(Low Speed)

旧コードでは Full/High Speed に 3(無効値)を設定していた。

対策(src/mailbox.rs, src/usb.rs, src/main.rs 修正済み):

// mailbox.rs に追加
pub fn power_on_usb() -> Result<(), ()> {
    // SET_POWER_STATE tag: device_id=3(USB), state=3(on+wait)
    ...
}

// usb.rs: HCFG clock 値修正
if DEV_SPEED == 2 { hcfg |= 2; } // 6MHz LS
else              { hcfg |= 1; } // 48MHz FS/HS

usb::init() の直前で mailbox::power_on_usb() を呼ぶように main.rs を修正。

4-2. GPU 表示崩れ(赤青反転)

根本原因: ピクセル順序タグが未設定

RPi3 ハードウェアのデフォルトのフレームバッファはBGR(青赤逆)。コードは ARGB32(0xAARRGGBB)で描画しているため赤と青が入れ替わって表示される。

対策(src/mailbox.rs, forRaspPi3B+/config.txt 修正済み):

FrameBufferRequestSET_PIXEL_ORDER タグ(0x00048006, 値 1=RGB)を追加。

// tags 配列に追加(SET_DEPTH の直後)
0x0004_8006, 4, 0, 1,  // SET_PIXEL_ORDER: 1=RGB

get_info() のタグインデックスをアップデート(pointer: [17]→[21], pitch: [22]→[26])。

config.txt に追加:

framebuffer_depth=32
framebuffer_ignore_alpha=1
dtparam=ant2

4-3. WiFi(CYW43455 SDIO)

実装内容:

src/sdio.rssrc/wifi.rs を新規作成。

WiFi を動作させるための追加手順:

  1. RPi OS 等から以下のファームウェアファイルを入手し SylFS の /boot/ に配置する:
  2. forRaspPi3B+/sudo make を実行して SD カードイメージを更新する

現状と今後:

SDIO 初期化・ファームウェアアップロード・WPA2-PSK 接続シーケンス(wifi::connect():CDC ioctl / iovar による sup_wpa/sup_wpa2_eapver 等の設定 + WSEC_AES + open auth でファームウェア内 WPA2 を 駆動)まで実装済み。残りは実機での疎通確認(QEMU には CYW43455 が無いため自己テスト不可)。 TODO.md の「実機対応(RPi3B+)P0 — WiFi 完全動作」で管理。


5. QEMU WiFi ブリッジ接続の制限

問題

macOS の CLI QEMU(Homebrew)で vmnet-bridged + Wi-Fi(en0)を使うと、ホストと同じネットワークセグメントの IP が取得できず 192.168.2.x(vmnet-shared)にフォールバックする。

根本原因

IEEE 802.11 の制限: Wi-Fi の 3 アドレスフレーム仕様により、1 つの Association に使えるMAC アドレスは 1 つのみ。仮想マシンが独自 MAC でパケットを送信しても AP がそれを破棄する。

UTM/VMWare がブリッジ接続できる理由は「MAC NAT(Proxy ARP)」を内部で実行しているため: - UTM: Virtualization.frameworkcom.apple.vm.networking 特権を利用 - VMWare: 独自 KEXT と特権ヘルパーで L2 パケットをフック

CLI QEMU はこれらの特権を持たないため Wi-Fi ブリッジが動作しない。

解決策

推奨: 有線 LAN アダプタを使用

# Makefile の ifname を有線 LAN インターフェース名に変更
-netdev vmnet-bridged,id=net0,ifname=en8  # en8 は例

代替 A: UTM から kernel8.img をカーネルブート指定して起動 - UTM 設定: Architecture=ARM64, System=raspi3b, Network=Bridged(en0), Card=usb-net - QEMU 引数: -device usb-hub -device usb-kbd -device usb-tablet -device usb-net,netdev=net0

代替 B: ポートフォワーディング(vmnet-shared のまま)

-netdev user,id=net0,hostfwd=tcp::8080-:80 -device usb-net,netdev=net0

ホストの 192.168.x.x:8080 が AtmOS の 80 番ポートに転送される。


6. UTM 起動時「SPICE 内部エラー」トラブルシューティング

QEMU が起動直後にクラッシュし画面接続が切れる現象。raspi3b は PCI バスを持たないため、UTM がデフォルトで追加する PCI 周辺機器が原因で即死する。

チェックリスト(すべてオフにする):

原因特定: 設定の「QEMU」タブで「デバッグログを有効化」→ 起動→「ログを書き出し」でエラーメッセージを確認。No 'PCI' bus found のようなメッセージが出ていれば PCI デバイスが残っている。


QEMU usb-net 受信不能問題の全面解決(2026-06-11)

「ブラウザが http request failed」の真因は多層に渡る受信系の不具合だった。 QEMU 自動操作(モニタ sendkey + screendump)と filter-dump pcap、QEMU ソース読解で各層を実証しながら修正。

修正の連鎖(すべて実画面で検証済み)

  1. TCP 送信漏れ×3(net.rs・リファクタ起因): SYN / データ / ACK が build_tcp_ipv4 後に transmit_nic_ipv4_packet を呼ばず未送信だった。
  2. RNDIS 誤認(usb.rs): QEMU usb-net の構成は bConfigurationValue=2 が RNDIS (制御IF class 02/sub 02/protocol 0xFF)。class 0xE0 のみ RNDIS 判定していたため CDC-ECM と誤認 → INITIALIZE 未送信 → デバイス側 RNDIS ゲートで受信永久 NAK。 protocol 0xFF も RNDIS と判定し、既存の INITIALIZE/SET_FILTER シーケンスを有効化。
  3. SET_CONFIGURATION ハードコード(usb.rs): 値 1 固定 → 記述子の bConfigurationValue を使用。
  4. bulk IN の運用方式(usb.rs): QEMU DWC2 モデルは bulk NAK 時に ChEna を維持し 内部 4kHz タイマ+wakeup で自動再試行する。旧実装の「スピン待ち→タイムアウトで強制 ChDis」は 保留パケットを毎回キャンセルし完了を取り逃がしていた。 → IN は張りっぱなし(async)にし、後続ポールで完了のみ収穫する方式へ全面変更。 RX 専用 DMA バッファ(RX_DMA_BUF)も分離(TX と共用だと保留中 DMA と衝突)。
  5. TLS 読みの EOF 誤判定(tls.rs): tcp_recv_real(...,1) の即時タイムアウト Err を EOF(Ok(0)) と誤解釈 → 応答到着前の読みで body 空。未着と切断を区別するよう修正。
  6. 受信ウィンドウ 8192→2880(net.rs): QEMU usb-net はデバイス内 1 フレームしか バッファできず、バースト送信の大半が破棄され TCP が再送頼みで低速化(5s read timeout 超過)。 ウィンドウを 2×MSS に絞り破棄ゼロの ACK 駆動フローに。1 ポール最大 8 フレーム収穫も追加。

結果

QEMU(SLIRP) で DHCP bound → DNS 解決 → TCP established → TLS1.3 handshake → HTTPS GET → body 1863B 取得 → lite.duckduckgo.com の実ページ(ロゴ・検索ボックス・ Search ボタン)が正しくレンダリングされることをスクリーンショットで確認。

関連: CSS 継承のテキストノード伝搬修正(css.rs/layout.rs、h1 折返し崩れの解消)も同日実施。

追記: 検索時フリーズ(ヒープ破損)の修正(2026-06-11)

症状: DuckDuckGo 検索実行→大きな chunked 応答受信中に [allocator] CORRUPTION (free_space): bad canary ... overflow from the preceding allocation → OOM → プロセスパニック。

真因: parse_tcp_ipv4_packet(net.rs)が NET_LOCK を取らずに TCP_SOCKETS[idx].clone() → 更新 → TCP_SOCKETS[idx] = s の書き戻しをしていた。 受信側(Core1: USB poll → ingest → parse_tcp)と読み出し側 (browser_loader: tcp_recv_real、NET_LOCK 使用)が並走すると、 rx_buffer: Vec<u8> の clone/take/書き戻しが競合し、同一ヒープ領域の 二重解放・解放済み書き込みが発生してアロケータの canary を破壊する。 検索で顕在化したのは、応答が大きく受信と読み出しの並走頻度が高いため。

修正: parse_tcp_ipv4_packet のソケット参照〜書き戻し全体を NET_LOCK で保護。 tcp_recv_real は NET_LOCK 保持中に USB_LOCK を取らないため ABBA デッドロックは発生しない。 (UDP 側の UDP_RX_QUEUE は固定長配列でヒープを持たないため対象外)


7. ブラウザ OOM カーネルパニックとヒープ破壊(2026-06-12〜13、根治済み)

現象

12KB 程度のページ読込中に === ALLOCATOR OOM DUMP ===(512MB ヒープなのに空き 1136 バイト) → クリティカルプロセス gui_shell の alloc 失敗 → カーネルパニック。

連鎖の構造

  1. 何らかの確保がデータ境界を超えて隣接フリーブロックヘッダの canary を破壊
  2. アロケータが破損検出でフリーリストを切り詰め → 破損点以降の約 480MB を見失う
  3. 以後の確保が OOM → Rust の handle_alloc_error は即 abort のため復旧不能 → パニック

対策(封じ込め: 実装済み)

発生源(2系統とも特定・修正済み)

  1. TCP 受信競合(§6 追記、2026-06-11 修正済み): parse_tcp_ipv4_packet の NET_LOCK 欠落
  2. EL0 例外復帰のスタックリーク(2026-06-13 根治、§10 参照): vector.Sel0_exception_returnsub sp,#800 に対し add sp,#288 しか戻さず、 EL0 例外のたびにカーネルスタックが 512 バイトずつ降下し隣接ヒープを破壊していた。

allocator::check_heap_integrity() は今後の早期検知のため常設。


8. レイアウトエンジン inline 配置バグ — textarea が画面外に飛ぶ(2026-06-13 解決済み)

現象

Note アプリで textarea を width/height: 100% 化したところ、表示されない・クリック できない・文字入力できない。旧来の「テキストエリア上部の謎の空白地帯」も同根。

根本原因

レイアウトエンジンには「包含ブロックの content.height = Y フローカーソル」という規約があり、 layout_inline は自身を cb.y + cb.height に配置する。ところが layout_block の inline 子 処理が子自身の確定済み寸法(height=450 等)をそのまま包含ブロックとして渡していたため、 子が「自分の高さぶん下」に再配置されていた。 - 旧: textarea 200px 固定 → y=200 にずれて表示(= 謎の空白の正体。見えていたので入力は可能) - 新: 100% 化で高さ 450 → y=450 → 完全に画面外 → ヒットテスト不能 = 入力不能

対策(layout.rs 実装済み)

inline 子の再帰レイアウトに height=0 の cb を渡し、呼び出し後に親が確定したスロット寸法を 最終値として再設定する(DESIGN.md「レイアウトエンジンの座標規約」参照)。 QEMU で textarea のウィンドウ全面フィット・クリックフォーカス・文字入力を自動操作で検証済み。


9. QEMU 自動操作による回帰テスト手法(2026-06-13 確立)

現状(追記): その後シリアル (UART) 入力をキーボード入力パイプラインへ接続したため、 現在の CI 回帰テストは expect test_browser.sh-serial stdio 経由の無人操作)が主手法。 本節はシリアル未接続時代に確立した QMP ベースの代替手法の記録(GUI をピクセル単位で 検証したい場合などに有効)。

(当時)シリアルコンソール未接続でも、QEMU のモニタ/QMP でログイン〜GUI 操作まで完全自動化できる。

構成

qemu-system-aarch64 ... -display none \
  -monitor unix:/tmp/amon,server,nowait \   # HMP (sendkey / screendump)
  -qmp     unix:/tmp/aqmp,server,nowait \   # QMP (マウス絶対座標)
  -netdev user,id=net0 -device usb-net,...  # sudo 不要・DHCP/外部 HTTPS 可
  -serial file:/tmp/serial.log

操作レシピ

操作 方法
キー入力 HMP sendkey a / sendkey ret(ログイン・ターミナルコマンドに使用)
マウス移動 QMP input-send-eventabs イベント。座標は 0〜32767 を画面解像度にスケール(例: x=450/1280 → 11519)
クリック QMP btn イベント down→up
画面確認 HMP screendump file.ppmsips -s format png で変換して目視/比較

落とし穴

10. EL0 例外復帰のカーネルスタックリーク(2026-06-13 根治済み)

§7 のヒープ破壊の真因。vector.S の EL0 同期/IRQ 例外ハンドラの復帰共通処理 el0_exception_return にあった非対称なスタック操作。

根本原因

EL0 例外(SVC システムコール・タイマ IRQ)のたびに SP_EL1(カーネルスタック)が 512 バイトずつ降下し続け、最終的に kernel_stack バッファ(64KB)の下限を突き破って 隣接ヒープブロックのヘッダを破壊していた。退避したレジスタ値の一部がヘッダの size フィールド位置に書かれ「canary 無傷・size ゼロ化」の特徴的パターンを生んでいた。 加えて SIMD レジスタ q0-q31 の復元も欠落しており、EL0 の FP 状態が SVC/IRQ を またいで破壊されていた(潜在バグ)。

対策

el0_exception_return を EL1 IRQ パス(正しい実装、add sp, sp, #800 + q 復元)と 同一に修正: - q0-q31 の復元 (ldp q0,q1,[sp,#288]ldp q30,q31,[sp,#768]) を追加 - add sp, sp, #288add sp, sp, #800(入口と対称)

検証

以前は起動時 100% 再現(EL0 テストプロセス user_test_abort 終了直後に allocator::check_heap_integrity() が必ず破損検出)していたが、修正後は SVC/abort 実行後・延長ランとも破損 0 件。vector.S の他経路(EL1 IRQ)は元から 800/800 で正常、 影響は EL0 復帰のみ。

教訓: 例外ハンドラの sub/add sp は必ず対称に。退避レジスタ群を増減したら 入口と復帰の両方を同期させる(フレームサイズをアセンブラ定数化するのが望ましい)。


11. DuckDuckGo 検索が検索ページに戻る(2026-06-13 解決済み)

ページ内フォームで検索すると、結果が出ず検索ページに戻る不具合。複数の根本原因が重なっていた。

原因と修正

  1. HTTPS POST 未実装(最大要因): net_stack::http_postis_https に関わらず 常に平文 HTTP (port 80) の http_post_real を呼んでいた。DDG は HTTPS フォーム (<form action="/lite/" method="post">) のため、POST ボディが TLS で届かず検索ページが返っていた。 → tls::https_post を実装し、net_stack::web_post(is_https, ...) で TLS/HTTP を振り分け。 browser の async ローダも web_post を使うよう変更。
  2. TLS 受信バッファ不足(決定的): tls.rsread_buf = 16384 が、DDG の結果ページ (~24KB) が送る最大 TLS レコード (16384 + 256 = 16640 バイト) を収容できず embedded_tlsInsufficientSpace を返し 0 バイト応答になっていた。 GET の検索ページ (~4.5KB) は小レコードで収まるため動作し、POST/大応答だけ失敗していた。 → read_buf を 18432 に拡大(https_get / https_post / https_get_binary の 3 箇所)。 pcap で「サーバは 29KB 応答済み・TCP ACK 済みなのに TLS が 0 バイト」を観測して特定。
  3. TcpIoWrapper 読みタイムアウト 5 秒: 大応答は小受信ウィンドウ (2880) の多数往復で 到着が遅延バーストになり 5 秒境界で取りこぼすことがあった → 15 秒に延長。
  4. on_mouse の二重送信: フォーム送信後も同一クリックでリンク判定 → navigate_to_href が 送信を上書きする経路があった → 送信したら return で打ち切り。
  5. autofocus 未対応: autofocus 属性のある input をロード時にフォーカス(DDG 検索ボックス等)。 実装ついでに追加(UX 改善 + シリアル自動テストでクリック不要に)。

検証

QEMU で browser 起動 → 検索ボックス(autofocus 済み)に rust 入力 → Enter → POST /lite/ → 24630 バイト受信 → 467 要素のレンダリング(検索結果)をスクリーンショットで確認。


ブラウザ画像対応: GIF デコーダ追加(2026-06-11)

web_engine.rsdecode_image に GIF(87a/89a) デコーダを追加。 - LZW 可変長コード復号(自前実装。clear/end コード・辞書成長・KwKwK ケース対応) - グローバル/ローカルカラーテーブル、透過色(Graphic Control Extension)、4パスインターレース対応 - アニメーションは先頭フレームのみ描画(静止画扱い) - 検証: LZW エンコーダとの往復テスト5ケース + 手組み 4x4 GIF89a のフルデコード (パレット・透過・インデックス展開)をホスト rustc で確認しパス。

対応画像形式: PNG / JPEG / BMP(24/32bit) / GIF 。 未対応: WebP(VP8/VP8L はデコーダが重く保留)・フルSVG画像(img)・アニメーション。


ブラウザ画像対応: WebP (lossy VP8 + lossless VP8L) 追加(2026-06-11)

image-webp 0.2.4 を src/os_lib/webp/ に vendoring し no_std 化。 - 改変: std::io(Read/BufRead/Seek/Cursor) と byteorder_lite を自前スライス IO シム (webp/io.rs) に置換、HashMapBTreeMapquick_error!→手書き enum、encoder.rs 除外。 アルゴリズム本体(vp8.rs 2897行・lossless.rs・loop_filter・yuv 等)は無改変。 - decode_image で RIFF….WEBP を検出し webp::decode_webp を呼ぶ。 - 検証(ホスト rustc, 公式 image-webp と照合): - lossless(VP8L) 8x8 RGBA: RGB 誤差 0・α 完全一致 - lossy(VP8) 550x368 実写真: 公式デコーダと 全バイト一致 (max error 0)

対応画像形式: PNG / JPEG / GIF / BMP / WebP(lossy+lossless)


ブラウザ画像対応: フル SVG 画像 () 追加(2026-06-11)

os_lib/svg.rs を拡張(従来は単色アイコンの単一パス→アルファマスク専用だった)。 - decode_svg_image(svg, w, h): 複数図形を fill 色つきで RGBA に合成。 対応要素: path(M/L/H/V/C/Z 相対絶対)・rect・circle・ellipse・line・polygon・polyline。 - fill は属性 / style="fill:..." / fill-opacity を解釈し、css::parse_color(名前/HEX/rgb) で解決。 fill="none" は透明、無指定は SVG 既定の黒。 - viewBox(x y w h) でアスペクト維持スケール、even-odd + 2x2 サンプリング、src-over αブレンド。 - decode_image が先頭バイトで SVG(テキスト)を判定し、viewBox 自然サイズ(上限512)でラスタライズ。 描画側の最近傍スケーラで el サイズへ再拡縮。 - 検証(ホスト rustc): rect/rect/circle 重ね + 透過で各ピクセル期待色一致。

対応画像形式: PNG / JPEG / GIF / BMP / WebP(lossy+lossless) / SVG 。 未対応: SVG の stroke のみ図形・gradient・transform・arc(A) コマンド・テキスト要素。


ブラウザ/端末 改善 3点(2026-06-11)

  1. 画面の波打ち(ブラウザ使用中)= 再描画ストームの解消: needs_timer_redraw が「画像ロード待ちの間ずっと true」で全デスクトップを毎フレーム clear+flush していた。image_work_pending(待機中ずっと)→image_results_ready (デコード完了結果がある時だけ)に変更し、完了ごとに1回だけ再描画するエッジトリガに。 ※ 当時の根本原因はフレームバッファ直書きが垂直帰線と同期していなかったこと (VSync 同期ページフリップ未実装)。追記(解決済み): その後 virtual_height×2 + SET_VIRTUAL_OFFSET によるダブルバッファページフリップ (kernel/draw.rsflush_dirty_pageflip/set_virtual_offset)を実装済み (spec/walkthrough.md 参照。本セッションの QEMU 起動ログでも [FB] VSync page-flip ENABLED (double buffer / 2 pages confirmed) を確認)。

  2. ネット画像が出ない = バイナリ chunked 未デコード: https_get_binary/http_get_binary が Transfer-Encoding: chunked を解さず、 ボディにチャンクサイズ行が混入して画像デコード失敗。バイナリ安全な extract_http_body_binary(net.rs) を追加し両経路に適用。gstatic の PNG 取得・表示を確認。

  3. タブのスレッド安全性 = ロード状態をタブ毎に分離: グローバル単一スロット ASYNC_LOAD: Option<..>AsyncLoadState{ requests: Vec, thread_active } に変更。各 WebEngine に一意 engine_id を付与し、リクエストに owner を持たせて 「自分のタブの完了結果だけ消費」。同一タブの連続ナビは旧リクエストを置換。 ローダースレッドは停止時のみ spawn(多重起動防止)。タブ切替時の結果混入を解消。

端末


VSync ページフリップ実装(ティアリング解消)(2026-06-11)

フレームバッファ直書きを VBlank と同期させ、ティアリング(画面の裂け/波打ち)を解消。

仕組み

検証

QEMU(raspi3b)で virtual_height×2 確保成功・パニックゼロ・表示崩れ無しを確認。 端末コマンド実行(再描画→フリップ)後も画面整合。実機 RPi3B+ でも同経路で動作する想定。

トレードオフ

更新ごとに全画面 DMA コピー(1280x720=約3.7MB)になるが、DMA 帯域で数 ms、 かつ変更時のみ flush するため実用上問題ない。部分更新最適化は vsync と両立しないため撤廃。


12. カーネル肥大で露呈した 2 件のカーネルバグ(2026-06-15 解決済み)

現象

JavaScript エンジン追加でカーネルバイナリが 24.4MB→26.5MB に肥大した直後から、起動後半 (user_test_abort という意図的 EL0 Data Abort のデバッグプロセスの直後)に KERNEL PANIC(EL1 Synchronous Exception / 命令アボート)。フォルトアドレスが起動ごとに 別の関数(scheduler::exit / spawn_with_arg / memcpy / zune_png 等)を指す非決定的挙動。

切り分け

根本原因と対策(2 件)

  1. kernel .text が 2MB 境界を越えた(mmu.rs: ユーザープロセスのページテーブル (create_page_table_internal)は block0(0〜2MB)のみ EL1 専用(AP=0b00)にし、block1 以降は EL0 アクセス可(AP=0b01)としていた。これは「カーネルは最初の 2MB に収まる」前提。JS エンジンで .text が 2MB を越えて block1(2〜4MB)にコードを置いたため、block1 のカーネルコードを EL1 で 実行すると命令アボートになった(block0 を EL1 専用にした設計意図そのものの不具合)。 対策: .user_text(0x400000) より下の kernel .text 領域(block0,1)を EL1 専用にマップ。 → 制約: 以後 kernel .text は 4MB(block0+1)未満に収める必要がある(越える場合は本マッピングの 拡張か .user_text の再配置が必要)。
  2. scheduler::exit() の use-after-free(scheduler.rs: Process は自分の kernel_stack / stack_memVec<u8>)を所有する。EL0 トラップ時はその kernel_stack(SP_EL1) 上で例外ハンドラ→exit() が動くのに、exit()rq.processes.remove(idx)実行中の自分自身を その場 drop=今立っているスタックを解放していた。配置次第で解放ブロックが再利用され破綻。 対策: 終了プロセスを「墓場 (ZOMBIES)」へ退避してドロップを遅延し、別プロセスへ文脈切替して そのスタック上に居なくなってから reap_zombies()(現在 SP を含むスタックは保持)で解放。

検証

QEMU スモークテストで KERNEL PANIC 0・JS_SELFTEST: PASSLoad page SUCCESSTEST_PASSED)。


13. SylFS 堅牢性バグ 3 件(2026-06-16 解決済み)

FS の「COW + 二重化 + B-Tree」の整合性検証で発見・修正。回帰防止に fs::selftest() (起動時 FS_SELFTEST: PASS 6/6、test_browser.sh の CI ガード)を新設した。

13-1. COW B-Tree: 分離キーの上書きで部分木を喪失(最重要)

現象/原因: btree.rs::insert_recursive の「キー一致=値を上書き」処理が葉/内部ノードを 区別せず実行されていた。B+Tree では葉分割時に分離キー(split_key = node.keys[mid])が 内部ノードへ昇格しつつ右葉にも残る。そのため分離キーになったファイルを上書き保存すると、 内部ノードの一致で values[idx](子ノードのセクタポインタ)をメタデータセクタ番号で上書きし、 右部分木を丸ごと喪失していた(= そのキー以降のファイルがインデックスから消える)。24 ファイル 超で分割が起きた後、分離キーに該当するファイル(例: 再取得されるブラウザキャッシュ)の上書きで発現。

対策: 内部ノードで一致した場合は search と同様に右の子へ降下(idx += 1; break)し、 上書きは葉ノード(node_type == 0)のときだけ行う。

13-2. read_file のメタデータ無境界添字(パニック)

mod.rs::read_filewhile total_read < meta.size { let sec = meta.data_sectors[i]; i += 1; }meta.data_sectors[u32; 44])を境界チェックなしで添字していた。sizedata_sectors が不整合だと領域外アクセスでパニックしうる。&& i < MAX_FILE_SECTORS を追加。

13-3. CRC が 0 の正規データを破損誤判定(block 層)

block.rs::read_sector の健全判定が crc_stored == crc_calc && crc_stored != 0 で、CRC32 が 偶然 0 になる正規データを破損扱いしていた。未書き込み(全ゼロ)セクタは crc_stored=0 に対し crc_calc=crc32(508 個のゼロ)≠0 で不一致となり弾かれるため != 0 は不要。条件を crc_stored == crc_calc のみにした。

補足(設計メモ)

現行の mount()RAMDisk を毎起動生成するため SylFS は揮発(再起動で再フォーマット)。 このため COW の「電源断で最後の整合状態へ戻れる」保証は永続バックエンド導入時に効く設計上の性質で、 現状の RAM 構成では検証対象外。また上書き保存は旧データ/メタデータセクタを解放しないため、 永続化時はセクタリークの回収(コミット後の旧セクタ deallocate)が別途必要。


14. Aura 評価機における AST::List 評価バグ(2026-06-17 解決済み)

現象

起動時の expect テスト実行中(特に IME や JS などのテスト)や、端末から Aura スクリプトを実行した際、評価が途中でタイムアウトするか、意図通りに実行されずにリストリテラルがそのまま出力されてしまう。

根本原因

src/os_lib/aura/eval.rsAST::List 評価ロジック内に、不完全な早期リターンコードや、波カッコ { の閉じ忘れ(unclosed delimiter)などの不正なコードが混入していた。これにより、os.run などの組み込み関数呼び出しを含む AST リストが正しく解釈されず、実行処理が途中で打ち切られていた。

対策

eval.rs における AST::List の評価シーケンスをクリーンアップし、混入していたデッドコードおよび閉じ括弧のない不正な構文を削除。これにより、Aura インタプリタが正しく AST をトラバースし、すべてのビルトイン関数やリスト呼び出しが正常に評価・実行されるように復元した。


15. 本物YouTube動画視聴機能の実装と同期設計(2026-06-17 解決済み)

現象・課題

外部の仲介プロキシを使用せず、ベアメタル環境で YouTube の動画ストリームを HTTPS で直接取得・デコードし、映像と音声を遅延なく同期再生する必要がある。また、「HDMIからも音声が出力されること」という要件を満たす必要がある。これには以下の技術的課題が存在した: 1. 描画同期: バックグラウンドスレッドで直接スクリーンに描画すると、ウィンドウの移動、クリッピング境界の制御、他プロセスとの重ね合わせ描画が競合し、画面表示が崩れる。 2. デュアルオーディオ同期: PWM(アナログイヤホンジャック用)と I2S(HDMIデジタルオーディオ用)は異なるコントローラとDMAチャンネルを必要とし、転送のタイムラグによるズレが懸念された。

対策

  1. WebEngine描画同期 (映像)
  2. HDMI / PWM デュアルDMA同期転送 (音声)
  3. ポートパース

16. 起動オプション、DNSフォールバック拡張および Clippy 警告/エラーの解決(2026-06-21 解決済み)

現象・課題

GitHub Actions による CI(cargo clippy --release および cargo check / cargo doc)実行時に、いくつかの箇所でコンパイルエラーや Clippy 指摘(clippy::string_slice および clippy::unwrap_used 違反)が発生し、ビルドおよびドキュメント生成が失敗する問題が存在した。

また、ウィンドウ起動時の挙動(サイズや状態指定の制限)や ping コマンドでホスト名(ドメイン名)が指定できないなどの利便性・仕様上の課題、さらにブラウザで特定の youtube 文字列を含むURLが強制的にローカルのデコードテストURLにリダイレクトされてしまう不具合が存在した。

対策と修正内容

  1. Clippy 警告/エラーの完全解消
  2. アプリ起動時のウィンドウ状態制御とサイズ指定
  3. ping 組み込み関数の DNS 解決フォールバック
  4. ブラウザにおける YouTube リダイレクトの解除

17. JSエンジン: 循環参照スタックオーバーフローの再発パターン(横断監査の指針、2026-07-13 時点で5箇所修正済み)

現象・課題

JS の値をオブジェクトグラフに沿って再帰的に処理する関数(シリアライズ・ 表示整形・文字列化等)で、循環参照(var a={}; a.self=a;)に対する祖先追跡 ガードが欠けていると、無限再帰に陥る。JS インタプリタ本体の評価ループには max_steps(ステップ数上限)/max_depth(再帰深度上限)という安全弁が 既にあるが、これらのガードは JS の評価ステップをカウントするものであり、 Rust ネイティブの関数呼び出しスタックを直接消費する再帰(Value の メソッドとして実装された変換関数等)までは保護しない。この no_std カーネル環境では Rust のスタックオーバーフローは(V8 のような RangeError への変換機構が無いため)そのままクラッシュ/ハングに直結し得る。

これまでに発見・修正した箇所(同型バグ、都度個別発見)

修正パターン(共通)

呼び出し元から見える公開シグネチャは変えず、内部実装を「訪問済みオブジェクト 参照(Rc::ptr_eq で比較する ObjRef のリスト)を引き回す」バージョンへ 分離し、オブジェクト種別ごとの再帰の入口で「既に祖先に同じオブジェクトが いれば打ち切る」チェックを入れる。打ち切り時の具体的な値(空文字列/ "[Circular]"/TypeError 等)は呼び出し元の意味論に合わせて選ぶ。

横断監査で今後チェックすべき候補

Value 側で他にオブジェクトグラフを再帰的に辿るメソッド・関数(例: Object.prototype.toString.call 相当の型判定、独自の deep-equal 実装があれば そこ、console.table/console.dir 相当の表示整形があればそこ)が新設・ 発見された場合は、同じ「祖先追跡」パターンを最初から組み込むこと。

2026-07-13 の横展開点検で Object.prototype.toString.call(型タグ判定、 ObjKind を見るだけで再帰なし)・JSON.stringifyreplacer/reviver フック・固定 depth の Array.prototype.flatMap(常に1段展開のみで Infinity を受け付けないため無関係)を確認し、いずれも再帰なし/既に 安全と判断した。

関連するが別種の未対応リスク(対象外・今後の検討候補)

上記の「祖先追跡」修正は循環参照のみを検知する。循環していない 極端に深いネスト(例: for ループで10万階層ネストした配列を作り structuredClone/to_js_string/JSON.stringify に渡す)に対しては、 seen に同じオブジェクトが現れないため無防備なままで、依然として Rust ネイティブ再帰でのスタックオーバーフローが起こり得る。JS インタプリタの max_depth は評価ステップの再帰深度を制限するが、 「ループで深い構造を組み立ててから1回のネイティブ関数呼び出しに渡す」 という経路はこのガードの対象外(構築自体は深い再帰を伴わない)。 根治するには対象の各関数を再帰から明示スタックを使う反復アルゴリズムへ 書き換える必要があり、循環参照対策より改修規模が大きいため、本キャンペーン では着手を見送り別タスク候補として記録するに留める。