メモリ リークをトラブルシューティングするための Valgrind ツール

メモリ リークをトラブルシューティングするための Valgrind ツール

概要

Valgrind は、メモリのデバッグ、メモリ リークの検出、およびパフォーマンス分析のためのソフトウェア開発ツールです。これを使用して、メモリ、マルチスレッド、パフォーマンスなどのさまざまな問題を分析できます。いわゆる非侵入的方法とは、コードに分析ツールのライブラリを挿入する必要がないことを意味します。これは開発者に優しいです。ツールをファイルにコンパイルする必要がある場合、またはツールによって提供されるいくつかの API を呼び出す必要がある場合、問題分析を実行できるため、ユーザーの学習コストと使用コストが間違いなく増加します。

Valgrind ディストリビューションには現在、メモリ エラー検出器、2 つのスレッド エラー検出器、キャッシュおよび分岐予測アナライザー、コール グラフ生成キャッシュおよび分岐予測アナライザー、および 2 つの異なるヒープ分析デバイスの 7 つの製品品質のツールが含まれています。また、実験的な SimPoint 基本ブロック ベクトル ジェネレーターも含まれています。次のプラットフォームで動作します: Solaris、AMD64/Solaris、ARM/Android (2.3.x 以降)、ARM64/Android、X86/Android (4.0 以降)、MIPS32/Android、X86/FreeBSD、AMD64/FreeBSD、X86 /Darwin および AMD64/Darwin (Mac OS X 10.12)

Valgrind関数の紹介

Valgrind は、動的分析ツールを構築するためのツール フレームワークであり、開発者がプロ​​グラムを改善するのに役立つ、ある種のデバッグ、プロファイリング、または同様のタスクを実行する一連のツールを備えています。Valgrind のアーキテクチャはモジュール式であるため、既存の構造に影響を与えることなく新しいツールを簡単に作成できます。この標準には多くの便利なツールが用意されています。

  • Memcheck はメモリ エラー検出器です。これは、プログラム、特に C および C++ で書かれたプログラムをより正確にするのに役立ちます。
  • Cachegrind は、キャッシュおよび分岐予測アナライザーです。プログラムの実行を高速化するのに役立ちます。
  • Callgrind は、コール グラフ生成キャッシュ アナライザーです。Cachegrind と重複する部分もありますが、Cachegrind が収集しない情報も収集します。
  • Helgrind はスレッド エラー検出器です。これは、マルチスレッド プログラムをより正確にするのに役立ちます。
  • DRD はスレッド エラー検出器でもあります。Helgrind に似ていますが、異なる分析手法を使用するため、異なる問題が見つかる可能性があります。
  • Massif はヒープ アナライザーです。これは、プログラムのメモリ使用量を減らすのに役立ちます。
  • DHAT は、別のタイプのヒープ アナライザーです。これは、ブロックの有効期間、ブロックの使用率、レイアウトの非効率性を理解するのに役立ちます。
  • SGcheck は、スタックとグローバル配列のオーバーフローを検出できる実験的なツールです。その機能は Memcheck の機能を補完します。SGcheck は Memcheck が解決できない問題を検出し、その逆も同様です。
  • BBV は、実験的な SimPoint 基本ブロック ベクトル ジェネレーターです。コンピュータ アーキテクチャの研究開発に従事する人にとって非常に役立ちます。

主に議論しますメモリエラー検出器 (Memcheck)、C/C++ プログラムの一般的な問題を検出するために使用されます。

  • オーバーフロー、ヒープオーバーフロー、スタック最上位のオーバーフローなど、使用すべきではないメモリへのアクセス、および解放されたメモリへのアクセス。
  • 未定義の値、つまり初期化されていない値、または別の未定義の値から派生した値を使用します。
  • ヒープ メモリの不適切な解放 (繰り返しヒープを解放するなど)、または malloc/new/new [] と free/delete/delete [] の使用が一致しない
  • memcpy および関連関数での src ポインターと dst ポインターの重複。
  • メモリ割り当て関数を呼び出すときは、size パラメータに負の値を渡します。
  • メモリーリーク。

Valgrind の使用

Valgrind の使用方法は非常に簡単で、valgrind コマンドの形式は次のとおりです。

valgrind [valgrind-options] your-prog [your-prog options] 

