Add defensive layers mapping and diagnostic logging enhancements

Documentation:
- Created docs/DEFENSIVE_LAYERS_MAPPING.md documenting all 5 defensive layers
- Maps which symptoms each layer suppresses
- Defines safe removal order after root cause fix
- Includes test methods for each layer removal

Diagnostic Logging Enhancements (ChatGPT work):
- TLS_SLL_HEAD_SET log with count and backtrace for NORMALIZE_USERPTR
- tiny_next_store_log with filtering capability
- Environment variables for log filtering:
  - HAKMEM_TINY_SLL_NEXTCLS: class filter for next store (-1 disables)
  - HAKMEM_TINY_SLL_NEXTTAG: tag filter (substring match)
  - HAKMEM_TINY_SLL_HEADCLS: class filter for head trace

Current Investigation Status:
- sh8bench 60/120s: crash-free, zero NEXT_INVALID/HDR_RESET/SANITIZE
- BUT: shot limit (256) exhausted by class3 tls_push before class1/drain
- Need: Add tags to pop/clear paths, or increase shot limit for class1

Purpose of this commit:
- Document defensive layers for safe removal later
- Enable targeted diagnostic logging
- Prepare for final root cause identification

Next Steps:
1. Add tags to tls_sll_pop tiny_next_write (e.g., "tls_pop_clear")
2. Re-run with HAKMEM_TINY_SLL_NEXTTAG=tls_pop
3. Capture class1 writes that lead to corruption

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm (CI)
2025-12-04 04:15:10 +09:00
parent f28cafbad3
commit ab612403a7
10 changed files with 466 additions and 3 deletions

View File

@ -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