Ubuntu Weekly Recipe

第584回 SystemTapでカーネルの挙動を確認する

この記事を読むのに必要な時間:およそ 8 分

カーネルを再構築することなくカーネルの動作を深く詳細に確認できると便利です。今回紹介するSystemTapを使うと,ちょっとしたスクリプト言語を書くだけで,カーネル上の特定の処理をフックし,必要な情報を収集・分析できます。

SystemTapがあるとうれしい理由

SystemTapは実行中のカーネルの処理をフックして,必要に応じて情報を収集し,出力するツールです。

カーネルのデバッグにおける代表的な手法と言えばprintk()の差し込み」です。printk()関連の関数を使えば多くの問題の状況を把握できますし,その情報がそのまま解決につながる例だってたくさんあります※1⁠。

※1
printk()を入れると起きなくなった」ことを「解決策」としているという都市伝説もあるぐらいです。あくまで都市伝説です。いやいや,まさか,そんな実例が現実にあるわけないじゃないですか。ハハハ。

いわゆるprintk()デバッグ」は強力な手法ではあるものの,いくつかの弊害も存在します。

  • printk()を差し込むためにカーネルやモジュールをビルドし直さなくてはならない
  • 環境に合わせたカーネルやモジュールのビルド方法を調べる必要がある
  • カーネルを作り直した場合は再起動が必要
  • 自分でビルドしたカーネルやモジュールだと現象が発生しない
  • printk()を入れる適切な場所を探るために何度もカーネル・モジュールの再構築を繰り返す
  • printk()を入れると現象が発生しない
  • いろいろなところにprintk()を入れすぎた結果,本当に見たかったログが流れてしまう
  • ログをメモリに貯めて現象が起きたあとにダンプするようにしたら現象が発生しない
  • そんな感じでトライアンドエラーを繰り返したら,本来起きていた現象がコードを元に戻そうが何しようがまったく発生しなくなった

要するに「なんでもかんでもprintk()だけで調査しようとするな」という話ではあります。そのprintk()以外の方法」のひとつが,今回紹介するSystemTapです。

SystemTapは「STPスクリプト」と呼ばれる独自言語のスクリプトを利用して記述します。SystemTapのコマンドであるstapコマンドがSTPスクリプトとカーネルのデバッグ情報から,C言語のコードを生成して,それをカーネルモジュールとしてビルド・ロードし,その結果を標準出力や独自のログバッファなどに保存するのです。

カーネル側で使用している要素技術は第443回でも紹介している「Livepatch」とほぼ同じです。むしろSystemTapはLivepatchよりもはるかに古くから存在する仕組みであり,Livepatchの先輩にあたる存在と言えるでしょう。

カーネルモジュールとして必要なコードをカーネルに挿入し,情報を取得します。よって第526回第528回のようなカーネルそのものをビルドし再起動することは不要ですし,モジュールビルドする側のスペックもそこまで高いものは不要です。モジュールをアンロードすれば動作を停止しますし,出力をオンメモリのログバッファにしてしまえば,ログ出力による影響もある程度は抑えられます。

STPスクリプトのフォーマットを覚える必要はありますが,シンプルな書式なのでそこまで難しくはないでしょう。むしろカーネルデバッグをするのですから,Linuxカーネルに関する知識やそのコードを読み解く能力のほうが重要です。とは言え低レイヤーの話なので,数々のコンポーネントが複雑に絡み合う上位レイヤーに比べるとカーネルのコードは,はるかに「わかりやすい」です。

SystemTapに関してはRed Hatのドキュメントが充実しています。本記事を一通り読んでなんとなくUbuntuにおけるSystemTapの導入方法が掴めたら,SystemTapビギナーズガイドSystemTapタップセットリファレンスを読むと良いでしょう。

UbuntuにSystemTapをインストールする

Ubuntuで必要なのは「SystemTap本体」「対象カーネルのデバッグ情報」のふたつです。これらに関してはUbuntuのWikiページに情報がまとまっています。言い方を変えるとこれらをインストールしてしまえば,SystemTapを利用できます※2⁠。