ソフトウェアを分析する必要がある場合、上記の形式で呼び出しを使用するだけで済みます。ここで、your-prog は分析対象のプログラム ファイルのパス、your-prog-options は分析対象のプログラムに元々渡されるパラメータです。valgrind-options は valgrind の一部のパラメータで、最も一般的に使用されるパラメータは –tool=[tool_name] です。memcheck を使用してメモリの問題を分析するなど、さまざまなツールを使用してさまざまな分析を実行できます。

一般的に使用されるオプションのいくつかは次のとおりです。

  • -h --help ヘルプ情報を表示します。

  • –version は valgrind カーネルのバージョンを表示します。各ツールには独自のバージョンがあります。

  • -q --quit 静かに実行し、エラー メッセージのみを出力します。

  • -v --verbose より詳細な情報を出力します。

  • –tool= [デフォルト: memcheck] 最も一般的に使用されるオプション。valgrind で toolname という名前のツールを実行します。ツール名を省略した場合、デフォルトで memcheck が実行されます。

  • –db-attach= [デフォルト: no] デバッグエラーを容易にするためにデバッガーにバインドします。

  • –run-libc-freeres=<yes|no> [デフォルト: yes]
    すべてのプログラムで共有される GNU C ライブラリ (libc.so) は、独自の使用のためにメモリの一部を割り当てる場合があります。通常、プログラムの終了時にメモリを解放することは問題ではありません。ここでは問題はありません。Linux カーネルは終了時にプロセスのすべてのリソースを再利用するため、速度が低下するだけです。glibc の作成者は、これにより、終了時にメモリ エラーをチェックするときに、Valgrind などのメモリ チェッカーが glibc でメモリ リークを報告する可能性があることを認識しており、この問題を回避するために、特に glibc に割り当てられたすべてのメモリを解放
    させるための __libc_freeres() ルーチンを提供しました。
    したがって、Memcheck は終了時に __libc_freeres() を実行しようとします。
    残念ながら、glibc の一部のバージョンでは、__libc_freeres にセグメンテーション違反を引き起こす可能性のあるバグがあります。これは Red Hat 7.1 で特に述べられています。したがって、このオプションを指定して、__libc_freeres を実行するかどうかを決定します。プログラムが Valgrind では正常に実行されているように見えても、終了時にセグメンテーション違反が発生する場合は、--run-libc-freeres=no を指定してこれを修正する必要がある場合があります。これにより、libc.so でのメモリ リークが誤って報告される可能性があります。

  • –leak-check=<no|summary|yes|full> [デフォルト: summary]
    このオプションがオンの場合、クライアント プログラムの終了時にメモリ リークがないか調べます。メモリ リークとは、メモリ ブロックが malloc を使用して割り当てられているが、free を使用して解放されておらず、このメモリを指すポインタがないことを意味します。このようなメモリ ブロックは、それらを指すポインタがないため、プログラムによって解放されることはありません。summary に設定すると、Valgrind は発生したメモリ リークの数を報告します。full または yes に設定すると、Valgrind は個々のリークに関する詳細情報を提供します。

  • –show-reachable=<yes|no> [デフォルト: no]
    このオプションがオフの場合、メモリ リーク ディテクタは、それらを指すポインタを持たないメモリ ブロックのみを表示するか、ブロックの中央を指すポインタのみを見つけることができます。 。このオプションをオンにすると、メモリ リーク ディテクタは、ポインタが指すメモリ ブロックも報告します。これらのブロックは、メモリ リークが最も発生しやすい場所です。おそらくプログラムは、少なくとも原理的には、終了する前にこれらのメモリ ブロックを解放する必要があります。ポインタによって指されるこれらのメモリ ブロックとポインタによって指されないメモリ ブロック、または内部ポインタのみによって指されるブロックは、実際には解放に使用できるブロックの先頭へのポインタが存在しないため、メモリ リークを引き起こす可能性があります。解放したいなら、それを。

  • –leak-resolution=<low|med|high>[デフォルト: low]
    メモリ リーク チェックを実行するときに、memcheck が異なるスタックが同じかどうかをどのように考慮するかを決定します。low に設定した場合、同じ状況とみなされるには最初の 2 つのスタック層のみが一致する必要があり、med に設定した場合は 4 つのスタック層が一致する必要があり、high に設定した場合はスタックのすべてのレベルが一致する必要があります。
    本格的なメモリ リーク チェックの場合は、おそらく --leak-resolution=high および --num-callers=40 以上の数値を使用する必要があります。これにより膨大な量の情報が生成されることに注意してください。そのため、デフォルトのオプションは 4 つの呼び出し元の一致と 1 つの低解像度の一致です。--leak-resolution= 設定は、memcheck のメモリ リークの検出機能には影響しないことに注意してください。結果の出力方法が変わるだけです。

  • –freelist-vol=[デフォルト: 5000000]
    クライアント プログラムがメモリを解放するために free (C) または delete (C++) を使用する場合、メモリはすぐには再割り当てに使用できません。メモリはアクセス不可としてマークされ、解放されたメモリのキューに入れられます。この目的は、解放されたメモリが再び使用される時点をできるだけ遅らせることです。これは、メモリ ブロックが解放された後の重要な期間中に、memcheck がブロックへの不正アクセスをチェックするのに役立ちます。
    このオプションは、キューが保持できる合計メモリ容量をバイト単位で指定します。デフォルト値は 5000000 バイトです。この数値を増やすと、memcheck が使用するメモリが増加しますが、解放されたメモリの不正な使用が検出される可能性も高くなります。

  • --workaround-gcc296-bugs=<yes|no>[デフォルト: no]
    このオプションがオンの場合、スタック ポインタより少し下の距離での読み取りと書き込みは gcc 2.96 のバグとみなされ、エラーとして報告されません。 。距離のデフォルトは 256 バイトです。一部の古い Linux ディストリビューション (RedHat 7.X) では gcc 2.96 がデフォルトのコンパイラであるため、このオプションの使用が必要になる場合があることに注意してください。必要がない場合は、このオプションを使用しないでください。実際のバグがすり抜ける可能性があります。より良い解決策は、このバグを修正した新しいバージョンの gcc/g++ を使用することです。

  • –partial-loads-ok=<yes|no>[デフォルト: no] は、
    memcheck がワード長、ワードアラインメントをどのように処理するかを制御します。したがって、アドレスからの読み取り時にどのバイトがアドレス可能でどのバイトがアドレス不可能かを制御します。「yes」に設定すると、そのような読み取りではアドレス指定エラーがスローされません。代わりに、不正なアドレスから読み取られた V バイトは未定義として表示され、正当なアドレスへのアクセスは通常どおりメモリにマップされます。
    no に設定すると、部分的に間違ったアドレスからの読み取りは、完全に間違ったアドレスからの読み取りと同じように処理されます。つまり、不正なアドレス エラーがスローされ、結果の V バイトが正当なデータとして表示されます。このコードの動作は ISO C/C++ 標準に違反しているため、問題があると考えられることに注意してください。可能であれば、このコードを修正する必要があります。このオプションは最後の手段としてのみ検討してください。

  • –undef-value-errors=<yes|no> [デフォルト: yes]
    未定義の値の危険な使用を memcheck がチェックするかどうかを制御します。Yes に設定すると、Memcheck は Addrcheck (Valgrind の一部である軽量メモリ チェック ツール) のように動作し、未定義値エラーをチェックしません。未定義の値エラーを表示したくない場合は、このオプションを使用します。

