SystemTap使ってみた

# 概要
CentOS6.9でSystemTapを使ってみた時の自分メモ。`深く使ったわけでもないので`、使いながら随時更新予定。

# インストール
## 必要なもの
* `kernel-devel`(実行しているカーネルバージョン)
    - インストール方法　→ https://wiki.centos.org/HowTos/I_need_the_Kernel_Source
    - `wget`して`rpm -ivh`する
* `kernel-debuginfo`(実行しているカーネルバージョン)
    - `yum install yum-utils`で`debuginfo-install`コマンドをインストール
    - `debuginfo-install kernel-`uname -r``
    - yumでインストールするとcentos.plusみたいなものがインストールされてしまう
* `kernel-debuginfo-common-arch`(実行しているカーネルバージョン)
    - kernel-debuginfoをインストールで一緒にインストールされる
* gcc
* systemtap

## `kernel-devel`とは
カーネルモジュール.koを作成するために必要なもの。systemtapではカーネルモジュールを作成するので必要になる。

```
Description :
This package provides kernel headers and makefiles sufficient to build modules
against the kernel package.
```

## `kernel-debuginfo`とは
* カーネルのdebug情報。/boot以下のカーネルのマップ情報から得られるものとは異なる？？カーネルモジュール用にまた何かあるのだろうか？

```
Description :
This package provides debug information for package kernel.
This is required to use SystemTap with kernel-2.6.32-642.15.1.el6.x86_64.
```

## インストールの確認
`stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}'`
エラーがなければOK


# SystemTapの実行
```bash
sudo stap -v ファイル.stp
```
* -vをつけてることでどこまで実行されたのかを確認したほうがよい。`-e`でワンライナーでも実行できる。
* `/usr/share/systemtap/tapset`に沢山のサンプルがある

# SystemTap書式
各プローブ(測定ポイント)を以下の様に指定する。プローブで指定したカーネル関数などのプローブポイントが呼び出されたタイミングで{}の中を実行して観測できる。`プローブ.return`でそのプローブポイントが終了するタイミングで呼べる。

```c
probe プローブ {statements}
```
下の場合だと、カーネル関数の`sys_sync`が呼び出された時に{}の中が実行される。

```c
global count=0

probe kernel.function("sys_sync") {
  count++
  printf( "sys_sync called %d times, currently by pid %d\n", count, pid );
}
```

# プローブポイント一覧
`stap -L`でポイントの一覧が確認できる。

```bash

# VFSのfile_operationイベントのブローブポイント一覧
stap -L vfs.*
# カーネル関数のブローブポイント一覧
stap -L 'kernel.function("*")'
#静的ブローブポイント一覧
stap -L 'kernel.trace("*")'
#モジュール内の関数のプローブポイント一覧
stap -L 'module("*").function("*")'
```
* 例
    - vfsでは下記の関数とその関数に関して利用できる型がある

```bash

$ stap -L vfs.*
vfs.__set_page_dirty_buffers dev:long devname:string ino:long index:long name:string argstr:string size:long units:string $page:struct page*
vfs.add_to_page_cache dev:long devname:string ino:long index:long nrpages:long size:long units:string name:string argstr:string $page:struct page* $mapping:struct address_space* $offset:long unsigned int $gfp_mask:gfp_t
vfs.block_sync_page dev:long devname:string ino:long index:long name:string argstr:string size:long units:string $page:struct page*
vfs.buffer_migrate_page dev:long ino:long devname:string index:long name:string argstr:string size:long units:string $mapping:struct address_space* $newpage:struct page* $page:struct page*
vfs.do_mpage_readpage dev:long devname:string ino:long index:long name:string argstr:string size:long units:string $bio:struct bio* $page:struct page* $nr_pages:unsigned int $last_block_in_bio:sector_t* $map_bh:struct buffer_head* $first_logical_block:long unsigned int* $get_block:get_block_t* $blocks:sector_t[]
vfs.do_sync_read dev:long devname:string ino:long file:long len:long pos:long buf:long name:string argstr:string size:long units:string bytes_to_read:long $filp:struct file* $buf:char* $len:size_t $ppos:loff_t* $iov:struct iovec $kiocb:struct kiocb
vfs.do_sync_write dev:long devname:string ino:long file:long len:long pos:long buf:long bytes_to_write:long name:string argstr:string size:long units:string $filp:struct file* $buf:char const* $len:size_t $ppos:loff_t* $iov:struct iovec $kiocb:struct kiocb
vfs.read file:long pos:long buf:long bytes_to_read:long dev:long devname:string ino:long name:string argstr:string $file:struct file* $buf:char* $count:size_t $pos:loff_t*
vfs.readv file:long dev:long devname:string ino:long pos:long vec:long vlen:long bytes_to_read:long name:string argstr:string $file:struct file* $vec:struct iovec const* $vlen:long unsigned int $pos:loff_t*
vfs.remove_from_page_cache dev:long devname:string ino:long index:long name:string argstr:string $page:struct page*
vfs.write file:long pos:long buf:long bytes_to_write:long dev:long devname:string ino:long name:string argstr:string $file:struct file* $buf:char const* $count:size_t $pos:loff_t*
vfs.writev file:long dev:long devname:string ino:long pos:long vlen:long vec:long bytes_to_write:long name:string argstr:string $file:struct file* $vec:struct iovec const* $vlen:long unsigned int $pos:loff_t*
```

# SystemTapの仕組み
https://www.ibm.com/developerworks/jp/linux/library/l-systemtap/
↑より
Kprobe はアーキテクチャーに特化されており、**プローブ対象の命令の先頭バイトにブレークポイント命令を挿入します。ブレークポイント命令にヒットするとプローブ固有のハンドラーが実行され、それが完了すると本来の命令の実行が (ブレークポイントから) 再開され、実行が継続されます。**
Kretprobe の場合は多少異なり、呼び出された関数から戻ってきた時点で動作します。関数には複数のリターン・ポイントがある場合もあるため、複雑なことのように思えますが、Kretprobe が実際に使用するのはトランポリン (trampoline) という単純な手法です。この手法では、関数のすべてのリターン・ポイントを計測するのではなく (これでは、すべての事例をキャッチできません)、わずかな量のコードを関数の入り口に追加します。**このコードはスタックのリターン・アドレスをトランポリン・アドレス、つまり Kretprobe のアドレスに置き換えます。そのため関数の実行が終了すると、呼び出し側に制御が返される代わりに、Kretprobe が呼び出されます。そして呼び出された Kretprobe がその機能を実行した後、実際に関数を呼び出した側に制御を返すという仕組みです。**




## 5 つの動作フェーズ
```
開始
↓
スクリプトを解析ツリーに変換 (pass 1)
↓
シンボル情報を使用してシンボルを解決(pass 2)
↓
解析ツリーを C ソースに変換(pass 3)
tapset スクリプト (SystemTap で定義された便利な機能を集めたライブラリー) も使用
↓
カーネル・モジュールの作成(pass 4)
↓
staprun と stapio
カーネルへのモジュールのインストールを管理し、その出力を stdout にルーティングします (pass 5)
↓
終了
(シェルで Ctrl-C が押された場合や、スクリプトが終了した場合は、クリーンアップ・プロセスによってモジュールがアンロードされ、その結果、関連するすべてのユーティリティーが終了)
```



# Systemtap使ってみた
## カーネルスタックの計測
* もっとも頻繁にカーネルスタックの値が同じである回数を知ることができ、頻繁に実行されているカーネルスタックの中身は何かがわかる

```c:kernelstack.stp
global s;

probe timer.profile{
    # 現在のカーネルスタックの16 進バックトレース
    if (execname() == "java")
        s[backtrace()] <<< 1
}
probe end{
    foreach(i in s+){
        # スタック番地からカーネルスタックの中身を出力
        print_stack(i)
        printf("\n\t%d\n",@count(s[i]))
    }
}
```

結果

```bash
 0xffffffff811b551a : __d_lookup+0x9a/0x150 [kernel]

        1
 0xffffffff810198db : syscall_trace_enter+0x16b/0x1e0 [kernel]

        2
 0xffffffff8111a6bf : prof_syscall_enter+0x1bf/0x270 [kernel]

        2
 0xffffffff810682ff : finish_task_switch+0x4f/0xf0 [kernel]

        11
 0xffffffffa12a407c : 0xffffffffa12a407c [stap_827021213c17f52ed8777c332d91e694_6260+0x807c/0x0]
 0xffffffffa12a57d3 : 0xffffffffa12a57d3 [stap_827021213c17f52ed8777c332d91e694_6260+0x97d3/0x0]
 0xffffffffa12a700e : 0xffffffffa12a700e [stap_827021213c17f52ed8777c332d91e694_6260+0xb00e/0x0]
 0xffffffff810b0d49 : profile_tick+0x99/0xa0 [kernel]
 0xffffffff810b8a50 : tick_sched_timer+0x70/0xc0 [kernel]
 0xffffffff810ab12e : __run_hrtimer+0x8e/0x1d0 [kernel]
 0xffffffff810ab4ce : hrtimer_interrupt+0xee/0x270 [kernel]
 0xffffffff8103879d : local_apic_timer_interrupt+0x3d/0x70 [kernel]
 0xffffffff81552f55 : smp_apic_timer_interrupt+0x45/0x60 [kernel]
 0xffffffff8100bc13 : apic_timer_interrupt+0x13/0x20 [kernel]
 0xcf9b000000ffff : 0xcf9b000000ffff

        128
```
## カーネル関数のコールグラフ

```c:kernelfunction-callgraph.stp
probe module("ext4").function("*").call
{
  if (execname() == "java")
  printf ("%s -> %s\n", thread_indent(1), probefunc())
}
probe module("ext4").function("*").return
{
  if (execname() == "java")
  printf ("%s <- %s\n", thread_indent(-1), probefunc())
}
```
結果

```bash
     0 java(3926): -> ext4_readdir
    10 java(3926):  -> free_rb_tree_fname
    15 java(3926):  <- ext4_readdir
    18 java(3926):  -> ext4_htree_fill_tree
    25 java(3926):   -> htree_dirblock_to_tree
    31 java(3926):    -> ext4_bread
    37 java(3926):     -> ext4_getblk
    43 java(3926):      -> ext4_get_blocks
    50 java(3926):       -> ext4_ext_get_blocks
    56 java(3926):       <- ext4_get_blocks
    60 java(3926):       -> ext4_data_block_valid
    67 java(3926):       <- ext4_get_blocks
    68 java(3926):      <- ext4_getblk
    74 java(3926):     <- ext4_bread
    76 java(3926):    <- htree_dirblock_to_tree
    79 java(3926):    -> ext4_check_dir_entry
    84 java(3926):    <- htree_dirblock_to_tree
    88 java(3926):    -> ext4fs_dirhash
    96 java(3926):     -> str2hashbuf_signed
   101 java(3926):     <- ext4fs_dirhash
   103 java(3926):    <- htree_dirblock_to_tree
   105 java(3926):    -> ext4_htree_store_dirent
   111 java(3926):    <- htree_dirblock_to_tree
   113 java(3926):    -> ext4_check_dir_entry
   115 java(3926):    <- htree_dirblock_to_tree
   117 java(3926):    -> ext4fs_dirhash
   119 java(3926):     -> str2hashbuf_signed
   121 java(3926):     <- ext4fs_dirhash
   122 java(3926):    <- htree_dirblock_to_tree
   124 java(3926):    -> ext4_htree_store_dirent
   127 java(3926):    <- htree_dirblock_to_tree
   128 java(3926):   <- ext4_htree_fill_tree
   130 java(3926):  <- ext4_readdir
   132 java(3926):  -> call_filldir
   137 java(3926):  <- ext4_readdir
   139 java(3926):  -> call_filldir
   141 java(3926):  <- ext4_readdir
   142 java(3926): <- vfs_readdir
```

# メモ
## ユーザー空間
* カーネル3.5以上だとユーザー空間もプローブできる。

## 関数でグローバルな変数へのアクセス
```
When a target variable is not local to the probe point, like a global external variable or a file local static variable defined in another file then it can be referenced through
```
```c
“@var("変数名@src/file.c")”.
```

## 構造体へのアクセス
```c
@var("変数名@src/file.c")->構造体メンバ
```

これで外部変数に入っている構造体のメンバにもアクセスできる

## キャスティング
* コードの中ではvoidのポインタかも、型情報は使えないかも
* プローブハンドラの中では型情報は使えるが、stapの中で自分で作成した関数では使えない。
* その関数の中では。ポインタはlong型を使うようになっている

よって
### @cast() operator
* 第一引数
    - ポインター
* 第二引数
    - キャストしたい型
* 第三引数(オプション４)
    - キャストしたい型が定義してあるファイル

```c
function task_state:long (task:long)
{
 return @cast(task, "task_struct", "kernel<linux/sched.h>")->state
}
```
これでtask_struct task->stateでアクセスできる


# まとめ
* カーネルスタックの値を動的に見れたのは初めてだった
* 本格的に使っていないのでなんともですが、perfコマンドやOSのコマンドである程度調査はできそう。
* SystemTapを使いながら内部の挙動に詳しくなる

# 参考
* https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/7/html-single/SystemTap_Beginners_Guide/index.html#scripts
* https://www.ibm.com/developerworks/jp/linux/library/l-systemtap/
* https://sourceware.org/systemtap/
* https://sourceware.org/systemtap/SystemTap_Beginners_Guide.pdf