hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

Valgrind(ヴァルグリンド)の使い方



1 Valgrindとは?

メモリリーク等のバグを検出するツールです。

2 検証環境

CentOS版数は以下のとおりです。仮想マシンは最小構成で作成しました。

[root@server ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
3.10.0-1160.el7.x86_64

3 インストール方法

valgrindパッケージをインストールします。

[root@server ~]# yum -y install valgrind

valgrindの版数を確認します。

[root@server ~]# valgrind --version
valgrind-3.15.0

4 オプション一覧

オプションは以下のとおりです

[root@server ~]# valgrind --help
usage: valgrind [options] prog-and-args

  tool-selection option, with default in [ ]:
    --tool=<name>             use the Valgrind tool named <name> [memcheck]

  basic user options for all Valgrind tools, with defaults in [ ]:
    -h --help                 show this message
    --help-debug              show this message, plus debugging options
    --version                 show version
    -q --quiet                run silently; only print error msgs
    -v --verbose              be more verbose -- show misc extra info
    --trace-children=no|yes   Valgrind-ise child processes (follow execve)? [no]
    --trace-children-skip=patt1,patt2,...    specifies a list of executables
                              that --trace-children=yes should not trace into
    --trace-children-skip-by-arg=patt1,patt2,...   same as --trace-children-skip=
                              but check the argv[] entries for children, rather
                              than the exe name, to make a follow/no-follow decision
    --child-silent-after-fork=no|yes omit child output between fork & exec? [no]
    --vgdb=no|yes|full        activate gdbserver? [yes]
                              full is slower but provides precise watchpoint/step
    --vgdb-error=<number>     invoke gdbserver after <number> errors [999999999]
                              to get started quickly, use --vgdb-error=0
                              and follow the on-screen directions
    --vgdb-stop-at=event1,event2,... invoke gdbserver for given events [none]
         where event is one of:
           startup exit valgrindabexit all none
    --track-fds=no|yes        track open file descriptors? [no]
    --time-stamp=no|yes       add timestamps to log messages? [no]
    --log-fd=<number>         log messages to file descriptor [2=stderr]
    --log-file=<file>         log messages to <file>
    --log-socket=ipaddr:port  log messages to socket ipaddr:port

  user options for Valgrind tools that report errors:
    --xml=yes                 emit error output in XML (some tools only)
    --xml-fd=<number>         XML output to file descriptor
    --xml-file=<file>         XML output to <file>
    --xml-socket=ipaddr:port  XML output to socket ipaddr:port
    --xml-user-comment=STR    copy STR verbatim into XML output
    --demangle=no|yes         automatically demangle C++ names? [yes]
    --num-callers=<number>    show <number> callers in stack traces [12]
    --error-limit=no|yes      stop showing new errors if too many? [yes]
    --exit-on-first-error=no|yes exit code on the first error found? [no]
    --error-exitcode=<number> exit code to return if errors found [0=disable]
    --error-markers=<begin>,<end> add lines with begin/end markers before/after
                              each error output in plain text mode [none]
    --show-error-list=no|yes  show detected errors list and
                              suppression counts at exit [no]
    -s                        same as --show-error-list=yes
    --keep-debuginfo=no|yes   Keep symbols etc for unloaded code [no]
                              This allows saved stack traces (e.g. memory leaks)
                              to include file/line info for code that has been
                              dlclose'd (or similar)
    --show-below-main=no|yes  continue stack traces below main() [no]
    --default-suppressions=yes|no
                              load default suppressions [yes]
    --suppressions=<filename> suppress errors described in <filename>
    --gen-suppressions=no|yes|all    print suppressions for errors? [no]
    --input-fd=<number>       file descriptor for input [0=stdin]
    --dsymutil=no|yes         run dsymutil on Mac OS X when helpful? [yes]
    --max-stackframe=<number> assume stack switch for SP changes larger
                              than <number> bytes [2000000]
    --main-stacksize=<number> set size of main thread's stack (in bytes)
                              [min(max(current 'ulimit' value,1MB),16MB)]

  user options for Valgrind tools that replace malloc:
    --alignment=<number>      set minimum alignment of heap allocations [16]
    --redzone-size=<number>   set minimum size of redzones added before/after
                              heap blocks (in bytes). [16]
    --xtree-memory=none|allocs|full   profile heap memory in an xtree [none]
                              and produces a report at the end of the execution
                              none: no profiling, allocs: current allocated
                              size/blocks, full: profile current and cumulative
                              allocated size/blocks and freed size/blocks.
    --xtree-memory-file=<file>   xtree memory report file [xtmemory.kcg.%p]

  uncommon user options for all Valgrind tools:
    --fullpath-after=         (with nothing after the '=')
                              show full source paths in call stacks
    --fullpath-after=string   like --fullpath-after=, but only show the
                              part of the path after 'string'.  Allows removal
                              of path prefixes.  Use this flag multiple times
                              to specify a set of prefixes to remove.
    --extra-debuginfo-path=path    absolute path to search for additional
                              debug symbols, in addition to existing default
                              well known search paths.
    --debuginfo-server=ipaddr:port    also query this server
                              (valgrind-di-server) for debug symbols
    --allow-mismatched-debuginfo=no|yes  [no]
                              for the above two flags only, accept debuginfo
                              objects that don't "match" the main object
    --smc-check=none|stack|all|all-non-file [all-non-file]
                              checks for self-modifying code: none, only for
                              code found in stacks, for all code, or for all
                              code except that from file-backed mappings
    --read-inline-info=yes|no read debug info about inlined function calls
                              and use it to do better stack traces.
                              [yes] on Linux/Android/Solaris for the tools
                              Memcheck/Massif/Helgrind/DRD only.
                              [no] for all other tools and platforms.
    --read-var-info=yes|no    read debug info on stack and global variables
                              and use it to print better error messages in
                              tools that make use of it (Memcheck, Helgrind,
                              DRD) [no]
    --vgdb-poll=<number>      gdbserver poll max every <number> basic blocks [5000]
    --vgdb-shadow-registers=no|yes   let gdb see the shadow registers [no]
    --vgdb-prefix=<prefix>    prefix for vgdb FIFOs [/tmp/vgdb-pipe]
    --run-libc-freeres=no|yes free up glibc memory at exit on Linux? [yes]
    --run-cxx-freeres=no|yes  free up libstdc++ memory at exit on Linux
                              and Solaris? [yes]
    --sim-hints=hint1,hint2,...  activate unusual sim behaviours [none]
         where hint is one of:
           lax-ioctls lax-doors fuse-compatible enable-outer
           no-inner-prefix no-nptl-pthread-stackcache fallback-llsc none
    --fair-sched=no|yes|try   schedule threads fairly on multicore systems [no]
    --kernel-variant=variant1,variant2,...
         handle non-standard kernel variants [none]
         where variant is one of:
           bproc android-no-hw-tls
           android-gpu-sgx5xx android-gpu-adreno3xx none
    --merge-recursive-frames=<number>  merge frames between identical
           program counters in max <number> frames) [0]
    --num-transtab-sectors=<number> size of translated code cache [32]
           more sectors may increase performance, but use more memory.
    --avg-transtab-entry-size=<number> avg size in bytes of a translated
           basic block [0, meaning use tool provided default]
    --aspace-minaddr=0xPP     avoid mapping memory below 0xPP [guessed]
    --valgrind-stacksize=<number> size of valgrind (host) thread's stack
                               (in bytes) [1048576]
    --show-emwarns=no|yes     show warnings about emulation limits? [no]
    --require-text-symbol=:sonamepattern:symbolpattern    abort run if the
                              stated shared object doesn't have the stated
                              text symbol.  Patterns can contain ? and *.
    --soname-synonyms=syn1=pattern1,syn2=pattern2,... synonym soname
              specify patterns for function wrapping or replacement.
              To use a non-libc malloc library that is
                  in the main exe:  --soname-synonyms=somalloc=NONE
                  in libxyzzy.so:   --soname-synonyms=somalloc=libxyzzy.so
    --sigill-diagnostics=yes|no  warn about illegal instructions? [yes]
    --unw-stack-scan-thresh=<number>   Enable stack-scan unwind if fewer
                  than <number> good frames found  [0, meaning "disabled"]
                  NOTE: stack scanning is only available on arm-linux.
    --unw-stack-scan-frames=<number>   Max number of frames that can be
                  recovered by stack scanning [5]
    --resync-filter=no|yes|verbose [yes on MacOS, no on other OSes]
              attempt to avoid expensive address-space-resync operations
    --max-threads=<number>    maximum number of threads that valgrind can
                              handle [500]

  user options for Memcheck:
    --leak-check=no|summary|full     search for memory leaks at exit?  [summary]
    --leak-resolution=low|med|high   differentiation of leak stack traces [high]
    --show-leak-kinds=kind1,kind2,.. which leak kinds to show?
                                            [definite,possible]
    --errors-for-leak-kinds=kind1,kind2,..  which leak kinds are errors?
                                            [definite,possible]
        where kind is one of:
          definite indirect possible reachable all none
    --leak-check-heuristics=heur1,heur2,... which heuristics to use for
        improving leak search false positive [all]
        where heur is one of:
          stdstring length64 newarray multipleinheritance all none
    --show-reachable=yes             same as --show-leak-kinds=all
    --show-reachable=no --show-possibly-lost=yes
                                     same as --show-leak-kinds=definite,possible
    --show-reachable=no --show-possibly-lost=no
                                     same as --show-leak-kinds=definite
    --xtree-leak=no|yes              output leak result in xtree format? [no]
    --xtree-leak-file=<file>         xtree leak report file [xtleak.kcg.%p]
    --undef-value-errors=no|yes      check for undefined value errors [yes]
    --track-origins=no|yes           show origins of undefined values? [no]
    --partial-loads-ok=no|yes        too hard to explain here; see manual [yes]
    --expensive-definedness-checks=no|auto|yes
                                     Use extra-precise definedness tracking [auto]
    --freelist-vol=<number>          volume of freed blocks queue     [20000000]
    --freelist-big-blocks=<number>   releases first blocks with size>= [1000000]
    --workaround-gcc296-bugs=no|yes  self explanatory [no].  Deprecated.
                                     Use --ignore-range-below-sp instead.
    --ignore-ranges=0xPP-0xQQ[,0xRR-0xSS]   assume given addresses are OK
    --ignore-range-below-sp=<number>-<number>  do not report errors for
                                     accesses at the given offsets below SP
    --malloc-fill=<hexnumber>        fill malloc'd areas with given value
    --free-fill=<hexnumber>          fill free'd areas with given value
    --keep-stacktraces=alloc|free|alloc-and-free|alloc-then-free|none
        stack trace(s) to keep for malloc'd/free'd areas       [alloc-and-free]
    --show-mismatched-frees=no|yes   show frees that don't match the allocator? [yes]

  Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, ./.valgrindrc

  Memcheck is Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
  Valgrind is Copyright (C) 2000-2017, and GNU GPL'd, by Julian Seward et al.
  LibVEX is Copyright (C) 2004-2017, and GNU GPL'd, by OpenWorks LLP et al.

  Bug reports, feedback, admiration, abuse, etc, to: www.valgrind.org.