Memcheck がデフォルトのツールです。–leak-check オプションは、詳細なメモリ リーク検出機能を有効にします。プログラムの実行は通常よりも大幅に遅くなり (例: 20 ~ 30 倍)、より多くのメモリを使用します。Memcheck は、メモリ エラーと検出されたリークに関するメッセージを出力します。

これらの問題は、他の手段では検出するのが難しい場合があり、多くの場合、長期間検出されず、診断が難しいクラッシュが発生することがあります。Memcheck は、コマンド ライン オプション –xtree-memory と監視コマンド xtmemory を使用した実行ツリーのメモリ分析も提供します。

valgrind --leak-check=yes ./valgrind_test
一开始是valgrind信息“==62414==”表示进程号
==62414== Memcheck, a memory error detector
==62414== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==62414== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==62414== Command: ./valgrind_test
程序访问非法地址的内存,无效写入
==62414== Invalid write of size 4
==62414==    at 0x40054B: fun (valgrind_test.c:21)
==62414==    by 0x400566: main (valgrind_test.c:28)
==62414==  Address 0x5201068 is 0 bytes after a block of size 40 alloc'd
==62414==    at 0x4C2AEC3: malloc (vg_replace_malloc.c:309)
==62414==    by 0x40053E: fun (valgrind_test.c:20)
==62414==    by 0x400566: main (valgrind_test.c:28)
==62414==

堆区情况:

==62414== HEAP SUMMARY:
==62414==     in use at exit: 40 bytes in 1 blocks
==62414==   total heap usage: 1 allocs, 0 frees, 40 bytes allocated
内存泄漏消息如下所示:

