1. ホーム
  2. c++

DEBUG アーティファクト valgrind の memcheck レポート解析

2022-02-19 21:06:16
<パス

memcheckの実行方法

valgrind --log-file=valgrind.log --tool=memcheck --leak-check=full --show-reachable=no --workaround-gcc296-bugs=yes . /mcsample arg1 arg2

-log-file は、相対パスまたはフルパスで、出力レポートファイルを示します。
-tool=memcheck メモリチェックを行うのはmemcheck、valgrindはツールセットであることはご存じでしょう。
-leak-check=full 完全に検出する。
-show-reachable=no 到達可能性を表示するかどうかの詳細については、メモリリークのセクションを参照してください、通常はnoですが、yesに変更することができます
-workaround-gcc296-bugs=yes gcc に対応するバグがある場合、yes に設定すると誤検出されます。
最後に、テストするプログラムとそのパラメータです。

memcheck レポートを見るには

まずは予期せぬ書き込みエラーから

int main(int argc, char *argv[])
{
    char* bigBuff = (char*)malloc[1024];
    free(bigBuff);
}
==3498== Invalid free() / delete / delete[] / realloc()
==3498== at 0x402B06C: free (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==3498== by 0x8048444: main (main.cpp:19)
==3498== Address 0x40c0500 is in the Text segment of /lib/i386-linux-gnu/libc-2.15.so


{problem description}   
at {address, function name, module or line of code} 
by {address, function name, line of code}
by ... {show call stack layer by layer}
Address 0x???????? {describe the relative relationship of addresses}


<ブロッククオート

このコードは malloc() を malloc[] と誤って記述しています。これは malloc 関数ポインタの後ろのアドレスを取得することと同じで、出力レポートでは .text セグメントにあることが示されています。

レポートの基本フォーマットが

1. copyright copyright notice
2. Exception Read/Write Report
2.1 Main thread abnormal read/write
2.2 Thread A Exception Read/Write Report
2.3 Thread B abnormal read/write report
2... Other threads
3. Heap Memory Leakage Report
3.1 Heap memory usage overview (HEAP SUMMARY)
3.2 Confirmed memory leak report (DEFINITELY LOST)
3.3 Suspicious memory operation report (show-reachable=no off)
3.4 Overview of leaks (LEAK SUMMARY)


一方、レポートの出力文書の全体的な書式は、次のようにまとめることができます。

int main(int argc, char *argv[])
{
    char* bigBuff = (char*)malloc(1024);
}
1,024 bytes in 1 blocks are definitely lost in loss record 1 of 1
 at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
 by 0x8048414: main (main.cpp:17)


よくある例外報告とは

メモリリーク

int main(int argc, char *argv[])
{
    char* bigBuff = (char*)malloc(1024);
    char* offsetBuff = bigBuff + 888;
    free(offsetBuff);
}
 Invalid free() / delete / delete[] / realloc()
  at 0x402B06C: free (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
  by 0x8048461: main (main.cpp:24)
 Address 0x41f23a0 is 888 bytes inside a block of size 1,024 alloc'd
  at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
  by 0x8048444: main (main.cpp:17)


int main(int argc, char *argv[])
{
    char* bigBuff = (char*)malloc(1024);
    uint64_t* bigNum = (uint64_t*)(bigBuff+1020);
    *bigNum = 0x12345678AABBCCDD;
    printf("bigNum is %llu\n",*bigNum);
    free(bigBuff);
}
Invalid write of size 4
 at 0x8048490: main (main.cpp:19)
Address 0x41f2428 is 0 bytes after a block of size 1,024 alloc'd
 at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
 by 0x8048474: main (main.cpp:17)

Invalid read of size 4
 at 0x804849B: main (main.cpp:20)
Address 0x41f2428 is 0 bytes after a block of size 1,024 alloc'd
 at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
 by 0x8048474: main (main.cpp:17)


<ブロッククオート

確実に失われる:メモリが解放されず、ポインタもない。間違いなく漏れています。レポートに記載されているスタックは、メモリが割り当てられたときのコールスタックで、メモリがどのビジネスロジックから作成されたかは基本的に明確になっています。
まだ到達可能:メモリは解放されていないが、それでもそれを指すポインタがあり、メモリがまだ使用されていることを意味し、これはリークと考えることができる。(プログラムが終了してもまだ動作している非同期システムコール?)
possibly lost: リークがあるかもしれないと言うことで、通常、二次ポインタ(ポインタへのポインタ)など、追跡が容易でない複雑なものがある場合です。
suppressed:valgrindの特定のパラメータを使用して特定のライブラリをキャンセルする特定のエラーをカウントし、ここに帰着させる。

例外解除

int main(int argc, char *argv[])
{
    int unused;
    char* bigBuff = (char*)malloc(1024);
    delete[] bigBuff;
    printf("unused=%d",unused);
}
Mismatched free() / delete / delete []
 at 0x402A8DC: operator delete[](void*) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
 by 0x80484FB: main (main.cpp:19)
Address 0x4323028 is 0 bytes inside a block of size 1,024 alloc'd
 at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
 by 0x80484E4: main (main.cpp:18)

Use of uninitialised value of size 4
 at 0x416E0DB: _itoa_word (_itoa.c:195)
 by 0x417221A: vfprintf (vfprintf.c:1629)
 by 0x4178B2E: printf (printf.c:35)
 by 0x41454D2: (below main) (libc-start.c:226)


class GlobalClass
{
public:
    GlobalClass()
    {
        char* buf = (char*)malloc(10);
        *(int*)(buf+8) = 100;
        free(buf);
    }
    ~GlobalClass()
    {
        char* buf = (char*)malloc(10);
        *(int*)(buf+8) = 100;
        free(buf);
    }
    void fake(){}
} g_globalClass;

int main(int argc, char *argv[])
{
    g_globalClass.fake();
}
Invalid write of size 4
 at 0x804857B: GlobalClass::GlobalClass() (main.cpp:21)
 by 0x804850F: __static_initialization_and_destruction_0(int, int) (main.cpp:31)
 by 0x8048551: _GLOBAL__sub_I_g_globalClass (main.cpp:55)
 by 0x8048631: __libc_csu_init (in /home/jinzeyu/codelocal/build-mcsample-Desktop_Qt_5_3_GCC_32bit-Debug/mcsample)
 by 0x4060469: (below main) (libc-start.c:185)
Address 0x41f2030 is 8 bytes inside a block of size 10 alloc'd
 at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
 by 0x8048571: GlobalClass::GlobalClass() (main.cpp:20)
 by 0x804850F: __static_initialization_and_destruction_0(int, int) (main.cpp:31)
 by 0x8048551: _GLOBAL__sub_I_g_globalClass (main.cpp:55)
 by 0x8048631: __libc_csu_init (in /home/jinzeyu/codelocal/build-mcsample-Desktop_Qt_5_3_GCC_32bit-Debug/mcsample)
 by 0x4060469: (below main) (libc-start.c:185)

Invalid write of size 4
 at 0x80485B9: GlobalClass::~GlobalClass() (main.cpp:27)
 by 0x4079B80: __run_exit_handlers (exit.c:78)
 by 0x4079C0C: exit (exit.c:100)
 by 0x40604DA: (below main) (libc-start.c:258)
Address 0x41f2070 is 8 bytes inside a block of size 10 alloc'd
 at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
 by 0x80485AF: GlobalClass::~GlobalClass() (main.cpp:26)
 by 0x4079B80: __run_exit_handlers (exit.c:78)
 by 0x4079C0C: exit (exit.c:100)
 by 0x40604DA: (below main) (libc-start.c:258)


<ブロッククオート

free() / delete / delete[] / realloc() 4つのうちどちらか、ここではfreeの不正な解放。アドレスの相対的な関係を記述する場合、以下のような形式の文章が使われます。 アドレス0x?????はサイズ{y}のブロックの{内部/前/後}に{x}バイト{alloc'd/free'd}している。

解放されたアドレスのy長ブロックに対する相対的な位置を示している。アドレスがブロックの前にある場合はbefore、ブロックの中にある場合はinside、後にある場合はafterとなる。また、最後のalloc'dはy長ブロックが有効な状態にあることを意味し、割り当て時のスタックは以下のようになる。free'dはy長ブロックが削除されたことを意味し、削除時のスタックは以下のようになる。

つまり、上記のレポートは、アドレス0x41f23a0が長さ1024の有効ブロック内の+888に位置し、その割り当て時のコールスタックが以下のようになることを意味していると解釈できます。

不正な読み取りと書き込み

--16198-- VALGRIND INTERNAL ERROR: Valgrind received a signal 11 (SIGSEGV) - exiting
--16198-- si_code=1; Faulting address: 0x74207972; sp: 0x6564ca5c
valgrind: the 'impossible' happened:
   Killed by fatal signal
==16198== at 0x380C0AD4: ???? (in /usr/lib/valgrind/memcheck-x86-linux)
==16198== by 0x380C12C5: ???? (in /usr/lib/valgrind/memcheck-x86-linux)
==16198== by 0x38040A63: ???? (in /usr/lib/valgrind/memcheck-x86-linux)
==16198== by 0x38040B36: ???? (in /usr/lib/valgrind/memcheck-x86-linux)
==16198== by 0x3803EA4B: ???? (in /usr/lib/valgrind/memcheck-x86-linux)
==16198== by 0x20202E78: ????

sched status:
  running_tid=3


Thread 1: status = VgTs_WaitSys
...

Thread 2: status = VgTs_WaitSys
...

Thread 3: status = VgTs_Runnable
==16198== at 0x402C9B4: operator new(unsigned int) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==16198== by 0x437D7D3: std::string::_Rep::_S_create(unsigned int, unsigned int, std::allocator<char> const&) (in /usr/lib/i386- linux-gnu/libstdc++.so.6.0.16)
==16198== by 0x437FBB5: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const *, std::allocator<char> const&) (in /usr/lib/i386-linux-gnu/libstdc++.so.6.0.16)
==16198== by 0x82A76A3: DataChecker::handle_data_check_resp_msg(void*) (data_checker.c:55)
==16198== by 0x8144411: main_thread(void*) (main_thread.c:198)
==16198== by 0x82839CF: thread_manager_start_routine(void*) (thread_manager.c:72)
==16198== by 0x42D3D4B: start_thread (pthread_create.c:308)
==16198== by 0x450BFDD: clone (clone.S:130)

Thread 4: status = VgTs_WaitSys
...



<ブロッククオート

無効な書き込み/読み取りは、メモリ領域の使用が割り当てられたサイズを超えた場合に発生し、その長さが通知されることがあります。この例では、uint64_tは8バイトであり、アクセスは4バイトを超えます。bigBuff+1020をbigBuff-20に変更すると、Address xxxが...のブロックの20バイト前であることが正確にレポートされるようになります。

もう一つ興味深いのは、uint64_tへの不正アクセスは、4バイト長の不正アクセスのレポートを2つ生成することがわかったのですが、これは何を物語っているのでしょうか?

ミスマッチ・リリース

int main(int argc, char *argv[])
{
    int unused;
    char* bigBuff = (char*)malloc(1024);
    delete[] bigBuff;
    printf("unused=%d",unused);
}

Mismatched free() / delete / delete []
 at 0x402A8DC: operator delete[](void*) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
 by 0x80484FB: main (main.cpp:19)
Address 0x4323028 is 0 bytes inside a block of size 1,024 alloc'd
 at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
 by 0x80484E4: main (main.cpp:18)

Use of uninitialised value of size 4
 at 0x416E0DB: _itoa_word (_itoa.c:195)
 by 0x417221A: vfprintf (vfprintf.c:1629)
 by 0x4178B2E: printf (printf.c:35)
 by 0x41454D2: (below main) (libc-start.c:226)


<ブロッククオート

mallocがアロケートしてdeleteやdelete[]で削除しても、new[]して不用意にdeleteで解放しても、Mismatched free() / delete / delete []というレポートが出て、レポートの本文は基本的に同じです。

未初期化の値を使用する

上記の例では、int unusedが代入されずに使用されているため、Use of uninitialised value of size 4というレポートが表示され、通常致命的ではありませんが、除外する必要があります。

スタックの最後のレベルには、最初の出現として (main以下) この現象を明確に説明することはまだできませんが、次のようなテストをしてみました: ...

静的な構築と解放

class GlobalClass
{
public:
    GlobalClass()
    {
        char* buf = (char*)malloc(10);
        *(int*)(buf+8) = 100;
        free(buf);
    }
    ~GlobalClass()
    {
        char* buf = (char*)malloc(10);
        *(int*)(buf+8) = 100;
        free(buf);
    }
    void fake(){}
} g_globalClass;

int main(int argc, char *argv[])
{
    g_globalClass.fake();
}

Invalid write of size 4
 at 0x804857B: GlobalClass::GlobalClass() (main.cpp:21)
 by 0x804850F: __static_initialization_and_destruction_0(int, int) (main.cpp:31)
 by 0x8048551: _GLOBAL__sub_I_g_globalClass (main.cpp:55)
 by 0x8048631: __libc_csu_init (in /home/jinzeyu/codelocal/build-mcsample-Desktop_Qt_5_3_GCC_32bit-Debug/mcsample)
 by 0x4060469: (below main) (libc-start.c:185)
Address 0x41f2030 is 8 bytes inside a block of size 10 alloc'd
 at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
 by 0x8048571: GlobalClass::GlobalClass() (main.cpp:20)
 by 0x804850F: __static_initialization_and_destruction_0(int, int) (main.cpp:31)
 by 0x8048551: _GLOBAL__sub_I_g_globalClass (main.cpp:55)
 by 0x8048631: __libc_csu_init (in /home/jinzeyu/codelocal/build-mcsample-Desktop_Qt_5_3_GCC_32bit-Debug/mcsample)
 by 0x4060469: (below main) (libc-start.c:185)

Invalid write of size 4
 at 0x80485B9: GlobalClass::~GlobalClass() (main.cpp:27)
 by 0x4079B80: __run_exit_handlers (exit.c:78)
 by 0x4079C0C: exit (exit.c:100)
 by 0x40604DA: (below main) (libc-start.c:258)
Address 0x41f2070 is 8 bytes inside a block of size 10 alloc'd
 at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
 by 0x80485AF: GlobalClass::~GlobalClass() (main.cpp:26)
 by 0x4079B80: __run_exit_handlers (exit.c:78)
 by 0x4079C0C: exit (exit.c:100)
 by 0x40604DA: (below main) (libc-start.c:258)


<ブロッククオート

静的クラスはmainの外部で構築・解放されているので、両方とも(main以下)と表示され、スタック上の関数名で2つの処理をうまく確認することができます。 私が連想するもうひとつの問題は、静的なコンストラクトは必ずしも期待された順番に並んでいないことで、静的なオブジェクトの間には依存関係がないことが強く推奨されています。

<強い クラッシュ

memcheckがプログラムを実行している間にクラッシュが発生した場合でも、有用な情報を提供することができます。

--16198-- VALGRIND INTERNAL ERROR: Valgrind received a signal 11 (SIGSEGV) - exiting
--16198-- si_code=1; Faulting address: 0x74207972; sp: 0x6564ca5c
valgrind: the 'impossible' happened:
   Killed by fatal signal
==16198== at 0x380C0AD4: ???? (in /usr/lib/valgrind/memcheck-x86-linux)
==16198== by 0x380C12C5: ???? (in /usr/lib/valgrind/memcheck-x86-linux)
==16198== by 0x38040A63: ???? (in /usr/lib/valgrind/memcheck-x86-linux)
==16198== by 0x38040B36: ???? (in /usr/lib/valgrind/memcheck-x86-linux)
==16198== by 0x3803EA4B: ???? (in /usr/lib/valgrind/memcheck-x86-linux)
==16198== by 0x20202E78: ????

sched status:
  running_tid=3


このレポートでは、クラッシュ時のスタックと各スレッドの実行状態を順番にリストアップします。

Thread 1: status = VgTs_WaitSys
...

Thread 2: status = VgTs_WaitSys
...

Thread 3: status = VgTs_Runnable
==16198== at 0x402C9B4: operator new(unsigned int) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==16198== by 0x437D7D3: std::string::_Rep::_S_create(unsigned int, unsigned int, std::allocator<char> const&) (in /usr/lib/i386- linux-gnu/libstdc++.so.6.0.16)
==16198== by 0x437FBB5: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const *, std::allocator<char> const&) (in /usr/lib/i386-linux-gnu/libstdc++.so.6.0.16)
==16198== by 0x82A76A3: DataChecker::handle_data_check_resp_msg(void*) (data_checker.c:55)
==16198== by 0x8144411: main_thread(void*) (main_thread.c:198)
==16198== by 0x82839CF: thread_manager_start_routine(void*) (thread_manager.c:72)
==16198== by 0x42D3D4B: start_thread (pthread_create.c:308)
==16198== by 0x450BFDD: clone (clone.S:130)

Thread 4: status = VgTs_WaitSys
...



ですから、当然、実行中のスレッドが最も疑わしいわけで、そのスタック情報を取り出してさらに分析することができます。