tags:

views:

830

answers:

3

I would like to monitor the use of mallocs and frees in an application by using the malloc and free hooks.

Here's the documentation http://www.gnu.org/s/libc/manual/html_node/Hooks-for-Malloc.html

From the example page you can see that my_malloc_hook transiently switches the malloc hook off (or to the previous hook in the chain) before re-invoking malloc.

This is a problem when monitoring multi-threaded applications (see end of question for explanation).

Other examples of the use of malloc hook that I have found on the internet have the same problem.

Is there a way to re-write this function to work correctly in a multi-threaded application?

For instance, is there an internal libc function that the malloc hook can invoke that completes the allocation, without the need to deactivate my hook.

I can't look at the libc source code due to corporate legal policy, so the answer may be obvious.

My design spec says I cannot replace malloc with a different malloc design.

I can assume that no other hooks are in play.


UPDATE

Since the malloc hook is temporarily removed while servicing the malloc, another thread may call malloc and NOT get the hook.

It has been suggested that malloc has a big lock around it that prevents this from happening, but it's not documented, and the fact that I effectively recursively call malloc suggests any lock must either exist after the hook, or be jolly clever:

caller -> 
  malloc -> 
    malloc-hook (disables hook) -> 
      malloc -> # possible hazard starts here
        malloc_internals
      malloc <-
    malloc-hook (enables hook) <-
  malloc
caller
+1  A: 

Since all calls to malloc() will go through your hook, you can synchronize on a semaphore (wait until it is free, lock it, juggle the hooks and free the semaphore).

[EDIT] IANAL but ... If you can use glibc in your code, then you can look at the code (since it's LGPL, anyone using it must be allowed to have a copy of the source). So I'm not sure you understood the legal situation correctly or maybe you're not legally allowed to use glibc by your company.

[EDIT2] After some thinking, I guess that this part of the call path must be protected by a lock of some kind which glibc creates for you. Otherwise, using hooks in multi-threaded code would never work reliably and I'm sure the docs would mention this. Since malloc() must be thread safe, the hooks must be as well.

If you're still worried, I suggest to write a small test program with two threads which allocate and free memory in a loop. Increment a counter in the hook. After a million rounds, the counter should be exactly two million. If this holds, then the hook is protected by the malloc() lock as well.

[EDIT3] If the test fails, then, because of your legal situation, it's not possible to implement the monitor. Tell your boss and let him make a decision about it.

[EDIT4] Googling turned up this comment from a bug report:

The hooks are not thread-safe. Period. What are you trying to fix?

This is part of a discussion from March 2009 about a bug in libc/malloc/malloc.c which contains a fix. So maybe a version of glibc after this date works but there doesn't seem to be a guarantee. It also seems to depend on your version of GCC.

Aaron Digulla
I'm not allowed to look at GPL code by my company. Them's the rules.
Alex Brown
Since the hook code must remove the hook code before re-invoking malloc, a second thread that invokes malloc while I have un-hooked my hook will not use the hook.
Alex Brown
@Alex I'm guessing that means your not allowed to look at or use GPL code?
Fire Crow
I can use (an authorised subset of) LGPL code that is already hosted on the systems our apps are deployed on, such as glibc.
Alex Brown
@Alex: Then you *must* be granted access to this part of the source code (as per the LGPL). Otherwise, your company is violating the license.
Aaron Digulla
@Alex: Also, I'm pretty sure that the hook is protected by a lock. See my edits.
Aaron Digulla
@Aaron The company can prevent its employees from doing all sorts of things. I don't believe using an LGPL library internally counts as distribution in GPL-speak. I agree that this is ludicrous though.
anon
thanks, I'll go check, it it fails then I'll know I have a problem, but if it works it might just be the scheduling algorithm on my laptop!. The fact that the author has granted me the right to see the code (via the GPL) does not mean that I have the right to agree to the terms of the GPL on behalf of my company, and is a wholly separate issue from whether my company would continue to let me work there after I am 'contaminated by viral licences', which is apparently what happens if you so much as glance at GPL code. Please remember SCO!
Alex Brown
@Alex: Interesting problem, legally. My first guess would be that in this case, your company is violating the GPL and therefore, no one there is allowed to *use* any GPLd code. So the first disgruntled employee could sue and a court could rule that you're forbidden to sell any of your products. But again, IANAL and your company probably paid lots of money to a real lawer, which means that there is some subtle issue that I'm not aware of :)
Aaron Digulla
@Alex: As for the scheduling algorithm: After a million loops, one thread should have corrupted the counter, no matter of the scheduler. Just make sure that each thread gets a chance to run.
Aaron Digulla
Oh, and because of the speed penalty and possible thread unsafety, I suggest not to deploy this with the final product.
Aaron Digulla
@Neil: it's a distribution to an employee who could sue.
Aaron Digulla
"Company lawyers said so" is something you might be stuck with, but you need to make sure that you let the people you work for know that this presents a burden to your ability to do your job, and that as a business process **you can't expect random people on the Internet to look at the source for you** every time you run into a problem with it, and observe that other companies are able to manage this problem in a way that doesn't unduly cripple the development process.
fennec
.... not that we don't like helping in general, but there's something about the situation of "I am perfectly capable of reading the source *but I won't*" is liable to breed a modicum of resentment in the community (and spawn IANAL discussions of this degree), company policy or no company policy. If your company is (metaphorically) blindfolding you and tying a hand behind your back, that's their prerogative, but they can't expect us to do all your work for you.
fennec
+1  A: 

