mcommit's message

大阪でソフトウェア開発の仕事をしている simotinといいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

C言語 共有ライブラリのメモリリーク調査方法

最近、C言語での開発を行っているのですがメモリリークの調査方法について調べてみたので上げておきます。

調べたと言っても、こちらの記事を参考に少し実験をしてみましたという感じですが。
※「プログラミングでメシが食えるか!?」で有名な小俣光之さんの記事でした。

記事では、メモリリークの検出方法として

・mtraceを使う方法
・valgrindを使う方法

の2つの方法が紹介されています。

私が実験してみた結論ですが、

valgrindを使う方法
の方が良さそうでした。

理由は、

「valgrindを使うと共有ライブラリ内のリーク箇所が分かるから」
です。

ある程度の規模になると共通関数などはライブラリ化すると思います。
ライブラリも、実行モジュールに静的リンクするのではなく、動的リンクすることが多いと思います。
Unix環境だと.soファイルとしてライブラリ化します。

さて、今回の私のメモリリークの調査方法として焦点を当てたのがこの

「共有ライブラリ(*.so)内で発生したメモリリークを検出できるか?」
でした。

さて、検証用に以下のような2ファイルのソースを用意しました。
※私はubuntuを使って検証しました。

[main.c]
検証用のメイン関数です。

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

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

	printf("main start\n");

	// 共有ライブラリの関数を呼び出す
	// ※func内でメモリリークさせる
	func();

	ptr = (char*)malloc(1024);

	return 0;
}

[lib.c]
共有ライブラリ化する関数です。

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

void func(void) {
	char *ptr;
	printf( "func in\n");
	ptr = (char *)malloc(1024);
	return;
}

これらをコンパイルします。


# 共有ライブラリをコンパイル
gcc -shared -o libMylib.so lib.c -g
# soファイルをライブラリ環境へコピー
sudo cp libMylib.so /usr/lib
# 呼び出し側mainをコンパイル(共有ライブラリをリンクする)
gcc -o leaktest -lMylib main.c -g
※ -gオプションは忘れずに!
準備が整ったので、valgrindを動かしてみます。

valgrind --tool=memcheck --leak-check=yes ./leaktest

出力結果はこんな感じになりました。


==2946== Memcheck, a memory error detector
==2946== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==2946== Using Valgrind-3.6.1 and LibVEX; rerun with -h for copyright info
==2946== Command: ./a.out
==2946==
main start
func in
==2946==
==2946== HEAP SUMMARY:
==2946== in use at exit: 2,048 bytes in 2 blocks
==2946== total heap usage: 2 allocs, 0 frees, 2,048 bytes allocated
==2946==
==2946== 1,024 bytes in 1 blocks are definitely lost in loss record 1 of 2
==2946== at 0x4026864: malloc (vg_replace_malloc.c:236)
==2946== by 0x403D469: func (lib.c:7)
==2946== by 0x80484FD: main (main.c:11)
==2946==
==2946== 1,024 bytes in 1 blocks are definitely lost in loss record 2 of 2
==2946== at 0x4026864: malloc (vg_replace_malloc.c:236)
==2946== by 0x8048509: main (main.c:13)
==2946==
==2946== LEAK SUMMARY:
==2946== definitely lost: 2,048 bytes in 2 blocks
==2946== indirectly lost: 0 bytes in 0 blocks
==2946== possibly lost: 0 bytes in 0 blocks
==2946== still reachable: 0 bytes in 0 blocks
==2946== suppressed: 0 bytes in 0 blocks
==2946==
==2946== For counts of detected and suppressed errors, rerun with: -v
==2946== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 13 from 6)

いろいろ出力されるみたいですが、
共有ライブラリ内のリーク箇所として、


==2946== by 0x403D469: func (lib.c:7)
の箇所にリークしている共有ライブラリのファイル名と関数名がしっかり出ています。
しかもfuncを呼び出しているmain側のラインも出ていますね。

完璧だ!

さてさて、もう一方のmtraceを使う方法ですが、結果から言いますとツールを使うと同じような結果のテキストが出力されるのですが、共有ライブラリの関数はアドレスとしてしか出力されませんでした。
※ファイル名まではわかりますが。

なので、ライブラリ内のリークを素早く見つけるにはちょっと厳しいです。
※objdumpとかlddとかaddr2lineとかを駆使すればいろいろと分かるとは思いますが、チームで開発する場合は、良くも悪くも「手っ取り早く誰でも使うこと」が優先されるのでその点valgrindが優れていると思います。

C言語の開発におすすめの書籍

今回の記事ではmtrace,valgrindを使ったメモリリークの検出方法について書いてみましたが、共有ライブラリや実行形式で何かトラブルがあった場合の解決方法についてはBINARY HACKでいろいろなテクニックが紹介されています。mtraceの使い方なんかもこの本で紹介されています。

Binary Hacks ―ハッカー秘伝のテクニック100選

Binary Hacks ―ハッカー秘伝のテクニック100選

BINARY HACKというだけあってコアな内容が書かれており、平常運転で仕事をしているときはあまり開かない本です。
ですが、今回の記事のようにメモリリークC++のよく分からない挙動などで「はまった」ときに解決策や解決の糸口となる情報が載っていて愛用しています。
特に、組込LinuxGCC,G++の開発の際にはありがたい一冊です。組込以外でもシステムプログラミングに関わる方には必携の書と言ってもいいかもしれません。