Try to fix GWPAsan

This commit is contained in:
Antonio Andelic 2024-05-24 14:34:42 +02:00
parent ee3e7f2fd0
commit 5c6c378fae
9 changed files with 177 additions and 28 deletions

View File

@ -291,7 +291,7 @@ if (TARGET ch_contrib::llvm)
endif ()
if (TARGET ch_contrib::gwp_asan)
target_link_libraries (clickhouse_common_io PRIVATE ch_contrib::gwp_asan)
target_link_libraries (clickhouse_common_io PUBLIC ch_contrib::gwp_asan)
target_link_libraries (clickhouse_new_delete PRIVATE ch_contrib::gwp_asan)
endif()

View File

@ -1,4 +1,5 @@
#include <Common/Allocator.h>
#include <Common/memory.h>
#include <Common/Exception.h>
#include <Common/logger_useful.h>
#include <Common/formatReadable.h>
@ -10,6 +11,12 @@
#include <Poco/Logger.h>
#include <sys/mman.h> /// MADV_POPULATE_WRITE
namespace ProfileEvents
{
extern const Event GWPAsanAllocateSuccess;
extern const Event GWPAsanAllocateFailed;
extern const Event GWPAsanFree;
}
namespace DB
{
@ -59,13 +66,37 @@ void prefaultPages([[maybe_unused]] void * buf_, [[maybe_unused]] size_t len_)
template <bool clear_memory, bool populate>
void * allocNoTrack(size_t size, size_t alignment)
{
void * buf;
#if USE_GWP_ASAN
if (unlikely(Memory::GuardedAlloc.shouldSample()))
{
if (void * ptr = Memory::GuardedAlloc.allocate(size, alignment))
{
if constexpr (clear_memory)
memset(ptr, 0, size);
if constexpr (populate)
prefaultPages(ptr, size);
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateSuccess);
return ptr;
}
else
{
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateFailed);
}
}
#endif
if (alignment <= MALLOC_MIN_ALIGNMENT)
{
if constexpr (clear_memory)
buf = ::calloc(size, 1);
else
{
buf = ::malloc(size);
}
if (nullptr == buf)
throw DB::ErrnoException(DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY, "Allocator: Cannot malloc {}.", ReadableSize(size));
@ -91,6 +122,15 @@ void * allocNoTrack(size_t size, size_t alignment)
void freeNoTrack(void * buf)
{
#if USE_GWP_ASAN
if (unlikely(Memory::GuardedAlloc.pointerIsMine(buf)))
{
ProfileEvents::increment(ProfileEvents::GWPAsanFree);
Memory::GuardedAlloc.deallocate(buf);
return;
}
#endif
::free(buf);
}
@ -144,8 +184,54 @@ void * Allocator<clear_memory_, populate>::realloc(void * buf, size_t old_size,
{
/// nothing to do.
/// BTW, it's not possible to change alignment while doing realloc.
return buf;
}
else if (alignment <= MALLOC_MIN_ALIGNMENT)
#if USE_GWP_ASAN
if (unlikely(Memory::GuardedAlloc.shouldSample()))
{
if (void * ptr = Memory::GuardedAlloc.allocate(new_size, alignment))
{
auto trace_free = CurrentMemoryTracker::free(old_size);
auto trace_alloc = CurrentMemoryTracker::alloc(new_size);
trace_free.onFree(buf, old_size);
memcpy(ptr, buf, std::min(old_size, new_size));
free(buf, old_size);
trace_alloc.onAlloc(buf, new_size);
if constexpr (clear_memory)
if (new_size > old_size)
memset(reinterpret_cast<char *>(ptr) + old_size, 0, new_size - old_size);
if constexpr (populate)
prefaultPages(ptr, new_size);
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateSuccess);
return ptr;
}
else
{
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateFailed);
}
}
if (unlikely(Memory::GuardedAlloc.pointerIsMine(buf)))
{
/// Big allocs that requires a copy. MemoryTracker is called inside 'alloc', 'free' methods.
void * new_buf = alloc(new_size, alignment);
memcpy(new_buf, buf, std::min(old_size, new_size));
free(buf, old_size);
buf = new_buf;
if constexpr (populate)
prefaultPages(buf, new_size);
return buf;
}
#endif
if (alignment <= MALLOC_MIN_ALIGNMENT)
{
/// Resize malloc'd memory region with no special alignment requirement.
auto trace_free = CurrentMemoryTracker::free(old_size);

View File

@ -5,6 +5,7 @@
#include <Common/CurrentMetrics.h>
#include <Common/filesystemHelpers.h>
#include <Common/logger_useful.h>
#include <Common/memory.h>
#include <IO/UncompressedCache.h>
#include <IO/MMappedFileCache.h>
#include <IO/ReadHelpers.h>

View File

@ -1,5 +1,7 @@
#pragma once
#include "config.h"
#include <Common/Allocator.h>
#include <Common/BitHelpers.h>
#include <Common/memcpySmall.h>
@ -11,12 +13,16 @@
#include <cstddef>
#include <cassert>
#include <algorithm>
#include <memory>
#ifndef NDEBUG
#include <sys/mman.h>
#endif
#if USE_GWP_ASAN
# include <gwp_asan/platform_specific/guarded_pool_allocator_tls.h>
#endif
/** Whether we can use memcpy instead of a loop with assignment to T from U.
* It is Ok if types are the same. And if types are integral and of the same size,
* example: char, signed char, unsigned char.
@ -112,6 +118,10 @@ protected:
template <typename ... TAllocatorParams>
void alloc(size_t bytes, TAllocatorParams &&... allocator_params)
{
#if USE_GWP_ASAN
gwp_asan::getThreadLocals()->NextSampleCounter = 1;
#endif
char * allocated = reinterpret_cast<char *>(TAllocator::alloc(bytes, std::forward<TAllocatorParams>(allocator_params)...));
c_start = allocated + pad_left;
@ -141,6 +151,10 @@ protected:
return;
}
#if USE_GWP_ASAN
gwp_asan::getThreadLocals()->NextSampleCounter = 1;
#endif
unprotect();
ptrdiff_t end_diff = c_end - c_start;

View File

@ -744,6 +744,10 @@ The server successfully detected this situation and will download merged part fr
\
M(ReadWriteBufferFromHTTPRequestsSent, "Number of HTTP requests sent by ReadWriteBufferFromHTTP") \
M(ReadWriteBufferFromHTTPBytes, "Total size of payload bytes received and sent by ReadWriteBufferFromHTTP. Doesn't include HTTP headers.") \
\
M(GWPAsanAllocateSuccess, "Number of successful allocations done by GWPAsan") \
M(GWPAsanAllocateFailed, "Number of failed allocations done by GWPAsan (i.e. filled pool)") \
M(GWPAsanFree, "Number of free operations done by GWPAsan") \
#ifdef APPLY_FOR_EXTERNAL_EVENTS

View File

@ -1,13 +1,14 @@
#include <Common/memory.h>
#include <cstdlib>
/** These functions can be substituted instead of regular ones when memory tracking is needed.
*/
extern "C" void * clickhouse_malloc(size_t size)
{
void * res = malloc(size);
void * res = nullptr;
res = malloc(size);
if (res)
{
AllocationTrace trace;

22
src/Common/memory.cpp Normal file
View File

@ -0,0 +1,22 @@
#include <gwp_asan/guarded_pool_allocator.h>
#include <Common/memory.h>
#if USE_GWP_ASAN
namespace Memory
{
gwp_asan::GuardedPoolAllocator GuardedAlloc;
static bool guarded_alloc_initialized = []
{
gwp_asan::options::initOptions();
gwp_asan::options::Options &opts = gwp_asan::options::getOptions();
opts.MaxSimultaneousAllocations = 256;
GuardedAlloc.init(opts);
///std::cerr << "GwpAsan is initialized, the options are { Enabled: " << opts.Enabled
/// << ", MaxSimultaneousAllocations: " << opts.MaxSimultaneousAllocations
/// << ", SampleRate: " << opts.SampleRate << " }\n";
return true;
}();
}
#endif

View File

@ -5,6 +5,7 @@
#include <Common/Concepts.h>
#include <Common/CurrentMemoryTracker.h>
#include <Common/ProfileEvents.h>
#include "config.h"
#if USE_JEMALLOC
@ -17,13 +18,24 @@
#if USE_GWP_ASAN
# include <gwp_asan/guarded_pool_allocator.h>
# include <gwp_asan/optional/options_parser.h>
static gwp_asan::GuardedPoolAllocator GuardedAlloc;
#endif
namespace ProfileEvents
{
extern const Event GWPAsanAllocateSuccess;
extern const Event GWPAsanAllocateFailed;
extern const Event GWPAsanFree;
}
namespace Memory
{
#if USE_GWP_ASAN
extern gwp_asan::GuardedPoolAllocator GuardedAlloc;
#endif
inline ALWAYS_INLINE size_t alignToSizeT(std::align_val_t align) noexcept
{
return static_cast<size_t>(align);
@ -39,12 +51,26 @@ inline ALWAYS_INLINE void * newImpl(std::size_t size, TAlign... align)
if constexpr (sizeof...(TAlign) == 1)
{
if (void * ptr = GuardedAlloc.allocate(size, alignToSizeT(align...)))
{
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateSuccess);
return ptr;
}
else
{
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateFailed);
}
}
else
{
if (void * ptr = GuardedAlloc.allocate(size))
{
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateSuccess);
return ptr;
}
else
{
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateFailed);
}
}
}
@ -69,7 +95,14 @@ inline ALWAYS_INLINE void * newNoExept(std::size_t size) noexcept
if (unlikely(GuardedAlloc.shouldSample()))
{
if (void * ptr = GuardedAlloc.allocate(size))
{
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateSuccess);
return ptr;
}
else
{
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateFailed);
}
}
#endif
return malloc(size);
@ -81,7 +114,14 @@ inline ALWAYS_INLINE void * newNoExept(std::size_t size, std::align_val_t align)
if (unlikely(GuardedAlloc.shouldSample()))
{
if (void * ptr = GuardedAlloc.allocate(size, alignToSizeT(align)))
{
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateSuccess);
return ptr;
}
else
{
ProfileEvents::increment(ProfileEvents::GWPAsanAllocateFailed);
}
}
#endif
return aligned_alloc(static_cast<size_t>(align), size);
@ -92,6 +132,7 @@ inline ALWAYS_INLINE void deleteImpl(void * ptr) noexcept
#if USE_GWP_ASAN
if (unlikely(GuardedAlloc.pointerIsMine(ptr)))
{
ProfileEvents::increment(ProfileEvents::GWPAsanFree);
GuardedAlloc.deallocate(ptr);
return;
}
@ -111,6 +152,7 @@ inline ALWAYS_INLINE void deleteSized(void * ptr, std::size_t size, TAlign... al
#if USE_GWP_ASAN
if (unlikely(GuardedAlloc.pointerIsMine(ptr)))
{
ProfileEvents::increment(ProfileEvents::GWPAsanFree);
GuardedAlloc.deallocate(ptr);
return;
}
@ -131,6 +173,7 @@ inline ALWAYS_INLINE void deleteSized(void * ptr, std::size_t size [[maybe_unuse
#if USE_GWP_ASAN
if (unlikely(GuardedAlloc.pointerIsMine(ptr)))
{
ProfileEvents::increment(ProfileEvents::GWPAsanFree);
GuardedAlloc.deallocate(ptr);
return;
}

View File

@ -1,5 +1,4 @@
#include <cassert>
#include <iostream>
#include <new>
#include "config.h"
#include <Common/memory.h>
@ -42,27 +41,6 @@ static struct InitializeJemallocZoneAllocatorForOSX
} initializeJemallocZoneAllocatorForOSX;
#endif
#if USE_GWP_ASAN
#include <gwp_asan/optional/options_parser.h>
/// Both clickhouse_new_delete and clickhouse_common_io links gwp_asan, but It should only init once, otherwise it
/// will cause unexpected deadlock.
static struct InitGwpAsan
{
InitGwpAsan()
{
gwp_asan::options::initOptions();
gwp_asan::options::Options &opts = gwp_asan::options::getOptions();
GuardedAlloc.init(opts);
///std::cerr << "GwpAsan is initialized, the options are { Enabled: " << opts.Enabled
/// << ", MaxSimultaneousAllocations: " << opts.MaxSimultaneousAllocations
/// << ", SampleRate: " << opts.SampleRate << " }\n";
}
} init_gwp_asan;
#endif
/// Replace default new/delete with memory tracking versions.
/// @sa https://en.cppreference.com/w/cpp/memory/new/operator_new
/// https://en.cppreference.com/w/cpp/memory/new/operator_delete