==62414== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==62414==    at 0x4C2AEC3: malloc (vg_replace_malloc.c:309)
==62414==    by 0x40053E: fun (valgrind_test.c:20)
==62414==    by 0x400566: main (valgrind_test.c:28)

有几种泄漏; 两个最重要的类别是,肯定泄露(definitely lost),可能已经泄露(possibly lost)

==62414== LEAK SUMMARY:
==62414==    definitely lost: 40 bytes in 1 blocks
==62414==    indirectly lost: 0 bytes in 0 blocks
==62414==      possibly lost: 0 bytes in 0 blocks
==62414==    still reachable: 0 bytes in 0 blocks
==62414==         suppressed: 0 bytes in 0 blocks
==62414==
==62414== For lists of detected and suppressed errors, rerun with: -s
==62414== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

レポートの基本的な形式は次のとおりです。

{问题描述}   
at {地址、函数名、模块或代码行} 
by {地址、函数名、代码行}
by ...{逐层依次显示调用堆栈}
Address 0x???????? {描述地址的相对关系}

レポート出力ドキュメントの全体的な形式は次のように要約できます。

1. copyright 版权声明
2. 异常读写报告
2.1 主线程异常读写
2.2 线程A异常读写报告
2.3 线程B异常读写报告
2... 其他线程
3. 堆内存泄露报告
3.1 堆内存使用情况概述(HEAP SUMMARY)
3.2 确信的内存泄露报告(definitely lost)
3.3 可疑内存操作报告 (show-reachable=no关闭)
3.4 泄露情况概述(LEAK SUMMARY)

公式説明と分析:
http://valgrind.org/docs/manual/faq.html#faq.deflost より抜粋

5.2.	With Memcheck's memory leak detector, what's the difference between "definitely lost", "indirectly lost", "possibly lost", "still reachable", and "suppressed"?
The details are in the Memcheck section of the user manual.

In short:

"definitely lost" means your program is leaking memory -- fix those leaks!

"indirectly lost" means your program is leaking memory in a pointer-based structure. (E.g. if the root node of a binary tree is "definitely lost", all the children will be "indirectly lost".) If you fix the "definitely lost" leaks, the "indirectly lost" leaks should go away.

"possibly lost" means your program is leaking memory, unless you're doing unusual things with pointers that could cause them to point into the middle of an allocated block; see the user manual for some possible causes. Use --show-possibly-lost=no if you don't want to see these reports.

"still reachable" means your program is probably ok -- it didn't free some memory it could have. This is quite common and often reasonable. Don't use --show-reachable=yes if you don't want to see these reports.

"suppressed" means that a leak error has been suppressed. There are some suppressions in the default suppression files. You can ignore suppressed errors.
  • 確実に紛失:
    紛失を確認しました。プログラムにメモリ リークがあるため、できるだけ早く修正する必要があります。プログラムの終了時に、動的に割り当てられたメモリが解放されておらず、プログラム内のポインタ変数を介してこのメ​​モリにアクセスできない場合、このエラーが報告されます。レポートで示されるスタックはメモリ割り当て時のコールスタックであり、基本的にはそのメモリがどのようなビジネスロジックによって生成されたのかを明らかにすることができます。
char *Fun1()  //definitely lost  
{
    
      
    char *pcTemp;  
  
    pcTemp=(char*)malloc(10);  
    return pcTemp;  
}
  • 間接的に
    失われます。このエラーは、ポインター メンバーを含むクラスまたは構造体を使用するときに報告されることがあります。このタイプのエラーは直接修正する必要はありません。エラーは常に「確実に失われた」とともに表示されます。「確実に失われた」を修正するだけです。
int Fun4()//definitely and indirectly lost  
{
    
    
    c1 *pobjTest;

    pobjTest=new c1();

    return 0;
}

おそらく失われた:
リークがある可能性があることを意味します。プログラムでポインタが動的に割り当てられたメモリを指すことを許可していない限り (ただし、このメモリの開始アドレス) を演算してこのメ​​モリの開始アドレスを取得し、解放します。
プログラムの終了時に、動的に割り当てられたメモリが解放されておらず、プログラム内のポインタ変数を介してこのメ​​モリの開始アドレスにアクセスできないが、データの特定の部分にはアクセスできる場合、このエラーが報告されます。 。これは通常、トレースが難しいセカンダリ ポインター (ポインターのポインター) などの複雑な状況が発生した場合に発生します。

