メモリリークの検出および修復技術の包括的な分析

この記事は、 Lion Long によるHuawei クラウド コミュニティ「ソースからのメモリ リーク問題の解決: メモリ リークの検出および修復テクノロジの包括的な分析」から共有されたものです。

1. 背景: メモリ リーク検出とは何ですか?

1.1. メモリリークの原因

メモリ リークは、自動 gc がないプログラミング言語ではよくある問題です。gc がないため、割り当てられたメモリはプログラム自体によって解放される必要があります。問題の核心は、呼び出しの割り当てと解放がオープンとクローズの原則に準拠しておらず、ペアになっていないため、割り当てられても解放されないポインタが形成され、メモリ リークが発生するという点です。
例えば:

void func(size_t s1) 
{ 
	void p1=malloc(s1); 
	void p2=malloc(s1); 
	// ... 
	free(p1); 
}

上記のコード セグメントは、p1 と p2 が指すサイズ s1 の 2 つのメモリ ブロックを割り当てます。コード ブロックの実行後、p1 は解放されますが、p2 は解放されません。割り当てられているが解放されていないポインタが形成され、メモリ リークが発生します。

1.2. メモリリークによって引き起こされる結果

エンジニアリング コードの量が増えると、メモリ リークのトラブルシューティングが非常に困難になり、プログラムの仮想メモリが増大し続けるため、プログラムに必要なメモリ リークなのかメモリ リークなのかを正確に判断できなくなります。割り当てはあっても解放がない場合、プロセス ヒープのメモリは自然にどんどん減っていき、ついには使い果たされてしまいますこれにより、後続の実行コードがメモリを正常に割り当てることができなくなります。プログラムがクラッシュする可能性もあります。

1.3. メモリリークを解決するにはどうすればよいですか?

自動 gc を持たないプログラミング言語が原因でメモリリークが発生する 解決策 1 つは gc を導入することです。これはメモリ リークを解決するための最良の解決策です。ただし、このようなソリューションでは、C/C++ 言語の利点が失われます。オプション 2: メモリ リークが発生した場合、どのコード行がメモリ リークの原因となったかを正確に特定できます。これは、メモリ リーク検出の中核となる実装要件でもあります。

(1) メモリリークを検出する機能。

(2) コードのどの行がメモリ リークの原因となったかを特定できる。

プログラムの仮想メモリは増加し続けるため、それがプログラムの必要性なのか、メモリ リークなのかを正確に判断することは不可能です。メモリ リークの場合、コードのどの行で発生しているのかわかりません。

2.アドレスを記号情報に変換

2.1. addr2line ツール

アドレスをファイル名と行番号に変換します。

addr2line [-a|--addresses] 
          [-b bfdname|--target=bfdname] 
          [-C|--demangle[=style]] 
          [-e filename|--exe=filename] 
          [-f|--functions ] [-s|--basename] 
          [-i|--inlines] 
          [-p|--pretty-print] 
          [-j|--section=name] 
          [-H|--help] [-V|- -バージョン] 
          [アドレス アドレス ...]

説明する:

addr2line はアドレスをファイル名と行番号に変換します。実行可能ファイル内のアドレスまたは再配置可能オブジェクト セクション内のオフセットを指定すると、デバッグ情報を使用して、それに関連付けられたファイル名と行番号を決定します。

使用する実行可能オブジェクトまたは再配置可能オブジェクトは、-e オプションで指定します。デフォルトはファイル a.out です。-jオプションでリロケータブルオブジェクト内の使用するセクションを指定します。

addr2line には 2 つの動作モードがあります。

  • 最初のコマンド ラインでは、16 進数のアドレスがコマンド ラインで指定され、addr2line は各アドレスのファイル名と行番号を表示します。
  • 2 番目のコマンドでは、addr2line は標準入力から 16 進アドレスを読み取り、各アドレスのファイル名と行番号を標準出力に出力します。このモードでは、addr2line をパイプラインで使用して、動的に選択されたアドレスを変換できます。

