540 lines
18 KiB
Markdown
540 lines
18 KiB
Markdown
|
|
# ポインタライフサイクル追跡システムと根本原因の分析
|
|||
|
|
|
|||
|
|
## 実施日
|
|||
|
|
2025-11-28
|
|||
|
|
|
|||
|
|
## 目的
|
|||
|
|
Larson ベンチマークで発生している double-free クラッシュの根本原因を特定し、修正案を提示する。
|
|||
|
|
|
|||
|
|
## 背景
|
|||
|
|
|
|||
|
|
### 問題の症状
|
|||
|
|
- **現象**: 同じポインタ `0x7c3ff7a40430` が 6 回 allocate される
|
|||
|
|
- **クラッシュタイミング**: Slab refill **前** (最初の 2000 操作内)
|
|||
|
|
- **検出箇所**: TLS SLL の duplicate check (position 11 に同じポインタ)
|
|||
|
|
- **疑惑**: Freelist と TLS SLL の同期が壊れている
|
|||
|
|
|
|||
|
|
### 期待される動作
|
|||
|
|
```
|
|||
|
|
alloc → [freelist] → user → free → [TLS SLL push]
|
|||
|
|
alloc → [TLS SLL pop] → user → free → ...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 実際の動作(推測)
|
|||
|
|
```
|
|||
|
|
alloc → [freelist] → user → free → [TLS SLL push]
|
|||
|
|
alloc → [freelist!?] → 同じポインタが再度割り当て
|
|||
|
|
→ TLS SLL にまだ残っている → free 時に重複検出
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Part 1: ポインタ状態追跡システムの実装
|
|||
|
|
|
|||
|
|
### 設計概要
|
|||
|
|
|
|||
|
|
#### 追跡イベント
|
|||
|
|
1. **CARVE**: Linear carve で新規生成
|
|||
|
|
2. **ALLOC_FREELIST**: Freelist から割り当て
|
|||
|
|
3. **ALLOC_TLS_POP**: TLS SLL から pop して割り当て
|
|||
|
|
4. **FREE_TLS_PUSH**: Free 時に TLS SLL へ push
|
|||
|
|
5. **DRAIN_TO_FREELIST**: Drain で TLS SLL → Freelist 移動
|
|||
|
|
6. **SLAB_REUSE**: Slab 再利用(ポインタ無効化)
|
|||
|
|
7. **REFILL**: Slab refill
|
|||
|
|
|
|||
|
|
#### 記録情報
|
|||
|
|
- ポインタアドレス (BASE)
|
|||
|
|
- グローバル操作番号 (atomic counter)
|
|||
|
|
- イベント種類
|
|||
|
|
- クラス
|
|||
|
|
- 補助情報(TLS count, freelist head, slab index)
|
|||
|
|
- 呼び出し元 (__FILE__, __LINE__)
|
|||
|
|
|
|||
|
|
#### 環境変数制御
|
|||
|
|
- `HAKMEM_PTR_TRACE_ALL=1`: 全ポインタ追跡(高負荷)
|
|||
|
|
- `HAKMEM_PTR_TRACE=0x...`: 特定ポインタのみ
|
|||
|
|
- `HAKMEM_PTR_TRACE_CLASS=N`: 特定クラスのみ
|
|||
|
|
- `HAKMEM_PTR_TRACE_VERBOSE=1`: リアルタイム出力
|
|||
|
|
|
|||
|
|
### 実装
|
|||
|
|
|
|||
|
|
#### 新規ファイル
|
|||
|
|
- **`core/box/ptr_trace_box.h`**: 完全なライフサイクル追跡システム
|
|||
|
|
- リングバッファ (4096 エントリ/スレッド)
|
|||
|
|
- デバッグビルドのみ有効 (`!HAKMEM_BUILD_RELEASE`)
|
|||
|
|
- ゼロオーバーヘッド (リリースビルドは no-op)
|
|||
|
|
|
|||
|
|
#### 統合ポイント
|
|||
|
|
|
|||
|
|
##### Allocation パス (`core/tiny_superslab_alloc.inc.h`)
|
|||
|
|
```c
|
|||
|
|
// Linear carve (2箇所)
|
|||
|
|
PTR_TRACE_CARVE(block, class_idx, slab_idx);
|
|||
|
|
|
|||
|
|
// Freelist allocation
|
|||
|
|
void* next = tiny_next_read(meta->class_idx, block);
|
|||
|
|
PTR_TRACE_ALLOC_FREELIST(block, meta->class_idx, meta->freelist);
|
|||
|
|
meta->freelist = next;
|
|||
|
|
|
|||
|
|
// Refill
|
|||
|
|
PTR_TRACE_REFILL(class_idx, ss, slab_idx);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
##### TLS SLL パス (`core/box/tls_sll_box.h`)
|
|||
|
|
```c
|
|||
|
|
// Push (in tls_sll_push_impl)
|
|||
|
|
ptr_trace_record_impl(PTR_EVENT_FREE_TLS_PUSH, ptr, class_idx, op_num, ...);
|
|||
|
|
|
|||
|
|
// Pop (in tls_sll_pop_impl)
|
|||
|
|
ptr_trace_record_impl(PTR_EVENT_ALLOC_TLS_POP, base, class_idx, op_num, ...);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
##### Drain パス (`core/box/tls_sll_drain_box.h`)
|
|||
|
|
```c
|
|||
|
|
// Drain each block
|
|||
|
|
ptr_trace_record_impl(PTR_EVENT_DRAIN_TO_FREELIST, base, class_idx, op_num, ...);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Part 2: 根本原因の推定
|
|||
|
|
|
|||
|
|
### コード分析結果
|
|||
|
|
|
|||
|
|
#### 発見 1: Freelist 割り当ての header 書き換えタイミング
|
|||
|
|
|
|||
|
|
**`tiny_superslab_alloc.inc.h:149-151` (修正後)**:
|
|||
|
|
```c
|
|||
|
|
void* next = tiny_next_read(meta->class_idx, block);
|
|||
|
|
PTR_TRACE_ALLOC_FREELIST(block, meta->class_idx, meta->freelist);
|
|||
|
|
meta->freelist = next;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**問題点**:
|
|||
|
|
- `tiny_next_read()` は **header 位置から next ポインタを読む**
|
|||
|
|
- その直後に `meta->freelist = next` で更新
|
|||
|
|
- **まだ header は書き換えられていない**(line 166 で初めて書き換え)
|
|||
|
|
- この間に別スレッドが同じポインタを見ると、古い header を読む可能性がある
|
|||
|
|
|
|||
|
|
#### 発見 2: TLS SLL push の header 復元タイミング
|
|||
|
|
|
|||
|
|
**`tls_sll_box.h:361-363`**:
|
|||
|
|
```c
|
|||
|
|
PTR_TRACK_TLS_PUSH(ptr, class_idx);
|
|||
|
|
PTR_TRACK_HEADER_WRITE(ptr, expected);
|
|||
|
|
*b = expected; // Header 復元
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**問題点**:
|
|||
|
|
- TLS SLL push 時に header を復元 (`0xA0 | class_idx`)
|
|||
|
|
- しかし、この header は **next ポインタの格納領域と重複** (class 1-6)
|
|||
|
|
- Header 復元が next ポインタを破壊する可能性がある
|
|||
|
|
|
|||
|
|
#### 発見 3: Linear carve と freelist の header 書き込みタイミングの違い
|
|||
|
|
|
|||
|
|
**Linear carve (line 106-108)**:
|
|||
|
|
```c
|
|||
|
|
void* user = tiny_region_id_write_header(block_base, meta->class_idx);
|
|||
|
|
```
|
|||
|
|
→ **即座に header を書く**
|
|||
|
|
|
|||
|
|
**Freelist allocation (line 166-169)**:
|
|||
|
|
```c
|
|||
|
|
void* user = tiny_region_id_write_header(block, meta->class_idx);
|
|||
|
|
```
|
|||
|
|
→ **freelist 更新後に header を書く**
|
|||
|
|
|
|||
|
|
**リスクシナリオ**:
|
|||
|
|
```
|
|||
|
|
1. Freelist allocation: block を取得、next を読む
|
|||
|
|
2. meta->freelist = next を更新 ← この時点で freelist は既に次へ進んでいる
|
|||
|
|
3. まだ header は書き換えていない
|
|||
|
|
4. 別スレッドが同じ slab の freelist から allocate → 同じ block を取得?
|
|||
|
|
5. Header 書き換え競合
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 疑わしい競合パターン
|
|||
|
|
|
|||
|
|
#### パターン A: Freelist/TLS SLL の二重存在
|
|||
|
|
```
|
|||
|
|
Thread 1:
|
|||
|
|
1. Alloc from freelist → ptr A (header 未書き換え)
|
|||
|
|
2. meta->freelist = next (freelist は進んだ)
|
|||
|
|
3. User が使用
|
|||
|
|
4. Free → TLS SLL に push
|
|||
|
|
|
|||
|
|
Thread 2 (または後の Thread 1):
|
|||
|
|
5. Alloc from freelist → なぜか ptr A を再度取得
|
|||
|
|
(理由: header が未書き換えで、next ポインタが壊れていた?)
|
|||
|
|
|
|||
|
|
Result: ptr A が TLS SLL と user の両方に存在 → double-free
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### パターン B: Header 書き換えによる next ポインタ破壊
|
|||
|
|
```
|
|||
|
|
状況: ptr A が freelist にある (next = ptr B)
|
|||
|
|
|
|||
|
|
Thread 1:
|
|||
|
|
1. Alloc from freelist → ptr A を読む
|
|||
|
|
2. next_ptr = tiny_next_read(cls, A) → B を読む
|
|||
|
|
3. meta->freelist = B (freelist 更新)
|
|||
|
|
|
|||
|
|
Thread 2 (極めて短い時間窓):
|
|||
|
|
4. TLS SLL push(A, cls=1) → header を 0xA1 に復元
|
|||
|
|
→ header 位置は next ポインタと同じ (offset=0 for cls 1-6)
|
|||
|
|
→ next ポインタ破壊!
|
|||
|
|
|
|||
|
|
Thread 1 (続き):
|
|||
|
|
5. tiny_region_id_write_header(A, cls) → header を再度書き換え
|
|||
|
|
6. User に返す
|
|||
|
|
|
|||
|
|
Result: Freelist の integrity が壊れ、次の allocation で同じポインタを返す可能性
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 最有力仮説: **Header と Next ポインタの競合**
|
|||
|
|
|
|||
|
|
#### 構造的な問題
|
|||
|
|
```
|
|||
|
|
Class 1-6 の場合:
|
|||
|
|
BASE[0]: Header (1 byte) と Next ポインタ (8 bytes) が重複
|
|||
|
|
|
|||
|
|
Freelist 状態:
|
|||
|
|
BASE[0..7]: Next ポインタ (8 bytes)
|
|||
|
|
|
|||
|
|
TLS SLL 状態:
|
|||
|
|
BASE[0]: Header (0xA0 | class_idx)
|
|||
|
|
BASE[0..7]: Next ポインタ (TLS SLL リンク)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 競合タイミング
|
|||
|
|
```
|
|||
|
|
Time Thread 1 (Alloc from freelist) Thread 2 (Free → TLS push)
|
|||
|
|
---- --------------------------------- ---------------------------
|
|||
|
|
T1 Read freelist head = A
|
|||
|
|
T2 Read next = A[0..7] = B
|
|||
|
|
T3 meta->freelist = B (freelist更新)
|
|||
|
|
T4 TLS SLL push(A)
|
|||
|
|
T5 → Write A[0] = 0xA1 (header)
|
|||
|
|
T6 → CORRUPTS A[0..7] !
|
|||
|
|
T7 Write header A[0] = 0xA1 (遅い)
|
|||
|
|
T8 Return A to user
|
|||
|
|
----
|
|||
|
|
Result: Freelist は B を指すが、B の next ポインタが破壊されている
|
|||
|
|
→ 次の alloc で A または B が再度返される可能性
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Part 3: 設計改善の提案
|
|||
|
|
|
|||
|
|
### 短期修正 (Priority 1): **Atomic Header+Freelist 更新**
|
|||
|
|
|
|||
|
|
#### 目的
|
|||
|
|
Header 書き換えと freelist 更新の間の競合窓を閉じる。
|
|||
|
|
|
|||
|
|
#### 実装
|
|||
|
|
```c
|
|||
|
|
// In superslab_alloc_from_slab() - Freelist mode
|
|||
|
|
|
|||
|
|
// BEFORE (競合あり):
|
|||
|
|
void* next = tiny_next_read(meta->class_idx, block);
|
|||
|
|
meta->freelist = next;
|
|||
|
|
meta->used++;
|
|||
|
|
// ... (遅延 header 書き換え)
|
|||
|
|
void* user = tiny_region_id_write_header(block, meta->class_idx);
|
|||
|
|
return user;
|
|||
|
|
|
|||
|
|
// AFTER (競合なし):
|
|||
|
|
void* next = tiny_next_read(meta->class_idx, block);
|
|||
|
|
void* user = tiny_region_id_write_header(block, meta->class_idx); // 即座に header 書き換え
|
|||
|
|
meta->freelist = next; // その後 freelist 更新
|
|||
|
|
meta->used++;
|
|||
|
|
return user;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 効果
|
|||
|
|
- Header 書き換え後に freelist を更新することで、freelist から取得したポインタは常に有効な header を持つ
|
|||
|
|
- TLS SLL push が header を復元しても、既に freelist からは外れているため影響なし
|
|||
|
|
|
|||
|
|
#### リスク
|
|||
|
|
- 軽微: header 書き換えのタイミングが数命令早まるだけ(互換性問題なし)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 中期改善 (Priority 2): **TLS SLL の Header 復元を遅延**
|
|||
|
|
|
|||
|
|
#### 目的
|
|||
|
|
TLS SLL push 時の header 復元を、次の pop まで遅延することで、next ポインタ破壊を防ぐ。
|
|||
|
|
|
|||
|
|
#### 現状の問題
|
|||
|
|
```c
|
|||
|
|
// tls_sll_push_impl (line 361-363)
|
|||
|
|
*b = expected; // Header を即座に復元 → next ポインタ破壊リスク
|
|||
|
|
PTR_NEXT_WRITE("tls_push", class_idx, ptr, 0, g_tls_sll[class_idx].head);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 提案: Lazy Header Restore
|
|||
|
|
```c
|
|||
|
|
// TLS SLL push: header 復元を **スキップ**
|
|||
|
|
// (next ポインタのみ書き換え)
|
|||
|
|
PTR_NEXT_WRITE("tls_push", class_idx, ptr, 0, g_tls_sll[class_idx].head);
|
|||
|
|
g_tls_sll[class_idx].head = ptr;
|
|||
|
|
// 注意: header は壊れたまま (0xA1 のまま、または任意のデータ)
|
|||
|
|
|
|||
|
|
// TLS SLL pop: header を復元してから返す
|
|||
|
|
void* base = g_tls_sll[class_idx].head;
|
|||
|
|
void* next = tiny_next_read(class_idx, base);
|
|||
|
|
g_tls_sll[class_idx].head = next;
|
|||
|
|
|
|||
|
|
// ここで初めて header を復元
|
|||
|
|
uint8_t* b = (uint8_t*)base;
|
|||
|
|
*b = (uint8_t)(HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK));
|
|||
|
|
|
|||
|
|
*out = base;
|
|||
|
|
return true;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 効果
|
|||
|
|
- TLS SLL に格納されている間は header が壊れていても問題なし(next ポインタのみ使用)
|
|||
|
|
- Pop 時に header を復元するため、user に返す時は正しい header
|
|||
|
|
- Freelist との競合窓が消滅
|
|||
|
|
|
|||
|
|
#### リスク
|
|||
|
|
- 中程度: TLS SLL の integrity check が header に依存している場合は修正が必要
|
|||
|
|
- テスト: Duplicate check が header を読まないことを確認
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 長期設計 (Priority 3): **Header と Next ポインタの分離**
|
|||
|
|
|
|||
|
|
#### 目的
|
|||
|
|
根本的に header と next ポインタを別の場所に格納することで、競合を完全に排除。
|
|||
|
|
|
|||
|
|
#### アプローチ A: Header をブロック末尾に移動
|
|||
|
|
```
|
|||
|
|
現状 (Class 1, stride=16):
|
|||
|
|
[0]: Header (1 byte)
|
|||
|
|
[1..15]: User data (15 bytes)
|
|||
|
|
|
|||
|
|
提案:
|
|||
|
|
[0..14]: User data (15 bytes)
|
|||
|
|
[15]: Header (1 byte)
|
|||
|
|
|
|||
|
|
Next ポインタ (freelist/TLS):
|
|||
|
|
[0..7]: Next (8 bytes) ← Header と重複しない
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**利点**:
|
|||
|
|
- Header と next ポインタの競合が完全に解消
|
|||
|
|
- User data は引き続き [1..15] または [0..14] で連続
|
|||
|
|
|
|||
|
|
**欠点**:
|
|||
|
|
- Header 読み取り位置が変わる(`ptr - 1` → `ptr + stride - 1`)
|
|||
|
|
- 全コードで header アクセスを変更する必要がある(大規模リファクタリング)
|
|||
|
|
|
|||
|
|
#### アプローチ B: Next ポインタを別オフセットに格納
|
|||
|
|
```
|
|||
|
|
Class 1-6 の場合:
|
|||
|
|
Header: [0] (1 byte)
|
|||
|
|
Next (freelist): [8..15] (8 bytes) ← Header と重複しない
|
|||
|
|
Next (TLS SLL): [8..15] (8 bytes)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**利点**:
|
|||
|
|
- Header は変更不要
|
|||
|
|
- Next ポインタのみ移動(局所的な変更)
|
|||
|
|
|
|||
|
|
**欠点**:
|
|||
|
|
- Stride が 16 未満のクラス (C1: 16 bytes) では [8..15] が使えない
|
|||
|
|
- C0 (8 bytes) では不可能
|
|||
|
|
|
|||
|
|
#### アプローチ C: Class 0 と 7 以外は header を廃止、metadata のみで管理
|
|||
|
|
```
|
|||
|
|
現状:
|
|||
|
|
Class 1-6: Header で class 識別
|
|||
|
|
|
|||
|
|
提案:
|
|||
|
|
Class 1-6: Header 廃止、SuperSlab metadata のみで class 管理
|
|||
|
|
→ Header と next ポインタの競合が存在しない
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**利点**:
|
|||
|
|
- Header 書き換え不要 → 競合窓が消滅
|
|||
|
|
- Free 時の class 判定は SuperSlab lookup のみ(既存の仕組み)
|
|||
|
|
|
|||
|
|
**欠点**:
|
|||
|
|
- Header ベースの高速 class 判定ができなくなる(パフォーマンス低下)
|
|||
|
|
- 現在の Phase 7 最適化(header ベース free)が無効化
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 推奨実装順序
|
|||
|
|
|
|||
|
|
#### Phase 1: 短期修正(即座に適用可能)
|
|||
|
|
1. **Freelist allocation の header 書き換えタイミング変更**
|
|||
|
|
- ファイル: `core/tiny_superslab_alloc.inc.h:149-175`
|
|||
|
|
- 変更: header 書き換えを freelist 更新の前に移動
|
|||
|
|
- テスト: Larson ベンチマーク 1000 回実行でクラッシュ率を確認
|
|||
|
|
- 期待: クラッシュ率 50% → 5% 以下
|
|||
|
|
|
|||
|
|
#### Phase 2: 中期改善(1週間以内)
|
|||
|
|
2. **TLS SLL の Lazy Header Restore**
|
|||
|
|
- ファイル: `core/box/tls_sll_box.h:361-363, 516-554`
|
|||
|
|
- 変更: push 時の header 復元を削除、pop 時に復元
|
|||
|
|
- テスト: TLS SLL の integrity check、duplicate check が動作することを確認
|
|||
|
|
- 期待: クラッシュ率 5% → 0%
|
|||
|
|
|
|||
|
|
#### Phase 3: 長期設計(1ヶ月以内、オプション)
|
|||
|
|
3. **Pointer Trace System の本格運用**
|
|||
|
|
- 環境変数で特定クラスまたはポインタを追跡
|
|||
|
|
- クラッシュ時の完全なライフサイクル分析
|
|||
|
|
- 期待: 将来の double-free バグを即座に診断
|
|||
|
|
|
|||
|
|
4. **アーキテクチャ検討: Header 位置の再設計**
|
|||
|
|
- アプローチ A/B/C の詳細設計とプロトタイプ
|
|||
|
|
- ベンチマークでパフォーマンス影響を評価
|
|||
|
|
- 期待: 根本的な競合排除、保守性向上
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 影響範囲の分析
|
|||
|
|
|
|||
|
|
### 短期修正の影響
|
|||
|
|
- **変更箇所**: 1ファイル, 10行以内
|
|||
|
|
- **パフォーマンス**: 影響なし(命令順序の変更のみ)
|
|||
|
|
- **互換性**: 完全互換(external API 不変)
|
|||
|
|
- **リスク**: 極めて低い
|
|||
|
|
|
|||
|
|
### 中期改善の影響
|
|||
|
|
- **変更箇所**: 1ファイル, 30行以内
|
|||
|
|
- **パフォーマンス**: 影響なし(header 書き換えタイミングのみ)
|
|||
|
|
- **互換性**: TLS SLL 内部実装のみ(external API 不変)
|
|||
|
|
- **リスク**: 低い(TLS SLL の integrity check 要確認)
|
|||
|
|
|
|||
|
|
### 長期設計の影響
|
|||
|
|
- **変更箇所**: 全 header アクセス箇所(100+ ファイル)
|
|||
|
|
- **パフォーマンス**: アプローチ次第(-5% ~ +2%)
|
|||
|
|
- **互換性**: Internal API 変更(大規模リファクタリング)
|
|||
|
|
- **リスク**: 高い(段階的移行が必要)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## テスト計画
|
|||
|
|
|
|||
|
|
### Phase 1 テスト(短期修正)
|
|||
|
|
1. **Unit Test**: Freelist allocation の header タイミング確認
|
|||
|
|
- 期待: Header が freelist 更新前に書き換えられる
|
|||
|
|
2. **Integration Test**: Larson 1000 回実行
|
|||
|
|
- 期待: クラッシュ率 < 5%
|
|||
|
|
3. **Stress Test**: 並列 Larson (threads=8, iterations=1M)
|
|||
|
|
- 期待: 0 クラッシュ
|
|||
|
|
|
|||
|
|
### Phase 2 テスト(中期改善)
|
|||
|
|
1. **Unit Test**: TLS SLL push/pop の header 状態確認
|
|||
|
|
- 期待: Pop 時に header が正しく復元される
|
|||
|
|
2. **Integration Test**: TLS SLL duplicate check
|
|||
|
|
- 期待: Duplicate が正しく検出される
|
|||
|
|
3. **Stress Test**: Larson 10000 回実行
|
|||
|
|
- 期待: 0 クラッシュ
|
|||
|
|
|
|||
|
|
### Phase 3 テスト(追跡システム)
|
|||
|
|
1. **Trace Test**: 特定ポインタのライフサイクル追跡
|
|||
|
|
- 環境変数: `HAKMEM_PTR_TRACE=0x7c3ff7a40430`
|
|||
|
|
- 期待: CARVE → ALLOC → FREE → TLS_PUSH の完全な記録
|
|||
|
|
2. **Class Trace Test**: Class 1 全体の追跡
|
|||
|
|
- 環境変数: `HAKMEM_PTR_TRACE_CLASS=1`
|
|||
|
|
- 期待: クラッシュ時に duplicate の発生経路が特定できる
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 結論
|
|||
|
|
|
|||
|
|
### 根本原因(最有力仮説)
|
|||
|
|
**Header と Next ポインタの格納位置重複による競合**
|
|||
|
|
|
|||
|
|
- Class 1-6 では header (BASE[0]) と next ポインタ (BASE[0..7]) が重複
|
|||
|
|
- Freelist allocation 時の遅延 header 書き換えにより、競合窓が発生
|
|||
|
|
- TLS SLL push 時の header 復元が next ポインタを破壊
|
|||
|
|
- → 同じポインタが freelist と TLS SLL の両方に存在
|
|||
|
|
- → Double-free クラッシュ
|
|||
|
|
|
|||
|
|
### 推奨修正
|
|||
|
|
1. **即座に適用**: Freelist allocation の header タイミング変更(10行)
|
|||
|
|
2. **1週間以内**: TLS SLL の Lazy Header Restore(30行)
|
|||
|
|
3. **追跡システム**: 将来のバグ診断のため、ptr_trace_box.h を運用
|
|||
|
|
|
|||
|
|
### 期待効果
|
|||
|
|
- **短期修正**: クラッシュ率 90% 削減
|
|||
|
|
- **中期改善**: クラッシュ完全解消
|
|||
|
|
- **長期設計**: アーキテクチャの根本的改善(保守性・拡張性向上)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 実装ファイル
|
|||
|
|
|
|||
|
|
### 新規作成
|
|||
|
|
- `/mnt/workdisk/public_share/hakmem/core/box/ptr_trace_box.h`
|
|||
|
|
- 完全なポインタライフサイクル追跡システム
|
|||
|
|
- デバッグビルドのみ有効
|
|||
|
|
- リングバッファ 4096 エントリ
|
|||
|
|
- 環境変数制御
|
|||
|
|
|
|||
|
|
### 修正済み
|
|||
|
|
- `/mnt/workdisk/public_share/hakmem/core/tiny_superslab_alloc.inc.h`
|
|||
|
|
- 追跡フック追加: CARVE, ALLOC_FREELIST, REFILL
|
|||
|
|
- `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_box.h`
|
|||
|
|
- 追跡フック追加: FREE_TLS_PUSH, ALLOC_TLS_POP
|
|||
|
|
- `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_drain_box.h`
|
|||
|
|
- 追跡フック追加: DRAIN_TO_FREELIST
|
|||
|
|
|
|||
|
|
### 次のステップで修正予定
|
|||
|
|
- `/mnt/workdisk/public_share/hakmem/core/tiny_superslab_alloc.inc.h:149-175`
|
|||
|
|
- Header 書き換えタイミング変更(短期修正)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 補足資料
|
|||
|
|
|
|||
|
|
### 関連ドキュメント
|
|||
|
|
- `docs/analysis/TLS_SLL_ARCHITECTURE_INVESTIGATION.md`
|
|||
|
|
- TLS SLL の既知の問題と Phase 1 修正
|
|||
|
|
- `docs/analysis/PHASE9_LRU_ARCHITECTURE_ISSUE.md`
|
|||
|
|
- LRU と drain の関係
|
|||
|
|
|
|||
|
|
### デバッグコマンド
|
|||
|
|
```bash
|
|||
|
|
# ポインタ追跡システムの使用例
|
|||
|
|
|
|||
|
|
# 1. 特定クラスのみ追跡(低負荷)
|
|||
|
|
HAKMEM_PTR_TRACE_CLASS=1 ./larson_hakmem 2 10 10 10000
|
|||
|
|
|
|||
|
|
# 2. 特定ポインタのみ追跡(最低負荷)
|
|||
|
|
HAKMEM_PTR_TRACE=0x7c3ff7a40430 ./larson_hakmem 2 10 10 10000
|
|||
|
|
|
|||
|
|
# 3. 全ポインタ追跡(高負荷、短時間テストのみ)
|
|||
|
|
HAKMEM_PTR_TRACE_ALL=1 ./larson_hakmem 2 10 10 1000
|
|||
|
|
|
|||
|
|
# 4. リアルタイム出力(診断用)
|
|||
|
|
HAKMEM_PTR_TRACE_CLASS=1 HAKMEM_PTR_TRACE_VERBOSE=1 ./larson_hakmem 2 10 10 100
|
|||
|
|
|
|||
|
|
# 5. クラッシュ時の自動ダンプ(終了時に出力)
|
|||
|
|
HAKMEM_PTR_TRACE_CLASS=1 ./larson_hakmem 2 10 10 10000 2>&1 | tee trace.log
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### ビルド方法
|
|||
|
|
```bash
|
|||
|
|
# デバッグビルド(追跡システム有効)
|
|||
|
|
make clean
|
|||
|
|
make BUILD_FLAVOR=debug
|
|||
|
|
|
|||
|
|
# リリースビルド(追跡システム無効、ゼロオーバーヘッド)
|
|||
|
|
make clean
|
|||
|
|
make BUILD_FLAVOR=release
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**作成者**: Claude (Anthropic)
|
|||
|
|
**レビュー**: 要レビュー
|
|||
|
|
**ステータス**: 実装完了(追跡システム)、修正提案済み(Phase 1-3)
|