最終更新: 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)に分割されています。該当ロジックは分割後の対応ファイルを参照してください。
OS 起動時に DHCP Discover から先に進まず、再起動すると回復することが多かった。
原因①: タイマーオーバーフローによるタイムアウト判定デッドロック
// 旧: 単純比較では u64 オーバーフロー時に判定が破綻する
if DHCP_SERVICE_TICK >= DHCP_ASYNC_DEADLINE_TICK { ... }SYSTEM_TICKS が u64::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;起動シーケンス初期(/os/config.json
の自動作成時)にカーネルパニックが発生。
panicked at src/fs/block.rs:50:42:
range end index 512 out of range for slice of length 0
fs::mount()
を実行し、ヒープに RAMDisk(8MB)を確保gui_shell プロセスを
Core 1 に割り当てVec<u8>
のポインタ・length・capacity)が Core 1 の L1
キャッシュに同期されず、Core 1 からは length=0
として読み取られた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)上で動作するため、キャッシュ不整合を回避。
ブラウザ起動時に以下のエラーが発生:
[INFO] TLS handshake error: HandshakeAborted(Fatal, HandshakeFailure)
Cargo.toml で embedded-tls に
default-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
ハンドシェイクが正常通過するようになった。
根本原因①: 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/HSusb::init() の直前で
mailbox::power_on_usb() を呼ぶように main.rs
を修正。
根本原因: ピクセル順序タグが未設定
RPi3 ハードウェアのデフォルトのフレームバッファはBGR(青赤逆)。コードは ARGB32(0xAARRGGBB)で描画しているため赤と青が入れ替わって表示される。
対策(src/mailbox.rs,
forRaspPi3B+/config.txt 修正済み):
FrameBufferRequest に SET_PIXEL_ORDER
タグ(0x00048006, 値 1=RGB)を追加。
// tags 配列に追加(SET_DEPTH の直後)
0x0004_8006, 4, 0, 1, // SET_PIXEL_ORDER: 1=RGBget_info() のタグインデックスをアップデート(pointer:
[17]→[21], pitch: [22]→[26])。
config.txt に追加:
framebuffer_depth=32
framebuffer_ignore_alpha=1
dtparam=ant2
実装内容:
src/sdio.rs と src/wifi.rs を新規作成。
WiFi を動作させるための追加手順:
/boot/ に配置する:
/boot/brcmfmac43455-sdio.bin(約 380KB)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 完全動作」で管理。
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.framework の
com.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
番ポートに転送される。
QEMU
が起動直後にクラッシュし画面接続が切れる現象。raspi3b は
PCI バスを持たないため、UTM がデフォルトで追加する PCI
周辺機器が原因で即死する。
チェックリスト(すべてオフにする):
Default / SPICE
ツールを オフKernel(kernel8.img)以外のドライブを全削除原因特定: 設定の「QEMU」タブで「デバッグログを有効化」→
起動→「ログを書き出し」でエラーメッセージを確認。No 'PCI' bus found
のようなメッセージが出ていれば PCI デバイスが残っている。
「ブラウザが http request failed」の真因は多層に渡る受信系の不具合だった。 QEMU 自動操作(モニタ sendkey + screendump)と filter-dump pcap、QEMU ソース読解で各層を実証しながら修正。
build_tcp_ipv4 後に
transmit_nic_ipv4_packet を呼ばず未送信だった。tcp_recv_real(...,1) の即時タイムアウト Err を EOF(Ok(0))
と誤解釈 → 応答到着前の読みで body
空。未着と切断を区別するよう修正。QEMU(SLIRP) で DHCP bound → DNS 解決 → TCP established → TLS1.3 handshake → HTTPS GET → body 1863B 取得 → lite.duckduckgo.com の実ページ(ロゴ・検索ボックス・ Search ボタン)が正しくレンダリングされることをスクリーンショットで確認。
関連: CSS 継承のテキストノード伝搬修正(css.rs/layout.rs、h1 折返し崩れの解消)も同日実施。
症状: 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
は固定長配列でヒープを持たないため対象外)
12KB 程度のページ読込中に
=== ALLOCATOR OOM DUMP ===(512MB ヒープなのに空き 1136
バイト) → クリティカルプロセス gui_shell の alloc 失敗 →
カーネルパニック。
handle_alloc_error は即
abort のため復旧不能 → パニックRecoverableMutex にも対応。allocator::check_heap_integrity():
旧方式との互換スタブに移行(現在はTLSFアロケータの独自境界タグ管理へ一元化)。parse_tcp_ipv4_packet の NET_LOCK 欠落vector.S の el0_exception_return が
sub sp,#800 に対し add sp,#288 しか戻さず、
EL0 例外のたびにカーネルスタックが 512
バイトずつ降下し隣接ヒープを破壊していた。allocator::check_heap_integrity()
は今後の早期検知のため常設。
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 → 完全に画面外 → ヒットテスト不能 =
入力不能
inline 子の再帰レイアウトに height=0 の cb を渡し、呼び出し後に親が確定したスロット寸法を 最終値として再設定する(DESIGN.md「レイアウトエンジンの座標規約」参照)。 QEMU で textarea のウィンドウ全面フィット・クリックフォーカス・文字入力を自動操作で検証済み。
現状(追記): その後シリアル (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-event の abs
イベント。座標は 0〜32767 を画面解像度にスケール(例:
x=450/1280 → 11519) |
| クリック | QMP btn イベント down→up |
| 画面確認 | HMP screendump file.ppm →
sips -s format png で変換して目視/比較 |
mouse_move は使わない:
絶対座標デバイス (usb-tablet) に対しても引数を相対移動として
扱うためカーソルが (0,0) に飛ぶ。マウスは必ず QMP
input-send-event を使うRender 5)→
パスワード設定 → note + Enter → QMP クリックでフォーカス →
sendkey で入力 → screendump で画面検証cargo build/ make all
が並行して走る git 操作(GitHub Desktop の自動 pull
等)とファイルロックで 競合すると、ソース変更が反映されない古い
kernel8.img のまま QEMU 起動を検証してしまい、
実際には既に直っているはずの自己テストが FAIL
に見えることがある(CSS_SELFTEST/ JS_SELFTEST
の双方で発生を確認)。切り分け方: 同一の
cargo build --release を
再実行して「変更なしで再ビルドが成功する」ことを確認しても解決しない場合、git diff
で疑わしいファイルの実差分を確認し、次に
cargo build --release → make all を
もう一度フルで(キャッシュに頼らず)流し直してから再起動する。原因不明な
FAIL が 出た際は、まず一時的な crate::println!
デバッグ出力で実際の値を可視化してから
コードを疑うと切り分けが速い(本セッションでの実例は walkthrough.md
2026-07-10 の 複数エントリ参照)。timeout 30 qemu-system-aarch64 ...
の起動確認が host 負荷次第で毎回結果が 変わる(2026-07-12
発見): 同一バイナリ・同一コマンドを連続実行しても、(1)
2. Memory System 直後で出力が止まる、(2)
自己テストは完走するが起動シーケンス後半 (USB
初期化等)まで到達しない、(3)
AtmOS OS Base Phase 3 running! まで完全に
起動する、という3通りの異なる結果が host
の負荷状況次第で出ることを確認した (詳細は walkthrough.md
2026-07-12「QEMU 起動ハングは誤検知」エントリ参照)。
教訓: timeout 30
内に自己テスト結果行(JS_SELFTEST: PASS 等)が出ずに
exit 124
になっても、それだけでは「決定論的なハングバグ」と断定できない —
同一の固定コマンドを2〜3回連続で再実行し、結果が実行ごとに変わるかどうかを
まず確認すること。結果が変わるなら host
タイミング変動(起動が単に遅い)であり、
コード側の実バグを疑う前に切り分けが済む。逆に何度実行しても同じ箇所で毎回
止まるなら実バグの可能性が高い。§7 のヒープ破壊の真因。vector.S の EL0 同期/IRQ
例外ハンドラの復帰共通処理 el0_exception_return
にあった非対称なスタック操作。
sub sp, sp, #800(GP レジスタ + ELR/SPSR + SIMD
q0-q31 の退避領域)add sp, sp, #288 ← 800 戻すべきところ 288
しか戻していなかった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, #288 →
add 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
は必ず対称に。退避レジスタ群を増減したら
入口と復帰の両方を同期させる(フレームサイズをアセンブラ定数化するのが望ましい)。
ページ内フォームで検索すると、結果が出ず検索ページに戻る不具合。複数の根本原因が重なっていた。
net_stack::http_post は is_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
を使うよう変更。tls.rs
の read_buf = 16384 が、DDG の結果ページ (~24KB) が送る最大
TLS レコード (16384 + 256 = 16640 バイト) を収容できず
embedded_tls が InsufficientSpace を返し 0
バイト応答になっていた。 GET の検索ページ (~4.5KB)
は小レコードで収まるため動作し、POST/大応答だけ失敗していた。 →
read_buf を 18432 に拡大(https_get / https_post /
https_get_binary の 3 箇所)。 pcap で「サーバは 29KB 応答済み・TCP ACK
済みなのに TLS が 0 バイト」を観測して特定。navigate_to_href
が 送信を上書きする経路があった → 送信したら return
で打ち切り。autofocus 属性のある
input をロード時にフォーカス(DDG 検索ボックス等)。
実装ついでに追加(UX 改善 + シリアル自動テストでクリック不要に)。QEMU で browser 起動 → 検索ボックス(autofocus 済み)に
rust 入力 → Enter → POST /lite/ → 24630
バイト受信 → 467
要素のレンダリング(検索結果)をスクリーンショットで確認。
web_engine.rs の decode_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)・アニメーション。
image-webp 0.2.4 を src/os_lib/webp/ に
vendoring し no_std 化。 - 改変:
std::io(Read/BufRead/Seek/Cursor) と
byteorder_lite を自前スライス IO シム
(webp/io.rs)
に置換、HashMap→BTreeMap、quick_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) 。
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) コマンド・テキスト要素。
画面の波打ち(ブラウザ使用中)=
再描画ストームの解消: needs_timer_redraw
が「画像ロード待ちの間ずっと true」で全デスクトップを毎フレーム
clear+flush
していた。image_work_pending(待機中ずっと)→image_results_ready
(デコード完了結果がある時だけ)に変更し、完了ごとに1回だけ再描画するエッジトリガに。
※
当時の根本原因はフレームバッファ直書きが垂直帰線と同期していなかったこと
(VSync 同期ページフリップ未実装)。追記(解決済み):
その後 virtual_height×2 + SET_VIRTUAL_OFFSET
によるダブルバッファページフリップ (kernel/draw.rs の
flush_dirty_pageflip/set_virtual_offset)を実装済み
(spec/walkthrough.md 参照。本セッションの QEMU 起動ログでも
[FB] VSync page-flip ENABLED (double buffer / 2 pages confirmed)
を確認)。
ネット画像が出ない = バイナリ chunked
未デコード:
https_get_binary/http_get_binary が
Transfer-Encoding: chunked を解さず、
ボディにチャンクサイズ行が混入して画像デコード失敗。バイナリ安全な
extract_http_body_binary(net.rs)
を追加し両経路に適用。gstatic の PNG 取得・表示を確認。
タブのスレッド安全性 = ロード状態をタブ毎に分離:
グローバル単一スロット ASYNC_LOAD: Option<..> を
AsyncLoadState{ requests: Vec, thread_active } に変更。各
WebEngine に一意 engine_id
を付与し、リクエストに owner を持たせて
「自分のタブの完了結果だけ消費」。同一タブの連続ナビは旧リクエストを置換。
ローダースレッドは停止時のみ
spawn(多重起動防止)。タブ切替時の結果混入を解消。
>
行の右端(右下の時計と同じ x 基準)に表示。 ログ文字列に ‘’
区切りで時刻を埋め込み、描画時に分離して右寄せ。フレームバッファ直書きを VBlank と同期させ、ティアリング(画面の裂け/波打ち)を解消。
mailbox.rs: フレームバッファを virtual_height =
物理高さ×2 で確保(2ページ分)。
set_virtual_offset(x,y)(SET_VIRTUAL_OFFSET, tag
0x00048009)を追加。RPi ファームウェアは 次の VBlank
で表示元オフセットを反映するため、表示中ページを書き換えなければ裂けない。draw.rs Screen: vsync/page
フィールド追加。flush() は裏ページへ全画面 DMA コピー後、
set_virtual_offset(0, page*H) で VBlank
同期フリップし、ページをトグル。
ページフリップは全画面コピー前提のため、flush_rect(部分転送)は
vsync 時 flush()(全面)に委譲。main.rs: enable_vsync()
を起動時に有効化。カーソル移動・部分 sysmon 更新も vsync 時は
flush_with_cursor(カーソル合成+全フリップ)に統一(draw_mouse_direct
の単一VRAM直書きを回避)。QEMU(raspi3b)で virtual_height×2 確保成功・パニックゼロ・表示崩れ無しを確認。 端末コマンド実行(再描画→フリップ)後も画面整合。実機 RPi3B+ でも同経路で動作する想定。
更新ごとに全画面 DMA コピー(1280x720=約3.7MB)になるが、DMA 帯域で数 ms、 かつ変更時のみ flush するため実用上問題ない。部分更新最適化は vsync と両立しないため撤廃。
JavaScript エンジン追加でカーネルバイナリが 24.4MB→26.5MB
に肥大した直後から、起動後半 (user_test_abort という意図的
EL0 Data Abort のデバッグプロセスの直後)に KERNEL PANIC(EL1
Synchronous Exception /
命令アボート)。フォルトアドレスが起動ごとに
別の関数(scheduler::exit / spawn_with_arg /
memcpy / zune_png 等)を指す非決定的挙動。
.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 の再配置が必要)。scheduler::exit() の
use-after-free(scheduler.rs):
Process は自分の kernel_stack /
stack_mem(Vec<u8>)を所有する。EL0
トラップ時はその kernel_stack(SP_EL1)
上で例外ハンドラ→exit() が動くのに、exit() が
rq.processes.remove(idx) で実行中の自分自身を
その場
drop=今立っているスタックを解放していた。配置次第で解放ブロックが再利用され破綻。
対策: 終了プロセスを「墓場
(ZOMBIES)」へ退避してドロップを遅延し、別プロセスへ文脈切替して
そのスタック上に居なくなってから reap_zombies()(現在 SP
を含むスタックは保持)で解放。QEMU スモークテストで KERNEL PANIC
0・JS_SELFTEST: PASS・Load page SUCCESS(TEST_PASSED)。
FS の「COW + 二重化 + B-Tree」の整合性検証で発見・修正。回帰防止に
fs::selftest() (起動時
FS_SELFTEST: PASS 6/6、test_browser.sh の CI
ガード)を新設した。
現象/原因: btree.rs::insert_recursive
の「キー一致=値を上書き」処理が葉/内部ノードを
区別せず実行されていた。B+Tree
では葉分割時に分離キー(split_key = node.keys[mid])が
内部ノードへ昇格しつつ右葉にも残る。そのため分離キーになったファイルを上書き保存すると、
内部ノードの一致で
values[idx](子ノードのセクタポインタ)をメタデータセクタ番号で上書きし、
右部分木を丸ごと喪失していた(=
そのキー以降のファイルがインデックスから消える)。24 ファイル
超で分割が起きた後、分離キーに該当するファイル(例:
再取得されるブラウザキャッシュ)の上書きで発現。
対策: 内部ノードで一致した場合は search
と同様に右の子へ降下(idx += 1; break)し、
上書きは葉ノード(node_type == 0)のときだけ行う。
read_file のメタデータ無境界添字(パニック)mod.rs::read_file の
while total_read < meta.size { let sec = meta.data_sectors[i]; i += 1; }
が
meta.data_sectors([u32; 44])を境界チェックなしで添字していた。size
と data_sectors
が不整合だと領域外アクセスでパニックしうる。&& i < MAX_FILE_SECTORS
を追加。
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)が別途必要。
起動時の expect テスト実行中(特に IME や JS などのテスト)や、端末から Aura スクリプトを実行した際、評価が途中でタイムアウトするか、意図通りに実行されずにリストリテラルがそのまま出力されてしまう。
src/os_lib/aura/eval.rs の AST::List
評価ロジック内に、不完全な早期リターンコードや、波カッコ {
の閉じ忘れ(unclosed
delimiter)などの不正なコードが混入していた。これにより、os.run
などの組み込み関数呼び出しを含む AST
リストが正しく解釈されず、実行処理が途中で打ち切られていた。
eval.rs における AST::List
の評価シーケンスをクリーンアップし、混入していたデッドコードおよび閉じ括弧のない不正な構文を削除。これにより、Aura
インタプリタが正しく AST
をトラバースし、すべてのビルトイン関数やリスト呼び出しが正常に評価・実行されるように復元した。
外部の仲介プロキシを使用せず、ベアメタル環境で YouTube の動画ストリームを HTTPS で直接取得・デコードし、映像と音声を遅延なく同期再生する必要がある。また、「HDMIからも音声が出力されること」という要件を満たす必要がある。これには以下の技術的課題が存在した: 1. 描画同期: バックグラウンドスレッドで直接スクリーンに描画すると、ウィンドウの移動、クリッピング境界の制御、他プロセスとの重ね合わせ描画が競合し、画面表示が崩れる。 2. デュアルオーディオ同期: PWM(アナログイヤホンジャック用)と I2S(HDMIデジタルオーディオ用)は異なるコントローラとDMAチャンネルを必要とし、転送のタイムラグによるズレが懸念された。
video_playback_thread
は映像のデコード処理のみに専念し、RGBピクセルデータを共有バッファ
CURRENT_VIDEO_FRAME に書き出す。WebEngine::draw を通じて CURRENT_VIDEO_FRAME
からピクセルを取り出し、現在のウィンドウ座標・クリッピング境界を考慮して
Nearest Neighbor 補間によりスケーリング描画を行う。needs_timer_redraw が true
を返し続けることで、ブラウザ側の描画ループを強制的に回転させ、コマ落ちなく映像を描画する。clean_dcache_range
を実行してキャッシュを強制クリーン後、DMA 3 / 4
コントロールブロックを同時に活性化(ACTIVE |
PRIORITY=8)して同期転送。これにより物理レイヤで完全同期したデュアルオーディオ出力を実現。net.rs に parse_host_port
を実装し、:port
付きURLの接続ポート番号パースをサポート。youtube.atmos-project.org を QEMU ゲートウェイIP
10.0.2.2 へ静的解決する QEMU
専用モックが存在したが、実機では無意味なため 2026-07-03
に削除。実URLへの本物のHTTPS接続で動作確認する方針に統一。)GitHub Actions による CI(cargo clippy --release および
cargo check /
cargo doc)実行時に、いくつかの箇所でコンパイルエラーや
Clippy 指摘(clippy::string_slice および
clippy::unwrap_used
違反)が発生し、ビルドおよびドキュメント生成が失敗する問題が存在した。
また、ウィンドウ起動時の挙動(サイズや状態指定の制限)や
ping
コマンドでホスト名(ドメイン名)が指定できないなどの利便性・仕様上の課題、さらにブラウザで特定の
youtube
文字列を含むURLが強制的にローカルのデコードテストURLにリダイレクトされてしまう不具合が存在した。
src/kernel/window_mgr.rs
(339行目付近):
self.windows.last().unwrap()
を呼び出して clippy::unwrap_used
エラーになっていたため、削除する前にウィンドウのサイズ・位置(Rect)をクローンして保持し、再利用することで
unwrap() を不要にしました。src/os_lib/aura/builtins.rs
(2171-2172行目付近):
s.find('x') を探して手動スライスをしていたため
clippy::string_slice でエラーとなっていました。これを Rust
標準ライブラリの安全な s.split_once('x')
に置き換えることで、UTF-8
文字境界を安全にハンドリングしエラーを解消しました。src/os_lib/h264_decode.rs
(565行目付近):
reference.unwrap()
を、.ok_or("P slice without reference frame")?
による安全なエラーハンドリング(Result
返却)へ変更し、clippy::unwrap_used を解消しました。WindowLaunchConfig 構造体および
parse_launch_options 解析ロジックを実装。"normal",
"maximized", "minimized",
"fullscreen")を解析し、"normal"
起動時はサイズ直接指定 ➜ マニフェスト(width /
height) ➜ 一律 500x600
の優先度でウィンドウサイズを算出して起動するようにしました。builtin_ping
において、指定されたホスト(IPアドレス文字列)が IPv4
アドレスとしてパースできなかった場合、自動的に
dns_query_a_via_udp を介した DNS
解決を試みるように拡張し、ドメイン名での ping
疎通を可能にしました。youtube.com 等の文字列入力時に強制的に mock
ページへ遷移させていた処理を削除し、デフォルトホームページ(HOME_HTML)内の「YouTube
を観る」リンクを本物の
URL(https://www.youtube.com/)に変更しました。JS
の値をオブジェクトグラフに沿って再帰的に処理する関数(シリアライズ・
表示整形・文字列化等)で、循環参照(var a={}; a.self=a;)に対する祖先追跡
ガードが欠けていると、無限再帰に陥る。JS
インタプリタ本体の評価ループには
max_steps(ステップ数上限)/max_depth(再帰深度上限)という安全弁が
既にあるが、これらのガードは JS
の評価ステップをカウントするものであり、 Rust
ネイティブの関数呼び出しスタックを直接消費する再帰(Value
の メソッドとして実装された変換関数等)までは保護しない。この
no_std カーネル環境では Rust のスタックオーバーフローは(V8 のような
RangeError
への変換機構が無いため)そのままクラッシュ/ハングに直結し得る。
structuredClone(deep_clone_value、2026-07-08)JSON.stringify(再帰パス上の祖先を追跡し仕様どおり
TypeError、2026-07-08)console.log
の表示整形(display_value_inner、祖先を追跡し
"[Circular]" 表示、2026-07-08)Value::to_js_string(Array.prototype.join/String()/テンプレートリテラル
補間が共通で使う変換、to_js_string_seen、2026-07-13)Array.prototype.flat(Infinity)(flatten_depth。depth
の減算だけでは Infinity - 1 = Infinity
のため終了条件にならない、2026-07-13。この監査
指針を書いた直後の横展開点検で発見——「他に同種のバグが無いか」を
実際に探した最初の成果)呼び出し元から見える公開シグネチャは変えず、内部実装を「訪問済みオブジェクト
参照(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.stringify
の replacer/reviver フック・固定 depth の
Array.prototype.flatMap(常に1段展開のみで
Infinity
を受け付けないため無関係)を確認し、いずれも再帰なし/既に
安全と判断した。
上記の「祖先追跡」修正は循環参照のみを検知する。循環していない
極端に深いネスト(例: for
ループで10万階層ネストした配列を作り
structuredClone/to_js_string/JSON.stringify
に渡す)に対しては、 seen
に同じオブジェクトが現れないため無防備なままで、依然として Rust
ネイティブ再帰でのスタックオーバーフローが起こり得る。JS インタプリタの
max_depth は評価ステップの再帰深度を制限するが、
「ループで深い構造を組み立ててから1回のネイティブ関数呼び出しに渡す」
という経路はこのガードの対象外(構築自体は深い再帰を伴わない)。
根治するには対象の各関数を再帰から明示スタックを使う反復アルゴリズムへ
書き換える必要があり、循環参照対策より改修規模が大きいため、本キャンペーン
では着手を見送り別タスク候補として記録するに留める。