知らせ:

addr2line はアドレスをファイル番号に変換し、ファイルはディスクに保存されます。プログラムが実行されるアドレスは仮想メモリ (コード セグメント) 内にあります。Linux の上位バージョンでは、アドレスがどこにあるか解析できない場合があります。ファイルの中にあります。addr2line は仮想領域のアドレスのみを参照できます。

2.2. dladdr1() 関数

アドレスを記号情報に変換します。関数プロトタイプ:

#define _GNU_SOURCE 
#include <dlfcn.h> 

int dladdr(void *addr, Dl_info *info); 

int dladdr1(void *addr, Dl_info *info, void **extra_info, int flags); 

// -ldl でリンクします。

説明する:

この関数は、dladdr()addr で指定されたアドレスが、呼び出し側アプリケーションによってロードされた共有オブジェクト内にあるかどうかを判断します。存在する場合、dladdr()addr と重複する共有オブジェクトおよびシンボルに関する情報が返されます。この情報はDl_info構造体として返されます。

typedef struct { 
    const char *dli_fname; /* アドレスを含む共有オブジェクトのパス名 */ 
    void *dli_fbase; /* 共有オブジェクトがロードされるベースアドレス */ 
    const char *dli_sname; /* 定義が重複するシンボル名 addr */ 
    void *dli_saddr; /* dli_sname で指定されたシンボルの正確なアドレス */ 
} Dl_info;

関数dladdr1()は似ていますdladdr()が、extra_infoパラメータを介して追加情報を返します。返される情報は、フラグで指定された値によって異なり、次のいずれかの値になります。

(1) RTLD_DL_LINKMAP一致するファイルのリンク マップへのポインターを取得します。extra_info パラメータは、<link.h>で定義された link_map 構造体へのポインタですstruct link_map**

struct link_map { 
    ElfW(Addr) l_addr; /* ELF ファイル内のアドレスとメモリ内のアドレスの差 */ 
    char *l_name; /* オブジェクトが見つかった絶対パス名 */ 
    ElfW(Dyn) *l_ld; /* 共有オブジェクトの動的セクション */ 
    struct link_map *l_next, *l_prev; 
                        /* ロードされたオブジェクトのチェーン */ 

    /* さらに、実装にプライベートな追加フィールド */ 
};

(2) RTLD_DL_SYMENT一致するシンボルの ELF シンボル テーブル エントリへのポインタを取得します。extra_info引数はシンボル ポインタへのポインタです: const ElfW(Sym)**ElfW()マクロ定義は、その引数をハードウェア アーキテクチャに適した ELF データ型の名前に変換します。たとえば、64 ビット プラットフォームでは、ElfW(Sym)データ型名が生成されElf64_Sym、次の<elf.h>ように定義されます。

typedef struct { 
    Elf64_Word st_name; /* シンボル名 */ 
    unsigned char st_info; /* シンボルのタイプとバインディング */ 
    unsigned char st_other; /* シンボルの可視性 */ 
    Elf64_Section st_shndx; /* セクション インデックス */ 
    Elf64_Addr st_value; /* シンボル値 */ 
    Elf64_Xword st_size; /* シンボル サイズ */ 
} Elf64_Sym;

パッケージ:

void * ConvertToElf(void *addr) 
{ 
	Dl_info 情報; 
	構造体 link_map *リンク; 

	dladdr1(addr, &info, (void **)&link, RTLD_DL_LINKMAP); 

	// 偏差正
	return (void *)((size_t)addr - link->l_addr); 
}

3. メモリリーク検出の実装

メモリ リークは、メモリの割り当てとメモリの解放の不一致によって発生します。メモリ割り当て関数 malloc/calloc/realloc およびメモリ解放の「ハイジャック」フック。メモリ割り当て位置とメモリ解放位置をカウントして、一致するかどうかを判断できます。

