保守

メモリリークを検出するvalgrindの使い方と解析

2023年6月17日

はじめに

ソフトウェアの開発終盤やリリース後に発覚する厄介な問題の中に「メモリリーク」があります。
「機器が動作しなくなる」や「アプリがクラッシュする」などの事象として表面に現れます。

リークしている原因箇所の特定には時間を要する場合が大半で、経験した方も多いかと思います。
また、「まだ直らないのか?」という上司からのプレッシャーとも戦う必要があります(笑)

この記事では、メモリリークの原因特定の一助となる valgrind についてご紹介いたします。

参考

解析の必要性にも触れている「メモリリークはナゼ起こるのか?」もご覧ください。

 

Valgrind の概要

概要と目的

Valgrind は、デバッグやプロファイリングのためのオープン・ソース・ソフトウェア(OSS)です。
こちらの sourceware.org で公開されています。

主な機能は、プログラムのメモリリーク、スレッドのメモリ競合アクセスなど、
実行時のメモリ操作に関する問題を検出することです。

 

主な機能とツール

Valgrind は、以下の主要ツールから構成されています。
また、Valgrind は、C/C++言語に特化していますが、他の言語にも一部対応しています。

  • Memcheck
    アクセスエラーやメモリリークを検出します。動的なメモリ割り当てと解放を追跡し、
    未初期化のメモリの使用、不正メモリアクセス、二重解放などの問題を検出します。
  • Cachegrind
    キャッシュヒット率やミス率、メモリアクセスのパフォーマンスに関する情報を提供します。
    プログラムのパフォーマンスのボトルネックを特定するのに役立ちます。
  • Callgrind
    プロファイリングを行い、関数の使用回数、時間、キャッシュ効率に関する情報を提供します。
    プログラムのパフォーマンスの改善点を特定するのに役立ちます。
  • Helgrind
    スレッドのデッドロックやアクセス競合などの問題を検出します。
    スレッド間の同期エラーや競合を特定し、修正するのに役立ちます。
  • Massif
    ヒープメモリの使用状況をプロファイリングし、ヒープメモリの割当/解放量、
    プロファイルを提供し、メモリ使用量の削減やメモリリークの検出に役立ちます。

ポイント

Valgrind を使用すると、プログラムの品質とパフォーマンスを向上させるために、
実行時に検出されるさまざまなメモリ関連の問題を特定できます。

SANACHAN
SANACHAN
Valgrind は、Linux 環境で広く使用されています。
詳細は、公式ページをご確認ください。

 

Valgrindのインストールと設定

入手方法

Linux の場合はパッケージマネージャを通じで入手することができます。

また、ソースコードが公開されているため、コードからビルドしてインストールすることも可能です。
コードは sourceware.org で公開されており、様々なCPUアーキテクチャに対応した Makefile が用意されています。

 

インストール手順

Valgrind をインストールしてセットアップする手順は、使用している OS によって異なります。

ここでは、Ubuntu または Debian ベースの Linux ディストリビューションを想定して、
Valgrindのインストール手順を説明いたします。

step
1
パッケージリポジトリの更新

 command
$ sudo apt update

step
2
Valgrind のインストール

 command
$ sudo apt install valgrind

step
3
インストールの確認

 command
$ valgrind --version
SANACHAN
SANACHAN
ターミナルに Valgrind のバージョン情報が表示されれば、インストール成功です。

 

メモリリークの検出と解析

Valgrind を使用した検出方法

Valgrind の主要なツールである Memcheck を使用して、メモリリークを検出します。
以下のコマンドを実行すると、Valgrind を使用してメモリリークを検出することができます。

 command
$ valgrind --leak-check=full <プログラム> [<プログラムの引数>]

Valgrind が対象のプログラムを実行します。
あとは、メモリリークの再現手順や、普段行っている操作を行うだけです。

実行中にメモリリークが検出されると、Valgrind は詳細な情報を出力します。

 

メモリリークを発生させるサンプル

以下のような簡単なサンプルを用意して実行してみます。

SANACHAN
SANACHAN
7行目で確保したメモリアドレスを上書きする形で12行目で新たに確保。
ここでリークが発生するサンプルです。

 

 コンパイル
$ gcc -g -o test -O0 -Wall -Werror valgrind_test.c
SANACHAN
SANACHAN
-g オプション(デバッグシンボル付)は解析に必須、-O0(最適化なし)は推奨です。

 

 解析実行
$ valgrind --leak-check=full ./test
SANACHAN
SANACHAN
プログラムを CTRL-C などで終了させても、それまでの解析結果が出力されます。

 

解析結果と読み方

上記のサンプルの解析実行すると、以下のような解析結果が出力されます。

 解析結果
