カーネルを再構築することなくカーネルの動作を深く詳細に確認できると便利です。今回紹介するSystemTapを使うと、
SystemTapがあるとうれしい理由
SystemTapは実行中のカーネルの処理をフックして、
カーネルのデバッグにおける代表的な手法と言えばprintk()
の差し込み」printk()
関連の関数を使えば多くの問題の状況を把握できますし、
いわゆるprintk()
デバッグ」
printk()
を差し込むためにカーネルやモジュールをビルドし直さなくてはならない- 環境に合わせたカーネルやモジュールのビルド方法を調べる必要がある
- カーネルを作り直した場合は再起動が必要
- 自分でビルドしたカーネルやモジュールだと現象が発生しない
printk()
を入れる適切な場所を探るために何度もカーネル・モジュールの再構築を繰り返す printk()
を入れると現象が発生しない- いろいろなところに
printk()
を入れすぎた結果、本当に見たかったログが流れてしまう - ログをメモリに貯めて現象が起きたあとにダンプするようにしたら現象が発生しない
- そんな感じでトライアンドエラーを繰り返したら、
本来起きていた現象がコードを元に戻そうが何しようがまったく発生しなくなった
要するにprintk()
だけで調査しようとするな」printk()
以外の方法」
SystemTapはstap
コマンドがSTPスクリプトとカーネルのデバッグ情報から、
カーネル側で使用している要素技術は第443回でも紹介している
カーネルモジュールとして必要なコードをカーネルに挿入し、
STPスクリプトのフォーマットを覚える必要はありますが、
SystemTapに関してはRed Hatのドキュメントが充実しています。本記事を一通り読んでなんとなくUbuntuにおけるSystemTapの導入方法が掴めたら、
UbuntuにSystemTapをインストールする
Ubuntuで必要なのは
さらにカーネルモジュールを自前でビルド・
まずはカーネルのデバッグ情報をインストールします。残念ながらデバッグ情報付きのカーネルは公式リポジトリには存在しません。おそらく用途が限定的であることと、
よって公式のデバッグシンボル付きパッケージリポジトリを導入します。
$ 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
これでリポジトリの導入は完了です。ちなみにこのリポジトリではカーネル以外のデバッグ情報付きパッケージも提供しています。もし特定のパッケージのデバッグを行いたい際にも役に立つでしょう
現在起動しているカーネルのデバッグシンボルパッケージをインストールしましょう。
$ sudo apt install -y linux-image-$(uname -r)-dbgsym
それなりのサイズなのでインストールに時間がかかるかもしれません。
インストールされたカーネルイメージやモジュールを確認してみましょう。with debug_
」
$ 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_
」
$ 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パッケージもインストールしておくと、
Kernel 5.0以降の対応
Ubuntu 18.
よって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.
PPA版のSystemTapは次の方法でインストールできます。
$ sudo add-apt-repository ppa:cosmos-door/systemtap $ sudo apt install systemtap
これで19.
プローブポイントのリストアップ
さて準備も整ったところで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")
特定のカーネルモジュールすべての関数を表示したい場合は、
$ 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マニュアルを参照してください。
プローブポイントにトリガーをセットする
プローブポイントにトリガーをセットすることで、
$ sudo stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}' Pass 1: parsed user script and 465 library scripts using 114324virt/49096res/6748shr/42648data kb, in 90usr/0sys/91real ms. Pass 2: analyzed script: 1 probe, 1 function, 7 embeds, 0 globals using 340476virt/276868res/8240shr/268800data kb, in 2170usr/290sys/2714real ms. Pass 3: translated to C into "/tmp/stap5wPauG/stap_2105033c25e89abb09691f212f5ebefc_2666_src.c" using 340476virt/277060res/8432shr/268800data kb, in 0usr/10sys/4real ms. Pass 4: compiled C into "stap_2105033c25e89abb09691f212f5ebefc_2666.ko" in 7480usr/600sys/8287real ms. Pass 5: starting run. read performed Pass 5: run completed in 20usr/0sys/323real ms.
stapコマンドはSTPファイル経由でのスクリプトの実行だけでなく、-e
」
上記の例では仮想ファイルシステムでのread()
呼び出しが発生したら、printf()
)、exit()
)。たとえばexit()
がないと、
「-v
」-p 数字
」
結果を見るとすぐにread()
」
より詳細なSTPスクリプトは、
#!/usr/bin/env stp
probe kernel.function("sys_read").call {
printf("%s -> %s\n", thread_indent(1), probefunc())
printf("%s args [%s]\n", probefunc(), $$parms)
exit()
}
上記はsys_
がfonction(FOO).call
のようにプローブポイントの後ろに.call
」.inline
」.exported
」.call
」
「%s -> %s
」thread_
」probefunc()
」man function::関数名
」
「$$params
」%s args [%s]
」関数名 args [引数リスト]
となります。
実際に実行してみましょう。
$ sudo stap -v test1.stp Pass 1: parsed user script and 465 library scripts using 114320virt/49172res/6828shr/42644data kb, in 90usr/0sys/88real ms. Pass 2: analyzed script: 1 probe, 18 functions, 5 embeds, 2 globals using 178440virt/114732res/8160shr/106764data kb, in 620usr/50sys/675real ms. Pass 3: translated to C into "/tmp/stapfy7sof/stap_0bfb2743588f284e88bf9e11013b8d23_7991_src.c" using 178440virt/114976res/8352shr/106764data kb, in 90usr/0sys/105real ms. Pass 4: compiled C into "stap_0bfb2743588f284e88bf9e11013b8d23_7991.ko" in 2850usr/220sys/3101real ms. Pass 5: starting run. 0 stapio(8415): -> SyS_read SyS_read args [fd=0x4 buf=0x7ffc8de2eb40 count=0x2004] Pass 5: run completed in 20usr/10sys/317real ms.
thread_
の結果としてsys_
を検知しているわけですね。
逆に関数から戻るときは.call
」.return
」
#!/usr/bin/env stp
probe kernel.function("sys_read").return {
printf("exit %s <- %s\n", thread_indent(-1), probefunc())
printf("%s args [%s]\n", probefunc(), $$return)
exit()
}
「$$return
」$$args
」.return
」-v
」
$ sudo stap test2.stp exit 0 stapio(20240): <- do_syscall_64 do_syscall_64 args [return=0xc]
stapioプロセスからのシステムコールとしてsys_
が呼ばれ、
情報表示の例としては、print_
や、print_
なども存在します。詳細はman function::関数名
」
ここまで読んだかたなら、printf()
」
そこで次回は、printf()
」