Everybody knows that valgrind is great.
Well, I was observing a problem in some MySQL code, it looked like we were writing over some memory that we weren’t meant to be (as the structure hadn’t been initialised yet). But, seeing as this was memory that had been allocated off a MEM_ROOT (one of our memory allocators), valgrind wasn’t gonig to spit out anything.
This is because this bit of memory had already been allocated and subsequently “freed”, but then reallocated. The “free”ing overwrites the memory with garbage (which is what the MEM_ROOT code does) so that you should see crashes (and a pattern) when you do something bad.
The traditional way to troubleshoot this in to modify your memory allocator so that it just calls malloc() and free() all the time (and valgrind will trap them). We have some code in there to do that too. However, this requires changing some ifdefs and probably not being as efficient.
Valgrind has some macros you can insert into your memory allocator code that tell valgrind that you have “freed” this memory (VALGRIND_MAKE_NOACCESS) or have allocated it (VALGRIND_MAKE_WRITABLE) or have written valid data to it (VALGRIND_MAKE_READABLE). These are semi-documented in the valgrind/valgrind.h file.
These are designed to only add a few CPU instructions to your code, so it should be possible to always have them in your build (you can disable them donig anything by building with -DNVALGRIND IIRC).
(I haven’t done any benchmarks on the code to see if there is any impact though).
Valgrind also has a great (largely undocumented) feature of being able to integrate with memory pools. Since our MEM_ROOT is largely just this, we can get some added benefits here too (one should be better valgrind warnings when we do some bad stuff).
It lets you associate memory with a memory pool, and then just say “this pool has been freed”. Saves you having to keep track of each pointer in the code to pass to “free”. It also can give you valgrind warnings when you try and allocate memory to something that hasn’t been initialised as a memory pool.
The most interesting thing of writing the patch was finding some false positive warnings. Namely, a trick used in a couple of places (i see 2) in the code is to create a temporary memory root on the stack, allocate a larger block of memory and then “swap” the memory roots to be based in this block of memory. I had to write a swap_root function to implement this as valgrind doesn’t export a “swap memory pool” function. It would be a useful addition, maybe I’ll go and suggest it to the developers.
Anyway, I got over that hurdle and now have this patch which seems to work pretty well. I still get a couple of (possible) false positives. We’ll see if this finds any neat bugs. Also, a good exercise would be to see how many extra instructions are really generated and if this has any affect on performance at all.
===== include/my_sys.h 1.196 vs edited =====
--- 1.196/include/my_sys.h 2006-05-22 20:04:34 +10:00
+++ edited/include/my_sys.h 2006-05-26 16:22:11 +10:00
@@ -804,6 +804,7 @@
extern void set_prealloc_root(MEM_ROOT *root, char *ptr);
extern void reset_root_defaults(MEM_ROOT *mem_root, uint block_size,
uint prealloc_size);
+extern void swap_root(MEM_ROOT* new_root, MEM_ROOT* old);
extern char *strdup_root(MEM_ROOT *root,const char *str);
extern char *strmake_root(MEM_ROOT *root,const char *str,uint len);
extern char *memdup_root(MEM_ROOT *root,const char *str,uint len);
===== mysys/my_alloc.c 1.33 vs edited =====
--- 1.33/mysys/my_alloc.c 2005-11-24 07:44:54 +11:00
+++ edited/mysys/my_alloc.c 2006-05-26 19:21:12 +10:00
@@ -22,6 +22,8 @@
#undef EXTRA_DEBUG
#define EXTRA_DEBUG
+#include "valgrind/valgrind.h"
+#include "valgrind/memcheck.h"
/*
Initialize memory root
@@ -66,9 +68,12 @@
mem_root->free->size= pre_alloc_size+ALIGN_SIZE(sizeof(USED_MEM));
mem_root->free->left= pre_alloc_size;
mem_root->free->next= 0;
+ VALGRIND_MAKE_NOACCESS(mem_root->free+ALIGN_SIZE(sizeof(USED_MEM)),
+ pre_alloc_size);
}
}
#endif
+ VALGRIND_CREATE_MEMPOOL(mem_root,0,0);
DBUG_VOID_RETURN;
}
@@ -217,6 +222,9 @@
mem_root->first_block_usage= 0;
}
DBUG_PRINT(“exit”,(“ptr: 0x%lx”, (ulong) point));
+// fprintf(stderr,”root: %lx point: %lx size:%lx\n”,mem_root,point,Size);
+ VALGRIND_MEMPOOL_ALLOC(mem_root,point,Size);
+ VALGRIND_MAKE_WRITABLE(point,Size);
DBUG_RETURN(point);
#endif
}
@@ -286,7 +294,8 @@
for (next= root->free; next; next= *(last= &next->next))
{
next->left= next->size – ALIGN_SIZE(sizeof(USED_MEM));
– TRASH_MEM(next);
+ VALGRIND_MAKE_NOACCESS(next+ALIGN_SIZE(sizeof(USED_MEM)),next->left);
+// TRASH_MEM(next);
}
/* Combine the free and the used list */
@@ -296,7 +305,8 @@
for (; next; next= next->next)
{
next->left= next->size – ALIGN_SIZE(sizeof(USED_MEM));
– TRASH_MEM(next);
+ VALGRIND_MAKE_NOACCESS(next+ALIGN_SIZE(sizeof(USED_MEM)),next->left);
+// TRASH_MEM(next);
}
/* Now everything is set; Indicate that nothing is used anymore */
@@ -357,12 +367,55 @@
{
root->free=root->pre_alloc;
root->free->left=root->pre_alloc->size-ALIGN_SIZE(sizeof(USED_MEM));
– TRASH_MEM(root->pre_alloc);
+ //TRASH_MEM(root->pre_alloc);
root->free->next=0;
}
root->block_num= 4;
root->first_block_usage= 0;
+ VALGRIND_DESTROY_MEMPOOL(root);
+ VALGRIND_CREATE_MEMPOOL(root,0,0);
+ VALGRIND_MAKE_READABLE(root,sizeof(MEM_ROOT));
+ if(root->pre_alloc)
+ {
+ VALGRIND_MAKE_READABLE(root->pre_alloc, ALIGN_SIZE(sizeof(USED_MEM)));
+ VALGRIND_MEMPOOL_ALLOC(root,root->pre_alloc,root->pre_alloc->size);
+ VALGRIND_MAKE_READABLE(root->pre_alloc, ALIGN_SIZE(sizeof(USED_MEM)));
+ }
DBUG_VOID_RETURN;
+}
+
+void swap_root(MEM_ROOT* new_root, MEM_ROOT* old)
+{
+ memcpy((char*) new_root, (char*) old, sizeof(MEM_ROOT));
+ VALGRIND_DESTROY_MEMPOOL(old);
+ VALGRIND_CREATE_MEMPOOL(new_root,0,0);
+
+ reg1 USED_MEM *next;
+
+ VALGRIND_MEMPOOL_ALLOC(new_root,new_root,sizeof(MEM_ROOT));
+ VALGRIND_MAKE_READABLE(new_root,sizeof(MEM_ROOT));
+
+ /* iterate through (partially) free blocks */
+ next= new_root->free;
+ do
+ {
+ if(!next)
+ break;
+ VALGRIND_MEMPOOL_ALLOC(new_root,next,next->size-next->left);
+ VALGRIND_MAKE_READABLE(next,next->size-next->left);
+ next= next->next;
+ } while(1);
+
+ /* now go through the used blocks and mark them free */
+ next= new_root->used;
+ do
+ {
+ if(!next)
+ break;
+ VALGRIND_MEMPOOL_ALLOC(new_root,next,next->size-next->left);
+ VALGRIND_MAKE_READABLE(next,next->size-next->left);
+ next= next->next;
+ } while(1);
}
/*
===== sql/table.cc 1.215 vs edited =====
— 1.215/sql/table.cc 2006-05-23 05:54:55 +10:00
+++ edited/sql/table.cc 2006-05-26 18:12:21 +10:00
@@ -150,7 +150,8 @@
#endif
– memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root));
+// memcpy((char*) &share->mem_root, (char*) &mem_root, sizeof(mem_root));
+ swap_root(&share->mem_root,&mem_root);
pthread_mutex_init(&share->mutex, MY_MUTEX_INIT_FAST);
pthread_cond_init(&share->cond, NULL);
}
@@ -252,7 +253,7 @@
hash_free(&share->name_hash);
/* We must copy mem_root from share because share is allocated through it */
– memcpy((char*) &mem_root, (char*) &share->mem_root, sizeof(mem_root));
+ swap_root(&mem_root,&share->mem_root);//memcpy((char*) &mem_root, (char*) &share->mem_root, sizeof(mem_root));
free_root(&mem_root, MYF(0)); // Free’s share
DBUG_VOID_RETURN;
}
===== storage/ndb/src/kernel/blocks/dbdict/Dbdict.cpp 1.87 vs edited =====
— 1.87/storage/ndb/src/kernel/blocks/dbdict/Dbdict.cpp 2006-04-25 22:02:07 +10:00
+++ edited/storage/ndb/src/kernel/blocks/dbdict/Dbdict.cpp 2006-05-26 12:15:43 +10:00
@@ -3148,9 +3148,23 @@
CreateTableRecordPtr createTabPtr;
ndbrequire(c_opCreateTable.find(createTabPtr, callbackData));
–
– //@todo check error
– ndbrequire(createTabPtr.p->m_errorCode == 0);
+
+ if(createTabPtr.p->m_errorCode != 0)
+ {
+ char buf[255];
+ TableRecordPtr tabPtr;
+ c_tableRecordPool.getPtr(tabPtr, createTabPtr.p->m_tablePtrI);
+
+ BaseString::snprintf(buf, sizeof(buf),
+ “Unable to restart, fail while creating table %d”
+ ” error: %d. Most likely change of configuration”,
+ tabPtr.p->tableId,
+ createTabPtr.p->m_errorCode);
+ progError(__LINE__,
+ NDBD_EXIT_INVALID_CONFIG,
+ buf);
+ ndbrequire(createTabPtr.p->m_errorCode == 0);
+ }
Callback callback;
callback.m_callbackData = callbackData;
For a function init_alloc_root, without VALGRIND macros,
we have this assembly code.
======================================
0000000000000570 :
570: 48 89 5c 24 e8 mov %rbx,-0x18(%rsp)
575: 48 89 6c 24 f0 mov %rbp,-0x10(%rsp)
57a: 83 ee 20 sub $0x20,%esi
57d: 4c 89 64 24 f8 mov %r12,-0x8(%rsp)
582: 48 83 ec 18 sub $0x18,%rsp
586: 85 d2 test %edx,%edx
588: 48 89 fb mov %rdi,%rbx
58b: 89 d5 mov %edx,%ebp
58d: 48 c7 47 10 00 00 00 movq $0x0,0x10(%rdi)
594: 00
595: 48 c7 47 08 00 00 00 movq $0x0,0x8(%rdi)
59c: 00
59d: 48 c7 07 00 00 00 00 movq $0x0,(%rdi)
5a4: c7 47 18 20 00 00 00 movl $0x20,0x18(%rdi)
5ab: 89 77 1c mov %esi,0x1c(%rdi)
5ae: 48 c7 47 28 00 00 00 movq $0x0,0x28(%rdi)
5b5: 00
5b6: c7 47 20 04 00 00 00 movl $0x4,0x20(%rdi)
5bd: c7 47 24 00 00 00 00 movl $0x0,0x24(%rdi)
5c4: 75 1a jne 5e0
5c6: 48 8b 1c 24 mov (%rsp),%rbx
5ca: 48 8b 6c 24 08 mov 0x8(%rsp),%rbp
5cf: 4c 8b 64 24 10 mov 0x10(%rsp),%r12
5d4: 48 83 c4 18 add $0x18,%rsp
5d8: c3 retq
5d9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
5e0: 44 8d 65 10 lea 0x10(%rbp),%r12d
5e4: 31 f6 xor %esi,%esi
5e6: 44 89 e7 mov %r12d,%edi
5e9: e8 00 00 00 00 callq 5ee
5ee: 48 85 c0 test %rax,%rax
5f1: 48 89 43 10 mov %rax,0x10(%rbx)
5f5: 48 89 03 mov %rax,(%rbx)
5f8: 74 cc je 5c6
5fa: 44 89 60 0c mov %r12d,0xc(%rax)
5fe: 89 68 08 mov %ebp,0x8(%rax)
601: 48 c7 00 00 00 00 00 movq $0x0,(%rax)
608: eb bc jmp 5c6
==============================================
With VALGRIND macros added, we got this:
==============================================
0000000000000c40 :
c40: 48 89 5c 24 e8 mov %rbx,-0x18(%rsp)
c45: 48 89 6c 24 f0 mov %rbp,-0x10(%rsp)
c4a: 83 ee 20 sub $0x20,%esi
c4d: 4c 89 64 24 f8 mov %r12,-0x8(%rsp)
c52: 48 83 ec 48 sub $0x48,%rsp
c56: 85 d2 test %edx,%edx
c58: 48 89 fb mov %rdi,%rbx
c5b: 89 d5 mov %edx,%ebp
c5d: 48 c7 47 10 00 00 00 movq $0x0,0x10(%rdi)
c64: 00
c65: 48 c7 47 08 00 00 00 movq $0x0,0x8(%rdi)
c6c: 00
c6d: 48 c7 07 00 00 00 00 movq $0x0,(%rdi)
c74: 48 89 e1 mov %rsp,%rcx
c77: c7 47 18 20 00 00 00 movl $0x20,0x18(%rdi)
c7e: 89 77 1c mov %esi,0x1c(%rdi)
c81: 48 c7 47 28 00 00 00 movq $0x0,0x28(%rdi)
c88: 00
c89: c7 47 20 04 00 00 00 movl $0x4,0x20(%rdi)
c90: c7 47 24 00 00 00 00 movl $0x0,0x24(%rdi)
c97: 75 57 jne cf0
c99: 48 c7 04 24 03 13 00 movq $0x1303,(%rsp)
ca0: 00
ca1: 31 d2 xor %edx,%edx
ca3: 48 89 5c 24 08 mov %rbx,0x8(%rsp)
ca8: 48 89 c8 mov %rcx,%rax
cab: 48 c7 44 24 10 00 00 movq $0x0,0x10(%rsp)
cb2: 00 00
cb4: 48 c7 44 24 18 00 00 movq $0x0,0x18(%rsp)
cbb: 00 00
cbd: 48 c7 44 24 20 00 00 movq $0x0,0x20(%rsp)
cc4: 00 00
cc6: c1 c0 1d rol $0x1d,%eax
cc9: c1 c0 03 rol $0x3,%eax
ccc: c1 c8 1b ror $0x1b,%eax
ccf: c1 c8 05 ror $0x5,%eax
cd2: c1 c0 0d rol $0xd,%eax
cd5: c1 c0 13 rol $0x13,%eax
cd8: 48 8b 5c 24 30 mov 0x30(%rsp),%rbx
cdd: 48 8b 6c 24 38 mov 0x38(%rsp),%rbp
ce2: 4c 8b 64 24 40 mov 0x40(%rsp),%r12
ce7: 48 83 c4 48 add $0x48,%rsp
ceb: c3 retq
cec: 0f 1f 40 00 nopl 0x0(%rax)
cf0: 44 8d 65 10 lea 0x10(%rbp),%r12d
cf4: 31 f6 xor %esi,%esi
cf6: 44 89 e7 mov %r12d,%edi
cf9: e8 00 00 00 00 callq cfe
cfe: 48 85 c0 test %rax,%rax
d01: 48 89 43 10 mov %rax,0x10(%rbx)
d05: 48 89 03 mov %rax,(%rbx)
d08: 48 89 e1 mov %rsp,%rcx
d0b: 74 8c je c99
d0d: 44 89 60 0c mov %r12d,0xc(%rax)
d11: 89 68 08 mov %ebp,0x8(%rax)
d14: 31 d2 xor %edx,%edx
d16: 48 c7 00 00 00 00 00 movq $0x0,(%rax)
d1d: 48 05 00 01 00 00 add $0x100,%rax
d23: 48 c7 04 24 00 00 43 movq $0x4d430000,(%rsp)
d2a: 4d
d2b: 48 89 44 24 08 mov %rax,0x8(%rsp)
d30: 89 e8 mov %ebp,%eax
d32: 48 89 44 24 10 mov %rax,0x10(%rsp)
d37: 48 c7 44 24 18 00 00 movq $0x0,0x18(%rsp)
d3e: 00 00
d40: 48 89 e0 mov %rsp,%rax
d43: 48 c7 44 24 20 00 00 movq $0x0,0x20(%rsp)
d4a: 00 00
d4c: c1 c0 1d rol $0x1d,%eax
d4f: c1 c0 03 rol $0x3,%eax
d52: c1 c8 1b ror $0x1b,%eax
d55: c1 c8 05 ror $0x5,%eax
d58: c1 c0 0d rol $0xd,%eax
d5b: c1 c0 13 rol $0x13,%eax
d5e: e9 36 ff ff ff jmpq c99
=============================================
You can clearly see extra instructions, doing rol, ror.