【PE】Valgrind memcheckツールによるC/C++メモリリーク検出方法


システムプログラミングの重要な側面は、メモリに関連する問題を効果的に処理することです.あなたの仕事がシステムに近づくほど、より多くのメモリの問題に直面する必要があります.これらの問題は非常に些細な場合がありますが、メモリをデバッグする問題の悪夢になることが多いです.そのため、メモリの問題をデバッグするために多くのツールが実際に使用されます.
本稿では,最もポピュラーなオープンソースメモリ管理フレームワークVALGRINDについて論じる.
Valgrind.org:
Valgrindは動解析ツールを構築するためのプローブフレームワークである.プログラムの改善を支援するために、各ツールがデバッグ、分析、または類似のタスクを実行するツールセットが含まれています.Valgrindのアーキテクチャはモジュール化されているので、既存の構造を乱さずに新しいツールを簡単に作成できます.
多くの有用なツールが標準として提供されています.
  • Memcheckはメモリエラー検出器です.それはあなたのプログラム、特にCとC++で書かれたプログラムをより正確にするのに役立ちます.
  • Cachegrindはキャッシュとブランチ予測アナライザです.プログラムの実行を高速化するのに役立ちます.
  • Callgrindは呼び出しマップキャッシュ生成アナライザである.Cachegrindの機能と重複していますが、Cachegrindが収集していない情報も収集されています.
  • Helgrindはスレッドエラー検出器です.マルチスレッドプログラムをより正確にするのに役立ちます.
  • DRDもスレッドエラー検出器です.Helgrindと似ていますが、異なる分析技術を使用しているので、異なる問題が見つかる可能性があります.
  • Massifはスタックアナライザです.プログラムがより少ないメモリを使用できるようにします.
  • DHATは別の異なるスタックアナライザです.ブロックのライフサイクル、ブロックの使用、レイアウトの非効率性などの問題を理解するのに役立ちます.
  • SGcheckはスタックとグローバル配列のオーバーフローを検出するための実験ツールである.その機能はMemcheckと相補的である:SGcheckはMemcheckが見つけられない問題を見つけ、逆も同様である.
  • BBVは実験的性質のSimPoint基本ブロックベクトル生成器である.コンピュータアーキテクチャの研究と開発に役立ちます.

  • ほとんどのユーザーに使用されていない小さなツールもあります.Lackeyはデモ機器の基礎のサンプルツールです.Nulgrindは最小化されたValgrindツールであり、分析や操作を行わず、テストの目的にのみ使用されます.
    この文章では「memcheck」ツールに注目します.
    Valgrind Memcheckの使用
    memcheckツールの使い方は次のとおりです.
    valgrind --tool=memcheck ./a.out

    上のコマンドから明らかなように、主なコマンドはvalgrindであり、私たちが使用したいツールは'-tool'オプションで指定されています.上の「a.out」とは、memcheckを使用して実行したい実行可能なファイルを指す.
    このツールでは、メモリに関連する次の問題を検出できます.
  • 未解放メモリの使用
  • リリースメモリの読み取り/書き込み
  • 割り当てられたメモリブロックの末尾の読み取り/書き込み
  • メモリリーク
  • が一致しないmalloc/new/new[]とfree/delete/delete[]
  • を使用
  • メモリを繰り返し解放する
  • 注意:上記は全面的ではないが、このツールによって検出できる多くの一般的な問題が含まれている.
    上のシーンについて一つ一つ議論しましょう.
    注意:以下で説明するすべてのテストコードはgccを使用し、memcheckの出力で行番号を生成するための-gオプションを加えてコンパイルすべきである.私たちが前に議論したCプログラムを実行可能なファイルにコンパイルするには、4つの異なる段階を経験する必要があると思います.
    1.初期化されていないメモリの使用
    Code :
    #include 
    #include  
    
    int main(void)
    {
        char *p; 
    
        char c = *p; 
    
        printf("
    [%c]
    "
    ,c); return 0; }

    上記のコードでは、初期化されていないポインタ「p」を使用しようとする.
    Memcheckを実行して結果を見てみましょう.
    $ valgrind --tool=memcheck ./val
    ==2862== Memcheck, a memory error detector
    ==2862== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
    ==2862== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
    ==2862== Command: ./val
    ==2862==
    ==2862== Use of uninitialised value of size 8
    ==2862==    at 0x400530: main (valgrind.c:8)
    ==2862==
    
    [#]
    ==2862==
    ==2862== HEAP SUMMARY:
    ==2862==     in use at exit: 0 bytes in 0 blocks
    ==2862==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
    ==2862==
    ==2862== All heap blocks were freed -- no leaks are possible
    ==2862==
    ==2862== For counts of detected and suppressed errors, rerun with: -v
    ==2862== Use --track-origins=yes to see where uninitialized values come from
    ==2862== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)

    上記の出力から、Valgrindは初期化されていない変数を検出し、警告を与えた(上に太い行がいくつかあるようだ).
    2.メモリが解放された後に読み取り/書き込みを行う
    Code :
    #include 
    #include  
    
    int main(void)
    {
        char *p = malloc(1);
        *p = 'a'; 
    
        char c = *p; 
    
        printf("
    [%c]
    "
    ,c); free(p); c = *p; return 0; }

    上記のコードでは、メモリを解放するポインタ「p」があり、ポインタを利用して値を取得しようとしています.
    memcheckを実行してValgrindがこの状況にどのように反応しているかを見てみましょう.
    $ valgrind --tool=memcheck ./val
    ==2849== Memcheck, a memory error detector
    ==2849== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
    ==2849== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
    ==2849== Command: ./val
    ==2849== 
    
     [a]
    ==2849== Invalid read of size 1
    ==2849==    at 0x400603: main (valgrind.c:30)
    ==2849==  Address 0x51b0040 is 0 bytes inside a block of size 1 free'd
    ==2849==    at 0x4C270BD: free (vg_replace_malloc.c:366)
    ==2849==    by 0x4005FE: main (valgrind.c:29)
    ==2849==
    ==2849==
    ==2849== HEAP SUMMARY:
    ==2849==     in use at exit: 0 bytes in 0 blocks
    ==2849==   total heap usage: 1 allocs, 1 frees, 1 bytes allocated
    ==2849==
    ==2849== All heap blocks were freed -- no leaks are possible
    ==2849==
    ==2849== For counts of detected and suppressed errors, rerun with: -v
    ==2849== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)

    上記の出力から、Valgrindは無効な読み出し動作を検出する、警告"Invalid read of size 1"を出力していることがわかる.
    なお、gdbを使用してcプログラムをデバッグする.
    3.割り当てられたメモリブロックの末尾から読み取り/書き込みを行う
    Code :
    #include 
    #include  
    
    int main(void)
    {
        char *p = malloc(1);
        *p = 'a'; 
    
        char c = *(p+1); 
    
        printf("
    [%c]
    "
    ,c); free(p); return 0; }

    上記のコードでは、「p」に1バイトのメモリを割り当てるが、値を「c」に読み出す際に使用するのはアドレスp+1である.
    Valgrindを使用して、上のコードを実行します.
    $ valgrind --tool=memcheck ./val
    ==2835== Memcheck, a memory error detector
    ==2835== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
    ==2835== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
    ==2835== Command: ./val
    ==2835==
    ==2835== Invalid read of size 1
    ==2835==    at 0x4005D9: main (valgrind.c:25)
    ==2835==  Address 0x51b0041 is 0 bytes after a block of size 1 alloc'd
    ==2835==    at 0x4C274A8: malloc (vg_replace_malloc.c:236)
    ==2835==    by 0x4005C5: main (valgrind.c:22)
    ==2835== 
    
     []
    ==2835==
    ==2835== HEAP SUMMARY:
    ==2835==     in use at exit: 0 bytes in 0 blocks
    ==2835==   total heap usage: 1 allocs, 1 frees, 1 bytes allocated
    ==2835==
    ==2835== All heap blocks were freed -- no leaks are possible
    ==2835==
    ==2835== For counts of detected and suppressed errors, rerun with: -v
    ==2835== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)

    同様に、この場合においても無効な読み出し動作が検出する.
    4.メモリ漏洩
    Code:
    #include 
    #include  
    
    int main(void)
    {
        char *p = malloc(1);
        *p = 'a'; 
    
        char c = *p; 
    
        printf("
    [%c]
    "
    ,c); return 0; }

    今回のコードでは、バイトを申請しましたが、解放されませんでした.Valgrindを実行して何が起こるか見てみましょう.
    $ valgrind --tool=memcheck --leak-check=full ./val
    ==2888== Memcheck, a memory error detector
    ==2888== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
    ==2888== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
    ==2888== Command: ./val
    ==2888== 
    
     [a]
    ==2888==
    ==2888== HEAP SUMMARY:
    ==2888==     in use at exit: 1 bytes in 1 blocks
    ==2888==   total heap usage: 1 allocs, 0 frees, 1 bytes allocated
    ==2888==
    ==2888== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1
    ==2888==    at 0x4C274A8: malloc (vg_replace_malloc.c:236)
    ==2888==    by 0x400575: main (valgrind.c:6)
    ==2888==
    ==2888== LEAK SUMMARY:
    ==2888==    definitely lost: 1 bytes in 1 blocks
    ==2888==    indirectly lost: 0 bytes in 0 blocks
    ==2888==      possibly lost: 0 bytes in 0 blocks
    ==2888==    still reachable: 0 bytes in 0 blocks
    ==2888==         suppressed: 0 bytes in 0 blocks
    ==2888==
    ==2888== For counts of detected and suppressed errors, rerun with: -v
    ==2888== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)

    メモリの漏洩を検出できる出力ライン(上に太くなった部分)が表示する.
    注意:ここでは、メモリ漏洩の詳細を得るために、「–leak-check=full」というオプションを追加しました.
    5.malloc/new/new[]とfree/delete/delete[]を不一致で使用
    Code:
    #include 
    #include 
    #include 
    
    int main(void)
    {
        char *p = (char*)malloc(1);
        *p = 'a'; 
    
        char c = *p; 
    
        printf("
    [%c]
    "
    ,c); delete p; return 0; }

    上記のコードではmalloc()を使用してメモリを割り当てるがdeleteオペレータを使用してメモリを削除する.
    注意:deleteオペレータはC++に導入するため、C++をコンパイルするにはg++を使用する必要がある.
    実行してみましょう.
    $ valgrind --tool=memcheck --leak-check=full ./val
    ==2972== Memcheck, a memory error detector
    ==2972== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
    ==2972== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
    ==2972== Command: ./val
    ==2972== 
    
     [a]
    ==2972== Mismatched free() / delete / delete []
    ==2972==    at 0x4C26DCF: operator delete(void*) (vg_replace_malloc.c:387)
    ==2972==    by 0x40080B: main (valgrind.c:13)
    ==2972==  Address 0x595e040 is 0 bytes inside a block of size 1 alloc'd
    ==2972==    at 0x4C274A8: malloc (vg_replace_malloc.c:236)
    ==2972==    by 0x4007D5: main (valgrind.c:7)
    ==2972==
    ==2972==
    ==2972== HEAP SUMMARY:
    ==2972==     in use at exit: 0 bytes in 0 blocks
    ==2972==   total heap usage: 1 allocs, 1 frees, 1 bytes allocated
    ==2972==
    ==2972== All heap blocks were freed -- no leaks are possible
    ==2972==
    ==2972== For counts of detected and suppressed errors, rerun with: -v
    ==2972== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)

    上の出力から(太字の行)が見えますが、Valgrindは「free()/delete/delete[]を不一致で使用している」ことを明確に説明しています.
    テストコードで「new」と「free」を組み合わせて、Valgrindが与えた結果を見てみましょう.
    6.メモリを2回解放
    Code :
    #include 
    #include  
    
    int main(void)
    {
        char *p = (char*)malloc(1);
        *p = 'a'; 
    
        char c = *p;
        printf("
    [%c]
    "
    ,c); free(p); free(p); return 0; }

    上のコードでは、「p」が指すメモリを2回解放しました.次にmemcheckを実行します.
    $ valgrind --tool=memcheck --leak-check=full ./val
    ==3167== Memcheck, a memory error detector
    ==3167== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
    ==3167== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
    ==3167== Command: ./val
    ==3167== 
    
     [a]
    ==3167== Invalid free() / delete / delete[]
    ==3167==    at 0x4C270BD: free (vg_replace_malloc.c:366)
    ==3167==    by 0x40060A: main (valgrind.c:12)
    ==3167==  Address 0x51b0040 is 0 bytes inside a block of size 1 free'd
    ==3167==    at 0x4C270BD: free (vg_replace_malloc.c:366)
    ==3167==    by 0x4005FE: main (valgrind.c:11)
    ==3167==
    ==3167==
    ==3167== HEAP SUMMARY:
    ==3167==     in use at exit: 0 bytes in 0 blocks
    ==3167==   total heap usage: 1 allocs, 2 frees, 1 bytes allocated
    ==3167==
    ==3167== All heap blocks were freed -- no leaks are possible
    ==3167==
    ==3167== For counts of detected and suppressed errors, rerun with: -v
    ==3167== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)

    上記の出力から、同じポインタに対してメモリの解放操作が2回呼び出されたことを検出する(太い行が見える).
    本稿では、メモリ管理フレームワークValgrindに注目し、memcheck(Valgrindフレームワークが提供する)ツールを使用して、メモリを頻繁に操作する必要があるプログラマーの負担をどのように低減するかを理解する.このツールでは、手動で検出できないメモリに関する多くの問題を検出できます.