3.1. 方法 1: mtrace を使用する

mtrace() および muntrace() 関数

mtrace トレース ログ。関数プロトタイプ:

#include <mcheck.h> 

void mtrace(void); 

void muntrace(void);

説明する:

mtrace() 関数は、メモリ割り当て関数のフック関数 [malloc()、realloc()、memalign()、free()] をインストールします。これらのフック関数は、メモリの割り当てと割り当て解除に関する追跡情報を記録します。トレース情報を使用してメモリ リークを発見し、プログラム内の未割り当てメモリの解放を試みることができます。

muntrace()Function は、mtrace()インストールされているフック関数を無効にし、メモリ割り当て関数のトレース情報がログに記録されないようにします。mtrace()フック関数が正常にインストールされなかっmuntrace()場合、アクションは実行されません。

呼び出されると、環境変数の値がmtrace()チェックされます。環境変数には、トレース情報が記録されるファイルのパス名が含まれている必要があります。MALLOC_TRACEパス名が正常に開かれると、その長さはゼロに切り捨てられます。

設定されていない場合MALLOC_TRACE、または指定されたパス名が無効または書き込み不可能な場合、フック関数はインストールされず、mtrace()効果がありません。set-user-ID およびプログラムではこれは無視され、効果はありません。 set-group-IDMALLOC_TRACEmtrace()

setenv() および unsetenv() 関数

環境変数を変更または追加します。関数プロトタイプ:

#include <stdlib.h> 

int setenv(const char *name, const char *value, int overwrite); 

int unsetenv(const char *name); 

/* 
glibc の機能テスト マクロ要件 (feature_test_macros(7) を参照): 

setenv(), unsetenv(): 
    _BSD_SOURCE || _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 
*/

説明する:

名前が存在しない場合、setenv() 関数は値 value を使用して変数名を環境に追加します。name が環境に存在し、overwrite が 0 以外の場合、その値は value に変更されますが、overwrite が 0 の場合、name の値は変更されません (setenv() は成功ステータスを返します)。この関数は、(putenv(3) とは対照的に) 名前と値によって指定された文字列をコピーします。

unsetenv() 関数は、環境から変数名を削除するために使用されます。名前が環境に存在しない場合、関数は成功し、環境は変更されません。

戻り値:

setenv() 関数は、成功した場合は 0 を返し、エラーが発生した場合は -1 を返し、エラーの原因を示す errno を設定します。

unsetenv() 関数は、成功した場合は 0、エラーの場合は -1 を返し、エラーの原因を示す errno を設定します。

間違い:

エラーコード 意味
単一選択 name が NULL で、長さ 0 の文字列を指しているか、「=」文字が含まれています。
エノメム 環境に新しい変数を追加するにはメモリが不十分です。

使用手順

(1) メモリ割り当て関数を呼び出す前に mtrace() を呼び出します;
(2) プログラムの最後またはメモリ リークを追跡する必要がない場所で muntrace() を呼び出します; (
3) 環境変数 MALLOC_TRACE の値を設定します ( setenv関数またはexportコマンド);
(4) コンパイル時に-gパラメータを指定します。
(5) メモリ リークが発生した場合は、addr2line ツールを使用してメモリ リークの場所を特定します。

$ addr2line -f -e memleak -a 0x4006b8

この例では、memleak がプログラム名、0x4006b8 がメモリ リークのアドレスです。

例えば:

$ cc -g t_mtrace.c -o t_mtrace 
$ import MALLOC_TRACE=/tmp/t 
$ ./t_mtrace 
$ mtrace ./t_mtrace $MALLOC_TRACE

サンプルコード

#include <stdio.h> 
#include <stdlib.h> 

#include <mcheck.h> 

