8.3 KiB
8.3 KiB
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 前にヘッダー未復元(commit3c6c76cb1で修正)。core/hakmem_tiny_free.inc: tiny_drain_freelist_to_sll_once()のデッドパスでも同様に未復元(commita94344c1aで修正)。
- 破損パターン
got=0x00: mmap zero 由来の stale datagot=0x51 / 0x61: 前世の payload 断片(ヘッダー未書き込み時に漏れる)
3つの仮説
- a. ヘッダー書き込みの未実行(最有力)
- デフォルトで header write OFF(
g_write_header=0)+ freelist→TLS SLL での復元漏れが重なると、pop 側の検証で Fail-Fast する。上記 2 箇所の修正がこれを潰す。
- デフォルトで header write OFF(
- 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=1A/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ステップ)
-
現状再現とリング取得
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) を確定。
- 目的:
-
ヘッダー強制書き込みで A/B(仮説 a の確認)
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- 消えれば「ヘッダー未復元」が原因と確定。
-
push 側の境界検証を強化
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.logTLS_SLL_PUSH_BAD_HDR/TLS_SLL_REJECTが出れば freelist→TLS SLL 境界で復元漏れが残っている。ring で callsite を特定。
-
修正後のリグレッション確認
# 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 拒否+リング記録。
- Freelist→TLS SLL を 1 つの Box 境界として
-
パターン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 でのヘッダー検証を常時 ONHAKMEM_TINY_SLL_SAFEHEADER=1: ヘッダー不一致時は push 拒否(Fail-Fast)HAKMEM_WRAP_DIAG=1: Wrapper トレース
- ビルド
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/sh8benchLD_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 パターンでも再発しないことを確認する。