diff --git a/EXIT_CODE: b/EXIT_CODE: new file mode 100644 index 00000000..9e32be07 --- /dev/null +++ b/EXIT_CODE: @@ -0,0 +1 @@ +/bin/bash: 行 1: MONITOR_PID=: コマンドが見つかりません diff --git a/EXIT_CODE= b/EXIT_CODE= new file mode 100644 index 00000000..9e32be07 --- /dev/null +++ b/EXIT_CODE= @@ -0,0 +1 @@ +/bin/bash: 行 1: MONITOR_PID=: コマンドが見つかりません diff --git a/core/box/tls_sll_box.h b/core/box/tls_sll_box.h index a05bdf19..964d5957 100644 --- a/core/box/tls_sll_box.h +++ b/core/box/tls_sll_box.h @@ -104,6 +104,9 @@ static inline hak_base_ptr_t tls_sll_normalize_base(int class_idx, hak_base_ptr_ fprintf(stderr, "[TLS_SLL_NORMALIZE_USERPTR] cls=%d node=%p -> base=%p stride=%zu\n", class_idx, raw, base, stride); + void* bt[16]; + int frames = backtrace(bt, 16); + backtrace_symbols_fd(bt, frames, fileno(stderr)); } return HAK_BASE_FROM_RAW(base); } @@ -306,9 +309,18 @@ bad: return 0; } +// Forward decl for head trace (definition below) +static inline void tls_sll_head_trace(int class_idx, + void* old_head, + void* new_head, + void* from_base, + const char* stage); + static inline void tls_sll_set_head(int class_idx, hak_base_ptr_t head, const char* stage) { void* raw = HAK_BASE_TO_RAW(head); + void* old_raw = HAK_BASE_TO_RAW(g_tls_sll[class_idx].head); + tls_sll_head_trace(class_idx, old_raw, raw, NULL, stage); if (!tls_sll_check_node(class_idx, raw, NULL, stage)) { abort(); } @@ -319,6 +331,8 @@ static inline void tls_sll_set_head(int class_idx, hak_base_ptr_t head, const ch static inline void tls_sll_set_head_from(int class_idx, hak_base_ptr_t head, void* from_base, const char* stage) { void* raw = HAK_BASE_TO_RAW(head); + void* old_raw = HAK_BASE_TO_RAW(g_tls_sll[class_idx].head); + tls_sll_head_trace(class_idx, old_raw, raw, from_base, stage); if (!tls_sll_check_node(class_idx, raw, from_base, stage)) { abort(); } @@ -396,6 +410,84 @@ static inline void tls_sll_diag_next(int class_idx, hak_base_ptr_t base, hak_bas #endif } +// Optional: trace head writes to locate corruption sources (env: HAKMEM_TINY_SLL_HEADLOG=1) +static inline void tls_sll_fetch_ptr_info(void* p, SuperSlab** out_ss, int* out_idx, uint8_t* out_cls) +{ + SuperSlab* ss = hak_super_lookup(p); + int cap = ss ? ss_slabs_capacity(ss) : 0; + int idx = (ss && ss->magic == SUPERSLAB_MAGIC) ? slab_index_for(ss, p) : -1; + uint8_t cls = (idx >= 0 && idx < cap) ? ss->slabs[idx].class_idx : 0xff; + if (out_ss) *out_ss = ss; + if (out_idx) *out_idx = idx; + if (out_cls) *out_cls = cls; +} + +static inline void tls_sll_head_trace(int class_idx, + void* old_head, + void* new_head, + void* from_base, + const char* stage) +{ + static int g_headlog_en = 1; // default ON for triage; disable with HAKMEM_TINY_SLL_HEADLOG=0 + static int g_headlog_cls = -2; // -1 = no filter; >=0 only that class + if (__builtin_expect(g_headlog_en == -1, 0)) { + const char* e = getenv("HAKMEM_TINY_SLL_HEADLOG"); + g_headlog_en = (e && *e && *e != '0') ? 1 : 0; + } else { + const char* e = getenv("HAKMEM_TINY_SLL_HEADLOG"); + if (e && *e == '0') g_headlog_en = 0; + } + if (g_headlog_cls == -2) { + const char* c = getenv("HAKMEM_TINY_SLL_HEADCLS"); + if (c && *c) { + g_headlog_cls = atoi(c); + } else { + g_headlog_cls = -1; + } + } + if (!__builtin_expect(g_headlog_en, 0)) return; + if (g_headlog_cls >= 0 && class_idx != g_headlog_cls) return; + + static _Atomic uint32_t g_headlog_shot = 0; + uint32_t shot = atomic_fetch_add_explicit(&g_headlog_shot, 1, memory_order_relaxed); + if (shot >= 256) return; + + uint32_t count_before = 0; + if (class_idx >= 0 && class_idx < TINY_NUM_CLASSES) { + count_before = g_tls_sll[class_idx].count; + } + + SuperSlab *new_ss = NULL, *old_ss = NULL, *from_ss = NULL; + int new_idx = -1, old_idx = -1, from_idx = -1; + uint8_t new_cls = 0xff, old_cls = 0xff, from_cls = 0xff; + tls_sll_fetch_ptr_info(new_head, &new_ss, &new_idx, &new_cls); + tls_sll_fetch_ptr_info(old_head, &old_ss, &old_idx, &old_cls); + tls_sll_fetch_ptr_info(from_base, &from_ss, &from_idx, &from_cls); + + fprintf(stderr, + "[TLS_SLL_HEAD_SET] shot=%u stage=%s cls=%d count=%u old=%p new=%p from=%p " + "new_ss=%p new_idx=%d new_cls=%u old_ss=%p old_idx=%d old_cls=%u " + "from_ss=%p from_idx=%d from_cls=%u last_writer=%s last_push=%p\n", + shot + 1, + stage ? stage : "(null)", + class_idx, + (unsigned)count_before, + old_head, + new_head, + from_base, + (void*)new_ss, + new_idx, + (unsigned)new_cls, + (void*)old_ss, + old_idx, + (unsigned)old_cls, + (void*)from_ss, + from_idx, + (unsigned)from_cls, + g_tls_sll_last_writer[class_idx] ? g_tls_sll_last_writer[class_idx] : "(null)", + HAK_BASE_TO_RAW(s_tls_sll_last_push[class_idx])); +} + // ========== Push ========== // // Push BASE pointer into TLS SLL for given class. diff --git a/core/hakmem_shared_pool_release.c b/core/hakmem_shared_pool_release.c index 4cb5a6db..41d91bba 100644 --- a/core/hakmem_shared_pool_release.c +++ b/core/hakmem_shared_pool_release.c @@ -74,6 +74,24 @@ shared_pool_release_slab(SuperSlab* ss, int slab_idx) uint8_t class_idx = slab_meta->class_idx; + // Guard: if SuperSlab is pinned (TLS/remote references), defer release to avoid + // class_map=255 while pointers are still in-flight. + uint32_t ss_refs_guard = superslab_ref_get(ss); + if (ss_refs_guard != 0) { +#if !HAKMEM_BUILD_RELEASE + if (dbg == 1) { + fprintf(stderr, + "[SP_SLOT_RELEASE_SKIP_PINNED] ss=%p slab_idx=%d class=%d refcount=%u\n", + (void*)ss, slab_idx, class_idx, (unsigned)ss_refs_guard); + } +#endif + if (g_lock_stats_enabled == 1) { + atomic_fetch_add(&g_lock_release_count, 1); + } + pthread_mutex_unlock(&g_shared_pool.alloc_lock); + return; + } + #if !HAKMEM_BUILD_RELEASE if (dbg == 1) { fprintf(stderr, "[SP_SLOT_RELEASE] ss=%p slab_idx=%d class=%d used=0 (marking EMPTY)\n", diff --git a/core/ptr_trace.h b/core/ptr_trace.h index f7f07a4e..0db26ac6 100644 --- a/core/ptr_trace.h +++ b/core/ptr_trace.h @@ -116,6 +116,12 @@ static inline void ptr_trace_dump_now(const char* reason) { (void)reason; } // Box API handles offset calculation internally based on class_idx. // `off` は呼び出し元互換用に受け取るが、アドレス計算には使わない(ログ専用)。 #define PTR_NEXT_WRITE(tag, cls, node, off, value) do { \ + g_tiny_next_tag = (tag); \ + g_tiny_next_file = __FILE__; \ + g_tiny_next_line = __LINE__; \ + g_tiny_next_ra0 = __builtin_return_address(0); \ + g_tiny_next_ra1 = __builtin_return_address(1); \ + g_tiny_next_ra2 = __builtin_return_address(2); \ (void)(off); \ tiny_next_write((cls), (node), (value)); \ ptr_trace_record((tag), (cls), (node), (value), (size_t)(off)); \ @@ -134,8 +140,17 @@ static inline void ptr_trace_dump_now(const char* reason) { (void)reason; } // Phase E1-CORRECT: Use Box API for all next pointer operations (Release mode) // `off` は互換用のダミーで、Box API が offset を決定する。 -#define PTR_NEXT_WRITE(tag, cls, node, off, value) \ - do { (void)(tag); (void)(off); tiny_next_write((cls), (node), (value)); } while (0) +#define PTR_NEXT_WRITE(tag, cls, node, off, value) \ + do { \ + g_tiny_next_tag = (tag); \ + g_tiny_next_file = __FILE__; \ + g_tiny_next_line = __LINE__; \ + g_tiny_next_ra0 = __builtin_return_address(0); \ + g_tiny_next_ra1 = __builtin_return_address(1); \ + g_tiny_next_ra2 = __builtin_return_address(2); \ + (void)(tag); (void)(off); \ + tiny_next_write((cls), (node), (value)); \ + } while (0) #define PTR_NEXT_READ(tag, cls, node, off, out_var) \ do { (void)(tag); (void)(off); (out_var) = tiny_next_read((cls), (node)); } while (0) diff --git a/core/tiny_free_fast.inc.h b/core/tiny_free_fast.inc.h index 06d9af01..48c52930 100644 --- a/core/tiny_free_fast.inc.h +++ b/core/tiny_free_fast.inc.h @@ -221,7 +221,8 @@ static inline void tiny_free_fast(void* ptr) { int slab_idx = slab_index_for(ss, ptr); // Convert USER → BASE for tiny_free_fast_ss (needed for next pointer operations) - void* base = (void*)((uint8_t*)ptr - tiny_user_offset(hak_slab_class(hak_slab_from_superslab(ss, slab_idx)))); + int class_idx = tiny_get_class_from_ss(ss, slab_idx); + void* base = (void*)((uint8_t*)ptr - tiny_user_offset(class_idx)); uint32_t self_tid = tiny_self_u32(); // Box 6 Boundary: Try same-thread fast path diff --git a/core/tiny_nextptr.h b/core/tiny_nextptr.h index 0ef29253..4d3a481d 100644 --- a/core/tiny_nextptr.h +++ b/core/tiny_nextptr.h @@ -47,6 +47,14 @@ #include "box/tiny_layout_box.h" #include "box/tiny_header_box.h" +// Per-thread trace context injected by PTR_NEXT_WRITE macro (for triage) +static __thread const char* g_tiny_next_tag = NULL; +static __thread const char* g_tiny_next_file = NULL; +static __thread int g_tiny_next_line = 0; +static __thread void* g_tiny_next_ra0 = NULL; +static __thread void* g_tiny_next_ra1 = NULL; +static __thread void* g_tiny_next_ra2 = NULL; + // Compute freelist next-pointer offset within a block for the given class. // P0.1 updated: C0 and C7 use offset 0, C1-C6 use offset 1 (header preserved) // Rationale for C0: 8B stride cannot fit [1B header][8B next pointer] without overflow @@ -54,6 +62,90 @@ static inline __attribute__((always_inline)) size_t tiny_next_off(int class_idx) return tiny_user_offset(class_idx); } +// Optional: log next-pointer writes for triage (env: HAKMEM_TINY_SLL_HEADLOG=1) +static inline void tiny_next_store_log(int class_idx, void* base, void* next, size_t off) +{ + static int g_nextlog_en = 1; // default ON for triage; disable with HAKMEM_TINY_SLL_HEADLOG=0 + static int g_nextlog_env_checked = 0; + static int g_nextlog_cls = -2; // -1 = no filter; >=0 = only that class + static const char* g_nextlog_tag_filter = NULL; // substring match; NULL = no filter + if (!g_nextlog_env_checked) { + const char* e = getenv("HAKMEM_TINY_SLL_HEADLOG"); + if (e && *e == '0') { + g_nextlog_en = 0; + } + const char* c = getenv("HAKMEM_TINY_SLL_NEXTCLS"); + if (c && *c) { + g_nextlog_cls = atoi(c); + } else { + g_nextlog_cls = -1; + } + g_nextlog_tag_filter = getenv("HAKMEM_TINY_SLL_NEXTTAG"); + g_nextlog_env_checked = 1; + } + if (!__builtin_expect(g_nextlog_en, 0)) return; + if (g_nextlog_cls >= 0 && class_idx != g_nextlog_cls) return; + + // Pull tag/callsite from TLS and clear immediately to avoid stale reuse + const char* tag = g_tiny_next_tag; + const char* file = g_tiny_next_file; + int line = g_tiny_next_line; + void* ra0 = g_tiny_next_ra0; + void* ra1 = g_tiny_next_ra1; + void* ra2 = g_tiny_next_ra2; + g_tiny_next_tag = NULL; + g_tiny_next_file = NULL; + g_tiny_next_line = 0; + g_tiny_next_ra0 = NULL; + g_tiny_next_ra1 = NULL; + g_tiny_next_ra2 = NULL; + if (!tag) return; + if (g_nextlog_tag_filter && !strstr(tag, g_nextlog_tag_filter)) return; + + static _Atomic uint32_t g_nextlog_shot = 0; + uint32_t shot = atomic_fetch_add_explicit(&g_nextlog_shot, 1, memory_order_relaxed); + if (shot >= 256) return; + + SuperSlab* ss = hak_super_lookup(base); + int cap = ss ? ss_slabs_capacity(ss) : 0; + int idx = (ss && ss->magic == SUPERSLAB_MAGIC) ? slab_index_for(ss, base) : -1; + uint8_t cls = (idx >= 0 && idx < cap) ? ss->slabs[idx].class_idx : 0xff; + void* ra = __builtin_return_address(0); + fprintf(stderr, + "[TINY_NEXT_STORE] shot=%u cls=%d base=%p next=%p off=%zu ss=%p idx=%d meta_cls=%u caller=%p tag=%s site=%s:%d ra0=%p ra1=%p ra2=%p\n", + shot + 1, + class_idx, + base, + next, + off, + (void*)ss, + idx, + (unsigned)cls, + ra, + tag, + file, + line, + ra0, + ra1, + ra2); + // Early frames for offline addr2line when caller symbols are missing + if (shot < 24) { + void* bt[16]; + int frames = backtrace(bt, 16); + backtrace_symbols_fd(bt, frames, fileno(stderr)); + } + // Backtrace only for clearly misaligned bases (likely user pointers) + if (((uintptr_t)base & 0xF) != 0) { + static _Atomic uint32_t g_next_bt = 0; + uint32_t bt_shot = atomic_fetch_add_explicit(&g_next_bt, 1, memory_order_relaxed); + if (bt_shot < 8) { + void* bt[16]; + int frames = backtrace(bt, 16); + backtrace_symbols_fd(bt, frames, fileno(stderr)); + } + } +} + // Safe load of next pointer from a block base. static inline __attribute__((always_inline)) void* tiny_next_load(const void* base, int class_idx) { size_t off = tiny_next_off(class_idx); @@ -109,12 +201,14 @@ static inline __attribute__((always_inline)) void tiny_next_store(void* base, in if (off == 0) { // Aligned access at base (overwrites header for C7). *(void**)base = next; + tiny_next_store_log(class_idx, base, next, off); return; } // off != 0: use memcpy for portability / UB-avoidance. uint8_t* p = (uint8_t*)base + off; memcpy(p, &next, sizeof(void*)); + tiny_next_store_log(class_idx, base, next, off); } #endif // TINY_NEXTPTR_H diff --git a/docs/DEFENSIVE_LAYERS_MAPPING.md b/docs/DEFENSIVE_LAYERS_MAPPING.md new file mode 100644 index 00000000..c79868b2 --- /dev/null +++ b/docs/DEFENSIVE_LAYERS_MAPPING.md @@ -0,0 +1,239 @@ +# 対処療法マッピング (2025-12-03) + +**目的**: 根本原因が特定された後、安全に対処療法を外すための記録 + +--- + +## 現在の対処療法(5層 + 診断ログ) + +### Layer 1: SuperSlab Refcount Pinning + +**場所**: `core/box/tls_sll_box.h` + +**実装**: +```c +// tls_sll_push_impl(): +SuperSlab* ss = hak_super_lookup(ptr); +if (ss) atomic_fetch_add(&ss->refcount, 1); // PIN + +// tls_sll_pop_impl(): +SuperSlab* ss = hak_super_lookup(ptr); +if (ss) atomic_fetch_sub(&ss->refcount, 1); // UNPIN +``` + +**目的**: TLS SLL がポイントしてる SuperSlab が解放されるのを防ぐ + +**隠蔽される症状**: +- Use-After-Free(SuperSlab が free された後のアクセス) +- [TLS_SLL_HDR_RESET] の一部 + +**削除時のリスク**: +- SuperSlab が参照中に解放される +- SIGSEGV の再発 + +**テスト方法**: +- Layer 1 を無効化 → sh8bench 60秒 +- [TLS_SLL_HDR_RESET] / SIGSEGV が発生するか確認 + +--- + +### Layer 2: SuperSlab Release Guards + +**場所**: +- `core/superslab_allocate.c` (superslab_free) +- `core/hakmem_shared_pool_release.c` (shared_pool_release_slab) +- `core/box/ss_allocation_box.c` + +**実装**: +```c +// superslab_free(): +if (ss->refcount > 0) { + return; // DEFER - do not free +} +``` + +**目的**: refcount > 0 の SuperSlab を解放しない + +**隠蔽される症状**: +- class_map が 255 (UNASSIGNED) になる +- meta_cls=255 ログ + +**削除時のリスク**: +- class_map が書き換わる +- TLS SLL の class_idx ミスマッチ + +**テスト方法**: +- Layer 2 を無効化 → sh8bench 60秒 +- [TLS_SLL_NEXT_INVALID] + meta_cls=255 が増えるか確認 + +--- + +### Layer 3: TLS SLL Next Pointer Validation + +**場所**: `core/box/tls_sll_box.h` + +**実装**: +```c +// tls_sll_pop_impl() での next pointer traversal: +if (!is_valid_pointer(next)) { + fprintf(stderr, "[TLS_SLL_NEXT_INVALID] ..."); + g_tls_sll[class_idx].head = NULL; // DROP +} +``` + +**目的**: 無効な next ポインタを検知して早期にリストを DROP + +**隠蔽される症状**: +- Freelist corruption +- 次ポインタが別領域を指す + +**削除時のリスク**: +- 無効なポインタが dereference される +- SIGSEGV + +**テスト方法**: +- Layer 3 を無効化 → sh8bench 60秒 +- SIGSEGV が発生するか確認 + +--- + +### Layer 4: Unified Cache Freelist Validation + +**場所**: `core/front/tiny_unified_cache.c` + +**実装**: +```c +// unified_cache_refill(): +if (!is_valid_slab(head)) { + fprintf(stderr, "[UNIFIED_FREELIST_INVALID] ..."); + freelist[i] = NULL; // DROP +} +``` + +**目的**: 無効な freelist head を検知して DROP + +**隠蔽される症状**: +- freelist が別スラブを指す +- スラブ境界を越えたリンク + +**削除時のリスク**: +- 不正なブロックが割り当てられる +- メモリ破壊 + +**テスト方法**: +- Layer 4 を無効化 → sh8bench 60秒 +- [UNIFIED_FREELIST_INVALID] が増えるか確認 + +--- + +### Layer 5: Early Decrement Fix + +**場所**: `core/tiny_free_fast.inc.h` + +**実装**: +```c +// 削除された行: +// ss_active_dec_one(ss); // 高速パスでの早期デクリメント +``` + +**目的**: refcount の過剰デクリメントを防止 + +**隠蔽される症状**: +- refcount が 0 に早く落ちすぎる +- Layer 1/2 のガードが効かなくなる + +**削除時のリスク**: +- Layer 1/2 の効果が無効化される + +**テスト方法**: +- 早期デクリメントを復元 → sh8bench 60秒 +- refcount が早く 0 になり、SIGSEGV が発生するか確認 + +--- + +### 診断ログ追加 + +**場所**: `core/box/tls_sll_box.h`, 各所 + +**追加されたログ**: +- `[TLS_SLL_HEAD_SET]` - head 設定時のトレース +- `[TLS_SLL_NEXT_INVALID]` - 無効な next ポインタ検知 +- `[UNIFIED_FREELIST_INVALID]` - freelist head 無効 +- `[TLS_SLL_NORMALIZE_USERPTR]` - userptr の正規化 +- `[TLS_SLL_SANITIZE]` - head の検証と修復 +- `[TINY_NEXT_STORE]` - next ポインタ書き込みのトレース + +**フィルタ環境変数**: +- `HAKMEM_TINY_SLL_NEXTCLS` - next store のクラスフィルタ (-1 で無効) +- `HAKMEM_TINY_SLL_NEXTTAG` - next store のタグフィルタ (部分一致) +- `HAKMEM_TINY_SLL_HEADCLS` - head trace のクラスフィルタ + +--- + +## 根本修正後の削除順序(推奨) + +### Phase 1: 診断ログを維持しながら確認 + +1. 根本修正を適用 +2. sh8bench 60秒テスト +3. 診断ログが**ゼロ**になることを確認 + +### Phase 2: Validation layers (Layer 3/4) を削除 + +1. Layer 3 (next validation) を無効化 +2. sh8bench 60秒テスト +3. [TLS_SLL_NEXT_INVALID] がゼロ(本当に発生しない)ことを確認 +4. Layer 4 (freelist validation) を無効化 +5. sh8bench 60秒テスト +6. 問題がないことを確認 + +### Phase 3: Refcount layers (Layer 1/2) を削除 + +1. Layer 2 (release guards) を無効化 +2. sh8bench 60秒テスト +3. class_map が正常に動作することを確認 +4. Layer 1 (refcount pinning) を無効化 +5. sh8bench 60秒テスト +6. SuperSlab lifecycle が正常であることを確認 + +### Phase 4: 診断ログを削除 + +1. ショット制限を復元(または ENV で無効化) +2. パフォーマンステスト +3. オーバーヘッドがないことを確認 + +--- + +## 削除チェックリスト + +| Layer | ファイル | 行 | 削除方法 | テスト | +|-------|---------|-----|---------|--------| +| 1 | tls_sll_box.h | push/pop | atomic_fetch_add/sub を削除 | sh8bench 60s | +| 2 | superslab_allocate.c | superslab_free | if (refcount>0) return を削除 | sh8bench 60s | +| 2 | shared_pool_release.c | release_slab | 同上 | sh8bench 60s | +| 2 | ss_allocation_box.c | 各所 | 同上 | sh8bench 60s | +| 3 | tls_sll_box.h | pop | validation + DROP を削除 | sh8bench 60s | +| 4 | tiny_unified_cache.c | refill | validation + DROP を削除 | sh8bench 60s | +| 5 | tiny_free_fast.inc.h | - | 既に削除済み(復元しない) | - | + +--- + +## 注意事項 + +1. **一度に全部外さない** - 1層ずつ外してテスト +2. **ログは最後に外す** - 問題が再発したら診断できるように +3. **テストは複数回** - タイミング依存の問題がある可能性 +4. **メモリ監視も継続** - RSS が増加しないことを確認 + +--- + +## 関連ドキュメント + +- `docs/ANALYSIS_SYMPTOM_PROLIFERATION.md` - 対処療法の分析 +- `docs/BREAKTHROUGH_STABILITY_ACHIEVED.md` - 安定性達成の報告 +- `docs/CRITICAL_DISCOVERY_TLS_HEAD_CORRUPTION.md` - TLS head 汚染の発見 + +--- + +*Document created: 2025-12-03* +*Purpose: Enable safe removal of defensive layers after root cause fix* diff --git a/echo b/echo new file mode 100644 index 00000000..9e32be07 --- /dev/null +++ b/echo @@ -0,0 +1 @@ +/bin/bash: 行 1: MONITOR_PID=: コマンドが見つかりません diff --git a/kill b/kill new file mode 100644 index 00000000..9e32be07 --- /dev/null +++ b/kill @@ -0,0 +1 @@ +/bin/bash: 行 1: MONITOR_PID=: コマンドが見つかりません