char *Fun3()//possibly lost  
{
    
    
    static char *s_pcTemp;
    char *pcData;

    pcData=(char*)malloc(10);
    s_pcTemp=pcData+1;

    return NULL;
}

まだ到達可能:
アクセス可能、失われてはいないが解放されていない。これは、メモリが解放されていないが、メモリを指すポインタがまだ存在し、メモリがまだ使用中であることを意味します。これはリークとしてカウントされません。(プログラムが終了してもまだ動作している非同期システム コール?) プログラムが正常に終了した場合、プログラムがクラッシュすることはありませんが、長時間実行するとシステム リソースを使い果たす可能性があるため、作成者は修正することをお勧めします。プログラムが正常に終了せずにクラッシュした場合 (不正なアドレスにアクセスしたときのクラッシュなど)、一時的に無視し、プログラムのクラッシュの原因となったエラーを修正して、再テストする必要があります。

char *Fun2()//still reachable  
{
    
      
    static char *s_pcTemp=NULL;  
    if(s_pcTemp==NULL) s_pcTemp=(char*)malloc(10);
    return NULL;
}

抑制されました:
解決されました。メモリ リークが発生しましたが、システムが自動的に処理しました。このタイプのエラーは無視してかまいません。ルーチンを使用してこの種のエラーをトリガーすることはできませんでした。公式の説明を見ても、オペレーティング システムによって処理されるのか、valgrind によって処理されるのかわかりません。また、このエラーに遭遇したことはありません。だから彼を無視してください。

その他のメモリ使用エラー

書き込み例外

#include <stdlib.h>
 
int main() {
    
    
    const int array_count = 4;
    int* p = malloc(array_count * sizeof(int));
    p[array_count] = 0; // Illegal read 
    free(p);
    return 0;
}

上記のコードは 4 つの int サイズのスペースのみを割り当てますが、6 行目ではこのスペースの後のスペースにデータを書き込むため、書き込み違反が発生します。valgrind を使用した分析では次のようになります。

==18100== Invalid write of size 4
==18100==    at 0x400658: main (mem_error.c:6)
==18100==  Address 0x51e0050 is 0 bytes after a block of size 16 alloc'd
==18100==    at 0x4C27BC3: malloc (vg_replace_malloc.c:299)
==18100==    by 0x40063F: main (mem_error.c:5)

1 行目は 4 バイトが不正に書き込まれたことを示し、3 行目は書き込み位置が割り当てられた 16 バイトより後であることを示しています。

読み取り違反

#include <stdlib.h>
 
int main() {
    
    
    const int array_count = 4;
    int* p = malloc(array_count * sizeof(int));
    int error_num = p[array_count]; // Illegal read
    free(p);
    return 0;
}

エラー箇所は上記の例と同じですが、今回は不正なアドレスからデータが読み取られる点が異なります。valgrind を使用して表示を分析する

==31461== Invalid read of size 4
==31461==    at 0x400658: main (mem_error.c:6)
==31461==  Address 0x51e0050 is 0 bytes after a block of size 16 alloc'd
==31461==    at 0x4C27BC3: malloc (vg_replace_malloc.c:299)
==31461==    by 0x40063F: main (mem_error.c:5)

1 行目は 4 バイトが不正に読み取られたことを示し、3 行目は読み取り位置が割り当てられた 16 バイトより後であることを示しています。

初期化されていない変数を使用する

これは、C/C++ プログラミングの初心者にとって非常によくある間違いです。

#include <stdlib.h>
#include <stdio.h>
 
int main() {
    
    
    const int array_count = 4;
    int* p = malloc(array_count * sizeof(int));
    printf("%d",  p[array_count - 1]);
    free(p);
 
    int undefine_num;
    printf("%d", undefine_num);
    return 0;
}

行 7 と行 11 は、それぞれヒープとスタック上の初期化されていない変数にアクセスします。valgrind 分析は次のことを示しています

==24104== Conditional jump or move depends on uninitialised value(s)
==24104==    at 0x4E79F7F: vfprintf (in /home/opt/gcc-4.8.2.bpkg-r4/gcc-4.8.2.bpkg-r4/lib64/libc-2.18.so)
==24104==    by 0x4E837A8: printf (in /home/opt/gcc-4.8.2.bpkg-r4/gcc-4.8.2.bpkg-r4/lib64/libc-2.18.so)
==24104==    by 0x4006BA: main (mem_error.c:7)
==24104== 
==24104== Conditional jump or move depends on uninitialised value(s)
==24104==    at 0x4E79E37: vfprintf (in /home/opt/gcc-4.8.2.bpkg-r4/gcc-4.8.2.bpkg-r4/lib64/libc-2.18.so)
==24104==    by 0x4E837A8: printf (in /home/opt/gcc-4.8.2.bpkg-r4/gcc-4.8.2.bpkg-r4/lib64/libc-2.18.so)
==24104==    by 0x4006DA: main (mem_error.c:11)
==24104== 

このレポートはすでに非常に詳細になっていますが、valgrind に –track-origins=yes を追加して、どの構造で問題が発生しているかを出力することもできます。もちろん、これにより valgrind 解析も遅くなります。

==29911== Conditional jump or move depends on uninitialised value(s)
==29911==    at 0x4E79F7F: vfprintf (in /home/opt/gcc-4.8.2.bpkg-r4/gcc-4.8.2.bpkg-r4/lib64/libc-2.18.so)
==29911==    by 0x4E837A8: printf (in /home/opt/gcc-4.8.2.bpkg-r4/gcc-4.8.2.bpkg-r4/lib64/libc-2.18.so)
==29911==    by 0x4006BA: main (mem_error.c:7)
==29911==  Uninitialised value was created by a heap allocation
==29911==    at 0x4C27BC3: malloc (vg_replace_malloc.c:299)
==29911==    by 0x40068F: main (mem_error.c:6)
==29911== 
==29911== Conditional jump or move depends on uninitialised value(s)
==29911==    at 0x4E79E37: vfprintf (in /home/opt/gcc-4.8.2.bpkg-r4/gcc-4.8.2.bpkg-r4/lib64/libc-2.18.so)
==29911==    by 0x4E837A8: printf (in /home/opt/gcc-4.8.2.bpkg-r4/gcc-4.8.2.bpkg-r4/lib64/libc-2.18.so)
==29911==    by 0x4006DA: main (mem_error.c:11)
==29911==  Uninitialised value was created by a stack allocation
==29911==    at 0x400670: main (mem_error.c:4)

システム関数での初期化されていない変数の使用

もう少し複雑な例を見てみましょう。次の例では、テスト関数は初期化されていない変数を操作するため、結果は予測できません。

#include <stdlib.h>
#include <stdio.h>
 
void test(int n) {
    
    
    n = n + 1;
}
 
int main() {
    
    
    const int array_count = 4;
    int* p = malloc(array_count * sizeof(int));
    test(p[array_count - 1]);
    free(p);
    return 0;
}

Valgrind は、上記のコードの作成者が何を表現したいのかを知らないため、エラーを報告しません。

==28259== Command: ./mem_error
==28259== 
==28259== 
==28259== HEAP SUMMARY:
==28259==     in use at exit: 0 bytes in 0 blocks
==28259==   total heap usage: 1 allocs, 1 frees, 16 bytes allocated
==28259== 
==28259== All heap blocks were freed -- no leaks are possible
==28259== 
==28259== For counts of detected and suppressed errors, rerun with: -v
==28259== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

ただし、エラー呼び出しがシステム関数に対するものである場合。Valgrind はシステム関数の入力要件を知っているため、この動作違反を判断できます。コードを少し変更してみましょう

#include <stdlib.h>
#include <stdio.h>
 
void test(int n) {
    
    
    n = n + 1;
    write(stdout, "xxx", n); 
}
 
int main() {
    
    
    const int array_count = 4;
    int* p = malloc(array_count * sizeof(int));
    test(p[array_count - 1]);
    free(p);
    return 0;
}

Valgrind は、6 行目にあるシステム メソッドの 3 番目のパラメーターが初期化されていないことを分析します。

==4344== Syscall param write(count) contains uninitialised byte(s)
==4344==    at 0x4F0BED0: __write_nocancel (in /home/opt/gcc-4.8.2.bpkg-r4/gcc-4.8.2.bpkg-r4/lib64/libc-2.18.so)
==4344==    by 0x4006CA: test (mem_error.c:6)
==4344==    by 0x40070D: main (mem_error.c:12)

スペースの解放中にエラーが発生しました

同じ空間を繰り返し解放したり、ヒープ上にないアドレスを解放関数に渡したり、非対称メソッドを使用して解放関数を適用したりすることがあります。このタイプのエラーは、free、delete、delete[]、realloc で発生します。

同じスペースを繰り返し解放する
#include <stdlib.h>
 
int main() {
    
    
    const int array_count = 4;
    int* p = malloc(array_count * sizeof(int));
    free(p);
    free(p);
    return 0;
}