int main(int argc,char **argv) 
{ 

	setenv("MALLOC_TRACE", "./mem.txt", 1); 

	mtrace(); 

	void *p1 = malloc(10); 
	void *p2 = malloc(20); 
	void *p3 = malloc(30); 

	無料(p3); 
	無料(p2); 
	muntrace(); 

	unsetenv("MALLOC_TRACE"); 

	0を返します。
}

メモリ リーク検出ファイルの内容:

$ cat mem.txt 
= 開始
@ ./memleak:[0x4006b8] + 0x1886580 0xa 
@ ./memleak:[0x4006c6] + 0x18865a0 0x14 
@ ./memleak:[0x4006d4] + 0x18865c0 0x1e 
@ ./memleak :[0x4006e4] - 0x18865c0 
@ ./memleak:[0x4006f0] - 0x18865a0 
= 終了

メモリ リークの場所を特定します。

$ addr2line -f -e memleak -a 0x4006b8 
0x00000000004006b8 
main 
memleak.c:13

3.2. 方法 2: マクロ定義を使用する

Linuxには__FILE__現在のファイル名、関数名、行番号を示す 2 つのマクロがあり、マクロ定義はメモリ割り当て関数と解放関数をカプセル化するために使用されます。__func____LINE__

#define malloc(size) _malloc(size,__FILE__,__LINE__) 
#define free(p) _free(p,__FILE__,__LINE__)

_malloc関数、_free関数の中でmalloc関数、free関数を自分で呼び出して、何らかの操作を行ってください。

前提条件: プリコンパイルフェーズで malloc が独自に実装された_mallocsumに置き換えられるように、メモリ割り当ての前にマクロを定義する必要があります_free

サンプルコード:

#include <stdio.h> 
#include <stdlib.h> 

void *_malloc(size_t size,const char*filename,int line) 
{ 
	void *p = malloc(size); 
	printf("[+] %s : %d, %p\n", ファイル名, 行,p); 
	p を返します。
} 