UPDATED

You are right to not trust __malloc_hooks; I have glanced at the code, and they are - staggeringly crazily - not thread safe.

Invoking the inherited hooks directly, rather than restoring and re-entering malloc, seems to be deviating from the the document you cite a little bit too much to feel comfortable suggesting.

From http://www.phpman.info/index.php/man/malloc_hook/3:

Hook variables are not thread-safe so they are deprecated now. Programmers should instead preempt calls to the relevant functions by defining and exporting functions like "malloc" and "free".

The appropriate way to inject debug malloc/realloc/free functions is to provide your own library that exports your 'debug' versions of these functions, and then defers itself to the real ones. C linking is done in explicit order, so if two libraries offer the same function, the first specified is usedth You can also inject your malloc at load-time on unix using the LD_PRELOAD mechanisms.

http://linux.die.net/man/3/efence describes Electric Fence, which details both these approaches.

You can use your own locking if in these debug functions if that is necessary.

Will
The question is probably: Will the lock be acquired before the hook is called or does that happen inside malloc()? I guess the hooks would be useless without the locking happening outside but I wonder how the recursive calling works, then.
Aaron Digulla
Recursive calling could work with recursive locks - once the thread has the lock, it's allowed to acquire it multiple times.
Novelocrat
Well, that may be true, but unless I know it's true I can't use it, since it may break. Also I can't tell if a future malloc implementation may allow multiple malloc zones with separate locks to enhance multi-thread performance.
Alex Brown
when it breaks, report the bug!In other news, did you know that longjmp is not thread-safe?
Will
Reporting the bug is no good if 1) it's not a bug, that just how it works 2) the bug fix won't arrive in time to help my customers. Do you have a reference for the longjmp problem? I use it extensively.
Alex Brown
re longjmp; that's why siglongjmp was created.
Will
Whilst I feel for the problem of coping with broken mallocs, and this is of course a part of malloc that likely only gets exercised in test code and is therefore not the most robust, it *should* be thread-safe i.e. within the lock. So don't crash, abort, or twiddle with unprotected shared variable.
Will
thanks for the update, and for looking at the code.
Alex Brown
A: 

I have the same problem. I have solved it with that example. If we do not define THREAD_SAFE, we have the example given by the man, and we have a segmentation error. If we define THREAD_SAFE, we have no segmentation error.

#include <malloc.h>
#include <pthread.h>

#define THREAD_SAFE
#undef  THREAD_SAFE

/** rqmalloc_hook_  */

static void* (*malloc_call)(size_t,const void*);

static void* rqmalloc_hook_(size_t taille,const void* appel)
{
void* memoire;

__malloc_hook=malloc_call; 
memoire=malloc(taille);    
#ifndef THREAD_SAFE
malloc_call=__malloc_hook;   
#endif
__malloc_hook=rqmalloc_hook_; 
return memoire;
}

/** rqfree_hook_ */   

static void  (*free_call)(void*,const void*);

static void rqfree_hook_(void* memoire,const void* appel)
{
__free_hook=free_call;   
free(memoire);            
#ifndef THREAD_SAFE
free_call=__free_hook;    
#endif
__free_hook=rqfree_hook_; 
}

/** rqrealloc_hook_ */

static void* (*realloc_call)(void*,size_t,const void*);

static void* rqrealloc_hook_(void* memoire,size_t taille,const void* appel)
{
__realloc_hook=realloc_call;     
memoire=realloc(memoire,taille); 
#ifndef THREAD_SAFE
realloc_call=__realloc_hook;    
#endif
__realloc_hook=rqrealloc_hook_; 
return memoire;
}

/** memory_init */

void memory_init(void)
{
  malloc_call  = __malloc_hook;
  __malloc_hook  = rqmalloc_hook_;

  free_call    = __free_hook;
  __free_hook    = rqfree_hook_;

  realloc_call = __realloc_hook;
  __realloc_hook = rqrealloc_hook_;
 }

 /** f1/f2 */

 void* f1(void* param)
 {
 void* m;
 while (1) {m=malloc(100); free(m);}
 }

 void* f2(void* param)
 {
 void* m;
 while (1) {m=malloc(100); free(m);}
 }

 /** main */
 int main(int argc, char *argv[])
 {
 memory_init();
 pthread_t t1,t2;

 pthread_create(&t1,NULL,f1,NULL);
 pthread_create(&t1,NULL,f2,NULL);
 sleep(60);
 return(0);
 }
François Paffoni