Valgrind Memcheck support

Started by Noah Mischover 12 years ago11 messages
#1Noah Misch
noah@leadboat.com
2 attachment(s)

Valgrind's Memcheck tool[1]http://valgrind.org/docs/manual/mc-manual.html 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]/messages/by-id/20110312133224.GA7833@tornado.gateway.2wire.net 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]: /messages/by-id/20110312133224.GA7833@tornado.gateway.2wire.net

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

Attachments:

aset-debug-refactor-v1.patchtext/plain; charset=us-asciiDownload
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;
 }
 
 /*
valgrind-hooks-201306-v1.patchtext/plain; charset=us-asciiDownload
*** 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
+ }
#2Andres Freund
andres@2ndquadrant.com
In reply to: Noah Misch (#1)
Re: Valgrind Memcheck support

On 2013-06-09 17:25:59 -0400, Noah Misch wrote:

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.

Very nice work. I've started to do this quite some time back to smoke
out some bugs in code of mine, but never got remotely to a point where
it was submittable. But I already found some bugs with it. So I'd very
happy if this could get committed.

Will take a look.

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.

At least on linux amd64 I'd strongly suggest using something newer than
(afair) 3.8.1, i.e. the svn version. Up to that version it corrupts the
stack alignment inside signal handlers which doesn't get fixed up even
after a fork(). This leads to mysterious segfaults, e.g. during elog()s
due to the usage of sse registers which have stronger alignment
requirements.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Andres Freund
andres@2ndquadrant.com
In reply to: Noah Misch (#1)
Re: Valgrind Memcheck support

Hi Noah,

On 2013-06-09 17:25:59 -0400, Noah Misch wrote:

*** 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
+ 

Is there a special reason for adding more logging here? I find this
makes the instrumentation much less useful since reports easily get
burried in those traces. What's the advantage of doing this instead of
log_statement=...? Especially as that location afaics won't help for the
extended protocol?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Atri Sharma
atri.jiit@gmail.com
In reply to: Andres Freund (#3)
Re: Valgrind Memcheck support

Sent from my iPad

On 27-Aug-2013, at 19:44, Andres Freund <andres@2ndquadrant.com> wrote:

Hi Noah,

On 2013-06-09 17:25:59 -0400, Noah Misch wrote:

*** 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
+

Is there a special reason for adding more logging here? I find this
makes the instrumentation much less useful since reports easily get
burried in those traces. What's the advantage of doing this instead of
log_statement=...? Especially as that location afaics won't help for the
extended protocol?

+1. I also feel that extra traces may tend to confuse up the actual hot spot. Extra debugging can be happily be done with higher log levels.

Regards,

Atri

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Noah Misch
noah@leadboat.com
In reply to: Andres Freund (#3)
Re: Valgrind Memcheck support

On Tue, Aug 27, 2013 at 04:14:27PM +0200, Andres Freund wrote:

On 2013-06-09 17:25:59 -0400, Noah Misch wrote:

***************
*** 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
+ 

Is there a special reason for adding more logging here? I find this
makes the instrumentation much less useful since reports easily get
burried in those traces. What's the advantage of doing this instead of
log_statement=...? Especially as that location afaics won't help for the
extended protocol?

I typically used "valgrind --log-file=...". To determine via log_statement
which SQL statement caused a particular Valgrind error, I would match by
timestamp; this was easier. In retrospect, log_statement would have sufficed
given both Valgrind and PostgreSQL logging to stderr. Emitting the message in
exec_simple_query() and not in exec_execute_message() is indeed indefensible.

That being said, could you tell me more about your workflow where the extra
messages cause a problem? Do you just typically diagnose each Valgrind error
without first isolating the pertinent SQL statement?

Thanks,
nm

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Andres Freund
andres@2ndquadrant.com
In reply to: Noah Misch (#5)
Re: Valgrind Memcheck support

On 2013-08-27 23:46:23 -0400, Noah Misch wrote:

On Tue, Aug 27, 2013 at 04:14:27PM +0200, Andres Freund wrote:

On 2013-06-09 17:25:59 -0400, Noah Misch wrote:

***************
*** 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
+ 

Is there a special reason for adding more logging here? I find this
makes the instrumentation much less useful since reports easily get
burried in those traces. What's the advantage of doing this instead of
log_statement=...? Especially as that location afaics won't help for the
extended protocol?

I typically used "valgrind --log-file=...". To determine via log_statement
which SQL statement caused a particular Valgrind error, I would match by
timestamp; this was easier. In retrospect, log_statement would have sufficed
given both Valgrind and PostgreSQL logging to stderr. Emitting the message in
exec_simple_query() and not in exec_execute_message() is indeed indefensible.

It's an understandable mistake, nothing indefensible. We don't really
test the extended protocol :(

That being said, could you tell me more about your workflow where the extra
messages cause a problem? Do you just typically diagnose each Valgrind error
without first isolating the pertinent SQL statement?

There's basically two scenarios where I found it annoying:
a) I manually reproduce some issue, potentially involving several psql
shells. All those fire up autocompletion and such queries which bloat
the log while I actually only want to analyze something specific.
b) I don't actually look for anything triggered by an SQL statement
itself, but am looking for issues in a walsender, background worker,
whatever. I still need some foreground activity to trigger the behaviour
I want to see, but once more, valgrind's logs hide the error.

Also, I sometimes use valgrind's --db-command/--vgdb which gives you
enough context from the backtrace itself since you can just get the
statement from there.

I vote for just removing that VALGRIND_PRINTF - it doesn't give you
anything you cannot easily achieve otherwise...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Noah Misch
noah@leadboat.com
In reply to: Andres Freund (#6)
Re: Valgrind Memcheck support

On Wed, Aug 28, 2013 at 03:16:14PM +0200, Andres Freund wrote:

On 2013-08-27 23:46:23 -0400, Noah Misch wrote:

On Tue, Aug 27, 2013 at 04:14:27PM +0200, Andres Freund wrote:

On 2013-06-09 17:25:59 -0400, Noah Misch wrote:

***************
*** 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
+ 

Is there a special reason for adding more logging here? I find this
makes the instrumentation much less useful since reports easily get
burried in those traces. What's the advantage of doing this instead of
log_statement=...? Especially as that location afaics won't help for the
extended protocol?

That being said, could you tell me more about your workflow where the extra
messages cause a problem? Do you just typically diagnose each Valgrind error
without first isolating the pertinent SQL statement?

There's basically two scenarios where I found it annoying:
a) I manually reproduce some issue, potentially involving several psql
shells. All those fire up autocompletion and such queries which bloat
the log while I actually only want to analyze something specific.
b) I don't actually look for anything triggered by an SQL statement
itself, but am looking for issues in a walsender, background worker,
whatever. I still need some foreground activity to trigger the behaviour
I want to see, but once more, valgrind's logs hide the error.

Also, I sometimes use valgrind's --db-command/--vgdb which gives you
enough context from the backtrace itself since you can just get the
statement from there.

Gotcha. My largest use case was running "make installcheck" in search of
longstanding bugs. The runs were unattended, and associating each Valgrind
error with its proximate command was a basic need.

I vote for just removing that VALGRIND_PRINTF - it doesn't give you
anything you cannot easily achieve otherwise...

I'd like to see a buildfarm member running "make installcheck" under Valgrind,
so I'd like the code to fit the needs thereof without patching beyond
pg_config_manual.h. Perhaps having the buildfarm member do "valgrind postgres
--log-statement=all 2>combined-logfile" is good enough.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Greg Stark
stark@mit.edu
In reply to: Noah Misch (#1)
Re: Valgrind Memcheck support

On Sun, Jun 9, 2013 at 10:25 PM, Noah Misch <noah@leadboat.com> wrote:

- Test recovery, such as by running a streaming replica under Memcheck
while
the primary runs "make installcheck-world".

In general we need a lot more testing on the recovery code.

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

I think this is missing the type of leaks we actually care about. The way
palloc works we can be virtually certain that if we did that we wouldn't
have any leaks. All it would detect are the random one-off mallocs we know
very well are there.

The problems we've had with leaks in the past are invariably things
allocated at the "wrong" memory context. Things that can grow for every row
processed but are stored per-query or for every query processed but stored
per-sesson. To detect that will requires more of a heuristic where when a
child memory context is reset any parent context growth is logged.

--
greg

#9Noah Misch
noah@leadboat.com
In reply to: Noah Misch (#7)
Re: Valgrind Memcheck support

On Wed, Aug 28, 2013 at 10:30:34PM -0400, Noah Misch wrote:

On Wed, Aug 28, 2013 at 03:16:14PM +0200, Andres Freund wrote:

I vote for just removing that VALGRIND_PRINTF - it doesn't give you
anything you cannot easily achieve otherwise...

Done.

I'd like to see a buildfarm member running "make installcheck" under Valgrind,
so I'd like the code to fit the needs thereof without patching beyond
pg_config_manual.h. Perhaps having the buildfarm member do "valgrind postgres
--log-statement=all 2>combined-logfile" is good enough.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Noah Misch
noah@leadboat.com
In reply to: Greg Stark (#8)
Re: Valgrind Memcheck support

On Fri, Sep 06, 2013 at 09:55:09PM +0100, Greg Stark wrote:

On Sun, Jun 9, 2013 at 10:25 PM, Noah Misch <noah@leadboat.com> wrote:

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

I think this is missing the type of leaks we actually care about. The way
palloc works we can be virtually certain that if we did that we wouldn't
have any leaks. All it would detect are the random one-off mallocs we know
very well are there.

Probably so; it's not too promising.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Andres Freund
andres@2ndquadrant.com
In reply to: Greg Stark (#8)
Re: Valgrind Memcheck support

On 2013-09-06 21:55:09 +0100, Greg Stark wrote:

On Sun, Jun 9, 2013 at 10:25 PM, Noah Misch <noah@leadboat.com> wrote:

- Test recovery, such as by running a streaming replica under Memcheck
while
the primary runs "make installcheck-world".

In general we need a lot more testing on the recovery code.

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

I think this is missing the type of leaks we actually care about. The way
palloc works we can be virtually certain that if we did that we wouldn't
have any leaks. All it would detect are the random one-off mallocs we know
very well are there.

Well, we do have a good number of things that allocate stuff in
TopMemoryContext. So it might already catch leaks into that. IIRC we
don't reset that, but even if, that can easily be removed.
Valgrind's detection for "unreachable memory" is nice for that.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers