# TLS SLL Header Corruption Investigation ## 症状とスコープ - sh8bench 実行時に `[TLS_SLL_HDR_RESET] cls=1 base=0x715210fbf858 got=0x51 expect=0xa1 count=0` などが発生(class 6 の `got=0x00 expect=0xa6` も報告)。 - 発生地点: `core/box/tls_sll_box.h` の TLS SLL pop 境界(Header Box による検証失敗 → SLL をリセット)。 - 再現範囲: sh8bench でほぼ毎回。Larson/cfrac では再現しにくい。 ## 箱マップ(Box Theory) - **Header Box (`core/box/tiny_header_box.h`)**: C1-C6 はヘッダー保持(HEADER_MAGIC|class_idx)。`tiny_header_write_if_preserved()` / `tiny_header_validate()` が単一の境界 API。 - **Freelist Box**: freelist ↔ TLS SLL の橋渡し。ここでヘッダーを復元しないと TLS SLL pop で Fail-Fast する。 - **TLS SLL Box (`core/box/tls_sll_box.h`)**: push でヘッダーを復元・検証、pop でヘッダー検証&リセット。リングイベント `TINY_RING_EVENT_TLS_SLL_HDR_CORRUPT` あり。 - **Class Map Box**: class_idx をヘッダー無しで特定する高速経路。デフォルトで header write をスキップするため、TLS SLL がヘッダーに依存する場合は境界不整合が起きる。 ## TLS_SLL_HDR_RESET 詳細分析 - pop 経路は `tiny_class_preserves_header(class_idx)` が true (C1-C6) の場合に必ず `tiny_header_validate()` を呼ぶ。失敗時は SLL をリセットして `[TLS_SLL_HDR_RESET]` を 10k 回に 1 回だけ出力。 - `core/tiny_region_id.h` では **デフォルト `g_write_header=1`**(A/B ガードで `HAKMEM_TINY_WRITE_HEADER=0` による切替可)。ヘッダーを書かずに freelist に入ると、Freelist→TLS SLL の移送時に復元漏れがあると pop で 0x00/0x51/0x61 を読み出してリセットする。 - 既知の漏れ(修正済みの根治ポイント) - `core/box/carve_push_box.c: box_carve_and_push_with_freelist()` で freelist pop→push 前にヘッダー未復元(commit `3c6c76cb1` で修正)。 - `core/hakmem_tiny_free.inc: tiny_drain_freelist_to_sll_once()` のデッドパスでも同様に未復元(commit `a94344c1a` で修正)。 - 破損パターン - `got=0x00`: mmap zero 由来の stale data - `got=0x51 / 0x61`: 前世の payload 断片(ヘッダー未書き込み時に漏れる) ## 3つの仮説 - **a. ヘッダー書き込みの未実行(最有力)** - デフォルトで header write OFF(`g_write_header=0`)+ freelist→TLS SLL での復元漏れが重なると、pop 側の検証で Fail-Fast する。上記 2 箇所の修正がこれを潰す。 - **b. TLS SLL と Class Map の境界不整合** - Class Map は「ヘッダー不要」を前提、一方 TLS SLL pop はヘッダー必須。境界(freelist→TLS SLL)でヘッダーを必ず再構成するか、Class Map 情報に切り替えるかのどちらかに統一すべき。 - **c. sh8bench 固有パターン** - 8 スレッドで class 1 の高速 churn により freelist→TLS SLL デッドパス(drain/carve)の頻度が高い。ヘッダー復元漏れが顕在化しやすい。 ## 今回の実行結果 - `LD_PRELOAD=./libhakmem.so`(リリースビルド)は `unified_cache_refill+0x46f` で SEGFAULT(`mov 0x1(%r15),%rdx`)。`r15` は TLS freelist から読みだしたポインタで、無効値を指していた。→ リリース版で sh8bench を回す前に unified cache 側のクラッシュ修正が必要。 - `LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libasan.so.6 ./libhakmem_asan.so"` で代替実行(LeakSanitizer のリーク警告付きだが完走): - ベースライン + `HAKMEM_TINY_SLL_RING=1`: `[TLS_SLL_HDR_RESET]` ゼロ件(`sh8bench_baseline.log`)。 - `HAKMEM_TINY_WRITE_HEADER=1` A/B(5 回)+ Ring: すべて `[TLS_SLL_HDR_RESET]` ゼロ件(`sh8bench_header_on_run*.log`)。 - `HAKMEM_TINY_SLL_SAFEHEADER=1 HAKMEM_TINY_SLL_VALIDATE_HDR=1 HAKMEM_DEBUG_LEVEL=3` + Ring: `[TLS_SLL_PUSH_BAD_HDR]` / `[TLS_SLL_REJECT]` ゼロ件(`sh8bench_safeheader.log`)。 - 現時点では TLS_SLL_HDR_RESET は再現せず。リリース版の unified cache 落ちを直した上で再確認が必要。 ## デバッグ手順(4ステップ) 1. **現状再現とリング取得** ```bash cd /mnt/workdisk/public_share/hakmem HAKMEM_TINY_SLL_RING=1 LD_PRELOAD=./libhakmem.so \ ./mimalloc-bench/out/bench/sh8bench 2>&1 | \ tee sh8bench_baseline.log | grep -E "TLS_SLL_HDR_RESET|TLS_SLL_HDR_CORRUPT|Total elapsed" ``` - 目的: `[TLS_SLL_HDR_RESET]` 発生位置と ring payload (got/expect) を確定。 2. **ヘッダー強制書き込みで A/B**(仮説 a の確認) ```bash for i in {1..5}; do echo "=== header_on run $i ===" HAKMEM_TINY_WRITE_HEADER=1 HAKMEM_TINY_SLL_RING=1 LD_PRELOAD=./libhakmem.so \ ./mimalloc-bench/out/bench/sh8bench 2>&1 | \ grep -E "TLS_SLL_HDR_RESET|Total elapsed" done ``` - 消えれば「ヘッダー未復元」が原因と確定。 3. **push 側の境界検証を強化** ```bash HAKMEM_TINY_SLL_SAFEHEADER=1 HAKMEM_TINY_SLL_VALIDATE_HDR=1 HAKMEM_DEBUG_LEVEL=3 \ LD_PRELOAD=./libhakmem.so ./mimalloc-bench/out/bench/sh8bench \ 2>&1 | tee sh8bench_safeheader.log ``` - `TLS_SLL_PUSH_BAD_HDR` / `TLS_SLL_REJECT` が出れば freelist→TLS SLL 境界で復元漏れが残っている。ring で callsite を特定。 4. **修正後のリグレッション確認** ```bash # sh8bench 10 回 for i in {1..10}; do LD_PRELOAD=./libhakmem.so ./mimalloc-bench/out/bench/sh8bench \ 2>&1 | grep -E "TLS_SLL_HDR_RESET|Total elapsed"; done # Larson / cfrac で広めに確認 LD_PRELOAD=./libhakmem.so ./mimalloc-bench/out/bench/larson 10 7 8 100 10000 LD_PRELOAD=./libhakmem.so ./mimalloc-bench/out/bench/cfrac 17545186520507317056371138836327483792789528 ``` - 目的: sh8bench だけでなく他ワークロードでも Fail-Fast が出ないことを確認。 ## 修正パターン(根治案) - **パターンA: 境界一箇所でヘッダー復元を徹底(推奨)** - Freelist→TLS SLL を 1 つの Box 境界として `tiny_header_write_if_preserved()` を必ず実行(現行の `box_carve_and_push_with_freelist()` / `tiny_drain_freelist_to_sll_once()` の形を維持)。 - 追加ガード: `HAKMEM_TINY_SLL_SAFEHEADER=1` を運用フラグとして残し、違反時は push 拒否+リング記録。 - **パターンB: ヘッダー常時書き込みに戻す** - `core/tiny_region_id.h` の `g_write_header` をデフォルト `1` にして Class Map 依存を減らす。 - **実装済み**: デフォルト ON に変更(`HAKMEM_TINY_WRITE_HEADER=0` で旧挙動に戻せる)。 - A/B 切替: 環境変数 `HAKMEM_TINY_WRITE_HEADER=0` でオフにできるよう保持(切り戻し容易)。 - **パターンC: TLS SLL を Class Map 準拠にリファクタ** - `tls_sll_pop()` でヘッダー検証を `tiny_header_validate()`→`class_map_lookup()` の優先順に変更(ヘッダーが無い場合は Class Map で検証)。 - `tiny_header_write_if_preserved()` を「Best Effort」扱いにしつつ、リングで Class Map vs header の不一致を Fail-Fast ログする。 ## 環境変数 / ビルド / テスト - **デバッグフラグ** - `HAKMEM_TINY_WRITE_HEADER=1` : ヘッダー強制書き込み(仮説 a 検証) - `HAKMEM_TINY_SLL_RING=1` : TLS SLL イベントをリング出力 - `HAKMEM_TINY_SLL_VALIDATE_HDR=1` または `HAKMEM_DEBUG_LEVEL=3` : push でのヘッダー検証を常時 ON - `HAKMEM_TINY_SLL_SAFEHEADER=1` : ヘッダー不一致時は push 拒否(Fail-Fast) - `HAKMEM_WRAP_DIAG=1` : Wrapper トレース - **ビルド** ```bash cd /mnt/workdisk/public_share/hakmem rm -f *.o libhakmem.so make shared -j8 # Release make shared -j8 EXTRA_CFLAGS="-g -O0 -UHAKMEM_BUILD_RELEASE" # Debug ``` - **テスト**(再掲) - `LD_PRELOAD=./libhakmem.so ./mimalloc-bench/out/bench/sh8bench` - `LD_PRELOAD=./libhakmem.so ./mimalloc-bench/out/bench/larson 10 7 8 100 10000` - いずれも `grep -E "TLS_SLL_HDR_RESET|TLS_SLL_HDR_CORRUPT"` でゼロ件を確認。 --- 根治の原則: 「ヘッダーを書かないなら検証しない」「検証するなら境界で必ず書く」を Box 境界 1 箇所に閉じ込め、Fail-Fast とリング可視化で sh8bench パターンでも再発しないことを確認する。