オプション 意味
--show-reachable yesを指定すると、無害かもしれないメモリリークも出力します
--trace-children yesを指定すると、解析中のプログラムがforkっで生成したプロセスも解析対象にします
--track-fds yesを指定すると、ファイルディスクリプタの閉じ忘れを報告します
--error-limit noを指定すると、エラー数が閾値を超えた場合でも、Valgrindが解析を継続します
--num-callers エラー箇所までのバックトレースを何段まで表示するかを指定します

5 メモリリークを検出する方法

5.1 テストプログラムの作成

メモリリークが発生するテストプログラムを作成します。2秒毎にメモリ領域(10byte)を獲得しますが、解放はしません。

[root@server ~]# cat test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int func()
{
  char *p;
  while(1) {
    p = malloc(10);
    sleep(2);
  }
  return 0;
}

int main(int argc, char *argv[])
{
  pid_t pid;
  int status;

  pid = fork();
  if(pid == 0){
    func();
    exit(EXIT_SUCCESS);
  }
  else if(pid > 0) {
    wait(&status);
    exit(EXIT_SUCCESS);
  }
  return 0;
}

テストプログラムコンパイルします。コンパイルすると警告が出力されますが、宣言した変数を使用していないだけなので、無視してください。

[root@server ~]# gcc -Wall -g -o test test.c