==3930== Memcheck, a memory error detector
==3930== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3930== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==3930== Command: ./test
==3930==
==3930==
==3930== HEAP SUMMARY:
==3930== in use at exit: 204,800 bytes in 200 blocks
==3930== total heap usage: 200 allocs, 0 frees, 204,800 bytes allocated
==3930==
==3930== 102,400 bytes in 100 blocks are definitely lost in loss record 1 of 2
==3930== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==3930== by 0x10915E: catch_me (main.c:7)
==3930== by 0x109196: valgrind_test (main.c:19)
==3930== by 0x1091B1: main (main.c:25)
==3930==
==3930== 102,400 bytes in 100 blocks are definitely lost in loss record 2 of 2
==3930== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==3930== by 0x109173: catch_me (main.c:12)
==3930== by 0x109196: valgrind_test (main.c:19)
==3930== by 0x1091B1: main (main.c:25)
==3930==
==3930== LEAK SUMMARY:
==3930== definitely lost: 204,800 bytes in 200 blocks
==3930== indirectly lost: 0 bytes in 0 blocks
==3930== possibly lost: 0 bytes in 0 blocks
==3930== still reachable: 0 bytes in 0 blocks
==3930== suppressed: 0 bytes in 0 blocks
==3930==
==3930== For lists of detected and suppressed errors, rerun with: -s
==3930== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
SANACHAN
SANACHAN
順にみていきましょう。

 

HEAP SUMMARY(ヒープサマリ)

ヒープを確保する関数(mallocなど)の呼び出し回数、ヒープを解放する関数(free)の呼び出し回数、
及び確保した容量の総数が最初に表示されます。

その後に、それぞれの詳細な情報が表示されます。
ヒープを操作した際の位置(ファイル名と行番号)や確保されたメモリのサイズなどが表示され、
スタックトレースからその関数の呼び出し経路も表示してくれます。

SANACHAN
SANACHAN
スタックトレースは非常にありがたいですね。

 

LEAK SUMMARY(リークサマリ)

リークサマリもヒープサマリと同様の情報が出力されます。

Valgrind の出力を注意深く解析し、リークが発生している場所や
関連するコードの特定に役立つ情報を探します。

メモリの割り当てや解放に関連する問題や、未解放のメモリ領域の特定など、
異常なメモリ操作を特定することができます。

 

おまけ(LD_PRELOAD)

 command
at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)

解析結果の出力の中で、アレっと思われた方もおられると思います。

Valgrind を経由して実行されたプログラムでは、libc の malloc/free ではなく、
Valgrind の malloc/free を利用していることが分かります

LD_PRELOAD という仕組みを使っており、「malloc/freeをフックする方法」で紹介しておりますので、
興味がございましたらご覧ください。

 

Valgrindのオプション

基本的な書式

 
$ valgrind [options] <program name> [<arguments>]

 

オプション

メモリリーク解析で使用する主なオプションをご紹介します。
その他のオプションは、公式ページ--help でご確認ください。

  • --leak-check=full
    全てのメモリリークチェック機能を有効にします。
  • --show-leak-kinds=all
    「リークの可能性あり」を含めた全てのメモリリーク解析結果を表示します。

 

メモリリークの防止と予防策

C++言語を使用している場合は、スマートポインタを利用することで予防することができます。
スマートポインタとは?」でも紹介していますので、ご覧ください。

C言語や他の言語を利用している場合は、以下のような手法・思想で実装すると予防効果が期待できます。

 

静的解析ツールの利用

静的解析ツールを利用することで、メモリリークの問題を検出できる場合があります。
ただし、検知漏れも当然ながらあります。(感触的には結構あります。)

 

スマートポインタと同等の機能を実装

スマートポインタと同等の機能を実装すれば、予防効果が期待できます。

スマートポインタはC++のテンプレートクラスで、内部にリファレンスカウンタを持っています。
参照者が増えるとインクリメントされ、参照が外れるとデクリメントされます。

カウンタが 0 になった際、参照者が居ない、すなわち今後利用されないと言えるため
自分自身でメモリを解放します。

構造体や関数ポインタを利用して、同様の仕組みを実装することができます。

 

メモリリークは起こるものという思想

注意深く設計・実装を行い、ツールやノウハウを活かしてもメモリリークは発生してしまうもの。
そこで、メモリリークは起こるものとして設計するのも一つの手法です。

OS を搭載している場合、アプリが扱えるメモリ空間は「仮想アドレス空間」です。
メモリが枯渇してプロセスが死ぬと、メモリの解放は OS が行ってくれます。

Linux では OOM-Killer(Out of Memory Killer)、Android では Low Memory Killer が実装されています。

どちらもメモリが枯渇した際に、一定のルールの元でプロセスが殺される仕組みなのですが、
Low Memory Killer の方は、殺すアプリをコントロール・選別に自由度がある点が異なります。
一般アプリかつ前面のアプリ以外を優先して殺し、HOME やシステムアプリを保護する考え方です。

このように、メモリが枯渇してプロセスが死んだとしても、再起動した際に元の状態に戻れれば良い、
という思想で設計・実装する方法です。

SANACHAN
SANACHAN
すべての手法・思想が取り入れられていれば、安定して動作するソフトが作れます。

 

こちらの記事もよく読まれています

  • この記事を書いた人
  • 最新記事
SANACHAN

SANACHAN

「生涯一エンジニア」を掲げ、大手グローバル企業でSE/PGとして8年勤め、キャリアアップ転職した現役のエンジニアです。世にあるメジャーな全プログラム言語(コボル除く)を自由に扱えます。一児の父。自分のため、家族のため、日々勉強してます。システムエンジニア、プログラミングに関する情報を蓄積している雑記帳です。

-保守
-, ,