void _free(void *p, const char*ファイル名, int line) 
{ 
	printf("[-] %s : %d, %p\n", ファイル名, line,p); 
	free(p) を返します。
#define malloc(size) _malloc(size,__FILE__,__LINE__) 
#define free(p) _free(p,__FILE__,__LINE__) 
int main(int argc,char **argv) 
{ 
	void *p1 = malloc(10) 
; 
	void *p2 = malloc(20); 
	void *p3 = malloc(30); 
	無料(p3); 
	無料(p2); 
	0を返します。
}



マクロ定義方法を使用する利点と欠点:

(1) メリット:実装が簡単。

(2) 欠点: 単一のファイルにのみ適しており、マクロ定義はファイルの先頭に配置する必要があります。

ファイル置換を使用して印刷します。

プログラムの実行中、常に不要な情報が出力されるため、効率が低下し、見苦しくなります。フォルダー内でファイルを作成および削除して、メモリ リークをカウントできます。

ポインタ値をファイル名として使用し、メモリを割り当ててファイルを作成し、メモリを解放してファイルを削除し、ファイル名と割り当てられたメモリの行番号をファイルに記録します。

フォルダー内にファイルがあればメモリ リークが発生し、ファイルが存在しなければメモリ リークは発生しません。

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 

#define LEAK_FILE_PATH "./mem/%p.mem" 

void *_malloc(size_t size,const char*filename,int line) 
{ 
	void *p = malloc(サイズ); 
	//printf("[+] %s : %d, %p\n", ファイル名, 行,p); 
	char buff[128] = { 0 }; 
	sprintf(buff, LEAK_FILE_PATH, p); 
	FILE *fp = fopen(buff, "w"); 
	fprintf(fp, "[+] %s : %d, addr: %p, サイズ: %ld\n", ファイル名, 行, p, サイズ); 
	fflush(fp);//刷新データ到文件中
	fclose(fp); 

	p を返します。
} 

void _free(void *p, const char*filename, int line) 
{ 
	//printf("[-] %s : %d, %p\n", filename, line,p); 
	char buff[128] = { 0 }; 
	sprintf(buff, LEAK_FILE_PATH, p); 
	if (unlink(buff) < 0) 
	{ 
		printf("double free %p\n", p); 
		戻る; 
	free(p) を返します
#define malloc(size) _malloc(size,__FILE__,__LINE__) 
#define free(p) _free(p,__FILE__,__LINE__) 
int main(int argc,char **argv) 
{ 
	void *p1 = malloc(10) 
; 
	void *p2 = malloc(20); 
	void *p3 = malloc(30); 
	無料(p3); 
	無料(p2); 
	0を返します。
}




複雑なシステムの状況はより複雑であるため、このツールは分析を高速化するだけであり、メモリ リークを 100% 判断することはできないことに注意してください。

メモリ リークの検出は最初から追加されるのではなく、通常は「ホット アップデート」を通じて必要なときにオンになります。つまり、メモリ リークの検出をオンにするフラグが設定ファイルにあります。必要な場合にのみオンにするため、プログラムの効率には影響しません。

3.3. 方法 3: フック (フック)

フックの使用手順:

(1) 関数ポインタを定義します。

typedef void *(*malloc_t)(size_t サイズ); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULL; 
free_t free_f = NULL;

(2) 関数の実装。関数名はターゲット関数名と一致します。

void *malloc(size_t size) 
{ 
// ... 
} 

void free(void *ptr) 
{ 
// ... 
}

(3) フックを初期化して dlsym() を呼び出します。

static init_hook() 
{ 
	if (malloc_f == NULL) 
		malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc"); 

	if (free_f == NULL) 
		free_f = (malloc_t)dlsym(RTLD_NEXT, "free"); 
}

知らせ:

フックする場合は、他の関数もフックされた関数を使用することを考慮する必要があります。たとえば、malloc は printf() 関数でも呼び出されるため、内部再帰が無限ループに入らないようにする必要があります。

例えば:

#define _GNU_SOURCE 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 

#include <dlfcn.h> 

typedef void *(*malloc_t)(size_t size); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULL; 
free_t free_f = NULL; 
void *malloc(size_t size) 
{ 
	printf("malloc サイズ: %ld", size); 
	NULL を返します。
} 

void free(void *ptr) 
{ 
	printf("free: %p\n",ptr); 
static int init_hook() 
{ 
	if (malloc_f == NULL) 
		malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc") 
; 
	if (free_f == NULL) 
		free_f = (free_t)dlsym(RTLD_NEXT, "free"); 
	0を返します。
int 
main(int argc,char **argv) 
{ 
	init_hook(); 
	void *p1 = malloc(10); 
	void *p2 = malloc(20); 
	void *p3 = malloc(30); 
	無料(p3); 
	無料(p2); 
	0を返します。
}




上記のコードはセグメンテーション違反を引き起こします。gdb を使用してデバッグすると、malloc 関数の printf() 呼び出しが無限再帰、つまりスタック オーバーフローに入っていることがわかります。

解決策はロゴを追加することです。

 

gcc 組み込み関数: void *__builtin_return_address (符号なし整数レベル)

この関数は、現在の関数またはその呼び出し元の 1 つの戻りアドレスを返します。引数は、呼び出しスタックをスキャンするフレーム数です。値は現在の関数の戻りアドレスを生成し、値は現在の関数の呼び出し元の戻りアドレスを生成します。期待される動作をインライン化する場合、関数は返された関数のアドレスを返します。この問題を回避するには、関数属性を使用します。

 

レベル:

このパラメータは定数の整数である必要があります。

一部のコンピュータでは、現在の関数以外の関数の戻りアドレスが決定されない場合があります。この場合、またはスタックの先頭に到達すると、この関数は不特定の値を返します。さらに、スタックの最上部に到達したかどうかを判断するために使用できます。

サンプルコード:

#define _GNU_SOURCE 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 

#define LEAK_FILE_PATH "./mem/%p.mem" 

#include <dlfcn.h> 

static int enable_malloc_hook = 1; 
static int Enable_free_hook = 1; 

typedef void *(*malloc_t)(size_t size); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULL; 
free_t free_f = NULL; 
void *malloc(size_t size) 
{ 
	void * p; 
	if (enable_malloc_hook) 
	{ 
		enable_malloc_hook = 0; 
		
		p = malloc_f(size); 
		printf("malloc size: %ld,p=%p\n", size,p); 
		// malloc の呼び出し元の場所を取得します前のレイヤーのアドレス。このアドレスは、行番号に変換するために addr2line ツールによって使用されます
		void *caller = __builtin_return_address(0); 

		char buff[128] = { 0 }; 
		sprintf(buff, LEAK_FILE_PATH, p); 
		FILE * fp = fopen(buff, "w"); 
		fprintf(fp, "[+] %p , addr: %p, size: %ld\n", caller, p, size); fflush (fp);//
		リフレッシュファイルへのデータ
		fclose( fp); 

		Enable_malloc_hook = 1; 
	} 
	else 
		p = malloc_f(size); 
	
	return p; 
} 

void free(void *p) 
{ 
	if (enable_free_hook) 
	{ 
		enable_free_hook = 0; 
		//printf("free: %p\n", p); 
		char buff[128] = { 0 }; 
		sprintf(buff, LEAK_FILE_PATH, p); 
		if (unlink(buff) < 0) 
		{ 
			printf("double free %p\n", p ); 
			//enable_free_hook = 1; 
			free_f(p); 
			return; 
		} 
		free_f(p); 
		Enable_free_hook = 1; 
	} 
	else 
		free_f(p); 
} 

static int init_hook() 
{ 
	if (malloc_f == NULL) 
		malloc_f = (malloc_t )dlsym(RTLD_NEXT, " malloc"); 

	if (free_f == NULL) 
		free_f = (free_t)dlsym(RTLD_NEXT, "free"); 

	return 0; 
} 

int main(int argc,char **argv) 
{ 

	init_hook() ; 
	void *p1 = malloc (10); 
	void *p2 = malloc(20); 
	void *p3 = malloc(30); 

	free(p3); 
	free(p2); 
	
	return 0; 
}

取得したアドレス__builtin_return_address(0)は、メモリ リークの場所を特定するために、addr2line ツールを使用してファイルの行番号に変換する必要があります。

3.4. 方法 4: __libc_malloc と __libc_free を使用する

__libc_mallocmalloc とfreeの基本的な呼び出しも と であるため、考え方はフックの考え方と同じです__libc_free

サンプルコード:

#define _GNU_SOURCE 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 

// mem フォルダーを手動で作成することを忘れないでください
#define LEAK_FILE_PATH "./mem/%p.mem" 

extern void * __libc_malloc (size_t サイズ); 
extern void __libc_free(void *p); 

static int enable_malloc_hook = 1; 

void *malloc(size_t size) 
{ 
	void *p; 
	if (enable_malloc_hook) 
	{ 
		enable_malloc_hook = 0; 

		p = __libc_malloc(size); 
		printf ( "malloc size: %ld,p=%p\n", size, p); //
		前の層で malloc が呼び出される場所のアドレスを取得します。このアドレスは、addr2line ツールによってアドレスに変換するために使用されます。行番号
		void *caller = __builtin_return_address (0); 

		char buff[128] = { 0 }; 
		sprintf(buff, LEAK_FILE_PATH, p); 
		FILE *fp = fopen(buff, "w"); 
		fprintf(fp, "[+ ] %p , addr: % p, size: %ld\n", caller, p, size); 
		fflush(fp);//データをファイルに更新
		fclose(fp); 

		enable_malloc_hook = 1; 
	} 
	else 
		p = __libc_malloc (サイズ); 

	return p; 
} 

void free(void *p) 
{ 
	char buff[128] = { 0 }; 
	sprintf(buff, LEAK_FILE_PATH, p); 
	if (unlink(buff) < 0) 
	{ 
		printf("double free %p\n", p ); 
	} 
	__libc_free(p); 
} 

int main(int argc,char **argv) 
{ 

	void *p1 = malloc(10); 
	void *p2 = malloc(20); 
	void *p3 = malloc(30); 

	free(p3); 
	free(p2); 

	return 0; 
}

3.5. 方法 5: __malloc_hook (非推奨)

このメソッドは古い Linux バージョンに適しており、古いバージョンの API に属し、__malloc_hookポインター メソッドであり、固定値です。これは本質的にはフック技術です。

関数プロトタイプ:

#include <malloc.h> 

void *(*__malloc_hook)(size_t サイズ, const void *caller); 

void *(*__realloc_hook)(void *ptr, size_t サイズ, const void *caller); 

void *(*__memalign_hook)(size_t アラインメント, size_t サイズ, const void *caller); 

void (*__free_hook)(void *ptr, const void *caller); 

void (*__malloc_initialize_hook)(void); 

void (*__after_morecore_hook)(void);

説明する:

GNUC ライブラリを使用すると、適切なフック関数を指定することで、malloc、realloc、free の動作を変更できます。たとえば、これらのフックを使用すると、動的メモリ割り当てを使用するプログラムのデバッグに役立ちます。

変数 __malloc_initialize_hook は、malloc 実装の初期化時に一度呼び出される関数を指します。これは弱い変数なので、次のような定義を使用してアプリケーションでオーバーライドできます。

void(*__malloc_initialize_hook)(void)=my_init_hook();

これで、関数 my_init_hook() はすべてのフックを初期化できるようになりました。

__malloc_hook、 、__realloc_hooksで示される 4 つの関数のプロトタイプは__memalign_hooke__free_hookyそれぞれ関数 malloc、realloc、memalign と対応付けられています。

プラン:

メソッドの交換、関数ポインターのカスタマイズ、特定の関数の実装、およびシステムが提供する __malloc_hook を使用して実装した関数の交換を行います。

サンプルコード:

#define _GNU_SOURCE 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 

// 要记得手アニメーション创建一个mem文件夹
#define LEAK_FILE_PATH "./mem/%p.mem" 

#include < malloc.h> 

/* 
typedef void *(*malloc_t)(size_t size); 
typedef void(*free_t)(void *p); 

malloc_t malloc_f = NULL; 
free_t free_f = NULL; 
*/

静的 int イネーブル_malloc_hook = 1; 

静的 void my_init_hook(void); 
static void *my_malloc_hook(size_t, const void *); 
static void my_free_hook(void *, const void *); 

/* 元のフックを保存する変数。*/ 
static void *(*old_malloc_hook)(size_t, const void *); 
static void(*old_free_hook)(void *, const void *); 
/* C ライブラリの初期化フックをオーバーライドします。*/ 
void(*__malloc_initialize_hook) (void) = my_init_hook; 

static void 
my_init_hook(void) 
{ 
	old_malloc_hook = __malloc_hook; 
	__malloc_hook = my_malloc_hook; 

	old_free_hook = __free_hook; 
	__free_hook = my_free_hook; 
} 

static void * 
my_malloc_hook(size_t size, const void *caller) 
{ 
	void *result; 

	/* 古いフックをすべて復元します */ 
	__malloc_hook = old_malloc_hook; 

	/* 再帰的に呼び出します */ 
	//結果 = malloc(size); 

	if (enable_malloc_hook) 
	{ 
		enable_malloc_hook = 0; 

		結果 = malloc(サイズ); 
		/* printf() は malloc() を呼び出す可能性があるため、これも保護します。*/ 
		printf("%p から呼び出された malloc(%u) は %p を返します\n", 
			(unsigned int)size, caller, result); 

		char buff[128] = { 0 }; 
		sprintf(buff, LEAK_FILE_PATH, result); 

		FILE *fp = fopen(buff, "w"); 
		fprintf(fp, "[+] %p , addr: %p, size: %ld\n", 呼び出し元, 結果, サイズ); 
		fflush(fp);//刷新データ到文件中
		fclose(fp); 

		イネーブル_malloc_hook = 1; 
	else
	結果
		= malloc(サイズ); 

	/* 基礎となるフックを保存します */ 
	old_malloc_hook = __malloc_hook; 
	/* 独自のフックを復元します */ 
	__malloc_hook = my_malloc_hook; 

	結果を返します。
静的

void my_free_hook(void *ptr, const void *caller) 
{ 
	__free_hook = old_free_hook; 

	無料(ptr); 

	old_free_hook = __free_hook; 

	/* printf() は malloc() を呼び出す可能性があるため、これも保護します。*/ 
	printf("free(%p) が %p から呼び出されました\n", 
		ptr, caller); 

	char buff[128] = { 0 }; 
	sprintf(buff, LEAK_FILE_PATH, ptr); 
	if (unlink(buff) < 0) 
	{ 
		printf("ダブルフリー %p\n",
	} 

	/* 独自のフックを復元します */ 
	__free_hook = my_free_hook; 
int main(int argc,char **argv) 
{ 
my_init_hook 
	(); 
	void *p1 = malloc(10); 
	void *p2 = malloc(20); 
	void *p3 = malloc(30); 
	無料(p3); 
	無料(p2); 
	0を返します。
}




コンパイル中に警告が表示されるため、システムはこの方法を推奨しません。

4. 完全なサンプルコード

コードは比較的長いですが、長すぎて読みにくくなるのを避けるため、ここには投稿しませんでした。必要に応じて、ブロガーに連絡するか、WeChat 公開アカウント「ライオン」をフォローして入手できます。

要約する

  • メモリ リーク検出の中核は、メモリ リークがあるかどうか、およびメモリ リークが発生した場所を知ることです。
  • メモリ リークを検出する方法には、mtrace、フック、マクロ定義、libc_malloc、__malloc_hook などがあります。このうち、mtrace は MALLOC_TRACE 環境変数の設定と再起動が必要、マクロ定義は単一ファイルに適している、__malloc_hook は削除されています。
  • プログラムのコンパイル時に -g を追加すると、addr2line ツールを使用してファイル内のメモリ リークの場所を特定できます。
  • プログラムの効率を向上させるために、リリース プログラムは「ホット アップデート」方式を使用して、必要に応じてメモリ リーク検出用の構成ファイル識別子を設定します。

クリックしてフォローし、できるだけ早くHuawei Cloudの新しいテクノロジーについて学びましょう~

Lei Jun: Xiaomi の新しいオペレーティング システム ThePaper OS の正式版がパッケージ化されました。Gome App の抽選ページのポップアップ ウィンドウは創設者を侮辱しています。Ubuntu 23.10 が正式にリリースされました。金曜日を利用してアップグレードするのもいいでしょう! Ubuntu 23.10 リリース エピソード: ヘイトスピーチが含まれていたため、ISO イメージが緊急に「リコール」されました 23 歳の博士課程の学生が Firefox で 22 年間続いた「ゴーストバグ」を修正しました RustDesk リモート デスクトップ 1.2.3 がリリースされましたWayland を強化して TiDB 7.4 をサポート リリース: MySQL 8.0 と正式互換. Logitech USB レシーバーを取り外した後、Linux カーネルがクラッシュしました. マスターは Scratch を使用して RISC-V シミュレータをこすり、Linux カーネルを正常に実行しました. JetBrains が Writerside ツールを開始しました技術文書の作成に。
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4526289/blog/10120087