Files
hakmem/docs/tls_sll_header_corruption_investigation.md

117 lines
8.3 KiB
Markdown
Raw Normal View History

# 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/B5 回)+ 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 パターンでも再発しないことを確認する。