591 lines
17 KiB
Markdown
591 lines
17 KiB
Markdown
|
|
# ポインタ変換バグの根本原因分析
|
||
|
|
|
||
|
|
## 🔍 調査結果サマリー
|
||
|
|
|
||
|
|
**バグの本質**: **DOUBLE CONVERSION** - BASE → USER 変換が2回実行されている
|
||
|
|
|
||
|
|
**影響範囲**: Class 7 (1KB headerless) で alignment error が発生
|
||
|
|
|
||
|
|
**修正方法**: TLS SLL は BASE pointer を保存し、HAK_RET_ALLOC で USER 変換を1回だけ実行
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📊 完全なポインタ契約マップ
|
||
|
|
|
||
|
|
### 1. ストレージレイアウト
|
||
|
|
|
||
|
|
```
|
||
|
|
Phase E1-CORRECT: ALL classes (C0-C7) have 1-byte header
|
||
|
|
|
||
|
|
Memory Layout:
|
||
|
|
storage[0] = 1-byte header (0xa0 | class_idx)
|
||
|
|
storage[1..N] = user data
|
||
|
|
|
||
|
|
Pointers:
|
||
|
|
BASE = storage (points to header at offset 0)
|
||
|
|
USER = storage+1 (points to user data at offset 1)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Allocation Path (正常)
|
||
|
|
|
||
|
|
#### 2.1 HAK_RET_ALLOC マクロ (hakmem_tiny.c:160-162)
|
||
|
|
|
||
|
|
```c
|
||
|
|
#define HAK_RET_ALLOC(cls, base_ptr) do { \
|
||
|
|
*(uint8_t*)(base_ptr) = HEADER_MAGIC | ((cls) & HEADER_CLASS_MASK); \
|
||
|
|
return (void*)((uint8_t*)(base_ptr) + 1); // ✅ BASE → USER 変換
|
||
|
|
} while(0)
|
||
|
|
```
|
||
|
|
|
||
|
|
**契約**:
|
||
|
|
- INPUT: BASE pointer (storage)
|
||
|
|
- OUTPUT: USER pointer (storage+1)
|
||
|
|
- **変換回数**: 1回 ✅
|
||
|
|
|
||
|
|
#### 2.2 Linear Carve (tiny_refill_opt.h:292-313)
|
||
|
|
|
||
|
|
```c
|
||
|
|
uint8_t* cursor = base + (meta->carved * stride);
|
||
|
|
void* head = (void*)cursor; // ← BASE pointer
|
||
|
|
|
||
|
|
// Line 313: Write header to storage[0]
|
||
|
|
*block = HEADER_MAGIC | class_idx;
|
||
|
|
|
||
|
|
// Line 334: Link chain using BASE pointers
|
||
|
|
tiny_next_write(class_idx, cursor, next); // ← BASE + next_offset
|
||
|
|
```
|
||
|
|
|
||
|
|
**契約**:
|
||
|
|
- 生成: BASE pointer chain
|
||
|
|
- Header: 書き込み済み (line 313)
|
||
|
|
- Next pointer: base+1 に保存 (C0-C6)
|
||
|
|
|
||
|
|
#### 2.3 TLS SLL Splice (tls_sll_box.h:449-561)
|
||
|
|
|
||
|
|
```c
|
||
|
|
static inline uint32_t tls_sll_splice(int class_idx, void* chain_head, ...) {
|
||
|
|
// Line 508: Restore headers for ALL nodes
|
||
|
|
*(uint8_t*)node = HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK);
|
||
|
|
|
||
|
|
// Line 557: Set SLL head to BASE pointer
|
||
|
|
g_tls_sll_head[class_idx] = chain_head; // ← BASE pointer
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**契約**:
|
||
|
|
- INPUT: BASE pointer chain
|
||
|
|
- 保存: BASE pointers in SLL
|
||
|
|
- Header: Defense in depth で再書き込み (line 508)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. ⚠️ BUG: TLS SLL Pop (tls_sll_box.h:224-430)
|
||
|
|
|
||
|
|
#### 3.1 Pop 実装 (BEFORE FIX)
|
||
|
|
|
||
|
|
```c
|
||
|
|
static inline bool tls_sll_pop(int class_idx, void** out) {
|
||
|
|
void* base = g_tls_sll_head[class_idx]; // ← BASE pointer
|
||
|
|
if (!base) return false;
|
||
|
|
|
||
|
|
// Read next pointer
|
||
|
|
void* next = tiny_next_read(class_idx, base);
|
||
|
|
g_tls_sll_head[class_idx] = next;
|
||
|
|
|
||
|
|
*out = base; // ✅ Return BASE pointer
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**契約 (設計意図)**:
|
||
|
|
- SLL stores: BASE pointers
|
||
|
|
- Returns: BASE pointer ✅
|
||
|
|
- Caller: HAK_RET_ALLOC で BASE → USER 変換
|
||
|
|
|
||
|
|
#### 3.2 Allocation 呼び出し側 (tiny_alloc_fast.inc.h:271-291)
|
||
|
|
|
||
|
|
```c
|
||
|
|
void* base = NULL;
|
||
|
|
if (tls_sll_pop(class_idx, &base)) {
|
||
|
|
// ✅ FIX #16 comment: "Return BASE pointer (not USER)"
|
||
|
|
// Line 290: "Caller will call HAK_RET_ALLOC → tiny_region_id_write_header"
|
||
|
|
return base; // ← BASE pointer を返す
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**契約**:
|
||
|
|
- `tls_sll_pop()` returns: BASE
|
||
|
|
- `tiny_alloc_fast_pop()` returns: BASE
|
||
|
|
- **Caller will apply HAK_RET_ALLOC** ✅
|
||
|
|
|
||
|
|
#### 3.3 tiny_alloc_fast() 呼び出し (tiny_alloc_fast.inc.h:580-582)
|
||
|
|
|
||
|
|
```c
|
||
|
|
ptr = tiny_alloc_fast_pop(class_idx); // ← BASE pointer
|
||
|
|
if (__builtin_expect(ptr != NULL, 1)) {
|
||
|
|
HAK_RET_ALLOC(class_idx, ptr); // ← BASE → USER 変換 (1回目) ✅
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**変換回数**: 1回 ✅ (正常)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 4. 🐛 **ROOT CAUSE: DOUBLE CONVERSION in Free Path**
|
||
|
|
|
||
|
|
#### 4.1 Application → hak_free_at()
|
||
|
|
|
||
|
|
```c
|
||
|
|
// Application frees USER pointer
|
||
|
|
void* user_ptr = malloc(1024); // Returns storage+1
|
||
|
|
free(user_ptr); // ← USER pointer
|
||
|
|
```
|
||
|
|
|
||
|
|
**INPUT**: USER pointer (storage+1)
|
||
|
|
|
||
|
|
#### 4.2 hak_free_at() → hak_tiny_free() (hak_free_api.inc.h:119)
|
||
|
|
|
||
|
|
```c
|
||
|
|
case PTR_KIND_TINY_HEADERLESS: {
|
||
|
|
// C7: Headerless 1KB blocks
|
||
|
|
hak_tiny_free(ptr); // ← ptr is USER pointer
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**契約**:
|
||
|
|
- INPUT: `ptr` = USER pointer (storage+1) ❌
|
||
|
|
- **期待**: BASE pointer を渡すべき ❌
|
||
|
|
|
||
|
|
#### 4.3 hak_tiny_free_superslab() (tiny_superslab_free.inc.h:28)
|
||
|
|
|
||
|
|
```c
|
||
|
|
static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) {
|
||
|
|
int slab_idx = slab_index_for(ss, ptr);
|
||
|
|
TinySlabMeta* meta = &ss->slabs[slab_idx];
|
||
|
|
|
||
|
|
// Phase E1-CORRECT: ALL classes (C0-C7) have 1-byte header
|
||
|
|
void* base = (void*)((uint8_t*)ptr - 1); // ← USER → BASE 変換 (1回目)
|
||
|
|
|
||
|
|
// ... push to freelist or remote queue
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**変換回数**: 1回 (USER → BASE)
|
||
|
|
|
||
|
|
#### 4.4 Alignment Check (tiny_superslab_free.inc.h:95-117)
|
||
|
|
|
||
|
|
```c
|
||
|
|
if (__builtin_expect(ss->size_class == 7, 0)) {
|
||
|
|
size_t blk = g_tiny_class_sizes[ss->size_class]; // 1024
|
||
|
|
uint8_t* slab_base = tiny_slab_base_for(ss, slab_idx);
|
||
|
|
uintptr_t delta = (uintptr_t)base - (uintptr_t)slab_base;
|
||
|
|
int align_ok = (delta % blk) == 0;
|
||
|
|
|
||
|
|
if (!align_ok) {
|
||
|
|
// 🚨 CRASH HERE!
|
||
|
|
fprintf(stderr, "[C7_ALIGN_CHECK_FAIL] ptr=%p base=%p\n", ptr, base);
|
||
|
|
fprintf(stderr, "[C7_ALIGN_CHECK_FAIL] delta=%zu blk=%zu delta%%blk=%zu\n",
|
||
|
|
delta, blk, delta % blk);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Task先生のエラーログ**:
|
||
|
|
```
|
||
|
|
[C7_ALIGN_CHECK_FAIL] ptr=0x7f605c414402 base=0x7f605c414401
|
||
|
|
[C7_ALIGN_CHECK_FAIL] delta=17409 blk=1024 delta%blk=1
|
||
|
|
```
|
||
|
|
|
||
|
|
**分析**:
|
||
|
|
```
|
||
|
|
ptr = 0x...402 (storage+2) ← 期待: storage+1 (USER) ❌
|
||
|
|
base = ptr - 1 = 0x...401 (storage+1)
|
||
|
|
expected = storage (0x...400)
|
||
|
|
|
||
|
|
delta = 17409 = 17 * 1024 + 1
|
||
|
|
delta % 1024 = 1 ← OFF BY ONE!
|
||
|
|
```
|
||
|
|
|
||
|
|
**結論**: `ptr` が storage+2 になっている = **DOUBLE CONVERSION**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔬 バグの伝播経路
|
||
|
|
|
||
|
|
### Phase 1: Carve → TLS SLL (正常)
|
||
|
|
|
||
|
|
```
|
||
|
|
[Linear Carve] cursor = base + carved*stride // BASE pointer (storage)
|
||
|
|
↓ (BASE chain)
|
||
|
|
[TLS SLL Splice] g_tls_sll_head = chain_head // BASE pointer (storage)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Phase 2: TLS SLL → Allocation (正常)
|
||
|
|
|
||
|
|
```
|
||
|
|
[TLS SLL Pop] base = g_tls_sll_head[cls] // BASE pointer (storage)
|
||
|
|
*out = base // Return BASE
|
||
|
|
↓ (BASE)
|
||
|
|
[tiny_alloc_fast] ptr = tiny_alloc_fast_pop() // BASE pointer (storage)
|
||
|
|
HAK_RET_ALLOC(cls, ptr) // BASE → USER (storage+1) ✅
|
||
|
|
↓ (USER)
|
||
|
|
[Application] p = malloc(1024) // Receives USER (storage+1) ✅
|
||
|
|
```
|
||
|
|
|
||
|
|
### Phase 3: Free → TLS SLL (**BUG**)
|
||
|
|
|
||
|
|
```
|
||
|
|
[Application] free(p) // USER pointer (storage+1)
|
||
|
|
↓ (USER)
|
||
|
|
[hak_free_at] hak_tiny_free(ptr) // ptr = USER (storage+1) ❌
|
||
|
|
↓ (USER)
|
||
|
|
[hak_tiny_free_superslab]
|
||
|
|
base = ptr - 1 // USER → BASE (storage) ← 1回目変換
|
||
|
|
↓ (BASE)
|
||
|
|
ss_remote_push(ss, slab_idx, base) // BASE pushed to remote queue
|
||
|
|
↓ (BASE in remote queue)
|
||
|
|
[Adoption: Remote → Local Freelist]
|
||
|
|
trc_pop_from_freelist(meta, ..., &chain) // BASE chain
|
||
|
|
↓ (BASE)
|
||
|
|
[TLS SLL Splice] g_tls_sll_head = chain_head // BASE stored in SLL ✅
|
||
|
|
```
|
||
|
|
|
||
|
|
**ここまでは正常!** BASE pointer が SLL に保存されている。
|
||
|
|
|
||
|
|
### Phase 4: 次回 Allocation (**DOUBLE CONVERSION**)
|
||
|
|
|
||
|
|
```
|
||
|
|
[TLS SLL Pop] base = g_tls_sll_head[cls] // BASE pointer (storage)
|
||
|
|
*out = base // Return BASE (storage)
|
||
|
|
↓ (BASE)
|
||
|
|
[tiny_alloc_fast] ptr = tiny_alloc_fast_pop() // BASE pointer (storage)
|
||
|
|
HAK_RET_ALLOC(cls, ptr) // BASE → USER (storage+1) ✅
|
||
|
|
↓ (USER = storage+1)
|
||
|
|
[Application] p = malloc(1024) // Receives USER (storage+1) ✅
|
||
|
|
... use memory ...
|
||
|
|
free(p) // USER pointer (storage+1)
|
||
|
|
↓ (USER = storage+1)
|
||
|
|
[hak_tiny_free] ptr = storage+1
|
||
|
|
base = ptr - 1 = storage // ✅ USER → BASE (1回目)
|
||
|
|
↓ (BASE = storage)
|
||
|
|
[hak_tiny_free_superslab]
|
||
|
|
base = ptr - 1 // ❌ USER → BASE (2回目!) DOUBLE CONVERSION!
|
||
|
|
↓ (storage - 1) ← WRONG!
|
||
|
|
|
||
|
|
Expected: base = storage (aligned to 1024)
|
||
|
|
Actual: base = storage - 1 (offset 1023 → delta % 1024 = 1) ❌
|
||
|
|
```
|
||
|
|
|
||
|
|
**WRONG!** `hak_tiny_free()` は USER pointer を受け取っているのに、`hak_tiny_free_superslab()` でもう一度 `-1` している!
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 矛盾点のまとめ
|
||
|
|
|
||
|
|
### A. 設計意図 (Correct Contract)
|
||
|
|
|
||
|
|
| Layer | Stores | Input | Output | Conversion |
|
||
|
|
|-------|--------|-------|--------|------------|
|
||
|
|
| Carve | - | - | BASE | None (BASE generated) |
|
||
|
|
| TLS SLL | BASE | BASE | BASE | None |
|
||
|
|
| Alloc Pop | - | - | BASE | None |
|
||
|
|
| HAK_RET_ALLOC | - | BASE | USER | BASE → USER (1回) ✅ |
|
||
|
|
| Application | - | USER | USER | None |
|
||
|
|
| Free Enter | - | USER | - | USER → BASE (1回) ✅ |
|
||
|
|
| Freelist/Remote | BASE | BASE | - | None |
|
||
|
|
|
||
|
|
**Total conversions**: 2回 (Alloc: BASE→USER, Free: USER→BASE) ✅
|
||
|
|
|
||
|
|
### B. 実際の実装 (Buggy Implementation)
|
||
|
|
|
||
|
|
| Function | Input | Processing | Output |
|
||
|
|
|----------|-------|------------|--------|
|
||
|
|
| `hak_free_at()` | USER (storage+1) | Pass through | USER |
|
||
|
|
| `hak_tiny_free()` | USER (storage+1) | Pass through | USER |
|
||
|
|
| `hak_tiny_free_superslab()` | USER (storage+1) | **base = ptr - 1** | BASE (storage) ❌ |
|
||
|
|
|
||
|
|
**問題**: `hak_tiny_free_superslab()` は BASE pointer を期待しているのに、USER pointer を受け取っている!
|
||
|
|
|
||
|
|
**結果**:
|
||
|
|
1. 初回 free: USER → BASE 変換 (正常)
|
||
|
|
2. Remote queue に BASE で push (正常)
|
||
|
|
3. Adoption で BASE chain を TLS SLL へ (正常)
|
||
|
|
4. 次回 alloc: BASE → USER 変換 (正常)
|
||
|
|
5. 次回 free: **USER → BASE 変換が2回実行される** ❌
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 💡 修正方針 (Option C: Explicit Conversion at Boundary)
|
||
|
|
|
||
|
|
### 修正戦略
|
||
|
|
|
||
|
|
**原則**: **Box API Boundary で明示的に変換**
|
||
|
|
|
||
|
|
1. **TLS SLL**: BASE pointers を保存 (現状維持) ✅
|
||
|
|
2. **Alloc**: HAK_RET_ALLOC で BASE → USER 変換 (現状維持) ✅
|
||
|
|
3. **Free Entry**: **USER → BASE 変換を1箇所に集約** ← FIX!
|
||
|
|
|
||
|
|
### 具体的な修正
|
||
|
|
|
||
|
|
#### Fix 1: `hak_free_at()` で USER → BASE 変換
|
||
|
|
|
||
|
|
**File**: `/mnt/workdisk/public_share/hakmem/core/box/hak_free_api.inc.h`
|
||
|
|
|
||
|
|
**Before** (line 119):
|
||
|
|
```c
|
||
|
|
case PTR_KIND_TINY_HEADERLESS: {
|
||
|
|
hak_tiny_free(ptr); // ← ptr is USER
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**After** (FIX):
|
||
|
|
```c
|
||
|
|
case PTR_KIND_TINY_HEADERLESS: {
|
||
|
|
// ✅ FIX: Convert USER → BASE at API boundary
|
||
|
|
void* base = (void*)((uint8_t*)ptr - 1);
|
||
|
|
hak_tiny_free_base(base); // ← Pass BASE pointer
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Fix 2: `hak_tiny_free_superslab()` を `_base` variant に
|
||
|
|
|
||
|
|
**File**: `/mnt/workdisk/public_share/hakmem/core/tiny_superslab_free.inc.h`
|
||
|
|
|
||
|
|
**Option A: Rename function** (推奨)
|
||
|
|
|
||
|
|
```c
|
||
|
|
// OLD: static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss)
|
||
|
|
// NEW: Takes BASE pointer explicitly
|
||
|
|
static inline void hak_tiny_free_superslab_base(void* base, SuperSlab* ss) {
|
||
|
|
int slab_idx = slab_index_for(ss, base); // ← Use base directly
|
||
|
|
TinySlabMeta* meta = &ss->slabs[slab_idx];
|
||
|
|
|
||
|
|
// ❌ REMOVE: void* base = (void*)((uint8_t*)ptr - 1); // DOUBLE CONVERSION!
|
||
|
|
|
||
|
|
// Alignment check now uses correct base
|
||
|
|
if (__builtin_expect(ss->size_class == 7, 0)) {
|
||
|
|
size_t blk = g_tiny_class_sizes[ss->size_class];
|
||
|
|
uint8_t* slab_base = tiny_slab_base_for(ss, slab_idx);
|
||
|
|
uintptr_t delta = (uintptr_t)base - (uintptr_t)slab_base; // ✅ Correct delta
|
||
|
|
int align_ok = (delta % blk) == 0; // ✅ Should be 0 now!
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
// ... rest of free logic
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Option B: Keep function name, add parameter**
|
||
|
|
|
||
|
|
```c
|
||
|
|
static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss, bool is_base) {
|
||
|
|
void* base = is_base ? ptr : (void*)((uint8_t*)ptr - 1);
|
||
|
|
// ... rest as above
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Fix 3: Update all call sites
|
||
|
|
|
||
|
|
**Files to update**:
|
||
|
|
1. `/mnt/workdisk/public_share/hakmem/core/box/hak_free_api.inc.h` (line 119, 127)
|
||
|
|
2. `/mnt/workdisk/public_share/hakmem/core/hakmem_tiny_free.inc` (line 173, 470)
|
||
|
|
|
||
|
|
**Pattern**:
|
||
|
|
```c
|
||
|
|
// OLD: hak_tiny_free_superslab(ptr, ss);
|
||
|
|
// NEW: hak_tiny_free_superslab_base(base, ss);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🧪 検証計画
|
||
|
|
|
||
|
|
### 1. Unit Test
|
||
|
|
|
||
|
|
```c
|
||
|
|
void test_pointer_conversion(void) {
|
||
|
|
// Allocate
|
||
|
|
void* user_ptr = hak_tiny_alloc(1024); // Should return USER (storage+1)
|
||
|
|
assert(user_ptr != NULL);
|
||
|
|
|
||
|
|
// Check alignment (USER pointer should be offset 1 from BASE)
|
||
|
|
void* base = (void*)((uint8_t*)user_ptr - 1);
|
||
|
|
assert(((uintptr_t)base % 1024) == 0); // BASE aligned
|
||
|
|
assert(((uintptr_t)user_ptr % 1024) == 1); // USER offset by 1
|
||
|
|
|
||
|
|
// Free (should accept USER pointer)
|
||
|
|
hak_tiny_free(user_ptr);
|
||
|
|
|
||
|
|
// Reallocate (should return same USER pointer)
|
||
|
|
void* user_ptr2 = hak_tiny_alloc(1024);
|
||
|
|
assert(user_ptr2 == user_ptr); // Same block reused
|
||
|
|
|
||
|
|
hak_tiny_free(user_ptr2);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Alignment Error Test
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Run with C7 allocation (1KB blocks)
|
||
|
|
./bench_fixed_size_hakmem 10000 1024 128
|
||
|
|
|
||
|
|
# Expected: No [C7_ALIGN_CHECK_FAIL] errors
|
||
|
|
# Before fix: delta%blk=1 (off by one)
|
||
|
|
# After fix: delta%blk=0 (aligned)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Stress Test
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Run long allocation/free cycles
|
||
|
|
./bench_random_mixed_hakmem 1000000 1024 42
|
||
|
|
|
||
|
|
# Expected: Stable, no crashes
|
||
|
|
# Monitor: [C7_ALIGN_CHECK_FAIL] should be 0
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Grep Audit (事前検証)
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Check for other USER → BASE conversions
|
||
|
|
grep -rn "(uint8_t\*)ptr - 1" core/
|
||
|
|
|
||
|
|
# Expected: Only 1 occurrence (at hak_free_at boundary)
|
||
|
|
# Before fix: 2+ occurrences (multiple conversions)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📝 影響範囲分析
|
||
|
|
|
||
|
|
### 影響するクラス
|
||
|
|
|
||
|
|
| Class | Size | Header | Impact |
|
||
|
|
|-------|------|--------|--------|
|
||
|
|
| C0 | 8B | Yes | ❌ Same bug (overwrite header with next) |
|
||
|
|
| C1-C6 | 16-512B | Yes | ❌ Same bug pattern |
|
||
|
|
| C7 | 1KB | Yes (Phase E1) | ✅ **Detected** (alignment check) |
|
||
|
|
|
||
|
|
**なぜ C7 だけクラッシュ?**
|
||
|
|
- C7 alignment check が厳密 (1024B aligned)
|
||
|
|
- Off-by-one が検出されやすい (delta % 1024 == 1)
|
||
|
|
- C0-C6 は smaller alignment (8-512B), エラーが silent になりやすい
|
||
|
|
|
||
|
|
### 他の Free Path も同じバグ?
|
||
|
|
|
||
|
|
**Yes!** 以下も同様に修正が必要:
|
||
|
|
|
||
|
|
1. **PTR_KIND_TINY_HEADER** (line 119):
|
||
|
|
```c
|
||
|
|
case PTR_KIND_TINY_HEADER: {
|
||
|
|
// ✅ FIX: Convert USER → BASE
|
||
|
|
void* base = (void*)((uint8_t*)ptr - 1);
|
||
|
|
hak_tiny_free_base(base);
|
||
|
|
goto done;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Direct SuperSlab free** (hakmem_tiny_free.inc line 470):
|
||
|
|
```c
|
||
|
|
if (ss && ss->magic == SUPERSLAB_MAGIC) {
|
||
|
|
// ✅ FIX: Convert USER → BASE before passing to superslab free
|
||
|
|
void* base = (void*)((uint8_t*)ptr - 1);
|
||
|
|
hak_tiny_free_superslab_base(base, ss);
|
||
|
|
HAK_STAT_FREE(ss->size_class);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 修正の最小化
|
||
|
|
|
||
|
|
### 変更ファイル (3ファイルのみ)
|
||
|
|
|
||
|
|
1. **`core/box/hak_free_api.inc.h`** (2箇所)
|
||
|
|
- Line 119: USER → BASE 変換追加
|
||
|
|
- Line 127: USER → BASE 変換追加
|
||
|
|
|
||
|
|
2. **`core/tiny_superslab_free.inc.h`** (1箇所)
|
||
|
|
- Line 28: `void* base = (void*)((uint8_t*)ptr - 1);` を削除
|
||
|
|
- Function signature に `_base` suffix 追加
|
||
|
|
|
||
|
|
3. **`core/hakmem_tiny_free.inc`** (2箇所)
|
||
|
|
- Line 173: Call site update
|
||
|
|
- Line 470: Call site update + USER → BASE 変換追加
|
||
|
|
|
||
|
|
### 変更行数
|
||
|
|
|
||
|
|
- 追加: 約 10 lines (USER → BASE conversions)
|
||
|
|
- 削除: 1 line (DOUBLE CONVERSION removal)
|
||
|
|
- 修正: 2 lines (function call updates)
|
||
|
|
|
||
|
|
**Total**: < 15 lines changed
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚀 実装順序
|
||
|
|
|
||
|
|
### Phase 1: Preparation (5分)
|
||
|
|
|
||
|
|
1. Grep audit で全ての `hak_tiny_free_superslab` 呼び出しをリスト化
|
||
|
|
2. Grep audit で全ての `ptr - 1` 変換をリスト化
|
||
|
|
3. Test baseline: 現状のベンチマーク結果を記録
|
||
|
|
|
||
|
|
### Phase 2: Core Fix (10分)
|
||
|
|
|
||
|
|
1. `tiny_superslab_free.inc.h`: Rename function, remove DOUBLE CONVERSION
|
||
|
|
2. `hak_free_api.inc.h`: Add USER → BASE at boundary (2箇所)
|
||
|
|
3. `hakmem_tiny_free.inc`: Update call sites (2箇所)
|
||
|
|
|
||
|
|
### Phase 3: Verification (10分)
|
||
|
|
|
||
|
|
1. Build test: `./build.sh bench_fixed_size_hakmem`
|
||
|
|
2. Unit test: Run alignment check test (1KB blocks)
|
||
|
|
3. Stress test: Run 100K iterations, check for errors
|
||
|
|
|
||
|
|
### Phase 4: Validation (5分)
|
||
|
|
|
||
|
|
1. Benchmark: Verify performance unchanged (< 1% regression acceptable)
|
||
|
|
2. Grep audit: Verify only 1 USER → BASE conversion point
|
||
|
|
3. Final test: Run full bench suite
|
||
|
|
|
||
|
|
**Total time**: 30分
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📚 まとめ
|
||
|
|
|
||
|
|
### Root Cause
|
||
|
|
|
||
|
|
**DOUBLE CONVERSION**: USER → BASE 変換が2回実行される
|
||
|
|
|
||
|
|
1. `hak_free_at()` が USER pointer を受け取る
|
||
|
|
2. `hak_tiny_free()` が USER pointer をそのまま渡す
|
||
|
|
3. `hak_tiny_free_superslab()` が USER → BASE 変換 (1回目)
|
||
|
|
4. 次回 free で再度 USER → BASE 変換 (2回目) ← **BUG!**
|
||
|
|
|
||
|
|
### Solution
|
||
|
|
|
||
|
|
**Box API Boundary で明示的に変換**
|
||
|
|
|
||
|
|
1. `hak_free_at()`: USER → BASE 変換 (1箇所に集約)
|
||
|
|
2. `hak_tiny_free_superslab()`: BASE pointer を期待 (変換削除)
|
||
|
|
3. All internal paths: BASE pointers only
|
||
|
|
|
||
|
|
### Impact
|
||
|
|
|
||
|
|
- **最小限の変更**: 3ファイル, < 15 lines
|
||
|
|
- **パフォーマンス**: 影響なし (変換回数は同じ)
|
||
|
|
- **安全性**: ポインタ契約が明確化, バグ再発を防止
|
||
|
|
|
||
|
|
### Verification
|
||
|
|
|
||
|
|
- C7 alignment check でバグ検出成功 ✅
|
||
|
|
- Fix 後は delta % 1024 == 0 になる ✅
|
||
|
|
- 全クラス (C0-C7) で一貫性が保たれる ✅
|