第17章 トレースツール

目次

17.1. strace を利用したシステムコールのトレース
17.2. ltrace を利用したライブラリ呼び出しのトレース
17.3. Valgrind を利用したデバッグとプロファイル
17.4. さらなる情報

openSUSE では、お使いのシステムに関する便利な情報を採取するための 様々なツールが提供されています。採取可能な情報としては、たとえばお使いの プログラムにおけるデバッグや問題発見のための情報や、性能を落としている 原因を発見するための情報、もしくは実行中のプロセスに対して使用している システム資源を追跡するための情報などがあります。これらのツールは主に インストールメディア内に含まれていますが、 SUSE ソフトウエア開発キット からインストールするもの もあります。

[Note]トレースと性能への影響

プロセスに対してシステムやライブラリ呼び出しのトレース (追跡) を行なった 場合、そのプロセスの性能は大きく落ちます。そのため、トレース関連のツールは データの収集を行なうときにだけご利用ください。

17.1. strace を利用したシステムコールのトレース

strace コマンドは、プロセスが利用するシステムコールや 受信するシグナルについて、それらを追跡するためのプログラムです。 strace はコマンドを新たに起動してシステムコールを追跡 させることができるほか、既に動作しているコマンドに対して strace を設定することもできます。このコマンドの出力には、 システムコールの名称のほか、そこで指定したパラメータ (括弧内) や戻り値など が含まれています。

新しいコマンドを起動してシステムコールの追跡を行なうには、コマンドラインの 冒頭に strace と入力してから、あとは通常通りのコマンド ラインを入力します:

tux@mercury:~> strace ls
execve("/bin/ls", ["ls"], [/* 52 vars */]) = 0
brk(0)                                  = 0x618000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) \
	= 0x7f9848667000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) \
	= 0x7f9848666000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT \
(No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=200411, ...}) = 0
mmap(NULL, 200411, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9848635000
close(3)                                = 0
open("/lib64/librt.so.1", O_RDONLY)     = 3
[...]
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) \
= 0x7fd780f79000
write(1, "Desktop\nDocuments\nbin\ninst-sys\n", 31Desktop
Documents
bin
inst-sys
) = 31
close(1)                                = 0
munmap(0x7fd780f79000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?

既に起動済みのプロセスに対して strace を設定するには、 -p オプションを利用して監視したいプロセスのプロセス ID (PID) を指定する必要があります:

tux@mercury:~> strace -p `pidof mysqld`
Process 2868 attached - interrupt to quit
select(15, [13 14], NULL, NULL, NULL)   = 1 (in [14])
fcntl(14, F_SETFL, O_RDWR|O_NONBLOCK)   = 0
accept(14, {sa_family=AF_FILE, NULL}, [2]) = 31
fcntl(14, F_SETFL, O_RDWR)              = 0
getsockname(31, {sa_family=AF_FILE, path="/var/run/mysql"}, [28]) = 0
fcntl(31, F_SETFL, O_RDONLY)            = 0
fcntl(31, F_GETFL)                      = 0x2 (flags O_RDWR)
fcntl(31, F_SETFL, O_RDWR|O_NONBLOCK)   = 0
[...]
setsockopt(31, SOL_IP, IP_TOS, [8], 4)  = -1 EOPNOTSUPP (Operation \
not supported)
clone(child_stack=0x7fd1864801f0, flags=CLONE_VM|CLONE_FS|CLONE_ \
FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_ \
PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7fd1864809e0, \
tls=0x7fd186480910, child_tidptr=0x7fd1864809e0) = 21993
select(15, [13 14], NULL, NULL, NULL

-e オプションでは複数のサブオプションやパラメータを 指定することができます。たとえばファイルを開く処理や書き込む処理だけ を追跡するには、下記のように実行します:

tux@mercury:~> strace -e trace=open,write ls ~
open("/etc/ld.so.cache", O_RDONLY)       = 3
open("/lib64/librt.so.1", O_RDONLY)      = 3
open("/lib64/libselinux.so.1", O_RDONLY) = 3
open("/lib64/libacl.so.1", O_RDONLY)     = 3
open("/lib64/libc.so.6", O_RDONLY)       = 3
open("/lib64/libpthread.so.0", O_RDONLY) = 3
[...]
open("/usr/lib/locale/cs_CZ.utf8/LC_CTYPE", O_RDONLY) = 3
open(".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
write(1, "addressbook.db.bak\nbin\ncxoffice\n"..., 311) = 311

ネットワーク関連のシステムコールだけを追跡するには、 -e trace=network と指定します:

tux@mercury:~> strace -e trace=network -p 26520
Process 26520 attached - interrupt to quit
socket(PF_NETLINK, SOCK_RAW, 0)         = 50
bind(50, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 0
getsockname(50, {sa_family=AF_NETLINK, pid=26520, groups=00000000}, \
[12]) = 0
sendto(50, "\24\0\0\0\26\0\1\3~p\315K\0\0\0\0\0\0\0\0", 20, 0,
{sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 20
[...]

-c オプションを指定すると、それぞれのシステムコールに 費やした時間が計算されるようになります:

tux@mercury:~> strace -c find /etc -name xorg.conf
/etc/X11/xorg.conf
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 32.38    0.000181         181         1           execve
 22.00    0.000123           0       576           getdents64
 19.50    0.000109           0       917        31 open
 19.14    0.000107           0       888           close
  4.11    0.000023           2        10           mprotect
  0.00    0.000000           0         1           write
[...]
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         3         1 futex
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0         4           fadvise64
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.000559                  3633        33 total

特定のプロセスに対して、それらの子プロセスもあわせて監視するように するには、 -f を指定します:

tux@mercury:~> strace -f rcapache2 status
execve("/usr/sbin/rcapache2", ["rcapache2", "status"], [/* 81 vars */]) = 0
brk(0)                                  = 0x69e000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) \
= 0x7f3bb553b000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) \
= 0x7f3bb553a000
[...]
[pid  4823] rt_sigprocmask(SIG_SETMASK, [],  <unfinished ...>
[pid  4822] close(4 <unfinished ...>
[pid  4823] <... rt_sigprocmask resumed> NULL, 8) = 0
[pid  4822] <... close resumed> )       = 0
[...]
[pid  4825] mprotect(0x7fc42cbbd000, 16384, PROT_READ) = 0
[pid  4825] mprotect(0x60a000, 4096, PROT_READ) = 0
[pid  4825] mprotect(0x7fc42cde4000, 4096, PROT_READ) = 0
[pid  4825] munmap(0x7fc42cda2000, 261953) = 0
[...]
[pid  4830] munmap(0x7fb1fff10000, 261953) = 0
[pid  4830] rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
[pid  4830] open("/dev/tty", O_RDWR|O_NONBLOCK) = 3
[pid  4830] close(3)
[...]
read(255, "\n\n# Inform the caller not only v"..., 8192) = 73
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
exit_group(0)

strace の出力を分析する必要がある場合や、出力される メッセージが長すぎるため、コンソールで直接確認するのが困難である場合は、 -o オプションをお使いください。この場合は、プロセスへの 接続や接続解除のメッセージなど、不要な情報は省略されます。これらの メッセージ (通常は標準出力に出力されます) を省略するには、 -q を指定します。また、各行に対してタイムスタンプを 表示したい場合は、 -t を使用します:

tux@mercury:~> strace -t -o strace_sleep.txt sleep 1; more strace_sleep.txt
08:44:06 execve("/bin/sleep", ["sleep", "1"], [/* 81 vars */]) = 0
08:44:06 brk(0)                         = 0x606000
08:44:06 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, \
-1, 0) = 0x7f8e78cc5000
[...]
08:44:06 close(3)                       = 0
08:44:06 nanosleep({1, 0}, NULL)        = 0
08:44:07 close(1)                       = 0
08:44:07 close(2)                       = 0
08:44:07 exit_group(0)                  = ?

strace の動作と出力形式は幅広い範囲でカスタマイズが可能です。詳しくは 関連するマニュアルページ (man 1 strace) をお読みください。

17.2. ltrace を利用したライブラリ呼び出しのトレース

ltrace はプロセスに対する動的なライブラリ呼び出しを 追跡します。 strace に似た方法を使用するもので、 パラメータの多くも似通っていたり、全く同じ意味であったりします。既定では ltrace/etc/ltrace.conf または ~/.ltrace.conf にある設定ファイルを使用します。 -F 設定ファイル を使用する ことで、独自の設定ファイルを指定することもできます。

ライブラリの呼び出しに加えて、 ltrace では -S オプションでシステムコールを同時に追跡することもできます:

tux@mercury:~> ltrace -S -o ltrace_find.txt find /etc -name \
xorg.conf; more ltrace_find.txt
SYS_brk(NULL)                                              = 0x00628000
SYS_mmap(0, 4096, 3, 34, 0xffffffff)                       = 0x7f1327ea1000
SYS_mmap(0, 4096, 3, 34, 0xffffffff)                       = 0x7f1327ea0000
[...]
fnmatch("xorg.conf", "xorg.conf", 0)                       = 0
free(0x0062db80)                                           = <void>
__errno_location()                                         = 0x7f1327e5d698
__ctype_get_mb_cur_max(0x7fff25227af0, 8192, 0x62e020, -1, 0) = 6
__ctype_get_mb_cur_max(0x7fff25227af0, 18, 0x7f1327e5d6f0, 0x7fff25227af0,
0x62e031) = 6
__fprintf_chk(0x7f1327821780, 1, 0x420cf7, 0x7fff25227af0, 0x62e031
<unfinished ...>
SYS_fstat(1, 0x7fff25227230)                               = 0
SYS_mmap(0, 4096, 3, 34, 0xffffffff)                       = 0x7f1327e72000
SYS_write(1, "/etc/X11/xorg.conf\n", 19)                   = 19
[...]

-e オプションを利用することで、追跡するイベントの種類を 変更することができます。下記の例では、 fnmatchstrlen の関数に関連したライブラリ呼び出しについて 表示を行なっています:

tux@mercury:~> ltrace -e fnmatch,strlen find /etc -name xorg.conf
[...]
fnmatch("xorg.conf", "xorg.conf", 0)             = 0
strlen("Xresources")                             = 10
strlen("Xresources")                             = 10
strlen("Xresources")                             = 10
fnmatch("xorg.conf", "Xresources", 0)            = 1
strlen("xorg.conf.install")                      = 17
[...]

特定のライブラリに含まれるシンボルだけを表示するには、 -l ライブラリへのパス を 使用します:

tux@mercury:~> ltrace -l /lib64/librt.so.1 sleep 1
clock_gettime(1, 0x7fff4b5c34d0, 0, 0, 0)                  = 0
clock_gettime(1, 0x7fff4b5c34c0, 0xffffffffff600180, -1, 0) = 0
+++ exited (status 0) +++

-n 空白数 を指定することで、 それぞれ再帰的に呼び出されているライブラリ呼び出しについて、それらを インデント表示させることができます。これによって、出力を読みやすく することができます。

17.3. Valgrind を利用したデバッグとプロファイル

Valgrind はお買いのプログラムに対してデバッグやプロファイル機能を提供 するツール集で、高速にエラーもなく動作できる仕組みです。 Valgrind は メモリ管理やスレッド処理に関する問題を検出することができるほか、新しい デバッグツールを構築する際のフレームワークとしても利用することができます。

17.3.1. インストール

Valgrind は標準の openSUSE 配布物には含まれていません。お使いの システムにインストールするには SUSE ソフトウエア開発キット を取得し、アドオン製品として追加した あと、

zypper install valgrind を実行します。

それ以外にも、 SUSE ソフトウエア開発キット のディレクトリツリーを参照して Valgrind パッケージの 場所を見つけ、

rpm -i valgrind-バージョン/アーキテクチャ.rpm のように実行してもかまいません。

17.3.2. サポート対象のアーキテクチャ

Valgrind は下記のアーキテクチャで動作します:

  • i386

  • x86_64 (AMD-64)

  • ppc

  • ppc64

  • System z

17.3.3. 一般的な情報

Valgrind を使用する主な目的は、既存のコンパイル済み実行ファイルを利用 して調査するという点にあります。 Valgrind を使用するにあたってコンパイル し直したり、プログラムを修正したりする必要はありません。 Valgrind は 下記のようにして実行します:

valgrind Valgrind のオプション プログラム プログラムのオプション

Valgrind は複数のツールから構成されていて、それぞれのツールは固有の機能が 用意されています。また、この章における情報は、使用するツールに関係なく一般的に 利用可能なものだけを紹介しています。その中でも最も重要な設定オプションは --tool で、このオプションは Valgrind に対して実行する ツールを指定するためのものです。このオプションを省略すると、既定で memcheck が選択されます。たとえば Valgrind の memcheck ツールを find ~ -name .bashrc というコマンドラインに対して 実行したい場合は、下記のように入力します:

valgrind --tool=memcheck find ~ -name .bashrc

また、下記には標準的な Valgrind ツールと、それについての簡単な説明を示します:

memcheck

メモリエラーの検出を行ないます。お使いのプログラムが正しく動作するか どうかを確かめることができます。

cachegrind

キャッシュの予測をプロファイルします。お使いのプログラムが高速に 動作するかどうかを確かめることができます。

callgrind

cachegrind に似た動作を行ないますが、 キャッシュプロファイル情報を追加で収集します。

exp-drd

スレッドエラーを検出します。複数のスレッドが動作するプログラムについて、 正しく動作するかどうかを確かめることができます。

helgrind

もう 1 つのスレッドエラー検出器です。 exp-drd に似た機能ですが、問題の分析時に異なる技術を使用します。

massif

ヒープのプロファイルを行ないます。ヒープとは動的に割り当てられるタイプの メモリ領域のことで、これによってお使いのプログラムをより少ないメモリで 動作できるように、チューニングすることができます。

lackey

基本的な仕組みを理解するためのサンプルツールです。

17.3.4. 既定のオプション

Valgrind は起動時にオプションを読み込みます。オプションは下記の 3 種類の 場所で指定できます:

  1. Valgrind を実行するユーザのホームディレクトリ内にある .valgrindrc ファイル。

  2. 環境変数 $VALGRIND_OPTS

  3. Valgrind を起動した時点でのカレントディレクトリにある .valgrindrc ファイル。

これらのパラメータは上記の順序で処理され、後者での設定は前者での設定を 上書きすることができるようになっています。特定の Valgrind に対する固有の オプションは、ツール名に続けてコロン (:) を追加し、その後にオプション名を 続けます。たとえば cachegrind に対して、 プロファイルデータを必ず /tmp/cachegrind_PID.log ファイルに書き込ませたい場合は、ホームディレクトリ内の .valgrindrc ファイルで、下記のように設定します:

--cachegrind:cachegrind-out-file=/tmp/cachegrind_%p.log

17.3.5. Valgrind の動作概要

Valgrind は、対象のプログラムが起動する前の段階から制御を獲得するように なっています。まずは実行ファイルからデバッグ情報を読み込み、関連する共有 ライブラリを読み込みます。実行ファイル内のコードは選択した Valgrind ツールに転送され、ツールはデバッグを行なうためのコードを追加します。 最後にコードは Valgrind の中枢部分に戻され、実行が開始されます。

たとえば memcheck では、それぞれのメモリアクセス に対してチェックを行なうコードを追加します。これにより、ネイティブな実行 環境に比べるとずっと遅い動作になってしまいます。

Valgrind では、プログラム内の各命令をシミュレートします。そのため、 プログラムのコードをチェックするだけではなく、関連するライブラリ (C ライブラリを含む) やグラフィカル環境のライブラリなどについてもチェックを 行なうことができます。つまり、 Valgrind でエラーを検出する場合は、関連する ライブラリ (C, X11, Gtk ライブラリなど) に関するエラーもチェックすることに なります。これらのエラーが不要な場合は、 Valgrind 側に設定を行なうことで 出力を省略することができます。 --gen-suppressions=yes を指定すると、これら省略したものをレポートさせることができます。

なお Valgrind を実行する際は、実際の実行ファイル (マシンコード) に対して 実行してください。たとえばシェルや Perl スクリプトからアプリケーションを 起動すると、 /bin/sh/usr/bin/perl を調査することになってしまい、誤った結果をもたらしてしまいます。このような 場合は --trace-children=yes を 指定するか、もしくはシェルやスクリプトではなく、実行ファイルを指定して実行 してください。

17.3.6. メッセージ

Valgrind の実行中には、詳細なエラー内容や重要なイベントを含むメッセージが 随時表示されます。たとえば下記のようになります:

tux@mercury:~> valgrind --tool=memcheck find ~ -name .bashrc
[...]
==6558== Conditional jump or move depends on uninitialised value(s)
==6558==    at 0x400AE79: _dl_relocate_object (in /lib64/ld-2.11.1.so)
==6558==    by 0x4003868: dl_main (in /lib64/ld-2.11.1.so)
[...]
==6558== Conditional jump or move depends on uninitialised value(s)
==6558==    at 0x400AE82: _dl_relocate_object (in /lib64/ld-2.11.1.so)
==6558==    by 0x4003868: dl_main (in /lib64/ld-2.11.1.so)
[...]
==6558== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==6558== malloc/free: in use at exit: 2,228 bytes in 8 blocks.
==6558== malloc/free: 235 allocs, 227 frees, 489,675 bytes allocated.
==6558== For counts of detected errors, rerun with: -v
==6558== searching for pointers to 8 not-freed blocks.
==6558== checked 122,584 bytes.
==6558== 
==6558== LEAK SUMMARY:
==6558==    definitely lost: 0 bytes in 0 blocks.
==6558==      possibly lost: 0 bytes in 0 blocks.
==6558==    still reachable: 2,228 bytes in 8 blocks.
==6558==         suppressed: 0 bytes in 0 blocks.
==6558== Rerun with --leak-check=full to see details of leaked memory.

==6558== として表示されている数値はプロセスの ID 番号 (PID) で、それに続けて Valgrind からのメッセージが表示されます。 これによりプログラム自体からの出力と Valgrind からの出力を区別すること ができるほか、どのプロセスに属するメッセージなのかがわかるようになって います。

Valgrind のメッセージをより詳細に表示したい場合は、 -v-v -v の各オプションを使用します。

基本的には Valgrind は自分自身のメッセージを 3 箇所に出力することが できます:

  1. 既定では Valgrind は自分自身のメッセージをファイルディスクリプタ 2 に 送信します。これは一般的には標準エラー出力 (stderr) と呼ばれているもので、 独自のファイルディスクリプタに送信させたい場合は、 --log-fd=ファイルディスクリプタ番号 オプションを使用します。

  2. 2 番目の出力先としては、 --log-file=ファイル名 で指定するファイル出力があります。なお、このオプションではいくつかの変数を 使用することができます。たとえば %p は、現在プロファイル されているプロセスのプロセス ID (PID) に置き換えることができます。この 方式を使用すると、プロセス ID ごとに異なるファイルに出力させることが できます。また %q{env_var} では、 env_var の名前を持つ環境変数について、設定されて いる値に置き換えることができます。

    下記の例では、 Apache Web サーバが再起動する際にメモリエラーが存在して いないかどうかをチェックしています。このとき、子プロセスについてもチェック を行なうものとし、詳細な Valgrind からのメッセージはそれぞれ現在の プロセス ID で示されるファイルに出力しています:

    tux@mercury:~> valgrind -v --tool=memcheck --trace-children=yes \
    --log-file=valgrind_pid_%p.log rcapache2 restart

    このプロセスはテストシステム内に 52 個のログファイルを作成し、 Valgrind を使用しない場合は通常 7 秒程度かかる rcapache2 restart コマンドが 10 倍の 75 秒程度かかる環境であるものとします。すると、下記のような 状況になります。

    tux@mercury:~> ls -1 valgrind_pid_*log
    valgrind_pid_11780.log
    valgrind_pid_11782.log
    valgrind_pid_11783.log
    [...]
    valgrind_pid_11860.log
    valgrind_pid_11862.log
    valgrind_pid_11863.log
  3. Valgrind のメッセージをネットワーク経由で送信したい場合は、それぞれ aa.bb.cc.dd の形式で IP アドレスを、 番号 の形式でポート番号を用意し、 --log-socket=aa.bb.cc.dd:port_num のようにして実行します。ポート番号を省略した場合は、 1500 が使用されます。

    なお、送信先のマシン側には Valgrind のメッセージを受信するため、 アプリケーションを起動する必要があります。このアプリケーションとしては、 Valgrind 同梱の valgrind-listener を利用するのが よいでしょう。これは指定したポートで接続を待ち受けて、受信した メッセージを標準出力に出力します。

17.3.7. エラーメッセージ

Valgrind はすべてのエラーメッセージを記憶し、新しいエラーを検出すると 古いエラーメッセージとの比較が行なわれます。この方法により、 Valgrind は エラーメッセージの重複をチェックしています。重複したエラーであることが 判明した場合、それは記憶されるだけで表示はされません。これにより、数多く のエラーメッセージが表示されることによる混乱を防ぐようになっています。

なお、 -v オプションを指定することで、 Valgrind の実行時 出力の最後に全レポートの概要 (発生回数順) を表示させることができます。 ただし、 Valgrind は 1,000 種類以上の異なるエラーを検出した場合や、全部で 10,000,000 回以上のエラーを検出した場合は、エラー収集を停止してしまうこと に注意してください。この制限を取り払ってすべてのエラーメッセージを表示 させたい場合は、 --error-limit=no を指定してください。

また、あるエラーが別のエラーを呼び起こす場合もあります。このような場合は、 発生した順序どおりに問題を修正し、プログラムを再度チェックすることを お勧めします。

17.4. さらなる情報

  • 上記のトレースツールに関連したオプションについて、完全な一覧を読みたい 場合は、それぞれのマニュアルページ (man 1 strace, man 1 ltrace, man 1 valgrind) をお読みください。

  • Valgrind のより高度な使用方法に関する説明は、 Valgrind User Manual (英語) をお読みください。これらのページは Valgrind を高度な機能を使用したり、標準ツールの使用方法や目的を 調べたりしたい場合には欠くことのできないものです。


openSUSE システム分析とチューニングガイド 13.1