作成したテストプログラムを確認します。

[root@server ~]# ls -l test*
-rwxr-xr-x. 1 root root 10040  2月 17 08:35 test
-rw-r--r--. 1 root root   448  2月 17 08:32 test.c

5.2 実行結果の確認

テストプログラムを引数に指定してvalgrindを実行します。

[root@server ~]# valgrind --leak-check=full --leak-resolution=high --show-reachable=yes ./test
==1771== Memcheck, a memory error detector
==1771== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1771== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==1771== Command: ./test
==1771==

適当な時間(sleep関数で指定した2秒以上)が経過したら、Ctrl+Cを押下してvalgrindを終了します。メモリ解放漏れが発生している旨(*)が出力されています。
(*) definitely lost: 40 bytes in 4 blocks

^C==1772==
==1772== Process terminating with default action of signal 2 (SIGINT)
==1771==
==1771== Process terminating with default action of signal 2 (SIGINT)
==1771==    at 0x4EFC3E2: wait (in /usr/lib64/libc-2.17.so)
==1772==    at 0x4EFC840: __nanosleep_nocancel (in /usr/lib64/libc-2.17.so)
==1771==    by 0x4006B1: main (test.c:29)
==1772==    by 0x4EFC6F3: sleep (in /usr/lib64/libc-2.17.so)
==1772==    by 0x40066C: func (test.c:13)
==1772==    by 0x400695: main (test.c:25)
==1772==
==1772== HEAP SUMMARY:
==1772==     in use at exit: 50 bytes in 5 blocks
==1772==   total heap usage: 5 allocs, 0 frees, 50 bytes allocated
==1772==
==1771==
==1772== 10 bytes in 1 blocks are still reachable in loss record 1 of 2
==1771== HEAP SUMMARY:
==1771==     in use at exit: 0 bytes in 0 blocks
==1771==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==1771==
==1772==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==1771== All heap blocks were freed -- no leaks are possible
==1772==    by 0x40065E: func (test.c:12)
==1771==
==1772==    by 0x400695: main (test.c:25)
==1771== For lists of detected and suppressed errors, rerun with: -s
==1772==
==1771== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==1772== 40 bytes in 4 blocks are definitely lost in loss record 2 of 2
==1772==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==1772==    by 0x40065E: func (test.c:12)
==1772==    by 0x400695: main (test.c:25)
==1772==
==1772== LEAK SUMMARY:
==1772==    definitely lost: 40 bytes in 4 blocks
==1772==    indirectly lost: 0 bytes in 0 blocks
==1772==      possibly lost: 0 bytes in 0 blocks
==1772==    still reachable: 10 bytes in 1 blocks
==1772==         suppressed: 0 bytes in 0 blocks
==1772==
==1772== For lists of detected and suppressed errors, rerun with: -s
==1772== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

