メモリリーク検出器の作成方法の選択と実現方法c++


目的
                ,        bug       ,              。
        。       ,               ;            ,            code。

メモリモニタリングでは、hackがメモリに割り当てられ、関連するコードを解放し、毎回のアクティビティをモニタリングする必要があります.
メソッドの選択
1.new/deleteの再ロード
                         。 new   delete     ,                    。

リロード::operator new()の理由Effective C++第3版第50条は、カスタムnew/deleteのいくつかの理由を列挙しています.
検出コードのメモリエラーパフォーマンスの最適化
メモリ使用量の統計を取得する
しかし、このような欠点も多く、「グローバルnewを再ロードしない」を参照してください.http://www.360doc.com/content/12/1211/17/9200790_253442412.shtml主な問題はcライブラリとの互換性の問題です
2.malloc,freeから
new/deleteをリロードしない以上、malloc freeからしか手に入らない.new deleteの下位層もmalloc freeによって実現される.ここでは次のようなものがあります.
1.#define malloc(x )  debug_malloc(x,__file__,__line__)
メリット:便利
欠点:sprintf newなどのシステム関数で使用されているmallocを置き換えることはできません.それともシステムmallocですか.
2.glibcまたは(libc.so)の変更
欠点:多くの関数があって、全体のシステムに影響して、現在のコードの使用だけではありません
3.__malloc_hook
長所:プログラムに書くだけでmalloc_hook = my_malloc_hook;”,その後のmalloc呼び出しではmy_が使用されますmalloc_hook関数、便利で実行しやすいです.
欠点:しかし、このデバッグ変数のセットはスレッドが安全ではありません.システムmallocを使いたいときは、彼らを変更しなければなりません.マルチスレッド呼び出しはロックされなければなりません.したがって、シナリオはシステムメモリの最適化に適しておらず、スレッドメモリの使用を簡単に管理するために無理に使用されます.
使用方法の詳細:http://linux.die.net/man/3/__malloc_hook
/* Prototypes for our hooks.  */
static void my_init_hook(void);
static void *my_malloc_hook(size_t, const void *);
/* Variables to save original hooks. */
static void *(*old_malloc_hook)(size_t, const void *);
/* Override initializing hook from the C library. */
void (*__malloc_initialize_hook) (void) = my_init_hook;
static void
my_init_hook(void)
{
    old_malloc_hook = __malloc_hook;
    __malloc_hook = my_malloc_hook;
}
static void *
my_malloc_hook(size_t size, const void *caller)
{
    void *result;
    /* Restore all old hooks */
    __malloc_hook = old_malloc_hook;
    /* Call recursively */
    result = malloc(size);
    /* Save underlying hooks */
    old_malloc_hook = __malloc_hook;
    /* printf() might call malloc(), so protect it too. */
    printf("malloc(%u) called from %p returns %p
", (unsigned int) size, caller, result); /* Restore our own hooks */ __malloc_hook = my_malloc_hook; return result; }

4.dlysm
dlsymを呼び出してシステムmalloc関数にアクセスしsys_として保存malloc.そしてmallocを書き換えることができ、書き換えたmallocでsys_を呼び出すことができます.mallocは、本格的なメモリ割り当てを行います.
詳しい使い方と注意事項:http://www.slideshare.net/tetsu.koba/tips-of-malloc-free
  :
              ,         。

     :
#include<dlfcn.h>
    :
void* dlsym(void* handle,const char*symbol)
    :
             (handle)   (symbol),         。                ,         。
handle: dlopen             ;
symbol:               。
   :
void*        ,     。

 
DLYSMを使用してシステムのメモリ管理関数を書き換える
1.関数プロトタイプ設計
extern "C" void* malloc(size_t size)
{
	void * ptr=sys_malloc(size);
	handle extra behavior...
	return ptr;
}

sys_の使用mallocは、本格的なメモリ割り当てを行い、記録などの追加操作を行い、ポインタを返します.
2.ストレージシステムの真のメモリ割り当て解放関数
static bool performance_enabled_ = false;
static void* (*sys_malloc)(size_t) = 0;
static void* (*sys_realloc)(void*,size_t) = 0;
static void* (*sys_calloc)(size_t,size_t) = 0;
static void (*sys_free)(void*) = 0;
 
static void initialize_functions()
{	
	sys_malloc = reinterpret_cast<void*(*)(size_t)>(dlsym(RTLD_NEXT, "malloc"));
	sys_realloc = reinterpret_cast<void*(*)(void*,size_t)>(dlsym(RTLD_NEXT, "realloc"));
	sys_calloc = reinterpret_cast<void*(*)(size_t,size_t)>(dlsym(RTLD_NEXT, "calloc"));
	sys_free = reinterpret_cast<void(*)(void*)>(dlsym(RTLD_NEXT, "free"));
}


 
プロセスはdlsym(3 C)を用いて特定のシンボルのアドレスを取得することができる.この関数はハンドルとシンボル名を使用し、シンボルアドレスを呼び出し元に返します.特殊ハンドルRTLD_NEXTでは、呼び出し元リンクマッピングリストの次の関連ターゲットファイルからシンボルを取得できます.malloc,RTLD_を書き直したのでNEXTはシステムのmallocを指しています.これでsys_malloc,sys_realloc,sys_calloc,sys_freeはシステムのメモリ管理関数を格納します.
3.初期化の問題
上の2つの項目では、malloc書き換えのコア操作について説明します.最初にmallocを使ったときsys_mallocを初期化します.
extern "C" void* malloc(size_t size)
{
    if(sys_malloc==0)
		initialize_functions();
	void * ptr=sys_malloc(size);
	handle extra behavior...
	return ptr;
}

しかし、dlsymを呼び出してシステムmalloc関数にアクセスするとcallocが呼び出され、callocもdlsymでリロードされるとループ呼び出しになります.dlsymという関数を呼び出すと、dlsymはdlerrorを呼び出し、dlerrorはcallocを呼び出し、callocはmallocを呼び出し、mallocはdlsymの戻りを待つ初期化中であり、デッドサイクルになります.
したがって、mallocまたはcallocを最初に呼び出すときに、dlsymがこの問題を解決するために静的メモリを割り当てることができます.
char tmpbuff[1024];
unsigned long tmppos = 0;
unsigned long tmpallocs = 0;
extern "C" void* malloc(size_t size)
{
	static bool is_initializing = false;
	if(sys_malloc == 0)
	{
		if(!is_initializing)
		{
			is_initializing = true;
			initialize_functions();
			is_initializing = false;
		}
		else
		{
			if(tmppos+size<sizeof(tmpbuff))
			{
				void *retptr = tmpbuff + tmppos;
				tmppos += size;
				++tmpallocs;
				return retptr;
			}
			else
				exit(1);
		}
	}
	void* ptr = sys_malloc(size);
	handle extra behaviors....
	return ptr;
}

4.ループ呼び出しの問題
extra behaviorでダイナミックメモリの割り当てと解放に関する動作が発生すると、ループコールが発生します.extra behavior->malloc->extra behavior->malloc->....したがって、flagを使用して、記録する必要がある外部変数を示す必要があります.このflagにより、追加操作を行うか否かを判断する.ここではthread local storageのフラグ__を使用していますthread、各スレッドを識別するためのis_externalの場合.
static __thread bool is_external = true;
extern "C" void* malloc(size_t size)
{
	static bool is_initializing = false;
	if(sys_malloc == 0)
	{
		if(!is_initializing)
		{
			is_initializing = true;
			initialize_functions();
			is_initializing = false;
		}
		else
		{
			if(tmppos+size<sizeof(tmpbuff))
			{
				void *retptr = tmpbuff + tmppos;
				tmppos += size;
				++tmpallocs;
				return retptr;
			}
			else
				exit(1);
		}
	}
	void* ptr = sys_malloc(size);
	if(is_external)
	{
		is_external=false;
		handle extra behaviors....
		is_external=true;
	}
	return ptr;
}

5.補助関数
int backtrace(void**buffer,int size)関数を使用して、現在のスレッドの呼び出しスタックを取得します.
size_の使用t malloc_usable_size(void*ptr)関数は、アドレスポインタが指すメモリサイズを取得する.
note:malloc(size)calloc(size,cnt)realloc(ptr,size)などの関数が割り当てるメモリサイズは、必ずしも関数を呼び出すときに与えるsizeのサイズではなく、主にアドレス揃えの考慮によるものである.だからmalloc_を使う必要がありますusable_sizeを使用して、本格的なメモリサイズを取得します.
モニタ設計
malloc,calloc,realloc,freeに対してdlsymを用いてシステム関数にアクセスする方式で書き換える.メモリ割り当て関数は、メモリの割り当てに加えて、メモリ割り当てを記録する操作もできます.
モニタの主なデータ構造は以下の通りです.
	//record general performance info
 	struct General_Performance
    {
        int64_t used_memory_;
        std::vector<void *> stack_trace_;
    };
	//record general performance info by address
	struct General_Performance_Map: public std::unordered_map<uint64_t, General_Performance>
    {
    };
	//record specific performance info
 	struct Specific_Performance
    {
        size_t count_;
        int64_t duration_; //mili seconds
        int64_t hold_memory_; //the memory change size during a monitor life time
        int64_t allocated_memory_;//the memory allocation size during a monitor life time
        int64_t start_time_; //mili seconds
        int64_t end_time_;  //mili seconds
        General_Performance_Map detail_;
    };
	//record specific performance info by guard's tag
	struct Specific_Performance_Map: public std::unordered_map<Ads_String, Specific_Performance>
    {
    };
	//record the specific & general info for each thread
    struct Thread_Performance_Info
    {
        int guards_num_; //the number of guards in current thread
        General_Performance_Map general_performance_map_;//record the general performance by address
        std::list<void*> stack_guard_father_call_names_;//record the father function called Guard as a stack
        std::list<Specific_Performance> stack_specific_performance_;//record the specific performance as a stack, each only manipulate the back element,
        Specific_Performance_Map specific_performance_map_;//record the specific performance by guard's tag
        Thread_Performance_Info();
        ~Thread_Performance_Info();//do merge work when destruct a thread info 
    };
	//when a guard object exist, the corresponding specific monitor will be at working status
    struct Guard
    {
        Ads_String tag_;
        int64_t start_time_;
        int64_t end_time_;
        Guard(const Ads_String& tag);
        ~Guard();
    };


1.グローバルなmemory leak detection
各メモリの割り当てと解放を記録し、backtraceを用いて呼び出し追跡を行い、グローバルなメモリ割り当て情報を取得するには、コンパイルオプションで開く必要がある.
1.1スレッドごとにunorderedを作成するmap,ここでkeyはメモリ割り当て用のポインタ,valueはこのポインタの現在のサイズ,および今回のメモリ割り当て/解放をもたらすcall stackである.
1.2メモリ操作のたびにそのスレッドのmap情報を更新する
1.3スレッド終了時、現在のmap情報mergeをグローバルなgeneral info mapに
2.specificのmemory leak detection
マクロ定義を使用して、Guardを構築するように展開し、Guardのオブジェクトの役割ドメイン内で割り当てメモリサイズ、停止時間、アクセス回数などの情報を統計します.関数のスタック呼び出し形式を利用して、Guardはスタック構造ストレージを使用することができて、スタックトップ要素だけを操作して、popが必要な時に情報mergeを新しいスタックトップに着きます
2.1 PERFORMANCE経由MONITORのマクロ展開は関数名のguard構造体となり,現在の時間情報をスタックstack_に保存する.specific_performance
2.2メモリ操作ごとにstack_を表示するspecific_performanceが空であるかどうか、空でない場合はメモリの変化情報を記録し、call stackをスタックトップに記録します.
2.3 guard構造体のライフサイクルがプロファイルを終了すると、現在の時間を記録し、duration、start time、end timeなどの情報を計算し、スタックトップをポップアップします.スタックが空でない場合は、現在のspecific infoを新しいスタックトップ要素にマージします.
3.パフォーマンスの最適化
3.1マルチスレッド最適化
ツールの使用環境はマルチスレッドであり、メモリ情報の記録のたびにグローバルな変数にmergeする場合は、競合しないように変数にロックをかける必要があります.これにより、効率が大幅に低下します.したがって,モニタリング情報を記録するスレッド内変数を構築し,スレッドが終了すると,これらの変数をグローバル変数にmergeする必要がある.これにより、ロックの使用を大幅に減らすことができます.
従って__を使用することができるthread_local Thread_Performance_Info>の形式でスレッド変数を構築し、Thread_Performance_Infoの解析関数にスレッド変数を加えたmerge挙動.TSS変数は解析時にタイプの解析関数を呼び出すためである.これにより,グローバル変数へのmergeの要求をオンラインスレッド解析の際に満たす.