From 8eda1b6039cca8d3045e7c396c9fcf6a4756dfb4 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Thu, 11 Jun 2026 18:01:12 +0200 Subject: [PATCH 1/4] fix(tsan): use __tsan_reset_range instead of __tsan_acquire for mmap VA reuse --- ddprof-lib/src/main/cpp/linearAllocator.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/ddprof-lib/src/main/cpp/linearAllocator.cpp b/ddprof-lib/src/main/cpp/linearAllocator.cpp index 4443ed44d..484b019d6 100644 --- a/ddprof-lib/src/main/cpp/linearAllocator.cpp +++ b/ddprof-lib/src/main/cpp/linearAllocator.cpp @@ -220,25 +220,24 @@ Chunk *LinearAllocator::allocateChunk(Chunk *current) { Chunk *chunk = (Chunk *)OS::safeAlloc(_chunk_size); if (chunk != NULL) { // OS::safeAlloc uses a raw mmap syscall that bypasses ASan and TSan - // interceptors by design (to avoid profiling self-instrumentation). - // When the OS reuses a VA that had stale sanitizer state from a previous - // allocation at that address, writing to the chunk header triggers: - // ASan: use-after-poison (f7 shadow from prior ASAN_POISON_MEMORY_REGION) - // TSan: data-race (prior access history for the same VA not cleared by munmap) - // Fix: unpoison the entire chunk and acquire TSan ownership BEFORE the first - // write, establishing a clean sanitizer baseline for this logical allocation. + // interceptors. When the OS reuses a VA from a prior munmap, TSan's shadow + // memory for that VA still holds the old access history. __tsan_acquire + // only establishes happens-before by pairing with a prior __tsan_release at + // the same address; it does NOT clear stale shadow state. + // Fix: use __tsan_reset_range to wipe the shadow for the entire chunk before + // the first write, giving TSan a clean baseline for this new allocation. #ifdef ASAN_ENABLED ASAN_UNPOISON_MEMORY_REGION(chunk, _chunk_size); #endif #ifdef __SANITIZE_THREAD__ - __tsan_acquire(chunk); + __tsan_reset_range(chunk, _chunk_size); #endif chunk->prev = current; chunk->offs = sizeof(Chunk); - // Publish the initialized chunk: release TSan ownership so that any thread - // which later acquires this chunk (via __tsan_acquire) will see these writes. + // Publish the initialized chunk: any thread that later acquires this chunk + // (via __tsan_acquire in freeChunks/detachChunks) will see these writes. #ifdef __SANITIZE_THREAD__ __tsan_release(chunk); #endif From a31cc4ab73018111d57291292186723406d27b6b Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Fri, 12 Jun 2026 10:03:01 +0200 Subject: [PATCH 2/4] fix(tsan): clear stale shadow via intercepted mmap re-map in allocateChunk --- ddprof-lib/src/main/cpp/linearAllocator.cpp | 21 +++++++++++------ ...26-06-12-tsan-write-range-replace-reset.md | 23 +++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 docs/sphinx/specs/2026-06-12-tsan-write-range-replace-reset.md diff --git a/ddprof-lib/src/main/cpp/linearAllocator.cpp b/ddprof-lib/src/main/cpp/linearAllocator.cpp index 484b019d6..e50bfa441 100644 --- a/ddprof-lib/src/main/cpp/linearAllocator.cpp +++ b/ddprof-lib/src/main/cpp/linearAllocator.cpp @@ -20,6 +20,9 @@ #include "os.h" #include "common.h" #include +#ifdef __SANITIZE_THREAD__ + #include +#endif // Enable ASan memory poisoning for better use-after-free detection #ifdef __has_feature @@ -220,17 +223,21 @@ Chunk *LinearAllocator::allocateChunk(Chunk *current) { Chunk *chunk = (Chunk *)OS::safeAlloc(_chunk_size); if (chunk != NULL) { // OS::safeAlloc uses a raw mmap syscall that bypasses ASan and TSan - // interceptors. When the OS reuses a VA from a prior munmap, TSan's shadow - // memory for that VA still holds the old access history. __tsan_acquire - // only establishes happens-before by pairing with a prior __tsan_release at - // the same address; it does NOT clear stale shadow state. - // Fix: use __tsan_reset_range to wipe the shadow for the entire chunk before - // the first write, giving TSan a clean baseline for this new allocation. + // interceptors by design (to avoid self-instrumentation in the profiler). + // When the OS reuses a VA from a prior munmap, TSan's shadow memory for + // that VA still holds stale access history from previous chunk users, + // causing false-positive data-race reports on user-data addresses. + // + // Fix: re-map the same VA through the libc mmap() wrapper. TSan intercepts + // mmap() and calls MemoryRangeImitateWrite, which sets all 4 shadow slots + // for the entire chunk range to the current thread's write — completely + // overwriting any stale entries. safeAlloc itself is unchanged. #ifdef ASAN_ENABLED ASAN_UNPOISON_MEMORY_REGION(chunk, _chunk_size); #endif #ifdef __SANITIZE_THREAD__ - __tsan_reset_range(chunk, _chunk_size); + mmap(chunk, _chunk_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); #endif chunk->prev = current; diff --git a/docs/sphinx/specs/2026-06-12-tsan-write-range-replace-reset.md b/docs/sphinx/specs/2026-06-12-tsan-write-range-replace-reset.md new file mode 100644 index 000000000..193fa76a4 --- /dev/null +++ b/docs/sphinx/specs/2026-06-12-tsan-write-range-replace-reset.md @@ -0,0 +1,23 @@ +# Replace __tsan_reset_range with __tsan_write_range + +## Problem +`__tsan_reset_range` is not declared in `` and does not +exist in the LLVM/compiler-rt source. Using it causes GCC TSan builds to fail with +"was not declared in this scope". + +## Solution +Replace `__tsan_reset_range(chunk, _chunk_size)` with `__tsan_write_range(chunk, _chunk_size)`. +Add an explicit `extern "C"` forward declaration for `__tsan_write_range` inside the +`#ifdef __SANITIZE_THREAD__` block, since the function exists in the runtime but is absent +from the public header. + +## Files to change +- `ddprof-lib/src/main/cpp/linearAllocator.cpp` + - Lines 39–41: add `extern "C"` declaration for `__tsan_write_range` + - Line 233: replace `__tsan_reset_range` with `__tsan_write_range` + +## Acceptance criteria +1. The `__tsan_reset_range` identifier no longer appears in the file. +2. A forward declaration `extern "C" void __tsan_write_range(void*, size_t);` exists + inside `#ifdef __SANITIZE_THREAD__`. +3. `__tsan_write_range(chunk, _chunk_size)` is called at line ~233. From ffc4d9f44b7b1377adf2e3b73a90da363db379f9 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Fri, 12 Jun 2026 11:57:49 +0200 Subject: [PATCH 3/4] fix(tsan): detect TSan via TSAN_ENABLED, not gcc-only __SANITIZE_THREAD__ The clang CI sanitizer build does not define __SANITIZE_THREAD__, so every TSan annotation guarded on it was dead code. Add a TSAN_ENABLED macro in common.h (clang __has_feature + gcc __SANITIZE_THREAD__) and migrate all sites. Also assert the mmap MAP_FIXED re-map, fix stale comments, and drop the stale write_range spec. Co-Authored-By: Claude Opus 4.8 (1M context) --- ddprof-lib/src/main/cpp/common.h | 28 +++++++++ ddprof-lib/src/main/cpp/gtest_crash_handler.h | 6 +- ddprof-lib/src/main/cpp/linearAllocator.cpp | 60 ++++++++++--------- ddprof-lib/src/test/cpp/ddprof_ut.cpp | 4 +- .../src/test/cpp/stress_callTraceStorage.cpp | 3 +- ...26-06-12-tsan-write-range-replace-reset.md | 23 ------- 6 files changed, 67 insertions(+), 57 deletions(-) delete mode 100644 docs/sphinx/specs/2026-06-12-tsan-write-range-replace-reset.md diff --git a/ddprof-lib/src/main/cpp/common.h b/ddprof-lib/src/main/cpp/common.h index 943c92fb4..13b8ae9ca 100644 --- a/ddprof-lib/src/main/cpp/common.h +++ b/ddprof-lib/src/main/cpp/common.h @@ -4,6 +4,34 @@ #include #include +// Sanitizer detection: define ASAN_ENABLED / TSAN_ENABLED uniformly across +// toolchains. clang exposes sanitizers only via __has_feature(...), while gcc +// defines __SANITIZE_ADDRESS__ / __SANITIZE_THREAD__. Guarding code on the gcc +// macros alone silently drops every sanitizer annotation under clang (the CI +// sanitizer compiler), so always go through these macros instead. +#if defined(__has_feature) +# if __has_feature(address_sanitizer) +# ifndef ASAN_ENABLED +# define ASAN_ENABLED 1 +# endif +# endif +# if __has_feature(thread_sanitizer) +# ifndef TSAN_ENABLED +# define TSAN_ENABLED 1 +# endif +# endif +#endif +#ifdef __SANITIZE_ADDRESS__ +# ifndef ASAN_ENABLED +# define ASAN_ENABLED 1 +# endif +#endif +#ifdef __SANITIZE_THREAD__ +# ifndef TSAN_ENABLED +# define TSAN_ENABLED 1 +# endif +#endif + // Knuth's multiplicative constant (golden ratio * 2^64 for 64-bit) // Used for hash distribution in various components constexpr size_t KNUTH_MULTIPLICATIVE_CONSTANT = 0x9e3779b97f4a7c15ULL; diff --git a/ddprof-lib/src/main/cpp/gtest_crash_handler.h b/ddprof-lib/src/main/cpp/gtest_crash_handler.h index 7afa6c5bd..7bc15bcb1 100644 --- a/ddprof-lib/src/main/cpp/gtest_crash_handler.h +++ b/ddprof-lib/src/main/cpp/gtest_crash_handler.h @@ -13,6 +13,8 @@ #include #include +#include "common.h" // TSAN_ENABLED (toolchain-agnostic sanitizer detection) + // Platform detection for execinfo.h availability #if defined(__GLIBC__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) #define HAVE_EXECINFO_H 1 @@ -123,7 +125,7 @@ void specificCrashHandler(int sig, siginfo_t *info, void *context) { // and overriding them causes TSan to crash before it can write its report. template void installGtestCrashHandler() { -#if !defined(__SANITIZE_THREAD__) +#if !defined(TSAN_ENABLED) struct sigaction sa; sa.sa_flags = SA_SIGINFO; // Get detailed info, keep handler active sigemptyset(&sa.sa_mask); @@ -140,7 +142,7 @@ void installGtestCrashHandler() { // Restore default signal handlers. inline void restoreDefaultSignalHandlers() { -#if !defined(__SANITIZE_THREAD__) +#if !defined(TSAN_ENABLED) signal(SIGSEGV, SIG_DFL); signal(SIGBUS, SIG_DFL); signal(SIGABRT, SIG_DFL); diff --git a/ddprof-lib/src/main/cpp/linearAllocator.cpp b/ddprof-lib/src/main/cpp/linearAllocator.cpp index e50bfa441..cef773831 100644 --- a/ddprof-lib/src/main/cpp/linearAllocator.cpp +++ b/ddprof-lib/src/main/cpp/linearAllocator.cpp @@ -20,26 +20,17 @@ #include "os.h" #include "common.h" #include -#ifdef __SANITIZE_THREAD__ +#ifdef TSAN_ENABLED #include + #include #endif -// Enable ASan memory poisoning for better use-after-free detection -#ifdef __has_feature - #if __has_feature(address_sanitizer) - #define ASAN_ENABLED 1 - #endif -#endif - -#ifdef __SANITIZE_ADDRESS__ - #define ASAN_ENABLED 1 -#endif - +// ASAN_ENABLED / TSAN_ENABLED are defined in common.h (toolchain-agnostic). #ifdef ASAN_ENABLED #include #endif -#ifdef __SANITIZE_THREAD__ +#ifdef TSAN_ENABLED #include #endif @@ -58,13 +49,13 @@ void LinearAllocator::clear() { // never clears shadow memory on munmap. Add explicit acquire/release around // every plain prev-field read so the happens-before chain from freeChunk's // __tsan_release reaches any thread that later reuses the same VA. - #ifdef __SANITIZE_THREAD__ + #ifdef TSAN_ENABLED __tsan_acquire(_reserve); #endif if (_reserve->prev == _tail) { freeChunk(_reserve); // __tsan_release inside } - #ifdef __SANITIZE_THREAD__ + #ifdef TSAN_ENABLED else { __tsan_release(_reserve); // not freed here; release for future VA-reuse acquirers } @@ -95,14 +86,14 @@ void LinearAllocator::clear() { // chain covers the condition check, not just the assignment inside the body. { Chunk *current = _tail; - #ifdef __SANITIZE_THREAD__ + #ifdef TSAN_ENABLED __tsan_acquire(current); // before the first current->prev read (loop condition) #endif while (current->prev != NULL) { Chunk *next = current->prev; freeChunk(current); // __tsan_release(current) + safeFree inside current = next; - #ifdef __SANITIZE_THREAD__ + #ifdef TSAN_ENABLED __tsan_acquire(current); // before the next iteration's current->prev read #endif } @@ -126,13 +117,13 @@ ChunkList LinearAllocator::detachChunks() { // have been allocated by another thread via reserveChunk() → allocateChunk(), // which released ownership with __tsan_release after writing chunk->prev. if (_reserve != _tail) { - #ifdef __SANITIZE_THREAD__ + #ifdef TSAN_ENABLED __tsan_acquire(_reserve); #endif if (_reserve->prev == _tail) { result.head = _reserve; } - #ifdef __SANITIZE_THREAD__ + #ifdef TSAN_ENABLED __tsan_release(_reserve); #endif } @@ -166,11 +157,11 @@ void LinearAllocator::freeChunks(ChunkList& chunks) { // __tsan_release in allocateChunk() that published the initialized chunk. // Without this, TSan cannot connect the writer's (e.g. reserveChunk thread) // initialization of chunk->prev to this read, and reports a false data race. - #ifdef __SANITIZE_THREAD__ + #ifdef TSAN_ENABLED __tsan_acquire(current); #endif Chunk* prev = current->prev; - #ifdef __SANITIZE_THREAD__ + #ifdef TSAN_ENABLED __tsan_release(current); #endif OS::safeFree(current, chunks.chunk_size); @@ -232,12 +223,23 @@ Chunk *LinearAllocator::allocateChunk(Chunk *current) { // mmap() and calls MemoryRangeImitateWrite, which sets all 4 shadow slots // for the entire chunk range to the current thread's write — completely // overwriting any stale entries. safeAlloc itself is unchanged. + // + // This MUST stay TSan-build-only: the libc mmap() wrapper and TSan's + // interceptor are not async-signal-safe, and allocateChunk() is reachable + // from a signal handler in the full profiler. It is safe here only because + // TSAN_ENABLED is active solely in the isolated gtest binaries, which never + // drive the allocator from a signal handler. #ifdef ASAN_ENABLED ASAN_UNPOISON_MEMORY_REGION(chunk, _chunk_size); #endif - #ifdef __SANITIZE_THREAD__ - mmap(chunk, _chunk_size, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + #ifdef TSAN_ENABLED + void *remap = mmap(chunk, _chunk_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + // MAP_FIXED unmaps before it maps, so a failure would leave a hole at + // `chunk`; assert loudly rather than silently leaving stale shadow (or + // crashing on the writes below). TSan builds are debug builds (NDEBUG unset). + assert(remap == chunk && "TSan shadow re-map (mmap MAP_FIXED) failed"); + (void)remap; #endif chunk->prev = current; @@ -245,7 +247,7 @@ Chunk *LinearAllocator::allocateChunk(Chunk *current) { // Publish the initialized chunk: any thread that later acquires this chunk // (via __tsan_acquire in freeChunks/detachChunks) will see these writes. - #ifdef __SANITIZE_THREAD__ + #ifdef TSAN_ENABLED __tsan_release(chunk); #endif @@ -257,10 +259,10 @@ Chunk *LinearAllocator::allocateChunk(Chunk *current) { void LinearAllocator::freeChunk(Chunk *current) { // Release TSan ownership before munmap so the sanitizer knows this thread is - // done with the memory. The paired __tsan_acquire in allocateChunk() ensures - // the next thread to receive this VA (after OS VA reuse) starts with a clean - // happens-before baseline rather than seeing stale access history. - #ifdef __SANITIZE_THREAD__ + // done with the memory. The mmap(MAP_FIXED) re-map in allocateChunk() resets + // the shadow for whichever thread later reuses this VA (after OS VA reuse), so + // it starts from a clean baseline rather than seeing stale access history. + #ifdef TSAN_ENABLED __tsan_release(current); #endif OS::safeFree(current, _chunk_size); diff --git a/ddprof-lib/src/test/cpp/ddprof_ut.cpp b/ddprof-lib/src/test/cpp/ddprof_ut.cpp index 3a5db92e5..afdb990fe 100644 --- a/ddprof-lib/src/test/cpp/ddprof_ut.cpp +++ b/ddprof-lib/src/test/cpp/ddprof_ut.cpp @@ -375,7 +375,7 @@ static DdprofGlobalSetup ddprof_global_setup; // Without the fix tryEnterCriticalSection() returns false (exit 5). // fork() is unsupported under TSan: the child inherits shadow memory in an // inconsistent state and crashes before any TSan report can be written. - #if !defined(__SANITIZE_THREAD__) + #if !defined(TSAN_ENABLED) TEST(ProfiledThreadTeardown, CriticalSectionExitsEvenAfterTLSCleared) { pid_t pid = fork(); ASSERT_NE(-1, pid); @@ -413,7 +413,7 @@ static DdprofGlobalSetup ddprof_global_setup; ASSERT_TRUE(WIFEXITED(status)) << "child crashed (signal " << WTERMSIG(status) << ")"; ASSERT_EQ(0, WEXITSTATUS(status)) << "child exited with code " << WEXITSTATUS(status); } - #endif // !__SANITIZE_THREAD__ + #endif // !TSAN_ENABLED int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/ddprof-lib/src/test/cpp/stress_callTraceStorage.cpp b/ddprof-lib/src/test/cpp/stress_callTraceStorage.cpp index 6d81fb0a2..a8706967d 100644 --- a/ddprof-lib/src/test/cpp/stress_callTraceStorage.cpp +++ b/ddprof-lib/src/test/cpp/stress_callTraceStorage.cpp @@ -7,6 +7,7 @@ #include "callTraceStorage.h" #include "callTraceHashTable.h" #include "guards.h" +#include "common.h" // TSAN_ENABLED (toolchain-agnostic sanitizer detection) #include #include #include @@ -2034,7 +2035,7 @@ TEST_F(StressTestSuite, HashTableSpinWaitEdgeCasesTest) { // correctness across expansion boundaries independently of memory ordering; // it catches logic regressions in all build configurations. TEST_F(StressTestSuite, FindCallTraceAtomicReadRaceTest) { -#if !defined(__SANITIZE_THREAD__) && !(defined(__has_feature) && __has_feature(thread_sanitizer)) +#if !defined(TSAN_ENABLED) GTEST_SKIP() << "TSan-only race regression: re-run with -fsanitize=thread"; #endif diff --git a/docs/sphinx/specs/2026-06-12-tsan-write-range-replace-reset.md b/docs/sphinx/specs/2026-06-12-tsan-write-range-replace-reset.md deleted file mode 100644 index 193fa76a4..000000000 --- a/docs/sphinx/specs/2026-06-12-tsan-write-range-replace-reset.md +++ /dev/null @@ -1,23 +0,0 @@ -# Replace __tsan_reset_range with __tsan_write_range - -## Problem -`__tsan_reset_range` is not declared in `` and does not -exist in the LLVM/compiler-rt source. Using it causes GCC TSan builds to fail with -"was not declared in this scope". - -## Solution -Replace `__tsan_reset_range(chunk, _chunk_size)` with `__tsan_write_range(chunk, _chunk_size)`. -Add an explicit `extern "C"` forward declaration for `__tsan_write_range` inside the -`#ifdef __SANITIZE_THREAD__` block, since the function exists in the runtime but is absent -from the public header. - -## Files to change -- `ddprof-lib/src/main/cpp/linearAllocator.cpp` - - Lines 39–41: add `extern "C"` declaration for `__tsan_write_range` - - Line 233: replace `__tsan_reset_range` with `__tsan_write_range` - -## Acceptance criteria -1. The `__tsan_reset_range` identifier no longer appears in the file. -2. A forward declaration `extern "C" void __tsan_write_range(void*, size_t);` exists - inside `#ifdef __SANITIZE_THREAD__`. -3. `__tsan_write_range(chunk, _chunk_size)` is called at line ~233. From deb0d60c4aaf9a0bf7972ee81fc946476b464430 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Fri, 12 Jun 2026 12:34:00 +0200 Subject: [PATCH 4/4] fix(tsan): harden MAP_FIXED re-map and clarify TSAN_ENABLED comment Replace assert() with an unconditional abort on mmap(MAP_FIXED) failure so the failure is loud even under NDEBUG, and reword the comment so it no longer implies TSAN_ENABLED is gtest-scoped (it tracks compiler sanitizer detection; the test-only property comes from the build). Co-Authored-By: Claude Opus 4.8 (1M context) --- ddprof-lib/src/main/cpp/linearAllocator.cpp | 23 ++++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/ddprof-lib/src/main/cpp/linearAllocator.cpp b/ddprof-lib/src/main/cpp/linearAllocator.cpp index cef773831..4aea288fe 100644 --- a/ddprof-lib/src/main/cpp/linearAllocator.cpp +++ b/ddprof-lib/src/main/cpp/linearAllocator.cpp @@ -22,7 +22,7 @@ #include #ifdef TSAN_ENABLED #include - #include + #include #endif // ASAN_ENABLED / TSAN_ENABLED are defined in common.h (toolchain-agnostic). @@ -226,9 +226,13 @@ Chunk *LinearAllocator::allocateChunk(Chunk *current) { // // This MUST stay TSan-build-only: the libc mmap() wrapper and TSan's // interceptor are not async-signal-safe, and allocateChunk() is reachable - // from a signal handler in the full profiler. It is safe here only because - // TSAN_ENABLED is active solely in the isolated gtest binaries, which never - // drive the allocator from a signal handler. + // from a signal handler in the full profiler. TSAN_ENABLED is defined for + // any TSan-instrumented translation unit (it tracks the compiler's + // sanitizer detection in common.h, not the build target), but the build + // only applies -fsanitize=thread to the isolated gtest binaries — never to + // the shared library the JVM loads. Those gtest binaries never drive the + // allocator from a signal handler, so the non-async-signal-safe mmap() call + // here is safe in practice. #ifdef ASAN_ENABLED ASAN_UNPOISON_MEMORY_REGION(chunk, _chunk_size); #endif @@ -236,10 +240,13 @@ Chunk *LinearAllocator::allocateChunk(Chunk *current) { void *remap = mmap(chunk, _chunk_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); // MAP_FIXED unmaps before it maps, so a failure would leave a hole at - // `chunk`; assert loudly rather than silently leaving stale shadow (or - // crashing on the writes below). TSan builds are debug builds (NDEBUG unset). - assert(remap == chunk && "TSan shadow re-map (mmap MAP_FIXED) failed"); - (void)remap; + // `chunk` and the writes below would fault. Abort unconditionally rather + // than via assert(), which an NDEBUG build would strip — turning a clean, + // diagnosable failure into a stale-shadow or use-after-unmap crash. + if (remap != chunk) { + perror("TSan shadow re-map (mmap MAP_FIXED) failed"); + abort(); + } #endif chunk->prev = current;