6 解放済みメモリー領域へのポインタを誤って使ってしまう問題の検出方法

6.1 テストプログラムの作成

解放したメモリ領域に書き込みをすると、メモリ破壊を引き起こします。このような問題はuse after free問題と言われています。以下のテストプログラムでは、解放した領域の先頭1バイトに1を書き込んでいます。

[root@server ~]# cat test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int func()
{
  char *p;

  while(1) {
    p = malloc(10);
    free(p);
    p[0] = 1;  //解放した領域に書き込みを行う
    sleep(2);
  }
  return 0;
}

int main(int argc, char *argv[])
{
  pid_t pid;
  int status;

  pid = fork();
  if(pid == 0){
    func();
    exit(EXIT_SUCCESS);
  }
  else if(pid > 0) {
    wait(&status);
    exit(EXIT_SUCCESS);
  }
  return 0;
}

6.2 実行結果の確認

テストプログラムを引数に指定してvalgrindを実行します。valgrindを実行すると、不正な書き込みをしたことを意味する"Invalid write of size 1が表示されます。

[root@server ~]# valgrind --leak-check=full --leak-resolution=high --show-reachable=yes ./test
==1796== Memcheck, a memory error detector
==1796== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==1796== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==1796== Command: ./test
==1796==
==1797== Invalid write of size 1
==1797==    at 0x4006C3: func (test.c:14)
==1797==    by 0x4006F8: main (test.c:27)
==1797==  Address 0x5205040 is 0 bytes inside a block of size 10 free'd
==1797==    at 0x4C2B06D: free (vg_replace_malloc.c:540)
==1797==    by 0x4006BE: func (test.c:13)
==1797==    by 0x4006F8: main (test.c:27)
==1797==  Block was alloc'd at
==1797==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==1797==    by 0x4006AE: func (test.c:12)
==1797==    by 0x4006F8: main (test.c:27)
==1797==

適当な時間(最低2秒以上)が経過したら、Ctrl+Cを押下してvalgrindを終了します。今回は、メモリの解放漏れがないので、"All heap blocks were freed -- no leaks are possible"が表示されています。