valgrind 分析を使用すると、レポートは、行 6 が解放したスペースを行 7 が解放することを示します。このスペースは行 5 で適用されました。

==6537== Invalid free() / delete / delete[] / realloc()
==6537==    at 0x4C28CBD: free (vg_replace_malloc.c:530)
==6537==    by 0x40065B: main (mem_error.c:7)
==6537==  Address 0x51e0040 is 0 bytes inside a block of size 16 free'd
==6537==    at 0x4C28CBD: free (vg_replace_malloc.c:530)
==6537==    by 0x40064F: main (mem_error.c:6)
==6537==  Block was alloc'd at
==6537==    at 0x4C27BC3: malloc (vg_replace_malloc.c:299)
==6537==    by 0x40063F: main (mem_error.c:5)
ヒープではないスペースを解放する
#include <stdlib.h>
 
int main() {
    
    
    int n = 1;
    int* p = &n;
    free(p);
    return 0;
}

valgrind はスタック上のスペースを解放するときにエラーを報告します

==32411== Invalid free() / delete / delete[] / realloc()
==32411==    at 0x4C28CBD: free (vg_replace_malloc.c:530)
==32411==    by 0x4005F2: main (mem_error.c:6)
==32411==  Address 0x1fff000234 is on thread 1's stack
==32411==  in frame #1, created by main (mem_error.c:3)
リリース申請方法は非対称です

対称メソッドとは以下を指します。

  • 新しいものは削除を使用してリリースされます
  • new[] は delete[] を使用して解放されます
  • malloc、realloc などの alloc 関数は、free を使用して解放されます。
#include <stdlib.h>
 
int main() {
    
    
    int* p = new int(1);
    free(p);
    return 0;
}

Valgrind はこの非対称呼び出しを分析できます。new はスペースに適用され、free はスペースを解放します。

==5666== Mismatched free() / delete / delete []
==5666==    at 0x4C28CBD: free (vg_replace_malloc.c:530)
==5666==    by 0x400737: main (mem_error.c:5)
==5666==  Address 0x59fc040 is 0 bytes inside a block of size 4 alloc'd
==5666==    at 0x4C281E3: operator new(unsigned long) (vg_replace_malloc.c:334)
==5666==    by 0x400721: main (mem_error.c:4)

スペースカバレッジ

メモリを操作すると、メモリの上書きが発生する可能性があります。

#include <stdlib.h>
#include <string.h>                                                
int main() {
    
    
    const int array_size = 8;
    char p[array_size] = {
    
    0};
    memcpy(p + 1, p, sizeof(char) * array_size);
    return 0;
}

このコードの宛先空間はソース空間をカバーしており、
valgrind 分析のレポートにもこのエラーが示されています。

==25991== Source and destination overlap in memcpy(0x1fff000231, 0x1fff000230, 8)
==25991==    at 0x4C2BFEC: memcpy@@GLIBC_2.14 (vg_replace_strmem.c:1022)
==25991==    by 0x4006E2: main (mem_error.c:7)

疑わしいパラメータ

C/C++ では、符号付き負の数の 2 進数の最上位ビットは 0x1 です。負の数値を符号なし型の数値として扱う場合、基になるバイナリ値が一貫しているため、0xFFFFFFFF (符号なしの値 4294967295、符号付きの値 -1) などの非常に大きな数値を表現できます。

メモリ割り当てを呼び出すときに、誤って領域サイズを負の数に設定してしまうことがあり、その場合、非常に大きな領域を適用する必要があり、これは明らかに問題です。

#include <stdlib.h>
 
int main() {
    
    
    const int array_size = -1;
    void* p = malloc(array_size);
    free(p);
    return 0;
}

この時点で、valgrind はパラメーターが疑わしいことを検出します。

==3364== Argument 'size' of function malloc has a fishy (possibly negative) value: -1
==3364==    at 0x4C27BC3: malloc (vg_replace_malloc.c:299)
==3364==    by 0x40070A: main (mem_error.c:5)

メモリーリーク

メモリ リークは一般的な問題であり、多くの場合トラブルシューティングが非常に困難です。

#include <stdlib.h>
 
int main() {
    
    
    const int array_size = 32; 
    void* p = malloc(array_size);
    return 0;
}

今回はvalgrindにオプション –leak-check=full を追加して詳細情報を表示します。

valgrind --tool=memcheck --leak-check=full ./mem_error

Valgrind は、5 行目に割り当てられたスペースが解放されていないと分析しました。確実に紛失とは「漏れが確認された」ことを指しますが、

