Ubuntu Weekly Recipe

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

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

Kernel 5.0以降の対応

Ubuntu 18.04 LTSのHWE用やUbuntu 19.04ではLinux Kenel 5.0が採用されています。しかしながらSystemTapはバージョン4.1以降でのみKernel 5.0に対応しておりUbuntuのそれぞれのリリースのリポジトリで提供されているSystemTapのバージョンである3.1や4.0ではKernel 5.0に対応していません。

よってSystemtapを使おうとすると次のようにエラーとなります。

$ sudo stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}'
Pass 1: parsed user script and 472 library scripts using 112688virt/88684res/7176shr/81800data kb, in 320usr/40sys/365real ms.
Pass 2: analyzed script: 2 probes, 1 function, 7 embeds, 0 globals using 350352virt/328168res/8868shr/319464data kb, in 4450usr/1590sys/7451real ms.
Pass 3: translated to C into "/tmp/stapAWAC1G/stap_420924b94a0c00e3cc489953451a803c_2994_src.c" using 350352virt/328304res/9004shr/319464data kb, in 10usr/0sys/8real ms.
cc1: fatal error: /tmp/stapAWAC1G/stapconf_4ff4fabb43cabe9eb683696051bfe0ac_726.h: そのようなファイルやディレクトリはありません
compilation terminated.
cc1: fatal error: /tmp/stapAWAC1G/stapconf_4ff4fabb43cabe9eb683696051bfe0ac_726.h: そのようなファイルやディレクトリはありません
compilation terminated.
make[1]: *** [scripts/Makefile.build:286: /tmp/stapAWAC1G/stap_420924b94a0c00e3cc489953451a803c_2994_aux_0.o] エラー 1
make[1]: *** 未完了のジョブを待っています....
make[1]: *** [scripts/Makefile.build:286: /tmp/stapAWAC1G/stap_420924b94a0c00e3cc489953451a803c_2994_src.o] エラー 1
make: *** [Makefile:1606: _module_/tmp/stapAWAC1G] エラー 2
WARNING: kbuild exited with status: 2
Pass 4: compiled C into "stap_420924b94a0c00e3cc489953451a803c_2994.ko" in 960usr/620sys/1533real ms.
Pass 4: compilation failed.  [man error::pass4]
Tip: /usr/share/doc/systemtap/README.Debian should help you get started.

もしKenrel 5.0以降のデバッグを行いたいならSystemTapの4.1以降をインストールしてください。ソースコードからビルドする方法が確実ではありますが,開発版19.10にある4.1パッケージをそのままコピーしたPPAも用意してあります

PPA版のSystemTapは次の方法でインストールできます。

$ sudo add-apt-repository ppa:cosmos-door/systemtap
$ sudo apt install systemtap

これで19.10やUbuntu 18.04 LTSでKernel 5.0が動いている場合も,SystemTapを利用できるはずです。

プローブポイントのリストアップ

さて準備も整ったところでSystemTapを使ってみましょう。SystemTapの基本は「プローブポイントを指定し,そこで任意のコードを実行する」ことです。

プローブポイントとはカーネル内で処理をフックできる箇所です。代表的なのは関数が呼び出されたタイミングと関数を出ていくタイミングですが,他にもいろいろなプローブポイントの指定方法が存在します。

試しに特定の関数のプローブポイントをリストアップしてみましょう。たとえば次のコマンドは,カーネル内に存在するacpi_で始まる関数のうち,プローブポイントとして指定できる関数のリストと具体的なコードの場所です。

$ stap -l 'kernel.function("acpi_*")' | head
kernel.function("acpi_ac_add@/build/linux-hwVdeu/linux-4.15.0/drivers/acpi/ac.c:331")
kernel.function("acpi_ac_battery_notify@/build/linux-hwVdeu/linux-4.15.0/drivers/acpi/ac.c:293")
kernel.function("acpi_ac_get_state@/build/linux-hwVdeu/linux-4.15.0/drivers/acpi/ac.c:128")
kernel.function("acpi_ac_notify@/build/linux-hwVdeu/linux-4.15.0/drivers/acpi/ac.c:257")
kernel.function("acpi_ac_remove@/build/linux-hwVdeu/linux-4.15.0/drivers/acpi/ac.c:415")
kernel.function("acpi_ac_resume@/build/linux-hwVdeu/linux-4.15.0/drivers/acpi/ac.c:392")
kernel.function("acpi_acquire_global_lock@/build/linux-hwVdeu/linux-4.15.0/drivers/acpi/acpica/evxface.c:1051")
kernel.function("acpi_acquire_mutex@/build/linux-hwVdeu/linux-4.15.0/drivers/acpi/acpica/utxfmutex.c:136")
kernel.function("acpi_add_id@/build/linux-hwVdeu/linux-4.15.0/drivers/acpi/scan.c:1172")
kernel.function("acpi_add_nondev_subnodes@/build/linux-hwVdeu/linux-4.15.0/drivers/acpi/property.c:148")

特定のカーネルモジュールすべての関数を表示したい場合は,次のように実行します。次の例だとbtrfsモジュールが対象です。

$ stap -l 'module("btrfs").function("*")' | head
module("btrfs").function("BTRFS_I@/build/linux-H8RPB2/linux-5.0.0/fs/btrfs/btrfs_inode.h:208")
module("btrfs").function("BTRFS_LEAF_DATA_SIZE@/build/linux-H8RPB2/linux-5.0.0/fs/btrfs/ctree.h:1351")
module("btrfs").function("BTRFS_MAX_INLINE_DATA_SIZE@/build/linux-H8RPB2/linux-5.0.0/fs/btrfs/ctree.h:1371")
module("btrfs").function("BTRFS_MAX_ITEM_SIZE@/build/linux-H8RPB2/linux-5.0.0/fs/btrfs/ctree.h:1359")
module("btrfs").function("BTRFS_MAX_XATTR_SIZE@/build/linux-H8RPB2/linux-5.0.0/fs/btrfs/ctree.h:1377")
module("btrfs").function("BTRFS_NODEPTRS_PER_BLOCK@/build/linux-H8RPB2/linux-5.0.0/fs/btrfs/ctree.h:1364")
module("btrfs").function("ClearPageChecked@/build/linux-H8RPB2/linux-5.0.0/include/linux/page-flags.h:296")
module("btrfs").function("ClearPageError@/build/linux-H8RPB2/linux-5.0.0/include/linux/page-flags.h:283")
module("btrfs").function("ClearPagePrivate2@/build/linux-H8RPB2/linux-5.0.0/include/linux/page-flags.h:318")
module("btrfs").function("ClearPagePrivate@/build/linux-H8RPB2/linux-5.0.0/include/linux/page-flags.h:316")

-lの代わりに-Lを使うと,参照できる変数と型も表示してくれます。

$ stap -L 'module("btrfs").function("zstd_compress*")'
module("btrfs").function("zstd_compress_pages.cold.5")
module("btrfs").function("zstd_compress_pages@/build/linux-H8RPB2/linux-5.0.0/fs/btrfs/zstd.c:79")
  $ws:struct list_head* $mapping:struct address_space* $start:u64
  $pages:struct page** $out_pages:long unsigned int*
  $total_in:long unsigned int* $total_out:long unsigned int*
  $ret:int $params:ZSTD_parameters

システムコールもプローブポイントです。

$ sudo stap -l 'syscall.a*' | head
syscall.accept
syscall.accept4
syscall.access
syscall.acct
syscall.add_key
syscall.adjtimex
syscall.alarm
syscall.arch_prctl

仮想ファイルシステムの操作をトリガーにしたいのならvfsを使うと良いでしょう。

$ stap -l 'vfs.*' | head
vfs.__add_to_page_cache
vfs.__set_page_dirty_buffers
vfs.add_to_page_cache
vfs.buffer_migrate_page
vfs.do_mpage_readpage
vfs.do_sync_read
vfs.do_sync_write
vfs.open
vfs.read
vfs.readv

その他のプローブポイントについてはsystemtap-docパッケージ付属のstapprobesマニュアルを参照してください。

著者プロフィール

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

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