* the requested space whenever the request is less than the actual chunk
* size, and verifies that the byte is undamaged when the chunk is freed.
*
+ *
+ * About USE_VALGRIND and Valgrind client requests:
+ *
+ * Valgrind provides "client request" macros that exchange information with
+ * the host Valgrind (if any). Under !USE_VALGRIND, memdebug.h stubs out
+ * currently-used macros.
+ *
+ * When running under Valgrind, we want a NOACCESS memory region both before
+ * and after the allocation. The chunk header is tempting as the preceding
+ * region, but mcxt.c expects to able to examine the standard chunk header
+ * fields. Therefore, we use, when available, the requested_size field and
+ * any subsequent padding. requested_size is made NOACCESS before returning
+ * a chunk pointer to a caller. However, to reduce client request traffic,
+ * it is kept DEFINED in chunks on the free list.
+ *
+ * The rounded-up capacity of the chunk usually acts as a post-allocation
+ * NOACCESS region. If the request consumes precisely the entire chunk,
+ * there is no such region; another chunk header may immediately follow. In
+ * that case, Valgrind will not detect access beyond the end of the chunk.
+ *
+ * See also the cooperating Valgrind client requests in mcxt.c.
+ *
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include "utils/memdebug.h"
#include "utils/memutils.h"
/* Define this to detail debug alloc information */
#define ALLOC_BLOCKHDRSZ MAXALIGN(sizeof(AllocBlockData))
#define ALLOC_CHUNKHDRSZ MAXALIGN(sizeof(AllocChunkData))
+/* Portion of ALLOC_CHUNKHDRSZ examined outside aset.c. */
+#define ALLOC_CHUNK_PUBLIC \
+ (offsetof(AllocChunkData, size) + sizeof(Size))
+
+/* Portion of ALLOC_CHUNKHDRSZ excluding trailing padding. */
+#ifdef MEMORY_CONTEXT_CHECKING
+#define ALLOC_CHUNK_USED \
+ (offsetof(AllocChunkData, requested_size) + sizeof(Size))
+#else
+#define ALLOC_CHUNK_USED \
+ (offsetof(AllocChunkData, size) + sizeof(Size))
+#endif
+
typedef struct AllocBlockData *AllocBlock; /* forward reference */
typedef struct AllocChunkData *AllocChunk;
static void
wipe_mem(void *ptr, size_t size)
{
+ VALGRIND_MAKE_MEM_UNDEFINED(ptr, size);
memset(ptr, 0x7F, size);
+ VALGRIND_MAKE_MEM_NOACCESS(ptr, size);
}
#endif
{
char *ptr = (char *) base + offset;
+ VALGRIND_MAKE_MEM_UNDEFINED(ptr, 1);
*ptr = 0x7E;
+ VALGRIND_MAKE_MEM_NOACCESS(ptr, 1);
}
static bool
const char *ptr = (const char *) base + offset;
bool ret;
+ VALGRIND_MAKE_MEM_DEFINED(ptr, 1);
ret = *ptr == 0x7E;
+ VALGRIND_MAKE_MEM_NOACCESS(ptr, 1);
return ret;
}
* very random, just a repeating sequence with a length that's prime. What
* we mainly want out of it is to have a good probability that two palloc's
* of the same number of bytes start out containing different data.
+ *
+ * The region may be NOACCESS, so make it UNDEFINED first to avoid errors as
+ * we fill it. Filling the region makes it DEFINED, so make it UNDEFINED
+ * again afterward. Whether to finally make it UNDEFINED or NOACCESS is
+ * fairly arbitrary. UNDEFINED is more convenient for AllocSetRealloc(), and
+ * other callers have no preference.
*/
static void
randomize_mem(char *ptr, size_t size)
{
static int save_ctr = 1;
+ size_t remaining = size;
int ctr;
ctr = save_ctr;
- while (size-- > 0)
+ VALGRIND_MAKE_MEM_UNDEFINED(ptr, size);
+ while (remaining-- > 0)
{
*ptr++ = ctr;
if (++ctr > 251)
ctr = 1;
}
+ VALGRIND_MAKE_MEM_UNDEFINED(ptr - size, size);
save_ctr = ctr;
}
#endif /* RANDOMIZE_ALLOCATED_MEMORY */
context->blocks = block;
/* Mark block as not to be released at reset time */
context->keeper = block;
+
+ /* Mark unallocated space NOACCESS; leave the block header alone. */
+ VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
+ blksize - ALLOC_BLOCKHDRSZ);
}
return (MemoryContext) context;
#ifdef CLOBBER_FREED_MEMORY
wipe_mem(datastart, block->freeptr - datastart);
+#else
+ /* wipe_mem() would have done this */
+ VALGRIND_MAKE_MEM_NOACCESS(datastart, block->freeptr - datastart);
#endif
block->freeptr = datastart;
block->next = NULL;
chunk->aset = set;
chunk->size = chunk_size;
#ifdef MEMORY_CONTEXT_CHECKING
+ /* Valgrind: Will be made NOACCESS below. */
chunk->requested_size = size;
/* set mark to catch clobber of "unused" space */
if (size < chunk_size)
}
AllocAllocInfo(set, chunk);
+
+ /*
+ * Chunk header public fields remain DEFINED. The requested
+ * allocation itself can be NOACCESS or UNDEFINED; our caller will
+ * soon make it UNDEFINED. Make extra space at the end of the chunk,
+ * if any, NOACCESS.
+ */
+ VALGRIND_MAKE_MEM_NOACCESS((char *) chunk + ALLOC_CHUNK_PUBLIC,
+ chunk_size + ALLOC_CHUNKHDRSZ - ALLOC_CHUNK_PUBLIC);
+
return AllocChunkGetPointer(chunk);
}
chunk->aset = (void *) set;
#ifdef MEMORY_CONTEXT_CHECKING
+ /* Valgrind: Free list requested_size should be DEFINED. */
chunk->requested_size = size;
+ VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size,
+ sizeof(chunk->requested_size));
/* set mark to catch clobber of "unused" space */
if (size < chunk->size)
set_sentinel(AllocChunkGetPointer(chunk), size);
chunk = (AllocChunk) (block->freeptr);
+ /* Prepare to initialize the chunk header. */
+ VALGRIND_MAKE_MEM_UNDEFINED(chunk, ALLOC_CHUNK_USED);
+
block->freeptr += (availchunk + ALLOC_CHUNKHDRSZ);
availspace -= (availchunk + ALLOC_CHUNKHDRSZ);
if (set->keeper == NULL && blksize == set->initBlockSize)
set->keeper = block;
+ /* Mark unallocated space NOACCESS. */
+ VALGRIND_MAKE_MEM_NOACCESS(block->freeptr,
+ blksize - ALLOC_BLOCKHDRSZ);
+
block->next = set->blocks;
set->blocks = block;
}
*/
chunk = (AllocChunk) (block->freeptr);
+ /* Prepare to initialize the chunk header. */
+ VALGRIND_MAKE_MEM_UNDEFINED(chunk, ALLOC_CHUNK_USED);
+
block->freeptr += (chunk_size + ALLOC_CHUNKHDRSZ);
Assert(block->freeptr <= block->endptr);
chunk->size = chunk_size;
#ifdef MEMORY_CONTEXT_CHECKING
chunk->requested_size = size;
+ VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size,
+ sizeof(chunk->requested_size));
/* set mark to catch clobber of "unused" space */
if (size < chunk->size)
set_sentinel(AllocChunkGetPointer(chunk), size);
AllocFreeInfo(set, chunk);
#ifdef MEMORY_CONTEXT_CHECKING
+ VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size,
+ sizeof(chunk->requested_size));
/* Test for someone scribbling on unused space in chunk */
if (chunk->requested_size < chunk->size)
if (!sentinel_ok(pointer, chunk->requested_size))
* Returns new pointer to allocated memory of given size; this memory
* is added to the set. Memory associated with given pointer is copied
* into the new memory, and the old memory is freed.
+ *
+ * Without MEMORY_CONTEXT_CHECKING, we don't know the old request size. This
+ * makes our Valgrind client requests less-precise, hazarding false negatives.
+ * (In principle, we could use VALGRIND_GET_VBITS() to rediscover the old
+ * request size.)
*/
static void *
AllocSetRealloc(MemoryContext context, void *pointer, Size size)
Size oldsize = chunk->size;
#ifdef MEMORY_CONTEXT_CHECKING
+ VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size,
+ sizeof(chunk->requested_size));
/* Test for someone scribbling on unused space in chunk */
if (chunk->requested_size < oldsize)
if (!sentinel_ok(pointer, chunk->requested_size))
if (oldsize >= size)
{
#ifdef MEMORY_CONTEXT_CHECKING
+ Size oldrequest = chunk->requested_size;
+
#ifdef RANDOMIZE_ALLOCATED_MEMORY
/* We can only fill the extra space if we know the prior request */
- if (size > chunk->requested_size)
- randomize_mem((char *) AllocChunkGetPointer(chunk) + chunk->requested_size,
- size - chunk->requested_size);
+ if (size > oldrequest)
+ randomize_mem((char *) pointer + oldrequest,
+ size - oldrequest);
#endif
chunk->requested_size = size;
+ VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size,
+ sizeof(chunk->requested_size));
+
+ /*
+ * If this is an increase, mark any newly-available part UNDEFINED.
+ * Otherwise, mark the obsolete part NOACCESS.
+ */
+ if (size > oldrequest)
+ VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + oldrequest,
+ size - oldrequest);
+ else
+ VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size,
+ oldsize - size);
+
/* set mark to catch clobber of "unused" space */
if (size < oldsize)
set_sentinel(pointer, size);
+#else /* !MEMORY_CONTEXT_CHECKING */
+
+ /*
+ * We don't have the information to determine whether we're growing
+ * the old request or shrinking it, so we conservatively mark the
+ * entire new allocation DEFINED.
+ */
+ VALGRIND_MAKE_MEM_NOACCESS(pointer, oldsize);
+ VALGRIND_MAKE_MEM_DEFINED(pointer, size);
#endif
+
return pointer;
}
/* Update pointers since block has likely been moved */
chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ);
+ pointer = AllocChunkGetPointer(chunk);
if (prevblock == NULL)
set->blocks = block;
else
#ifdef MEMORY_CONTEXT_CHECKING
#ifdef RANDOMIZE_ALLOCATED_MEMORY
/* We can only fill the extra space if we know the prior request */
- randomize_mem((char *) AllocChunkGetPointer(chunk) + chunk->requested_size,
+ randomize_mem((char *) pointer + chunk->requested_size,
size - chunk->requested_size);
#endif
+ /*
+ * realloc() (or randomize_mem()) will have left the newly-allocated
+ * part UNDEFINED, but we may need to adjust trailing bytes from the
+ * old allocation.
+ */
+ VALGRIND_MAKE_MEM_UNDEFINED((char *) pointer + chunk->requested_size,
+ oldsize - chunk->requested_size);
+
chunk->requested_size = size;
+ VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size,
+ sizeof(chunk->requested_size));
+
/* set mark to catch clobber of "unused" space */
if (size < chunk->size)
set_sentinel(AllocChunkGetPointer(chunk), size);
+#else /* !MEMORY_CONTEXT_CHECKING */
+
+ /*
+ * We don't know how much of the old chunk size was the actual
+ * allocation; it could have been as small as one byte. We have to be
+ * conservative and just mark the entire old portion DEFINED.
+ */
+ VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize);
#endif
+ /* Make any trailing alignment padding NOACCESS. */
+ VALGRIND_MAKE_MEM_NOACCESS((char *) pointer + size, chksize - size);
return AllocChunkGetPointer(chunk);
}
else
/* allocate new chunk */
newPointer = AllocSetAlloc((MemoryContext) set, size);
+ /*
+ * AllocSetAlloc() just made the region NOACCESS. Change it to
+ * UNDEFINED for the moment; memcpy() will then transfer definedness
+ * from the old allocation to the new. If we know the old allocation,
+ * copy just that much. Otherwise, make the entire old chunk defined
+ * to avoid errors as we copy the currently-NOACCESS trailing bytes.
+ */
+ VALGRIND_MAKE_MEM_UNDEFINED(newPointer, size);
+#ifdef MEMORY_CONTEXT_CHECKING
+ oldsize = chunk->requested_size;
+#else
+ VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize);
+#endif
+
/* transfer existing data (certain to fit) */
memcpy(newPointer, pointer, oldsize);
dsize;
chsize = chunk->size; /* aligned chunk size */
+ VALGRIND_MAKE_MEM_DEFINED(&chunk->requested_size,
+ sizeof(chunk->requested_size));
dsize = chunk->requested_size; /* real data */
+ if (dsize > 0) /* not on a free list */
+ VALGRIND_MAKE_MEM_NOACCESS(&chunk->requested_size,
+ sizeof(chunk->requested_size));
/*
* Check chunk size
#include "postgres.h"
+#include "utils/memdebug.h"
#include "utils/memutils.h"
{
(*context->methods->reset) (context);
context->isReset = true;
+ VALGRIND_DESTROY_MEMPOOL(context);
+ VALGRIND_CREATE_MEMPOOL(context, 0, false);
}
}
MemoryContextSetParent(context, NULL);
(*context->methods->delete_context) (context);
+ VALGRIND_DESTROY_MEMPOOL(context);
pfree(context);
}
parent->firstchild = node;
}
+ VALGRIND_CREATE_MEMPOOL(node, 0, false);
+
/* Return to type-specific creation routine to finish up */
return node;
}
context->isReset = false;
ret = (*context->methods->alloc) (context, size);
+ VALGRIND_MEMPOOL_ALLOC(context, ret, size);
return ret;
}
context->isReset = false;
ret = (*context->methods->alloc) (context, size);
+ VALGRIND_MEMPOOL_ALLOC(context, ret, size);
MemSetAligned(ret, 0, size);
context->isReset = false;
ret = (*context->methods->alloc) (context, size);
+ VALGRIND_MEMPOOL_ALLOC(context, ret, size);
MemSetLoop(ret, 0, size);
CurrentMemoryContext->isReset = false;
ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size);
+ VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
return ret;
}
CurrentMemoryContext->isReset = false;
ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size);
+ VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size);
MemSetAligned(ret, 0, size);
AssertArg(MemoryContextIsValid(context));
(*context->methods->free_p) (context, pointer);
+ VALGRIND_MEMPOOL_FREE(context, pointer);
}
/*
Assert(!context->isReset);
ret = (*context->methods->realloc) (context, pointer, size);
+ VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
return ret;
}