==17393== HEAP SUMMARY:
==17393==     in use at exit: 32 bytes in 1 blocks
==17393==   total heap usage: 1 allocs, 0 frees, 32 bytes allocated
==17393== 
==17393== 32 bytes in 1 blocks are definitely lost in loss record 1 of 1
==17393==    at 0x4C27BC3: malloc (vg_replace_malloc.c:299)
==17393==    by 0x4006B8: main (mem_error.c:5)
==17393== 
==17393== LEAK SUMMARY:
==17393==    definitely lost: 32 bytes in 1 blocks
==17393==    indirectly lost: 0 bytes in 0 blocks
==17393==      possibly lost: 0 bytes in 0 blocks
==17393==    still reachable: 0 bytes in 0 blocks
==17393==         suppressed: 0 bytes in 0 blocks

valgrind (memcheck) にはタイプ 7 エラーが含まれています

1,illegal read/illegal write errors  
 提示信息:[invalid read of size 4]
2,use of uninitialised values 
  提示信息:[Conditional jump or move depends on uninitialised value]
3,use of uninitialised or unaddressable values in system calls  
  提示信息:[syscall param write(buf) points to uninitilaised bytes]
4,illegal frees  
  提示信息:[invalid free()]
5,when a heap block is freed with an inappropriate deallocation function  
  提示信息:[Mismatched free()/delete/delete[]]
6,overlapping source and destination blocks
  提示信息:[source and destination overlap in memcpy(,)]
7,memory leak detection
 1),still reachable 
    内存指针还在还有机会使用或释放,指针指向的动态内存还没有被释放就退出了
 2),definitely lost 
    确定的内存泄露,已经不能访问这块内存
 3),indirectly lost 
    指向该内存的指针都位于内存泄露处
 4),possibly lost 
    可能的内存泄露,仍然存在某个指针能够快速访问某块内存,但该指针指向的已经不是内存首位置

原理

分析対象のプログラム フラグメントが初めて実行されるとき、valgrind はコード フラグメントをメモリ デバッグに使用される memcheck などのツールに渡し、ツールはコード内の分析を支援するためにいくつかのコード フラグメントを挿入します。新しいコードは、valgrind によってシミュレートされた CPU 上で実行されます。そして、valgrindは、事前に読み込んだ実行対象プログラムのデバッグ情報と、関連するライブラリファイルを組み合わせて解析結果を出力します。

新しく挿入されたコード ロジックのため、valgrind によって実行されるプログラムは、独立して実行される場合よりも遅くなります。選択したツールによっては、効率が通常の1/4~1/50になる場合があります。したがって、valgrind をパフォーマンス分析に使用する場合、通常は絶対データは使用されず、同じ環境内の相対データが比較に使用されます。

valgrind が正確なデバッグ情報を読み取るためには、-O0 を使用して分析対象プログラムのコンパイラの最適化を無効にし、-g を使用してコンパイラに行番号情報をファイルにコンパイルさせることが最善です。

Memcheck がメモリの問題を検出できる鍵となるのは、2 つのグローバル テーブルを作成することです。

有効値テーブル
プロセスのアドレス空間全体の各バイトには 8 つの対応するビットがあり、CPU の各レジスタには対応するビット ベクトルもあります。これらのビットは、バイトまたはレジスタ値が有効な初期化された値を持つかどうかを記録する役割を果たします。

有効アドレス表
プロセスのアドレス空間全体の各バイトには、アドレスが読み書きできるかどうかを記録する役割を担う対応するビットがあります。

検出原理:

メモリ内のバイトを読み書きしたい場合は、まずこのバイトに対応する A ビットをチェックします。A ビットがその位置が無効な位置であることを示している場合、memcheck は読み取りおよび書き込みエラーを報告します。コアは仮想 CPU 環境に似ているため、メモリ内の特定のバイトが実際の CPU にロードされると、そのバイトに対応する V ビットも仮想 CPU 環境にロードされます。レジスタ内の値がメモリ アドレスの生成に使用されるか、値がプログラム出力に影響を与える可能性がある場合、memcheck は対応する V ビットをチェックします。値が初期化されていない場合は、初期化されていないメモリ エラーが報告されます。

参照アドレス:

https://valgrind.org/docs/manual/manual.html
http://www.cppblog.com/Wealth/archive/2008/06/04/52118.html
https://blog.csdn.net/breaksoftware/記事/詳細/79445591

Guess you like

Origin blog.csdn.net/u010523811/article/details/129141596