※2
SystemTapを使用するにはLinuxカーネルも必要です。ubuntuBSDではおそらく動きませんので注意してください。もちろんWSL(Windows Subsystem for Linux)でも裏で動いているのはWindowsカーネルなのでSystemTapは使えません。WSL2ならカーネルコンフィグを調整すれば動くようになるかもしれません。

さらにカーネルモジュールを自前でビルド・ロードするため,SecureBootもオフにしておくと良いでしょう。

まずはカーネルのデバッグ情報をインストールします。残念ながらデバッグ情報付きのカーネルは公式リポジトリには存在しません。おそらく用途が限定的であることと,公式ミラーリポジトリのサイズをあまり大きくしないようにするためでしょう。

よって公式のデバッグシンボル付きパッケージリポジトリを導入します。

$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C8CAB6595FDFF622
$ codename=$(lsb_release -c | awk  '{print $2}')
$ sudo tee /etc/apt/sources.list.d/ddebs.list << EOF
deb http://ddebs.ubuntu.com/ ${codename}      main restricted universe multiverse
#deb http://ddebs.ubuntu.com/ ${codename}-security main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-updates  main restricted universe multiverse
deb http://ddebs.ubuntu.com/ ${codename}-proposed main restricted universe multiverse
EOF
$ sudo apt update

これでリポジトリの導入は完了です。ちなみにこのリポジトリではカーネル以外のデバッグ情報付きパッケージも提供しています。もし特定のパッケージのデバッグを行いたい際にも役に立つでしょう※3⁠。

※3
大抵のパッケージは公式リポジトリにある「foo-dbg」パッケージで事足ります。ただしパッケージによっては「foo-dbg」パッケージが提供されていないので,今回のようなデバッグシンボル用リポジトリから「foo-dbgsym」をインストールする必要があるのです

現在起動しているカーネルのデバッグシンボルパッケージをインストールしましょう。

$ sudo apt install -y linux-image-$(uname -r)-dbgsym

それなりのサイズなのでインストールに時間がかかるかもしれません。

インストールされたカーネルイメージやモジュールを確認してみましょう。with debug_infoの表示があるはずです。

$ file /usr/lib/debug/boot/vmlinux-5.0.0-25-generic
/usr/lib/debug/boot/vmlinux-5.0.0-25-generic:
  ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked,
  BuildID[sha1]=001ef6c2e051165f5d367f76cb69134d51080491,
  with debug_info, not stripped

$ file /usr/lib/debug/lib/modules/5.0.0-25-generic/kernel/fs/btrfs/btrfs.ko
/usr/lib/debug/lib/modules/5.0.0-25-generic/kernel/fs/btrfs/btrfs.ko:
  ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV),
  BuildID[sha1]=b3045fb79f4d0bc72a24d9311f5e703ccfcc03fe,
  with debug_info, not stripped

それに対して通常のカーネルモジュールにはwith debug_infoがありません。

$ file /usr/lib/modules/5.0.0-25-generic/kernel/fs/btrfs/btrfs.ko
/usr/lib/modules/5.0.0-25-generic/kernel/fs/btrfs/btrfs.ko:
  ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV),
  BuildID[sha1]=b3045fb79f4d0bc72a24d9311f5e703ccfcc03fe,
  not stripped

これでデバッグ情報の準備は整いました。あとはSystemTap本体もインストールしておきます。

$ sudo apt install -y systemtap gcc

ちなみにsystemtap-docパッケージもインストールしておくと,膨大なマニュアル類もマシン上のmanコマンド経由で参照できます。使い方を学びたいなら,一緒にインストールしておくと良いでしょう。

著者プロフィール

柴田充也(しばたみつや)

Ubuntu Japanese Team Member株式会社 創夢所属。数年前にLaunchpad上でStellariumの翻訳をしたことがきっかけで,Ubuntuの翻訳にも関わるようになりました。