🐧 Linux 総合学習プラットフォーム
システムコール ・ 上級

strace でシステムコールを観察する

strace は、あるコマンドが内部でどのシステムコールを、どんな引数で呼び、何を返したかを順番に表示するツールです。プログラムのソースが手元になくても、OSとどうやり取りしているかを外から覗けるのが強みです。各行は「呼び出し名(引数) = 戻り値」の形で並び、失敗した呼び出しには errno の名前(例: ENOENT)も添えられます。-e trace=open,read のように対象を絞ったり、-f で子プロセスまで追跡できます。「設定ファイルをどこから読もうとして失敗したのか」といった障害調査でも非常に役立ちます。

プログラムが内部で何をしているかを知りたいとき、ソースコードを読むのが正攻法だ。だが、ソースが手元にない、あるいは膨大すぎて追いきれない、ということもある。

そんなときに頼りになるのがstraceだ。

straceは、あるコマンドが実行中にどんなシステムコール(syscall)を、どんな引数で呼び、何を返したかを、時間順にすべて表示してくれるツールだ。

🔗
たとえstraceは、プログラムとカーネルが交わす「会話」を外からそっくり盗み聞きする道具。中身がブラックボックスでも、OSとのやり取りなら客観的に観察できる。

プログラムとカーネルのあいだで交わされる「会話」を、外からそっくり盗み聞きするようなものだと考えると分かりやすい。

中身がブラックボックスに見えるプログラムでも、「OSとどんなやり取りをしているか」という観点からなら、その振る舞いを客観的に観察できる。

しかも、ソースを書き換えたりデバッガを仕込んだりしなくても使える手軽さが大きな魅力だ。

📖 一行一行の読み解き方

使い方は拍子抜けするほど単純で、調べたいコマンドの前にstraceを付けるだけだ。たとえばstrace ./helloのように実行する。

strace ./hello ……調べたいコマンドの前にstraceを付けるだけ。各行は「呼び出し名(引数の並び) = 戻り値」という形になる。

すると画面には、そのプログラムが発行したシステムコールが一行ずつ流れていく。各行は基本的に「呼び出し名(引数の並び) = 戻り値」という決まった形をしている。

strace の各行の形write(1, "Hello", 14)=14呼び出し名引数の並び戻り値1番(標準出力)へ14バイト書き込み、14バイト処理できた、と読む

実例で見てみよう。execve("./hello", ["./hello"], ...) = 0 はプログラムの起動そのもの。openat(...) = 3 はファイルを開いてファイルディスクリプタ(fd)の3番を得たこと。write(1, "Hello, World!\n", 14) = 14 は1番(標準出力)へ14バイトを書き込んで14バイト処理できたこと、を表す。

プログラムの起動から終了まで、実際に発行された生のシステムコールが順番に見えるため、「このプログラムは結局どんな順で、カーネルに何を頼んだのか」が一目で追える。

コツ最初は見慣れない行が大量に並んで圧倒される。自分が書いたコードに対応する行(writeやopenなど)を拾い読みするところから慣れるとよい。

最初は見慣れない呼び出しが大量に並んで圧倒されるが、自分が書いたコードに対応する行(writeやopenなど)を拾い読みするところから慣れていくとよい。

❌ 失敗した呼び出しの見つけ方

straceがとりわけ威力を発揮するのは、失敗した呼び出しを見つけるときだ。

システムコールが失敗すると戻り値は-1になり、その理由はerrno(errno)に記録される。straceは気の利いたことに、その-1のうしろに、errnoの名前とその意味する英文まで添えて表示してくれる。

openat("/etc/myapp.conf", O_RDONLY) = -1 ENOENT (No such file or directory) ……-1のうしろにerrno名と意味の英文が添えられる。
失敗した行はこう読むopenat("/etc/myapp.conf", O_RDONLY) = -1 ENOENT戻り値 -1 = 失敗ENOENT = ファイルが無いどのファイルを、なぜ失敗したかが、ソースを追わずに分かる

たとえば openat("/etc/myapp.conf", O_RDONLY) = -1 ENOENT (No such file or directory) という一行を見れば、「このプログラムは/etc/myapp.confという設定ファイルを読もうとしたが、そのファイルが存在せず失敗した」と即座に分かる。

