404 lines
13 KiB
Markdown
404 lines
13 KiB
Markdown
|
|
# 4T Larson 残存クラッシュ完全分析 (30% Crash Rate)
|
|||
|
|
|
|||
|
|
**日時:** 2025-11-07
|
|||
|
|
**目標:** 残り 30% のクラッシュを完全解消し、100% 成功達成
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 現状サマリー
|
|||
|
|
|
|||
|
|
- **成功率:** 70% (14/20 runs)
|
|||
|
|
- **クラッシュ率:** 30% (6/20 runs)
|
|||
|
|
- **エラーメッセージ:** `free(): invalid pointer` → SIGABRT
|
|||
|
|
- **Backtrace:** `log_superslab_oom_once()` 内の `fclose()` → `__libc_free()` で発生
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔍 発見したバグ一覧
|
|||
|
|
|
|||
|
|
### **BUG #7: malloc() wrapper の getenv() 呼び出し (CRITICAL!)**
|
|||
|
|
**ファイル:** `/mnt/workdisk/public_share/hakmem/core/box/hak_wrappers.inc.h:51`
|
|||
|
|
**症状:** `g_hakmem_lock_depth++` より**前**に `getenv()` を呼び出している
|
|||
|
|
|
|||
|
|
**問題のコード:**
|
|||
|
|
```c
|
|||
|
|
void* malloc(size_t size) {
|
|||
|
|
// ... (line 40-45: g_initializing check - OK)
|
|||
|
|
|
|||
|
|
// BUG: getenv() is called BEFORE g_hakmem_lock_depth++
|
|||
|
|
static _Atomic int debug_enabled = -1;
|
|||
|
|
if (__builtin_expect(debug_enabled < 0, 0)) {
|
|||
|
|
debug_enabled = (getenv("HAKMEM_SFC_DEBUG") != NULL) ? 1 : 0; // ← BUG!
|
|||
|
|
}
|
|||
|
|
if (debug_enabled && debug_count < 100) {
|
|||
|
|
int n = atomic_fetch_add(&debug_count, 1);
|
|||
|
|
if (n < 20) fprintf(stderr, "[SFC_DEBUG] malloc(%zu)\n", size); // ← BUG!
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (__builtin_expect(hak_force_libc_alloc(), 0)) { // ← BUG! (calls getenv)
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int ld_mode = hak_ld_env_mode(); // ← BUG! (calls getenv + strstr)
|
|||
|
|
// ...
|
|||
|
|
|
|||
|
|
g_hakmem_lock_depth++; // ← TOO LATE!
|
|||
|
|
void* ptr = hak_alloc_at(size, HAK_CALLSITE());
|
|||
|
|
g_hakmem_lock_depth--;
|
|||
|
|
return ptr;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**なぜクラッシュするか:**
|
|||
|
|
1. **fclose() が malloc() を呼ぶ** (internal buffer allocation)
|
|||
|
|
2. **malloc() wrapper が getenv("HAKMEM_SFC_DEBUG") を呼ぶ** (line 51)
|
|||
|
|
3. **getenv() 自体は malloc しない**が、**fprintf(stderr, ...)** (line 55) が malloc を呼ぶ可能性
|
|||
|
|
4. **再帰:** malloc → fprintf → malloc → ... (無限ループまたはクラッシュ)
|
|||
|
|
|
|||
|
|
**影響範囲:**
|
|||
|
|
- `getenv("HAKMEM_SFC_DEBUG")` (line 51)
|
|||
|
|
- `fprintf(stderr, ...)` (line 55)
|
|||
|
|
- `hak_force_libc_alloc()` → `getenv("HAKMEM_FORCE_LIBC_ALLOC")`, `getenv("HAKMEM_WRAP_TINY")` (line 115, 119)
|
|||
|
|
- `hak_ld_env_mode()` → `getenv("LD_PRELOAD")` + `strstr()` (line 101, 102)
|
|||
|
|
- `hak_jemalloc_loaded()` → **`dlopen()`** (line 135) - **これが最も危険!**
|
|||
|
|
- `getenv("HAKMEM_LD_SAFE")` (line 77)
|
|||
|
|
|
|||
|
|
**修正方法:**
|
|||
|
|
```c
|
|||
|
|
void* malloc(size_t size) {
|
|||
|
|
// CRITICAL FIX: Increment lock depth FIRST, before ANY libc calls
|
|||
|
|
g_hakmem_lock_depth++;
|
|||
|
|
|
|||
|
|
// Guard against recursion during initialization
|
|||
|
|
if (__builtin_expect(g_initializing != 0, 0)) {
|
|||
|
|
g_hakmem_lock_depth--;
|
|||
|
|
extern void* __libc_malloc(size_t);
|
|||
|
|
return __libc_malloc(size);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Now safe to call getenv/fprintf/dlopen (will use __libc_malloc if needed)
|
|||
|
|
static _Atomic int debug_enabled = -1;
|
|||
|
|
if (__builtin_expect(debug_enabled < 0, 0)) {
|
|||
|
|
debug_enabled = (getenv("HAKMEM_SFC_DEBUG") != NULL) ? 1 : 0;
|
|||
|
|
}
|
|||
|
|
if (debug_enabled && debug_count < 100) {
|
|||
|
|
int n = atomic_fetch_add(&debug_count, 1);
|
|||
|
|
if (n < 20) fprintf(stderr, "[SFC_DEBUG] malloc(%zu)\n", size);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (__builtin_expect(hak_force_libc_alloc(), 0)) {
|
|||
|
|
g_hakmem_lock_depth--;
|
|||
|
|
extern void* __libc_malloc(size_t);
|
|||
|
|
return __libc_malloc(size);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int ld_mode = hak_ld_env_mode();
|
|||
|
|
if (ld_mode) {
|
|||
|
|
if (hak_ld_block_jemalloc() && hak_jemalloc_loaded()) {
|
|||
|
|
g_hakmem_lock_depth--;
|
|||
|
|
extern void* __libc_malloc(size_t);
|
|||
|
|
return __libc_malloc(size);
|
|||
|
|
}
|
|||
|
|
if (!g_initialized) { hak_init(); }
|
|||
|
|
if (g_initializing) {
|
|||
|
|
g_hakmem_lock_depth--;
|
|||
|
|
extern void* __libc_malloc(size_t);
|
|||
|
|
return __libc_malloc(size);
|
|||
|
|
}
|
|||
|
|
static _Atomic int ld_safe_mode = -1;
|
|||
|
|
if (__builtin_expect(ld_safe_mode < 0, 0)) {
|
|||
|
|
const char* lds = getenv("HAKMEM_LD_SAFE");
|
|||
|
|
ld_safe_mode = (lds ? atoi(lds) : 1);
|
|||
|
|
}
|
|||
|
|
if (ld_safe_mode >= 2 || size > TINY_MAX_SIZE) {
|
|||
|
|
g_hakmem_lock_depth--;
|
|||
|
|
extern void* __libc_malloc(size_t);
|
|||
|
|
return __libc_malloc(size);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void* ptr = hak_alloc_at(size, HAK_CALLSITE());
|
|||
|
|
g_hakmem_lock_depth--;
|
|||
|
|
return ptr;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**優先度:** ⭐⭐⭐⭐⭐ (CRITICAL - これが 30% クラッシュの主原因!)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### **BUG #8: calloc() wrapper の getenv() 呼び出し**
|
|||
|
|
**ファイル:** `/mnt/workdisk/public_share/hakmem/core/box/hak_wrappers.inc.h:122`
|
|||
|
|
**症状:** `g_hakmem_lock_depth++` より**前**に `getenv()` を呼び出している
|
|||
|
|
|
|||
|
|
**問題のコード:**
|
|||
|
|
```c
|
|||
|
|
void* calloc(size_t nmemb, size_t size) {
|
|||
|
|
if (g_hakmem_lock_depth > 0) { /* ... */ }
|
|||
|
|
if (__builtin_expect(g_initializing != 0, 0)) { /* ... */ }
|
|||
|
|
if (size != 0 && nmemb > (SIZE_MAX / size)) { errno = ENOMEM; return NULL; }
|
|||
|
|
if (__builtin_expect(hak_force_libc_alloc(), 0)) { /* ... */ } // ← BUG!
|
|||
|
|
int ld_mode = hak_ld_env_mode(); // ← BUG!
|
|||
|
|
if (ld_mode) {
|
|||
|
|
if (hak_ld_block_jemalloc() && hak_jemalloc_loaded()) { /* ... */ } // ← BUG!
|
|||
|
|
if (!g_initialized) { hak_init(); }
|
|||
|
|
if (g_initializing) { /* ... */ }
|
|||
|
|
static _Atomic int ld_safe_mode_calloc = -1;
|
|||
|
|
if (__builtin_expect(ld_safe_mode_calloc < 0, 0)) {
|
|||
|
|
const char* lds = getenv("HAKMEM_LD_SAFE"); // ← BUG!
|
|||
|
|
ld_safe_mode_calloc = (lds ? atoi(lds) : 1);
|
|||
|
|
}
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
g_hakmem_lock_depth++; // ← TOO LATE!
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修正方法:** malloc() と同様に `g_hakmem_lock_depth++` を先頭に移動
|
|||
|
|
|
|||
|
|
**優先度:** ⭐⭐⭐⭐⭐ (CRITICAL)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### **BUG #9: realloc() wrapper の malloc/free 呼び出し**
|
|||
|
|
**ファイル:** `/mnt/workdisk/public_share/hakmem/core/box/hak_wrappers.inc.h:146-151`
|
|||
|
|
**症状:** `g_hakmem_lock_depth` チェックはあるが、`malloc()`/`free()` を直接呼び出している
|
|||
|
|
|
|||
|
|
**問題のコード:**
|
|||
|
|
```c
|
|||
|
|
void* realloc(void* ptr, size_t size) {
|
|||
|
|
if (g_hakmem_lock_depth > 0) { /* ... */ }
|
|||
|
|
// ... (various checks)
|
|||
|
|
if (ptr == NULL) { return malloc(size); } // ← OK (malloc handles lock_depth)
|
|||
|
|
if (size == 0) { free(ptr); return NULL; } // ← OK (free handles lock_depth)
|
|||
|
|
void* new_ptr = malloc(size); // ← OK
|
|||
|
|
if (!new_ptr) return NULL;
|
|||
|
|
memcpy(new_ptr, ptr, size); // ← OK (memcpy doesn't malloc)
|
|||
|
|
free(ptr); // ← OK
|
|||
|
|
return new_ptr;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**実際のところ:** これは**問題なし** (malloc/free が再帰を処理している)
|
|||
|
|
|
|||
|
|
**優先度:** - (False positive)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### **BUG #10: dlopen() による malloc 呼び出し (CRITICAL!)**
|
|||
|
|
**ファイル:** `/mnt/workdisk/public_share/hakmem/core/hakmem.c:135`
|
|||
|
|
**症状:** `hak_jemalloc_loaded()` 内の `dlopen()` が malloc を呼ぶ
|
|||
|
|
|
|||
|
|
**問題のコード:**
|
|||
|
|
```c
|
|||
|
|
static inline int hak_jemalloc_loaded(void) {
|
|||
|
|
if (g_jemalloc_loaded < 0) {
|
|||
|
|
// dlopen() は内部で malloc() を呼ぶ!
|
|||
|
|
void* h = dlopen("libjemalloc.so.2", RTLD_NOLOAD | RTLD_NOW); // ← BUG!
|
|||
|
|
if (!h) h = dlopen("libjemalloc.so.1", RTLD_NOLOAD | RTLD_NOW); // ← BUG!
|
|||
|
|
g_jemalloc_loaded = (h != NULL) ? 1 : 0;
|
|||
|
|
if (h) dlclose(h); // ← BUG!
|
|||
|
|
}
|
|||
|
|
return g_jemalloc_loaded;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**なぜクラッシュするか:**
|
|||
|
|
1. **dlopen() は内部で malloc() を呼ぶ** (dynamic linker が内部データ構造を確保)
|
|||
|
|
2. **malloc() wrapper が `hak_jemalloc_loaded()` を呼ぶ**
|
|||
|
|
3. **再帰:** malloc → hak_jemalloc_loaded → dlopen → malloc → ...
|
|||
|
|
|
|||
|
|
**修正方法:**
|
|||
|
|
この関数は `g_hakmem_lock_depth++` より**前**に呼ばれるため、**dlopen が呼ぶ malloc は wrapper に戻ってくる**!
|
|||
|
|
|
|||
|
|
**解決策:** `hak_jemalloc_loaded()` を**初期化時に一度だけ**実行し、wrapper hot path から削除
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// In hakmem.c (initialization function):
|
|||
|
|
void hak_init(void) {
|
|||
|
|
// ... existing init code ...
|
|||
|
|
|
|||
|
|
// Pre-detect jemalloc ONCE during init (not on hot path!)
|
|||
|
|
if (g_jemalloc_loaded < 0) {
|
|||
|
|
g_hakmem_lock_depth++; // Protect dlopen's internal malloc
|
|||
|
|
void* h = dlopen("libjemalloc.so.2", RTLD_NOLOAD | RTLD_NOW);
|
|||
|
|
if (!h) h = dlopen("libjemalloc.so.1", RTLD_NOLOAD | RTLD_NOW);
|
|||
|
|
g_jemalloc_loaded = (h != NULL) ? 1 : 0;
|
|||
|
|
if (h) dlclose(h);
|
|||
|
|
g_hakmem_lock_depth--;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// In wrapper:
|
|||
|
|
void* malloc(size_t size) {
|
|||
|
|
g_hakmem_lock_depth++;
|
|||
|
|
|
|||
|
|
if (__builtin_expect(g_initializing != 0, 0)) {
|
|||
|
|
g_hakmem_lock_depth--;
|
|||
|
|
extern void* __libc_malloc(size_t);
|
|||
|
|
return __libc_malloc(size);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int ld_mode = hak_ld_env_mode();
|
|||
|
|
if (ld_mode) {
|
|||
|
|
// Now safe - g_jemalloc_loaded is pre-computed during init
|
|||
|
|
if (hak_ld_block_jemalloc() && g_jemalloc_loaded) {
|
|||
|
|
g_hakmem_lock_depth--;
|
|||
|
|
extern void* __libc_malloc(size_t);
|
|||
|
|
return __libc_malloc(size);
|
|||
|
|
}
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**優先度:** ⭐⭐⭐⭐⭐ (CRITICAL - dlopen による再帰は非常に危険!)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### **BUG #11: fprintf(stderr, ...) による潜在的 malloc**
|
|||
|
|
**ファイル:** 複数 (hakmem_batch.c, slab_handle.h, etc.)
|
|||
|
|
**症状:** fprintf(stderr, ...) が内部バッファ確保で malloc を呼ぶ可能性
|
|||
|
|
|
|||
|
|
**問題のコード:**
|
|||
|
|
```c
|
|||
|
|
// hakmem_batch.c:92 (初期化時)
|
|||
|
|
fprintf(stderr, "[Batch] Initialized (threshold=%d MB, min_size=%d KB, bg=%s)\n",
|
|||
|
|
BATCH_THRESHOLD / (1024 * 1024), BATCH_MIN_SIZE / 1024, g_bg_enabled?"on":"off");
|
|||
|
|
|
|||
|
|
// slab_handle.h:95 (debug build only)
|
|||
|
|
#ifdef HAKMEM_DEBUG_VERBOSE
|
|||
|
|
fprintf(stderr, "[SLAB_HANDLE] drain_remote: invalid handle\n");
|
|||
|
|
#endif
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**実際のところ:**
|
|||
|
|
- **stderr は通常 unbuffered** (no malloc)
|
|||
|
|
- **ただし初回 fprintf 時に内部構造を確保する可能性がある**
|
|||
|
|
- `log_superslab_oom_once()` では既に `g_hakmem_lock_depth++` している (OK)
|
|||
|
|
|
|||
|
|
**修正不要な理由:**
|
|||
|
|
1. `hakmem_batch.c:92` は初期化時 (`g_initializing` チェック後)
|
|||
|
|
2. `slab_handle.h` の fprintf は `#ifdef HAKMEM_DEBUG_VERBOSE` (本番では無効)
|
|||
|
|
3. その他の fprintf は `g_hakmem_lock_depth` 保護下
|
|||
|
|
|
|||
|
|
**優先度:** ⭐ (Low - 本番環境では問題なし)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### **BUG #12: strstr() と atoi() の安全性**
|
|||
|
|
**ファイル:** `/mnt/workdisk/public_share/hakmem/core/hakmem.c:102, 117`
|
|||
|
|
|
|||
|
|
**実際のところ:**
|
|||
|
|
- **strstr():** malloc しない (単なる文字列検索)
|
|||
|
|
- **atoi():** malloc しない (単純な変換)
|
|||
|
|
|
|||
|
|
**優先度:** - (False positive)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 修正優先順位
|
|||
|
|
|
|||
|
|
### **最優先 (CRITICAL):**
|
|||
|
|
1. **BUG #7:** `malloc()` wrapper の `g_hakmem_lock_depth++` を**最初**に移動
|
|||
|
|
2. **BUG #8:** `calloc()` wrapper の `g_hakmem_lock_depth++` を**最初**に移動
|
|||
|
|
3. **BUG #10:** `dlopen()` 呼び出しを初期化時に移動
|
|||
|
|
|
|||
|
|
### **中優先:**
|
|||
|
|
- なし
|
|||
|
|
|
|||
|
|
### **低優先:**
|
|||
|
|
- **BUG #11:** fprintf(stderr, ...) の監視 (debug build のみ)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 修正パッチ案
|
|||
|
|
|
|||
|
|
### **パッチ 1: hak_wrappers.inc.h (BUG #7, #8)**
|
|||
|
|
|
|||
|
|
**修正箇所:** `/mnt/workdisk/public_share/hakmem/core/box/hak_wrappers.inc.h`
|
|||
|
|
|
|||
|
|
**変更内容:**
|
|||
|
|
1. `malloc()`: `g_hakmem_lock_depth++` を line 41 (関数開始直後) に移動
|
|||
|
|
2. `calloc()`: `g_hakmem_lock_depth++` を line 109 (関数開始直後) に移動
|
|||
|
|
3. 全ての early return 前に `g_hakmem_lock_depth--` を追加
|
|||
|
|
|
|||
|
|
**影響範囲:**
|
|||
|
|
- wrapper のすべての呼び出しパス
|
|||
|
|
- 30% クラッシュの主原因を修正
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### **パッチ 2: hakmem.c (BUG #10)**
|
|||
|
|
|
|||
|
|
**修正箇所:** `/mnt/workdisk/public_share/hakmem/core/hakmem.c`
|
|||
|
|
|
|||
|
|
**変更内容:**
|
|||
|
|
1. `hak_init()` 内で `hak_jemalloc_loaded()` を**一度だけ**実行
|
|||
|
|
2. wrapper hot path から `hak_jemalloc_loaded()` 呼び出しを削除し、キャッシュ済み `g_jemalloc_loaded` 変数を直接参照
|
|||
|
|
|
|||
|
|
**影響範囲:**
|
|||
|
|
- LD_PRELOAD モードの初期化
|
|||
|
|
- dlopen による再帰を完全排除
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 検証方法
|
|||
|
|
|
|||
|
|
### **テスト 1: 4T Larson (100 runs)**
|
|||
|
|
```bash
|
|||
|
|
for i in {1..100}; do
|
|||
|
|
echo "Run $i/100"
|
|||
|
|
./larson_hakmem 4 8 128 1024 1 12345 4 || echo "CRASH at run $i"
|
|||
|
|
done
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**期待結果:** 100/100 成功 (0% crash rate)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### **テスト 2: Valgrind (memory leak detection)**
|
|||
|
|
```bash
|
|||
|
|
valgrind --leak-check=full --show-leak-kinds=all \
|
|||
|
|
./larson_hakmem 2 8 128 1024 1 12345 2
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**期待結果:** No invalid free, no memory leaks
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### **テスト 3: gdb (crash analysis)**
|
|||
|
|
```bash
|
|||
|
|
gdb -batch -ex "run 4 8 128 1024 1 12345 4" \
|
|||
|
|
-ex "bt" -ex "info registers" ./larson_hakmem
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**期待結果:** No SIGABRT, clean exit
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 期待される効果
|
|||
|
|
|
|||
|
|
| 項目 | 修正前 | 修正後 |
|
|||
|
|
|------|--------|--------|
|
|||
|
|
| **成功率** | 70% | **100%** ✅ |
|
|||
|
|
| **クラッシュ率** | 30% | **0%** ✅ |
|
|||
|
|
| **SIGABRT** | 6/20 runs | **0/20 runs** ✅ |
|
|||
|
|
| **Invalid pointer** | Yes | **No** ✅ |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🚨 Critical Insight
|
|||
|
|
|
|||
|
|
**根本原因:**
|
|||
|
|
- `g_hakmem_lock_depth++` の位置が**遅すぎる**
|
|||
|
|
- getenv/fprintf/dlopen などの LIBC 関数が**ガード前**に実行されている
|
|||
|
|
- これらの関数が内部で malloc を呼ぶと**無限再帰**または**クラッシュ**
|
|||
|
|
|
|||
|
|
**修正の本質:**
|
|||
|
|
- **ガードを最初に設定** → すべての LIBC 呼び出しが `__libc_malloc` にルーティングされる
|
|||
|
|
- **dlopen を初期化時に実行** → hot path から除外
|
|||
|
|
|
|||
|
|
**これで 30% クラッシュは完全解消される!** 🎉
|