^C==1797==
==1797== Process terminating with default action of signal 2 (SIGINT)
==1796==
==1796== Process terminating with default action of signal 2 (SIGINT)
==1797==    at 0x4EFC840: __nanosleep_nocancel (in /usr/lib64/libc-2.17.so)
==1797==    by 0x4EFC6F3: sleep (in /usr/lib64/libc-2.17.so)
==1797==    by 0x4006CF: func (test.c:15)
==1797==    by 0x4006F8: main (test.c:27)
==1796==    at 0x4EFC3E2: wait (in /usr/lib64/libc-2.17.so)
==1796==    by 0x400714: main (test.c:31)
==1796==
==1796== HEAP SUMMARY:
==1796==     in use at exit: 0 bytes in 0 blocks
==1796==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==1796==
==1796== All heap blocks were freed -- no leaks are possible
==1796==
==1796== For lists of detected and suppressed errors, rerun with: -s
==1796== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==1797==
==1797== HEAP SUMMARY:
==1797==     in use at exit: 0 bytes in 0 blocks
==1797==   total heap usage: 5 allocs, 5 frees, 50 bytes allocated
==1797==
==1797== All heap blocks were freed -- no leaks are possible
==1797==
==1797== For lists of detected and suppressed errors, rerun with: -s
==1797== ERROR SUMMARY: 5 errors from 1 contexts (suppressed: 0 from 0)

7 確保したメモリ領域外への不正アクセスの検出方法

7.1 テストプログラムの作成

確保したメモリ領域外への書き込みをすると、隣接するメモリ領域を破壊してしまいます。ここでは、このような問題の検出方法を説明します。

[root@server ~]# cat test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>

int func()
{
  char *p;

  while(1) {
    p = malloc(10);
    p[10] = 1;  //確保した領域外への書き込みを行う。
    free(p);
    sleep(2);

  }
  return 0;
}

int main(int argc, char *argv[])
{
  pid_t pid;
  int status;

  pid = fork();
  if(pid == 0){
    func();
    exit(EXIT_SUCCESS);
  }
  else if(pid > 0) {
    wait(&status);
    exit(EXIT_SUCCESS);
  }
  return 0;
}

7.2 実行結果の確認

テストプログラムを引数に指定してvalgrindを実行します。valgrindを実行すると、不正な書き込みをしたことを意味する"Invalid write of size 1が表示されます。

[root@server ~]# valgrind --leak-check=full --leak-resolution=high --show-reachable=yes ./test
==2099== Memcheck, a memory error detector
==2099== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2099== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==2099== Command: ./test
==2099==
==2100== Invalid write of size 1
==2100==    at 0x4006BB: func (test.c:13)
==2100==    by 0x4006FC: main (test.c:28)
==2100==  Address 0x520504a is 0 bytes after a block of size 10 alloc'd
==2100==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==2100==    by 0x4006AE: func (test.c:12)
==2100==    by 0x4006FC: main (test.c:28)
==2100==
||

適当な時間(最低2秒以上)が経過したら、Ctrl+Cを押下してvalgrindを終了します。メモリの解放漏れがないので、"All heap blocks were freed -- no leaks are possible"が表示されています。
>||
^C==2099==
==2099== Process terminating with default action of signal 2 (SIGINT)
==2099==    at 0x4EFC3E2: wait (in /usr/lib64/libc-2.17.so)
==2099==    by 0x400718: main (test.c:32)
==2100==
==2100== Process terminating with default action of signal 2 (SIGINT)
==2100==    at 0x4EFC840: __nanosleep_nocancel (in /usr/lib64/libc-2.17.so)
==2100==    by 0x4EFC6F3: sleep (in /usr/lib64/libc-2.17.so)
==2100==    by 0x4006D3: func (test.c:15)
==2100==    by 0x4006FC: main (test.c:28)
==2100==
==2100== HEAP SUMMARY:
==2100==     in use at exit: 0 bytes in 0 blocks
==2100==   total heap usage: 9 allocs, 9 frees, 90 bytes allocated
==2100==
==2100== All heap blocks were freed -- no leaks are possible
==2100==
==2100== For lists of detected and suppressed errors, rerun with: -s
==2100== ERROR SUMMARY: 9 errors from 1 contexts (suppressed: 0 from 0)
==2099==
==2099== HEAP SUMMARY:
==2099==     in use at exit: 0 bytes in 0 blocks
==2099==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==2099==
==2099== All heap blocks were freed -- no leaks are possible
==2099==
==2099== For lists of detected and suppressed errors, rerun with: -s
==2099== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Z 参考図書

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