アプリが原因不明で起動しない、設定が反映されない、ライブラリが見つからない。こうした障害の調査で、「どのファイルを、どこから読もうとして、なぜ失敗したのか」を、ソースを一行も追わずに突き止められる。これは実務で計り知れないほど重宝する。

💡
ポイントエラーメッセージが不親切なプログラムでも、straceをかければ本当の失敗箇所が浮かび上がることは少なくない。

エラーメッセージが不親切なプログラムでも、straceをかければ本当の失敗箇所が浮かび上がる、ということは少なくない。

🎯 情報の洪水から知りたい行を絞る

実際のプログラムは起動するだけで何百ものシステムコールを呼ぶため、そのまま流すと情報の洪水になり、肝心の行を見失う。そこで対象を絞り込むオプションが役立つ。

-e trace=openat,read ……指定した種類のシステムコールだけ表示。-e trace=file はファイル関連、-e trace=network はネットワーク関連をまとめて指定。

-e trace=openat,read のように書くと、指定した種類のシステムコールだけを表示できる。

ファイル関連だけを見たいなら-e trace=file、ネットワーク関連だけなら-e trace=networkといった、まとまった指定も可能だ。

-fを付けると、forkなどで生まれた子プロセスまで追跡対象に含める。前章で見たfork→exec→waitの流れを観察したいときは、この-fが欠かせない。

コツ-o ログファイル名 で出力をファイルに保存、-T で各呼び出しの所要時間を計測、-c で回数や所要時間を集計。組み合わせて知りたい情報だけを浮かび上がらせる。

ほかにも目的ごとに道具がそろっている。出力を画面ではなくファイルに保存して後でじっくり読みたいときは-o ログファイル名を、各呼び出しにどれだけ時間がかかったかを計測したいときは-Tを、システムコールごとの回数や所要時間を集計したいときは-cを使う。

これらを組み合わせて、知りたい情報だけを浮かび上がらせるのが、straceを使いこなすコツだ。

🪜 ltrace と strace は見ている層が違う

よく似たツールにltraceがある。両者は見ている層が違う。

💡
ポイントstraceはカーネルへのシステムコールを表示し、ltraceはC標準ライブラリ(libc)の関数呼び出しを表示する。見ている層が違う。

straceがカーネルへのシステムコールを表示するのに対し、ltraceはC標準ライブラリ(libc)の関数呼び出しを表示する。

アプリのコード(printf を呼ぶ)C標準ライブラリ libc の関数カーネルへのシステムコール(write)ltracestrace

たとえば自作プログラムがprintfを呼ぶ様子はltraceで見え、その結果として実際に発行されるwriteはstraceで見える、という棲み分けだ。

両者を見比べると、ライブラリ関数という上の層と、システムコールという下の層の関係が立体的に理解できる。

straceが効く典型的な使いどころをまとめておく。起動に失敗するプログラムの原因究明、設定ファイルや共有ライブラリをどこから探しているかの確認、想定外に遅い処理でどのシステムコールが時間を食っているかの特定、そして「このプログラムは外部とどんな通信をしているのか」という挙動の調査だ。

-p プロセス番号 ……すでに動いているプロセスを途中からアタッチして覗ける。

すでに動いているプロセスを途中から覗きたいときは、-p プロセス番号 のように対象のPIDを指定してアタッチすることもできる。

つまずきstraceで監視するとシステムコールごとに横から覗かれるぶん動作が遅くなる。時間に敏感な本番計測には向かない。挙動調査・原因究明の道具と心得る。

一点だけ注意がある。straceで監視されたプログラムはシステムコールごとに横から覗かれるぶん動作が遅くなるため、時間に敏感な処理の本番計測には向かない。あくまで挙動の調査や原因究明の道具と心得よう。

出力に出てきた呼び出し名で正確な仕様が知りたくなったら、その名前をそのままman 2で引けば、引数や戻り値、設定されうるerrnoの意味を一次情報として確認できる。

straceで現象をつかみ、man 2で仕様を裏取りする。この往復が調査の王道だ。

この項目に出てくる用語

システムコールしすてむこーる
アプリがカーネルに処理を依頼する公式の窓口。
errnoえらーなんばー
直近のシステムコール失敗の原因を表す番号。
ファイルディスクリプタふぁいるでぃすくりぷた
開いたファイルを指す小さな整数(0以上)。

関連コマンド

straceltrace

▶ 学習アプリでこの続きを学ぶ・演習する