I heart valgrind (or: an early patch integrating the MySQL MEM_ROOT stuff with valgrind)

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;

One thought on “I heart valgrind (or: an early patch integrating the MySQL MEM_ROOT stuff with valgrind)

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

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.