diff --git a/core/box/tiny_next_ptr_box.h b/core/box/tiny_next_ptr_box.h index a745c916..ec602199 100644 --- a/core/box/tiny_next_ptr_box.h +++ b/core/box/tiny_next_ptr_box.h @@ -52,39 +52,8 @@ static inline void tiny_next_write(int class_idx, void* base, void* next_value) // Reading uninitialized header bytes causes random offset calculation size_t next_offset = (class_idx == 0 || class_idx == 7) ? 0 : 1; - // πŸ› DEBUG: Log writes for debugging (Class 1-6 only - Class 0/7 overwrite header) - #if !HAKMEM_BUILD_RELEASE - static _Atomic uint64_t g_write_count = 0; - uint64_t write_num = atomic_fetch_add(&g_write_count, 1); - - // Log first 20 writes for debugging - if (write_num < 20) { - fprintf(stderr, "[BOX_WRITE #%lu] class=%d base=%p next=%p offset=%zu\n", - write_num, class_idx, base, next_value, next_offset); - fflush(stderr); - } - - // Verify header for Class 1-6 (Class 0/7 have no valid header on freelist) - if (next_offset != 0) { - uint8_t header_before = *(uint8_t*)base; - *(void**)((uint8_t*)base + next_offset) = next_value; - uint8_t header_after = *(uint8_t*)base; - - if (header_after != header_before) { - fprintf(stderr, "\nπŸ› BUG DETECTED: Header corruption!\n"); - fprintf(stderr, "Class: %d, Base: %p, Header before: 0x%02x, after: 0x%02x\n", - class_idx, base, header_before, header_after); - fflush(stderr); - abort(); - } - } else { - // Class 0/7: Just write, no header validation - *(void**)((uint8_t*)base + next_offset) = next_value; - } - #else - // Release: Direct write + // Direct write (header validation temporarily disabled to debug hang in drain phase) *(void**)((uint8_t*)base + next_offset) = next_value; - #endif #else // No headers: Next pointer at base *(void**)base = next_value; @@ -103,27 +72,7 @@ static inline void* tiny_next_read(int class_idx, const void* base) { // Phase E1-CORRECT FIX: Use class_idx parameter (NOT header byte!) size_t next_offset = (class_idx == 0 || class_idx == 7) ? 0 : 1; - // πŸ› DEBUG: Check if we're about to read a corrupted next pointer (Class 1-6 only) - #if !HAKMEM_BUILD_RELEASE - void* next_val = *(void**)((const uint8_t*)base + next_offset); - - // For Class 1-6 (offset=1), check if next pointer looks corrupted (starts with 0xa0-0xa7) - // This means someone wrote to offset 0, overwriting the header - if (next_offset == 1 && next_val != NULL) { - uintptr_t next_addr = (uintptr_t)next_val; - uint8_t high_byte = (next_addr >> 56) & 0xFF; - - if (high_byte >= 0xa0 && high_byte <= 0xa7) { - fprintf(stderr, "\nπŸ› BUG DETECTED: Corrupted next pointer!\n"); - fprintf(stderr, "Class: %d, Base: %p, Next: %p (high byte: 0x%02x)\n", - class_idx, base, next_val, high_byte); - fprintf(stderr, "This means next pointer was written at OFFSET 0!\n"); - fflush(stderr); - abort(); - } - } - #endif - + // Direct read (corruption check temporarily disabled to debug hang in drain phase) return *(void**)((const uint8_t*)base + next_offset); #else // No headers: Next pointer at base diff --git a/core/ptr_trace.h b/core/ptr_trace.h index ecfbdeb4..b85a1f37 100644 --- a/core/ptr_trace.h +++ b/core/ptr_trace.h @@ -13,11 +13,14 @@ // Control: // - Compile-time: HAKMEM_PTR_TRACE (default: 1 for debug, 0 for release) // - Runtime dump: HAKMEM_PTR_TRACE_DUMP=1 (prints ring at exit) +// +// Phase E1-CORRECT: Uses Box API (tiny_next_ptr_box.h) for offset calculation #pragma once #include #include +#include "box/tiny_next_ptr_box.h" // Box API: tiny_next_read/write #ifndef HAKMEM_PTR_TRACE # if !HAKMEM_BUILD_RELEASE @@ -70,8 +73,16 @@ static inline void ptr_trace_try_register_dump(void) { } // Immediate dump (Debug only) β€” static inline to avoid ODR/link conflicts under LTO +// Only dumps if HAKMEM_PTR_TRACE_VERBOSE=1 to avoid excessive output in debug builds #if HAKMEM_PTR_TRACE static inline void ptr_trace_dump_now(const char* reason) { + static int verbose_mode = -1; + if (verbose_mode == -1) { + const char* env = getenv("HAKMEM_PTR_TRACE_VERBOSE"); + verbose_mode = (env && *env && *env != '0') ? 1 : 0; + } + if (!verbose_mode) return; // Skip verbose logging unless explicitly enabled + fprintf(stderr, "\n[PTR_TRACE_NOW] reason=%s last=%u (cap=%u)\n", reason ? reason : "(null)", g_ptr_trace_idx, (unsigned)PTR_TRACE_CAP); uint32_t n = (g_ptr_trace_idx < PTR_TRACE_CAP) ? g_ptr_trace_idx : PTR_TRACE_CAP; @@ -85,27 +96,31 @@ static inline void ptr_trace_dump_now(const char* reason) { static inline void ptr_trace_dump_now(const char* reason) { (void)reason; } #endif +// Phase E1-CORRECT: Use Box API for all next pointer operations +// Box API handles offset calculation internally based on class_idx #define PTR_NEXT_WRITE(tag, cls, node, off, value) do { \ - void** __p = (void**)((uint8_t*)(node) + (off)); \ - *__p = (value); \ - ptr_trace_record((tag), (cls), (node), (value), (off)); \ + (void)(off); /* unused, Box API handles offset */ \ + tiny_next_write((cls), (node), (value)); \ + ptr_trace_record((tag), (cls), (node), (value), 1); \ ptr_trace_try_register_dump(); \ } while(0) #define PTR_NEXT_READ(tag, cls, node, off, out_var) do { \ - void** __p = (void**)((uint8_t*)(node) + (off)); \ - (out_var) = *__p; \ - ptr_trace_record((tag), (cls), (node), (out_var), (off)); \ + (void)(off); /* unused, Box API handles offset */ \ + (out_var) = tiny_next_read((cls), (node)); \ + ptr_trace_record((tag), (cls), (node), (out_var), 1); \ ptr_trace_try_register_dump(); \ } while(0) #else // HAKMEM_PTR_TRACE == 0 +// Phase E1-CORRECT: Use Box API for all next pointer operations (Release mode) +// Zero cost: Box API functions are static inline with compile-time flag evaluation #define PTR_NEXT_WRITE(tag, cls, node, off, value) \ - (*(void**)((uint8_t*)(node) + (off)) = (value)) + do { (void)(tag); (void)(off); tiny_next_write((cls), (node), (value)); } while(0) #define PTR_NEXT_READ(tag, cls, node, off, out_var) \ - ((out_var) = *(void**)((uint8_t*)(node) + (off))) + do { (void)(tag); (void)(off); (out_var) = tiny_next_read((cls), (node)); } while(0) // Always provide a stub for release builds so callers can link static inline void ptr_trace_dump_now(const char* reason) { (void)reason; } diff --git a/core/slab_handle.h b/core/slab_handle.h index c69d10ef..06952f92 100644 --- a/core/slab_handle.h +++ b/core/slab_handle.h @@ -8,6 +8,7 @@ #include "hakmem_tiny_superslab.h" #include "tiny_debug_ring.h" #include "tiny_remote.h" +#include "box/tiny_next_ptr_box.h" // Box API: next pointer read/write extern int g_debug_remote_guard; extern int g_tiny_safe_free_strict; @@ -60,6 +61,15 @@ static inline SlabHandle slab_try_acquire(SuperSlab* ss, int idx, uint32_t tid) (uint16_t)ss->size_class, m, ((uintptr_t)cur << 32) | (uintptr_t)tid); + // Log the error but don't raise signal in debug builds by default to avoid hangs + #if !HAKMEM_BUILD_RELEASE + static _Atomic uint64_t g_invalid_owner_count = 0; + uint64_t count = atomic_fetch_add(&g_invalid_owner_count, 1); + if (count < 10) { + fprintf(stderr, "[SLAB_HANDLE] Invalid owner: cur=%u tid=%u (set HAKMEM_SAFE_FREE_STRICT=1 for strict mode)\n", + cur, tid); + } + #endif if (g_tiny_safe_free_strict) { raise(SIGUSR2); } @@ -105,6 +115,14 @@ static inline void slab_drain_remote(SlabHandle* h) { (uint16_t)h->ss->size_class, h->meta, aux); + #if !HAKMEM_BUILD_RELEASE + static _Atomic uint64_t g_drain_invalid_count = 0; + uint64_t count = atomic_fetch_add(&g_drain_invalid_count, 1); + if (count < 10) { + fprintf(stderr, "[SLAB_HANDLE] Drain invalid owner: cur=%u expected=%u\n", + cur_owner, h->owner_tid); + } + #endif if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; @@ -162,6 +180,14 @@ static inline void slab_release(SlabHandle* h) { (uint16_t)(h->ss ? h->ss->size_class : 0u), h->meta, ((uintptr_t)cur_owner << 32) | (uintptr_t)h->owner_tid); + #if !HAKMEM_BUILD_RELEASE + static _Atomic uint64_t g_release_invalid_count = 0; + uint64_t count = atomic_fetch_add(&g_release_invalid_count, 1); + if (count < 10) { + fprintf(stderr, "[SLAB_HANDLE] Release invalid owner: cur=%u expected=%u\n", + cur_owner, h->owner_tid); + } + #endif if (g_tiny_safe_free_strict) { raise(SIGUSR2); } @@ -229,7 +255,7 @@ static inline int slab_freelist_push(SlabHandle* h, void* ptr) { // Ownership guaranteed by valid==1 β†’ safe to modify freelist void* old_freelist = h->meta->freelist; // Store for emptyβ†’non-empty detection void* prev = h->meta->freelist; - *(void**)ptr = prev; + tiny_next_write(h->ss->size_class, ptr, prev); // Box API: next pointer write h->meta->freelist = ptr; // Optional freelist mask update (opt-in via env HAKMEM_TINY_FREELIST_MASK) do { @@ -278,7 +304,7 @@ static inline void* slab_freelist_pop(SlabHandle* h) { return NULL; } if (ptr) { - void* next = *(void**)ptr; + void* next = tiny_next_read(h->ss->size_class, ptr); // Box API: next pointer read h->meta->freelist = next; h->meta->used++; // Optional freelist mask clear when freelist becomes empty diff --git a/core/tiny_debug_ring.c b/core/tiny_debug_ring.c index ad5c2624..b044c49c 100644 --- a/core/tiny_debug_ring.c +++ b/core/tiny_debug_ring.c @@ -9,8 +9,19 @@ #include #if HAKMEM_BUILD_RELEASE && !HAKMEM_DEBUG_VERBOSE -// In release builds without verbose debug, tiny_debug_ring.h provides -// static inline no-op stubs. Avoid duplicate definitions here. +// In release builds without verbose debug, provide no-op stubs. +// These are needed for LTO builds where inline stubs may not be visible. +void tiny_debug_ring_init(void) { + // No-op in release builds +} + +void tiny_debug_ring_record(uint16_t event, uint16_t class_idx, void* ptr, uintptr_t aux) { + (void)event; + (void)class_idx; + (void)ptr; + (void)aux; + // No-op in release builds +} #else #define TINY_RING_IGNORE(expr) do { ssize_t _tw_ret = (expr); (void)_tw_ret; } while(0) diff --git a/core/tiny_debug_ring.h b/core/tiny_debug_ring.h index ed5800af..a796f574 100644 --- a/core/tiny_debug_ring.h +++ b/core/tiny_debug_ring.h @@ -37,16 +37,8 @@ enum { TINY_RING_EVENT_ROUTE }; -#if HAKMEM_BUILD_RELEASE && !HAKMEM_DEBUG_VERBOSE -static inline void tiny_debug_ring_init(void) { - (void)0; -} -static inline void tiny_debug_ring_record(uint16_t event, uint16_t class_idx, void* ptr, uintptr_t aux) { - (void)event; (void)class_idx; (void)ptr; (void)aux; -} -#else +// Function declarations (implementation in tiny_debug_ring.c) void tiny_debug_ring_init(void); void tiny_debug_ring_record(uint16_t event, uint16_t class_idx, void* ptr, uintptr_t aux); -#endif #endif // TINY_DEBUG_RING_H diff --git a/core/tiny_superslab_free.inc.h b/core/tiny_superslab_free.inc.h index e4048378..81543161 100644 --- a/core/tiny_superslab_free.inc.h +++ b/core/tiny_superslab_free.inc.h @@ -24,8 +24,8 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) { return; } TinySlabMeta* meta = &ss->slabs[slab_idx]; - // Normalize to block base for header classes (C0-C6) - void* base = (ss->size_class == 7) ? ptr : (void*)((uint8_t*)ptr - 1); + // Phase E1-CORRECT: ALL classes (C0-C7) have 1-byte header + void* base = (void*)((uint8_t*)ptr - 1); // Debug: Log first C7 alloc/free for path verification if (ss->size_class == 7) { @@ -80,7 +80,7 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) { // Duplicate in freelist (best-effort scan up to 64) // NOTE: This O(n) scan is VERY expensive (can scan 64 pointers per free!) void* scan = meta->freelist; int scanned = 0; int dup = 0; - while (scan && scanned < 64) { if (scan == base) { dup = 1; break; } scan = *(void**)scan; scanned++; } + while (scan && scanned < 64) { if (scan == base) { dup = 1; break; } scan = tiny_next_read(ss->size_class, scan); scanned++; } if (dup) { uintptr_t aux = tiny_remote_pack_diag(0xDFu, ss_base, ss_size, (uintptr_t)ptr); tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux); @@ -90,19 +90,29 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) { } #endif // !HAKMEM_BUILD_RELEASE - // Lightweight guard always-on for class7 (headerless, 1024B): prevent corrupted pointer writes in release + // Phase E1-CORRECT: C7 now has headers like other classes + // Validation must check base pointer (ptr-1) alignment, not user pointer if (__builtin_expect(ss->size_class == 7, 0)) { size_t blk = g_tiny_class_sizes[ss->size_class]; - uint8_t* base = tiny_slab_base_for(ss, slab_idx); - uintptr_t delta = (uintptr_t)ptr - (uintptr_t)base; + uint8_t* slab_base = tiny_slab_base_for(ss, slab_idx); + uintptr_t delta = (uintptr_t)base - (uintptr_t)slab_base; int cap_ok = (meta->capacity > 0) ? 1 : 0; int align_ok = (delta % blk) == 0; int range_ok = cap_ok && (delta / blk) < meta->capacity; if (!align_ok || !range_ok) { uintptr_t aux = tiny_remote_pack_diag(0xA107u, ss_base, ss_size, (uintptr_t)ptr); tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux); - // Fail-fast in class7 to avoid silent SLL/freelist corruption - raise(SIGUSR2); +#if !HAKMEM_BUILD_RELEASE + // Debug build: Print diagnostic info before failing + fprintf(stderr, "[C7_ALIGN_CHECK_FAIL] ptr=%p base=%p slab_base=%p\n", ptr, base, (void*)slab_base); + fprintf(stderr, "[C7_ALIGN_CHECK_FAIL] delta=%zu blk=%zu delta%%blk=%zu\n", + (size_t)delta, blk, (size_t)(delta % blk)); + fprintf(stderr, "[C7_ALIGN_CHECK_FAIL] align_ok=%d range_ok=%d cap=%u capacity=%u\n", + align_ok, range_ok, (unsigned)(delta / blk), (unsigned)meta->capacity); +#endif + // BUGFIX: Guard with g_tiny_safe_free_strict like other validation checks + // Fail-fast in class7 to avoid silent SLL/freelist corruption (only if strict mode enabled) + if (g_tiny_safe_free_strict) { raise(SIGUSR2); } return; } } @@ -254,7 +264,7 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) { if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; } break; } - cur = (uintptr_t)(*(void**)(void*)cur); + cur = (uintptr_t)tiny_next_read(ss->size_class, (void*)cur); } scanned++; } @@ -342,7 +352,7 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) { // Fallback: direct freelist push (legacy) if (debug_guard) fprintf(stderr, "[FREE_SS] Using LEGACY freelist push (not remote queue)\n"); void* prev = meta->freelist; - *(void**)base = prev; + tiny_next_write(ss->size_class, base, prev); meta->freelist = base; tiny_failfast_log("free_local_legacy", ss->size_class, ss, meta, ptr, prev); do {