2010-01-07 62 views
11

我想通過使用malloc和自由鉤來監視應用程序中malloc和free的使用。使用glibc malloc掛鉤以線程安全的方式

這裏的文檔http://www.gnu.org/s/libc/manual/html_node/Hooks-for-Malloc.html

從示例頁面,你可以看到my_malloc_hook瞬時切換的malloc重新調用malloc的前摘機(或在鏈中前鉤)。

當監視多線程應用程序時,這是一個問題(請參閱問題的結尾處的解釋)。

我在互聯網上找到的使用malloc掛鉤的其他例子也有同樣的問題。

有沒有辦法重寫這個函數在多線程應用程序中正常工作?

例如,是否有malloc掛鉤可以調用的內部libc函數來完成分配,而無需停用我的掛鉤。

由於公司法律政策,我無法看看libc源代碼,所以答案可能很明顯。

我的設計規範說我不能用一個不同的malloc設計替換malloc。

我可以假設沒有其他鉤子在場。


UPDATE

由於在維修中的malloc malloc的掛鉤暫時刪除,另一個線程可以調用malloc,並沒有得到鉤。

有人建議malloc有一個大的鎖,可以防止這種情況發生,但它沒有記錄,而且我有效地遞歸調用malloc的事實表明任何鎖必須在掛鉤之後存在,或者是快活的聰明:

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

如果我們其中一個人查看libc源代碼並根據它給出信息,您將合法地處於相同的位置。 – 2010-01-07 14:48:28

+0

爲什麼你不看* libc源代碼? – Will 2010-01-07 14:55:01

+0

因爲我可能會用GPL代碼污染我們的專有代碼。簡單地被告知一個特定的功能會做我想做的事情,但沒有這個問題。 – 2010-01-07 15:13:15

回答

8

修訂

您是right不信任__malloc_hooks;我已經在代碼掃了一眼,他們是 - 驚人的瘋狂 - 不是線程安全的。

直接調用繼承掛鉤,而不是恢復並重新進入的malloc,似乎是從文檔偏離你舉一個有點太多感覺舒適暗示。

http://manpages.sgvulcan.com/malloc_hook.3.php

掛鉤變量不是線程安全的所以他們現在已經過時。程序員應該通過定義和導出像「malloc」和「free」這樣的函數來搶佔對相關函數的調用。

適當的方式注入調試的malloc/realloc的/自由的功能是提供自己的庫,出口這些功能你的「調試」版本,然後推遲自己的真正所在。 C鏈接按照明確的順序完成,因此如果兩個庫提供相同的功能,則使用第一個指定的功能。您也可以使用LD_PRELOAD機制在unix上的加載時注入您的malloc。

http://linux.die.net/man/3/efence介紹了電子圍欄,裏面詳細介紹這兩種方法。

你可以,如果使用自己鎖定在這些調試功能,如果這是必要的。

+0

問題可能是:在調用掛接之前獲取鎖還是在malloc()內部發生?我猜這些鉤子在外面沒有發生鎖定的情況下是沒用的,但是我想知道遞歸調用是如何工作的。 – 2010-01-07 14:56:12

+0

遞歸調用可以使用遞歸鎖定 - 一旦線程擁有鎖定,就可以多次獲取它。 – Novelocrat 2010-01-07 15:08:47

+0

那麼,這可能是真的,但除非我知道這是真的,否則我不能使用它,因爲它可能會破壞。此外,我不知道未來的malloc實現是否可以允許具有單獨鎖定的多個malloc區域來增強多線程性能。 – 2010-01-07 15:15:58

2

由於所有調用malloc()會經過你的鉤子,你可以在同步信號(等到它是免費的,將其鎖定,忙裏忙外的掛鉤和釋放的信號)。

[編輯] IANAL但是......如果你能使用的glibc在你的代碼,那麼你可以看一下代碼(因爲它是LGPL,用它人必須被允許在源副本)。所以我不確定你是否正確地理解了法律情況,或者你在法律上不允許你的公司使用glibc。

經過一番思考,我想這部分調用路徑必須由glibc爲您創建的某種類型的鎖保護。否則,在多線程代碼中使用鉤子將無法可靠地工作,我相信這些文檔會提到這一點。由於malloc()必須是線程安全的,因此鉤子也必須是一樣的。

如果您仍然擔心,我建議編寫一個帶有兩個線程的小測試程序,它們分配和釋放循環中的內存。在鉤子中增加一個計數器。經過一百萬輪後,櫃檯應該是兩百萬。如果這成立,則掛鉤也受到malloc()鎖的保護。

[EDIT3如果測試失敗,那麼,因爲你的法律地位,其實施監控是不可能的。告訴你的老闆,讓他做出決定。

[EDIT4]谷歌搜索從一個bug報告止跌回升此評論:

掛鉤不是線程安全的。期。你想解決什麼問題?

這是從2009年3月約一個錯誤libc/malloc/malloc.c其中包含一個修復的討論的一部分。所以也許在這個日期後glibc 的版本有效,但似乎沒有保證。它似乎也取決於你的GCC版本。

+0

我不允許我的公司查看GPL代碼。他們的規則。 – 2010-01-07 15:12:29

+0

因爲在重新調用malloc之前,鉤子代碼必須刪除鉤子代碼,所以在我已經解除掛鉤的情況下調用malloc的第二個線程將不會使用該鉤子。 – 2010-01-07 15:14:44

+0

@Alex我猜這意味着您不允許查看或使用GPL代碼? – 2010-01-07 15:17:46

3

我有同樣的問題。我用這個例子解決了它。如果我們沒有定義THREAD_SAFE,我們就給出了這個人給出的例子,並且我們有一個分割錯誤。 如果我們定義THREAD_SAFE,我們沒有分割錯誤。

#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); 
} 
0

無法在遞歸到malloc的同時以線程安全的方式使用malloc掛鉤。界面設計糟糕,可能無法修復。

即使你在你的鉤子代碼中放置了一個互斥鎖,問題在於調用malloc直到他們通過鉤子機制並且通過鉤子機制之後纔會看到這些鎖,它們會查看全局變量(鉤指針)沒有獲得你的互斥體。當你在一個線程中保存,更改和恢復這些指針時,另一個線程中的分配器調用會受到它們的影響。

主要設計問題是默認情況下鉤子是空指針。如果接口簡單地提供了非空默認的鉤子,這些鉤子是分配器本身(底層的分配器不會調用任何更多的鉤子),那麼添加鉤子將是簡單和安全的:您可以保存以前的鉤子,並且在新的鉤子中,通過調用保持鉤子遞歸到malloc中,而不用任何全局指針(除了在鉤子安裝時,這可以在任何線程啓動之前完成)擺弄。

或者,glibc可以提供一個不調用鉤子的內部malloc接口。

另一個理智的設計是使用線程本地存儲的鉤子。重寫和恢復一個鉤子將在一個線程中完成,而不會干擾另一個線程看到的鉤子。

現在,您可以安全地使用glibc malloc鉤子,以避免遞歸到malloc中。不要更改鉤子回調中的鉤子指針,只需調用你自己的分配器。