Valgrind's Memcheck tool[1] is handy for finding bugs, but our use of a custom allocator limits its ability to detect problems in unmodified PostgreSQL. During the 9.1 beta cycle, I found some bugs[2] with a rough patch adding instrumentation to aset.c and mcxt.c such that Memcheck understood our allocator. I've passed that patch around to a few people over time, and I've now removed the roughness such that it's ready for upstream. In hopes of making things clearer in the commit history, I've split out a preliminary refactoring patch from the main patch and attached each separately.
Besides the aset.c/mcxt.c instrumentation, this patch adds explicit checks for undefined memory to PageAddItem() and printtup(); this has caught C-language functions that fabricate a Datum without initializing all bits. The inclusion of all this is controlled by a pg_config_manual.h setting. The patch also adds a "suppression file" that directs Valgrind to silences nominal errors we don't plan to fix. To smoke-test the instrumentation, I used "make installcheck" runs on x86_64 GNU/Linux and ppc64 GNU/Linux. This turned up various new and newly-detected memory bugs, which I will discuss in a separate thread. With those fixed, "make installcheck" has a clean report (in my one particular configuration). I designed the suppression file to work across platforms; I specifically anticipated eventual use on x86_64 Darwin and x86_64 FreeBSD. Valgrind 3.8.1 quickly crashed when running PostgreSQL on Darwin; I did not dig further. Since aset.c and mcxt.c contain the hottest code paths in the backend, I verified that a !USE_VALGRIND, non-assert build produces the same code before and after the patch. Testing that revealed the need to move up the AllocSizeIsValid() check in repalloc(), though I don't understand why GCC reacted that way. Peter Geoghegan and Korry Douglas provided valuable feedback on earlier versions of this code. Possible future directions: - Test "make installcheck-world". When I last did this in past years, contrib did trigger some errors. - Test recovery, such as by running a streaming replica under Memcheck while the primary runs "make installcheck-world". - Test newer compilers and higher optimization levels. I used GCC 4.2 at -O1. A brief look at -O2 results showed a new error that I have not studied. GCC 4.8 at -O3 might show still more due to increasingly-aggressive assumptions. - A buildfarm member running its installcheck steps this way. - Memcheck has support for detecting leaks. I have not explored that side at all, always passing --leak-check=no. We could add support for freeing "everything" at process exit, thereby making the leak detection meaningful. Brief notes for folks reproducing my approach: I typically start the Memcheck-hosted postgres like this: valgrind --leak-check=no --gen-suppressions=all \ --suppressions=src/tools/valgrind.supp --time-stamp=yes \ --log-file=$HOME/pg-valgrind/%p.log postgres If that detected an error on which I desired more detail, I would rerun a smaller test case with "--track-origins=yes --read-var-info=yes". That slows things noticeably but gives more-specific messaging. When even that left the situation unclear, I would temporarily hack allocChunkLimit so every palloc() turned into a malloc(). I strongly advise installing the latest-available Valgrind, particularly because recent releases suffer far less of a performance drop processing the instrumentation added by this patch. A "make installcheck" run takes 273 minutes under Vaglrind 3.6.0 but just 27 minutes under Valgrind 3.8.1. Thanks, nm [1] http://valgrind.org/docs/manual/mc-manual.html [2] http://www.postgresql.org/message-id/20110312133224.ga7...@tornado.gateway.2wire.net -- Noah Misch EnterpriseDB http://www.enterprisedb.com
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c index 6a111c7..de64377 100644 --- a/src/backend/utils/mmgr/aset.c +++ b/src/backend/utils/mmgr/aset.c @@ -308,6 +308,37 @@ AllocSetFreeIndex(Size size) return idx; } +#ifdef CLOBBER_FREED_MEMORY + +/* Wipe freed memory for debugging purposes */ +static void +wipe_mem(void *ptr, size_t size) +{ + memset(ptr, 0x7F, size); +} +#endif + +#ifdef MEMORY_CONTEXT_CHECKING +static void +set_sentinel(void *base, Size offset) +{ + char *ptr = (char *) base + offset; + + *ptr = 0x7E; +} + +static bool +sentinel_ok(const void *base, Size offset) +{ + const char *ptr = (const char *) base + offset; + bool ret; + + ret = *ptr == 0x7E; + + return ret; +} +#endif + #ifdef RANDOMIZE_ALLOCATED_MEMORY /* @@ -492,8 +523,7 @@ AllocSetReset(MemoryContext context) char *datastart = ((char *) block) + ALLOC_BLOCKHDRSZ; #ifdef CLOBBER_FREED_MEMORY - /* Wipe freed memory for debugging purposes */ - memset(datastart, 0x7F, block->freeptr - datastart); + wipe_mem(datastart, block->freeptr - datastart); #endif block->freeptr = datastart; block->next = NULL; @@ -502,8 +532,7 @@ AllocSetReset(MemoryContext context) { /* Normal case, release the block */ #ifdef CLOBBER_FREED_MEMORY - /* Wipe freed memory for debugging purposes */ - memset(block, 0x7F, block->freeptr - ((char *) block)); + wipe_mem(block, block->freeptr - ((char *) block)); #endif free(block); } @@ -545,8 +574,7 @@ AllocSetDelete(MemoryContext context) AllocBlock next = block->next; #ifdef CLOBBER_FREED_MEMORY - /* Wipe freed memory for debugging purposes */ - memset(block, 0x7F, block->freeptr - ((char *) block)); + wipe_mem(block, block->freeptr - ((char *) block)); #endif free(block); block = next; @@ -598,7 +626,7 @@ AllocSetAlloc(MemoryContext context, Size size) chunk->requested_size = size; /* set mark to catch clobber of "unused" space */ if (size < chunk_size) - ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E; + set_sentinel(AllocChunkGetPointer(chunk), size); #endif #ifdef RANDOMIZE_ALLOCATED_MEMORY /* fill the allocated space with junk */ @@ -644,7 +672,7 @@ AllocSetAlloc(MemoryContext context, Size size) chunk->requested_size = size; /* set mark to catch clobber of "unused" space */ if (size < chunk->size) - ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E; + set_sentinel(AllocChunkGetPointer(chunk), size); #endif #ifdef RANDOMIZE_ALLOCATED_MEMORY /* fill the allocated space with junk */ @@ -801,7 +829,7 @@ AllocSetAlloc(MemoryContext context, Size size) chunk->requested_size = size; /* set mark to catch clobber of "unused" space */ if (size < chunk->size) - ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E; + set_sentinel(AllocChunkGetPointer(chunk), size); #endif #ifdef RANDOMIZE_ALLOCATED_MEMORY /* fill the allocated space with junk */ @@ -827,7 +855,7 @@ AllocSetFree(MemoryContext context, void *pointer) #ifdef MEMORY_CONTEXT_CHECKING /* Test for someone scribbling on unused space in chunk */ if (chunk->requested_size < chunk->size) - if (((char *) pointer)[chunk->requested_size] != 0x7E) + if (!sentinel_ok(pointer, chunk->requested_size)) elog(WARNING, "detected write past chunk end in %s %p", set->header.name, chunk); #endif @@ -860,8 +888,7 @@ AllocSetFree(MemoryContext context, void *pointer) else prevblock->next = block->next; #ifdef CLOBBER_FREED_MEMORY - /* Wipe freed memory for debugging purposes */ - memset(block, 0x7F, block->freeptr - ((char *) block)); + wipe_mem(block, block->freeptr - ((char *) block)); #endif free(block); } @@ -873,8 +900,7 @@ AllocSetFree(MemoryContext context, void *pointer) chunk->aset = (void *) set->freelist[fidx]; #ifdef CLOBBER_FREED_MEMORY - /* Wipe freed memory for debugging purposes */ - memset(pointer, 0x7F, chunk->size); + wipe_mem(pointer, chunk->size); #endif #ifdef MEMORY_CONTEXT_CHECKING @@ -901,7 +927,7 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) #ifdef MEMORY_CONTEXT_CHECKING /* Test for someone scribbling on unused space in chunk */ if (chunk->requested_size < oldsize) - if (((char *) pointer)[chunk->requested_size] != 0x7E) + if (!sentinel_ok(pointer, chunk->requested_size)) elog(WARNING, "detected write past chunk end in %s %p", set->header.name, chunk); #endif @@ -924,7 +950,7 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) chunk->requested_size = size; /* set mark to catch clobber of "unused" space */ if (size < oldsize) - ((char *) pointer)[size] = 0x7E; + set_sentinel(pointer, size); #endif return pointer; } @@ -987,7 +1013,7 @@ AllocSetRealloc(MemoryContext context, void *pointer, Size size) chunk->requested_size = size; /* set mark to catch clobber of "unused" space */ if (size < chunk->size) - ((char *) AllocChunkGetPointer(chunk))[size] = 0x7E; + set_sentinel(AllocChunkGetPointer(chunk), size); #endif return AllocChunkGetPointer(chunk); @@ -1136,11 +1162,9 @@ AllocSetCheck(MemoryContext context) AllocChunk chunk = (AllocChunk) bpoz; Size chsize, dsize; - char *chdata_end; chsize = chunk->size; /* aligned chunk size */ dsize = chunk->requested_size; /* real data */ - chdata_end = ((char *) chunk) + (ALLOC_CHUNKHDRSZ + dsize); /* * Check chunk size @@ -1170,7 +1194,8 @@ AllocSetCheck(MemoryContext context) /* * Check for overwrite of "unallocated" space in chunk */ - if (dsize > 0 && dsize < chsize && *chdata_end != 0x7E) + if (dsize > 0 && dsize < chsize && + !sentinel_ok(chunk, ALLOC_CHUNKHDRSZ + dsize)) elog(WARNING, "problem in alloc set %s: detected write past chunk end in block %p, chunk %p", name, block, chunk); diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 8dd3cf4..c72e347 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -569,6 +569,8 @@ MemoryContextCreate(NodeTag tag, Size size, void * MemoryContextAlloc(MemoryContext context, Size size) { + void *ret; + AssertArg(MemoryContextIsValid(context)); if (!AllocSizeIsValid(size)) @@ -577,7 +579,9 @@ MemoryContextAlloc(MemoryContext context, Size size) context->isReset = false; - return (*context->methods->alloc) (context, size); + ret = (*context->methods->alloc) (context, size); + + return ret; } /* @@ -638,6 +642,8 @@ void * palloc(Size size) { /* duplicates MemoryContextAlloc to avoid increased overhead */ + void *ret; + AssertArg(MemoryContextIsValid(CurrentMemoryContext)); if (!AllocSizeIsValid(size)) @@ -646,7 +652,9 @@ palloc(Size size) CurrentMemoryContext->isReset = false; - return (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size); + ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size); + + return ret; } void * @@ -677,7 +685,7 @@ palloc0(Size size) void pfree(void *pointer) { - StandardChunkHeader *header; + MemoryContext context; /* * Try to detect bogus pointers handed to us, poorly though we can. @@ -690,12 +698,12 @@ pfree(void *pointer) /* * OK, it's probably safe to look at the chunk header. */ - header = (StandardChunkHeader *) - ((char *) pointer - STANDARDCHUNKHEADERSIZE); + context = ((StandardChunkHeader *) + ((char *) pointer - STANDARDCHUNKHEADERSIZE))->context; - AssertArg(MemoryContextIsValid(header->context)); + AssertArg(MemoryContextIsValid(context)); - (*header->context->methods->free_p) (header->context, pointer); + (*context->methods->free_p) (context, pointer); } /* @@ -705,7 +713,12 @@ pfree(void *pointer) void * repalloc(void *pointer, Size size) { - StandardChunkHeader *header; + MemoryContext context; + void *ret; + + if (!AllocSizeIsValid(size)) + elog(ERROR, "invalid memory alloc request size %lu", + (unsigned long) size); /* * Try to detect bogus pointers handed to us, poorly though we can. @@ -718,20 +731,17 @@ repalloc(void *pointer, Size size) /* * OK, it's probably safe to look at the chunk header. */ - header = (StandardChunkHeader *) - ((char *) pointer - STANDARDCHUNKHEADERSIZE); + context = ((StandardChunkHeader *) + ((char *) pointer - STANDARDCHUNKHEADERSIZE))->context; - AssertArg(MemoryContextIsValid(header->context)); - - if (!AllocSizeIsValid(size)) - elog(ERROR, "invalid memory alloc request size %lu", - (unsigned long) size); + AssertArg(MemoryContextIsValid(context)); /* isReset must be false already */ - Assert(!header->context->isReset); + Assert(!context->isReset); + + ret = (*context->methods->realloc) (context, pointer, size); - return (*header->context->methods->realloc) (header->context, - pointer, size); + return ret; } /*
*** a/src/backend/access/common/printtup.c --- b/src/backend/access/common/printtup.c *************** *** 20,25 **** --- 20,26 ---- #include "libpq/pqformat.h" #include "tcop/pquery.h" #include "utils/lsyscache.h" + #include "utils/memdebug.h" static void printtup_startup(DestReceiver *self, int operation, *************** *** 324,332 **** printtup(TupleTableSlot *slot, DestReceiver *self) --- 325,350 ---- /* * If we have a toasted datum, forcibly detoast it here to avoid * memory leakage inside the type's output routine. + * + * Here we catch undefined bytes in tuples that are returned to the + * client without hitting disk; see comments at the related check in + * PageAddItem(). Whether to test before or after detoast is somewhat + * arbitrary, as is whether to test external/compressed data at all. + * Undefined bytes in the pre-toast datum will have triggered Valgrind + * errors in the compressor or toaster; any error detected here for + * such datums would indicate an (unlikely) bug in a type-independent + * facility. Therefore, this test is most useful for uncompressed, + * non-external datums. + * + * We don't presently bother checking non-varlena datums for undefined + * data. PageAddItem() does check them. */ if (thisState->typisvarlena) + { + VALGRIND_CHECK_MEM_IS_DEFINED(origattr, VARSIZE_ANY(origattr)); + attr = PointerGetDatum(PG_DETOAST_DATUM(origattr)); + } else attr = origattr; *** a/src/backend/storage/page/bufpage.c --- b/src/backend/storage/page/bufpage.c *************** *** 17,22 **** --- 17,23 ---- #include "access/htup_details.h" #include "access/xlog.h" #include "storage/checksum.h" + #include "utils/memdebug.h" bool ignore_checksum_failure = false; *************** *** 298,303 **** PageAddItem(Page page, --- 299,318 ---- /* set the item pointer */ ItemIdSetNormal(itemId, upper, size); + /* + * Items normally contain no uninitialized bytes. Core bufpage consumers + * conform, but this is not a necessary coding rule; a new index AM could + * opt to depart from it. However, data type input functions and other + * C-language functions that synthesize datums should initialize all + * bytes; datumIsEqual() relies on this. Testing here, along with the + * similar check in printtup(), helps to catch such mistakes. + * + * Values of the "name" type retrieved via index-only scans may contain + * uninitialized bytes; see comment in btrescan(). Valgrind will report + * this as an error, but it is safe to ignore. + */ + VALGRIND_CHECK_MEM_IS_DEFINED(item, size); + /* copy the item's data onto the page */ memcpy((char *) page + upper, item, size); *** a/src/backend/tcop/postgres.c --- b/src/backend/tcop/postgres.c *************** *** 69,74 **** --- 69,75 ---- #include "tcop/tcopprot.h" #include "tcop/utility.h" #include "utils/lsyscache.h" + #include "utils/memdebug.h" #include "utils/memutils.h" #include "utils/ps_status.h" #include "utils/snapmgr.h" *************** *** 846,851 **** exec_simple_query(const char *query_string) --- 847,856 ---- TRACE_POSTGRESQL_QUERY_START(query_string); + #ifdef USE_VALGRIND + VALGRIND_PRINTF("statement: %s\n", query_string); + #endif + /* * We use save_log_statement_stats so ShowUsage doesn't report incorrect * results because ResetUsage wasn't called. *** a/src/backend/utils/mmgr/aset.c --- b/src/backend/utils/mmgr/aset.c *************** *** 59,69 **** --- 59,92 ---- * 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 */ *************** *** 116,121 **** --- 139,157 ---- #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; *************** *** 314,320 **** AllocSetFreeIndex(Size size) --- 350,358 ---- 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 *************** *** 324,330 **** set_sentinel(void *base, Size offset) --- 362,370 ---- { char *ptr = (char *) base + offset; + VALGRIND_MAKE_MEM_UNDEFINED(ptr, 1); *ptr = 0x7E; + VALGRIND_MAKE_MEM_NOACCESS(ptr, 1); } static bool *************** *** 333,339 **** sentinel_ok(const void *base, Size offset) --- 373,381 ---- 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; } *************** *** 346,365 **** sentinel_ok(const void *base, Size offset) * 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. */ static void randomize_mem(char *ptr, size_t size) { static int save_ctr = 1; int ctr; ctr = save_ctr; ! while (size-- > 0) { *ptr++ = ctr; if (++ctr > 251) ctr = 1; } save_ctr = ctr; } #endif /* RANDOMIZE_ALLOCATED_MEMORY */ --- 388,416 ---- * 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; ! 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 */ *************** *** 455,460 **** AllocSetContextCreate(MemoryContext parent, --- 506,515 ---- 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; *************** *** 524,529 **** AllocSetReset(MemoryContext context) --- 579,587 ---- #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; *************** *** 623,628 **** AllocSetAlloc(MemoryContext context, Size size) --- 681,687 ---- 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) *************** *** 649,654 **** AllocSetAlloc(MemoryContext context, Size size) --- 708,723 ---- } 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); } *************** *** 669,675 **** AllocSetAlloc(MemoryContext context, Size size) --- 738,747 ---- 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); *************** *** 730,735 **** AllocSetAlloc(MemoryContext context, Size size) --- 802,810 ---- 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); *************** *** 811,816 **** AllocSetAlloc(MemoryContext context, Size size) --- 886,895 ---- 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; } *************** *** 820,825 **** AllocSetAlloc(MemoryContext context, Size size) --- 899,907 ---- */ 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); *************** *** 827,832 **** AllocSetAlloc(MemoryContext context, Size size) --- 909,916 ---- 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); *************** *** 853,858 **** AllocSetFree(MemoryContext context, void *pointer) --- 937,944 ---- 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)) *************** *** 916,921 **** AllocSetFree(MemoryContext context, void *pointer) --- 1002,1012 ---- * 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) *************** *** 925,930 **** AllocSetRealloc(MemoryContext context, void *pointer, Size size) --- 1016,1023 ---- 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)) *************** *** 940,957 **** AllocSetRealloc(MemoryContext context, void *pointer, Size size) if (oldsize >= size) { #ifdef MEMORY_CONTEXT_CHECKING #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); #endif chunk->requested_size = size; /* set mark to catch clobber of "unused" space */ if (size < oldsize) set_sentinel(pointer, size); #endif return pointer; } --- 1033,1076 ---- 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 > 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; } *************** *** 997,1002 **** AllocSetRealloc(MemoryContext context, void *pointer, Size size) --- 1116,1122 ---- /* Update pointers since block has likely been moved */ chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ); + pointer = AllocChunkGetPointer(chunk); if (prevblock == NULL) set->blocks = block; else *************** *** 1006,1021 **** AllocSetRealloc(MemoryContext context, void *pointer, Size size) #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, size - chunk->requested_size); #endif chunk->requested_size = size; /* set mark to catch clobber of "unused" space */ if (size < chunk->size) set_sentinel(AllocChunkGetPointer(chunk), size); #endif return AllocChunkGetPointer(chunk); } else --- 1126,1162 ---- #ifdef MEMORY_CONTEXT_CHECKING #ifdef RANDOMIZE_ALLOCATED_MEMORY /* We can only fill the extra space if we know the prior request */ ! 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 *************** *** 1036,1041 **** AllocSetRealloc(MemoryContext context, void *pointer, Size size) --- 1177,1196 ---- /* 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); *************** *** 1164,1170 **** AllocSetCheck(MemoryContext context) --- 1319,1330 ---- 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 *** a/src/backend/utils/mmgr/mcxt.c --- b/src/backend/utils/mmgr/mcxt.c *************** *** 24,29 **** --- 24,30 ---- #include "postgres.h" + #include "utils/memdebug.h" #include "utils/memutils.h" *************** *** 135,140 **** MemoryContextReset(MemoryContext context) --- 136,143 ---- { (*context->methods->reset) (context); context->isReset = true; + VALGRIND_DESTROY_MEMPOOL(context); + VALGRIND_CREATE_MEMPOOL(context, 0, false); } } *************** *** 184,189 **** MemoryContextDelete(MemoryContext context) --- 187,193 ---- MemoryContextSetParent(context, NULL); (*context->methods->delete_context) (context); + VALGRIND_DESTROY_MEMPOOL(context); pfree(context); } *************** *** 555,560 **** MemoryContextCreate(NodeTag tag, Size size, --- 559,566 ---- parent->firstchild = node; } + VALGRIND_CREATE_MEMPOOL(node, 0, false); + /* Return to type-specific creation routine to finish up */ return node; } *************** *** 580,585 **** MemoryContextAlloc(MemoryContext context, Size size) --- 586,592 ---- context->isReset = false; ret = (*context->methods->alloc) (context, size); + VALGRIND_MEMPOOL_ALLOC(context, ret, size); return ret; } *************** *** 605,610 **** MemoryContextAllocZero(MemoryContext context, Size size) --- 612,618 ---- context->isReset = false; ret = (*context->methods->alloc) (context, size); + VALGRIND_MEMPOOL_ALLOC(context, ret, size); MemSetAligned(ret, 0, size); *************** *** 632,637 **** MemoryContextAllocZeroAligned(MemoryContext context, Size size) --- 640,646 ---- context->isReset = false; ret = (*context->methods->alloc) (context, size); + VALGRIND_MEMPOOL_ALLOC(context, ret, size); MemSetLoop(ret, 0, size); *************** *** 653,658 **** palloc(Size size) --- 662,668 ---- CurrentMemoryContext->isReset = false; ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size); + VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size); return ret; } *************** *** 672,677 **** palloc0(Size size) --- 682,688 ---- CurrentMemoryContext->isReset = false; ret = (*CurrentMemoryContext->methods->alloc) (CurrentMemoryContext, size); + VALGRIND_MEMPOOL_ALLOC(CurrentMemoryContext, ret, size); MemSetAligned(ret, 0, size); *************** *** 704,709 **** pfree(void *pointer) --- 715,721 ---- AssertArg(MemoryContextIsValid(context)); (*context->methods->free_p) (context, pointer); + VALGRIND_MEMPOOL_FREE(context, pointer); } /* *************** *** 740,745 **** repalloc(void *pointer, Size size) --- 752,758 ---- Assert(!context->isReset); ret = (*context->methods->realloc) (context, pointer, size); + VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size); return ret; } *** a/src/include/pg_config_manual.h --- b/src/include/pg_config_manual.h *************** *** 207,212 **** --- 207,227 ---- */ /* + * Include Valgrind "client requests", mostly in the memory allocator, so + * Valgrind understands PostgreSQL memory contexts. This permits detecting + * memory errors that Valgrind would not detect on a vanilla build. See also + * src/tools/valgrind.supp. "make installcheck" runs 20-30x longer under + * Valgrind. Note that USE_VALGRIND slowed older versions of Valgrind by an + * additional order of magnitude; Valgrind 3.8.1 does not have this problem. + * The client requests fall in hot code paths, so USE_VALGRIND also slows + * native execution by a few percentage points. + * + * You should normally use MEMORY_CONTEXT_CHECKING with USE_VALGRIND; + * instrumentation of repalloc() is inferior without it. + */ + /* #define USE_VALGRIND */ + + /* * Define this to cause pfree()'d memory to be cleared immediately, to * facilitate catching bugs that refer to already-freed values. * Right now, this gets defined automatically if --enable-cassert. *************** *** 218,226 **** /* * Define this to check memory allocation errors (scribbling on more * bytes than were allocated). Right now, this gets defined ! * automatically if --enable-cassert. */ ! #ifdef USE_ASSERT_CHECKING #define MEMORY_CONTEXT_CHECKING #endif --- 233,241 ---- /* * Define this to check memory allocation errors (scribbling on more * bytes than were allocated). Right now, this gets defined ! * automatically if --enable-cassert or USE_VALGRIND. */ ! #if defined(USE_ASSERT_CHECKING) || defined(USE_VALGRIND) #define MEMORY_CONTEXT_CHECKING #endif *** /dev/null --- b/src/include/utils/memdebug.h *************** *** 0 **** --- 1,34 ---- + /*------------------------------------------------------------------------- + * + * memdebug.h + * Memory debugging support. + * + * Currently, this file either wraps <valgrind/memcheck.h> or substitutes + * empty definitions for Valgrind client request macros we use. + * + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/memdebug.h + * + *------------------------------------------------------------------------- + */ + #ifndef MEMDEBUG_H + #define MEMDEBUG_H + + #ifdef USE_VALGRIND + #include <valgrind/memcheck.h> + #else + #define VALGRIND_CHECK_MEM_IS_DEFINED(addr, size) do {} while (0) + #define VALGRIND_CREATE_MEMPOOL(context, redzones, zeroed) do {} while (0) + #define VALGRIND_DESTROY_MEMPOOL(context) do {} while (0) + #define VALGRIND_MAKE_MEM_DEFINED(addr, size) do {} while (0) + #define VALGRIND_MAKE_MEM_NOACCESS(addr, size) do {} while (0) + #define VALGRIND_MAKE_MEM_UNDEFINED(addr, size) do {} while (0) + #define VALGRIND_MEMPOOL_ALLOC(context, addr, size) do {} while (0) + #define VALGRIND_MEMPOOL_FREE(context, addr) do {} while (0) + #define VALGRIND_MEMPOOL_CHANGE(context, optr, nptr, size) do {} while (0) + #endif + + #endif /* MEMDEBUG_H */ *** /dev/null --- b/src/tools/valgrind.supp *************** *** 0 **** --- 1,94 ---- + # This is a suppression file for use with Valgrind tools. File format + # documentation: + # http://valgrind.org/docs/manual/mc-manual.html#mc-manual.suppfiles + + # The libc symbol that implements a particular standard interface is + # implementation-dependent. For example, strncpy() shows up as "__GI_strncpy" + # on some platforms. Use wildcards to avoid mentioning such specific names. + + + # We have occasion to write raw binary structures to disk or to the network. + # These may contain uninitialized padding bytes. Since recipients also ignore + # those bytes as padding, this is harmless. + + { + padding_pgstat_send + Memcheck:Param + socketcall.send(msg) + + fun:*send* + fun:pgstat_send + } + + { + padding_pgstat_sendto + Memcheck:Param + socketcall.sendto(msg) + + fun:*send* + fun:pgstat_send + } + + { + padding_pgstat_write + Memcheck:Param + write(buf) + + ... + fun:pgstat_write_statsfiles + } + + { + padding_XLogRecData_CRC + Memcheck:Value8 + + fun:XLogInsert + } + + { + padding_XLogRecData_write + Memcheck:Param + write(buf) + + ... + fun:XLogWrite + } + + { + padding_relcache + Memcheck:Param + write(buf) + + ... + fun:write_relcache_init_file + } + + + # resolve_polymorphic_tupdesc(), a subroutine of internal_get_result_type(), + # can instigate a memcpy() call where the two pointer arguments are exactly + # equal. The behavior thereof is formally undefined, but implementations + # where it's anything other than a no-op are thought unlikely. + { + noopmemcpy_internal_get_result_type + Memcheck:Overlap + + fun:*strncpy* + fun:namestrcpy + fun:TupleDescInitEntry + ... + fun:internal_get_result_type + } + + + # gcc on ppc64 can generate a four-byte read to fetch the final "char" fields + # of a FormData_pg_cast. This is valid compiler behavior, because a proper + # FormData_pg_cast has trailing padding. Tuples we treat as structures omit + # that padding, so Valgrind reports an invalid read. Practical trouble would + # entail the missing pad bytes falling in a different memory page. So long as + # the structure is aligned, that will not happen. + { + overread_tuplestruct_pg_cast + Memcheck:Addr4 + + fun:IsBinaryCoercible + }
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers