rbbcc 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35d11d74dd0d8eacf88978ec066ede1d64aef374027d819f5891a124822db4e9
4
- data.tar.gz: 22125362cf25181c33b253d7c6ece7d2e9b9fa2d8933d8c7f120c453384df5d8
3
+ metadata.gz: b6eda25040fa0dc56bed7b9d672af03ab3f6dbd70a4ebca3e5310331c1f9098c
4
+ data.tar.gz: ef43f8bd481718f9baf21e2ba79c2a3d35b18d2767fc9572d436338244e83731
5
5
  SHA512:
6
- metadata.gz: d145f5307df79f42679892b26b4f3f320dbdfb7d931f65f9f2b84ba4032d780991c2f2d0fb36a06b030bc59e45d16b4cca201d42e0368eb5ed4d4853958d44f3
7
- data.tar.gz: 86c50507da5cc6ba947def8ce7c169937a432579ce92e0b70c0d24d4316fbba7c45a2de22d602b0c166f473db8e8fddeab1d961b07b97dd719b0a30f15fd9234
6
+ metadata.gz: f9e19d1eb21813f2aef023ca6bb8c71e51052bf1dfe6b66013e4918129b8f538390098fca5f60f50fbba305482e2f23c2d378ca2f8b7422dc34c31e2d70bf8c3
7
+ data.tar.gz: 4f506d2a3350304fcad7de75d02b832c11a204588540ca8d210322fa7aef677587b855f2fa64fbeba3a63ba5a35c6a50f480b6d8095f6b081d4e12d383eca0b5
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbbcc (0.3.1)
4
+ rbbcc (0.4.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -5,7 +5,7 @@
5
5
 
6
6
  ---
7
7
 
8
- This tutorial is about developing bcc tools and programs using the Ruby interface, using [RbBCC](https://github.com/udzura/rbbcc/). In this time the oart of observability is implemented. Snippets are taken from various programs in [bcc](https://github.com/iovisor/bcc/tree/master/tools): see their files for licences. And we have implemented their Ruby versions and put them on [`answers/`](answers/).
8
+ This tutorial is about developing bcc tools and programs using the Ruby interface, using [RbBCC](https://github.com/udzura/rbbcc/). In this time the part of observability is implemented. Snippets are taken from various programs in [bcc](https://github.com/iovisor/bcc/tree/master/tools): see their files for licences. And we have implemented their Ruby versions and put them on [`answers/`](answers/).
9
9
 
10
10
  Also see the bcc developer's [reference_guide.md](https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#bpf-c) for C interface.
11
11
 
@@ -115,16 +115,16 @@ This is similar to hello_world.rb, and traces new processes via sys_clone() agai
115
115
 
116
116
  1. ```b.attach_kprobe(event: b.get_syscall_fnname("clone"), fn_name: "hello")```: Creates a kprobe for the kernel clone system call function, which will execute our defined hello() function. You can call attach_kprobe() more than once, and attach your C function to multiple kernel functions.
117
117
 
118
- 1. ```b.trace_fields do |...|```: Loop wirth a fixed set of fields from trace_pipe(without blcok, this method just returns the same set of fields). Similar to trace_print(), this is handy for hacking, but for real tooling we should switch to BPF_PERF_OUTPUT().
118
+ 1. ```b.trace_fields do |...|```: Loop with a fixed set of fields from trace_pipe(without blcok, this method just returns the same set of fields). Similar to trace_print(), this is handy for hacking, but for real tooling we should switch to BPF_PERF_OUTPUT().
119
119
 
120
- ### Lesson 4. sync_timing.py
120
+ ### Lesson 4. sync_timing.rb
121
121
 
122
122
  Remember the days of sysadmins typing ```sync``` three times on a slow console before ```reboot```, to give the first asynchronous sync time to complete? Then someone thought ```sync;sync;sync``` was clever, to run them all on one line, which became industry practice despite defeating the original purpose! And then sync became synchronous, so more reasons it was silly. Anyway.
123
123
 
124
124
  The following example times how quickly the ```do_sync``` function is called, and prints output if it has been called more recently than one second ago. A ```sync;sync;sync``` will print output for the 2nd and 3rd sync's:
125
125
 
126
126
  ```
127
- # ./examples/tracing/sync_timing.py
127
+ # ruby answers/04-sync_timing.rb
128
128
  Tracing for quick sync's... Ctrl-C to end
129
129
  At time 0.00 s: multiple syncs detected, last 95 ms ago
130
130
  At time 0.10 s: multiple syncs detected, last 96 ms ago
@@ -186,7 +186,7 @@ Things to learn (all in C):
186
186
 
187
187
  *Note for RbBCC developers:* Type of `trace_fields` return values differ from python's This should be fixed.
188
188
 
189
- ### Lesson 5. sync_count.py
189
+ ### Lesson 5. sync_count.rb
190
190
 
191
191
  Modify the sync_timing.rb program (prior lesson) to store the count of all kernel sync system calls (both fast and slow), and print it with the output. This count can be recorded in the BPF program by adding a new key index to the existing hash.
192
192
 
@@ -427,7 +427,7 @@ Example is at [answers/10-disklatency.rb](answers/10-disklatency.rb).
427
427
 
428
428
  ### Lesson 11. vfsreadlat.rb
429
429
 
430
- This example is split into separate Python and C files. Example output:
430
+ This example is split into separate Ruby and C files. Example output:
431
431
 
432
432
  ```
433
433
  # bundle exec answers/11-vfsreadlat.rb 1
@@ -519,7 +519,7 @@ end
519
519
 
520
520
  Things to learn:
521
521
 
522
- 1. ```TRACEPOINT_PROBE(random, urandom_read)```: Instrument the kernel tracepoint ```random:urandom_read```. These have a stable API, and thus are recommend to use instead of kprobes, wherever possible. You can run ```perf list``` for a list of tracepoints. Linux >= 4.7 is required to attach BPF programs to tracepoints.
522
+ 1. ```TRACEPOINT_PROBE(random, urandom_read)```: Instrument the kernel tracepoint(it's different from Ruby's `TracePoint` class) ```random:urandom_read```. These have a stable API, and thus are recommend to use instead of kprobes, wherever possible. You can run ```perf list``` for a list of tracepoints. Linux >= 4.7 is required to attach BPF programs to tracepoints.
523
523
  1. ```args->got_bits```: ```args``` is auto-populated to be a structure of the tracepoint arguments. The comment above says where you can see that structure. Eg:
524
524
 
525
525
  ```
@@ -632,7 +632,7 @@ Things to learn:
632
632
 
633
633
  1. ```PT_REGS_PARM1(ctx)```: This fetches the first argument to ```strlen()```, which is the string.
634
634
  1. ```b.attach_uprobe(name: "c", sym: "strlen", fn_name: "count")```: Attach to library "c" (if this is the main program, use its pathname), instrument the user-level function ```strlen()```, and on execution call our C function ```count()```.
635
- 1. For ```BPF_HASH```, you should call ```k/v.to_bcc_value``` to iterate in Ruby block. This behavior is Ruby specific and would be changed in the future.
635
+ 1. Currently you should call ```k/v.to_bcc_value``` to iterate objects from ```BPF_HASH``` in Ruby block. This behavior is Ruby specific and would be changed in the future.
636
636
 
637
637
  ### Lesson 15. nodejs_http_server.rb
638
638
 
@@ -692,6 +692,8 @@ Things to learn:
692
692
  1. ```u.enable_probe(probe: "http__server__request", fn_name: "do_trace")```: Attach our ```do_trace()``` BPF C function to the Node.js ```http__server__request``` USDT probe.
693
693
  1. ```b = BCC.new(text: bpf_text, usdt_contexts: [u])```: Need to pass in our USDT object, ```u```, to BPF object creation.
694
694
 
695
+ Of cource, there are also USDT probes embedded in Ruby(MRI) itself, so we are going to add some new Ruby USDT lessons. Contributions are welcomed.
696
+
695
697
  ### Lesson 16. task_switch.c
696
698
 
697
699
  This is an older tutorial included as a bonus lesson. Use this for recap and to reinforce what you've already learned.
@@ -738,7 +740,7 @@ The userspace component loads the file shown above, and attaches it to the
738
740
  `finish_task_switch` kernel function.
739
741
  The `[]` operator of the BPF object gives access to each BPF_HASH in the
740
742
  program, allowing pass-through access to the values residing in the kernel. Use
741
- the object as you would any other python dict object: read, update, and deletes
743
+ the object as you would any other Ruby Hash object: read, update, and deletes
742
744
  are all allowed.
743
745
 
744
746
  ```ruby
@@ -763,7 +765,7 @@ These programs can be found in the files [answers/16-task_switch.c](answers/16-t
763
765
 
764
766
  For further study, see [BCC original docs](https://github.com/iovisor/bcc/tree/master/docs) and Sasha Goldshtein's [linux-tracing-workshop](https://github.com/goldshtn/linux-tracing-workshop), which contains additional labs. There are also many tools in rbbcc/bcc /tools to study.
765
767
 
766
- Please read [CONTRIBUTING-SCRIPTS.md](../CONTRIBUTING-SCRIPTS.md) if you wish to contrubite tools to rbbcc. At the bottom of the main [README.md](../README.md), you'll also find methods for contacting us. Good luck, and happy tracing!
768
+ Please read [CONTRIBUTING-SCRIPTS.md](../CONTRIBUTING-SCRIPTS.md) if you wish to contrubite tools to rbbcc(it's preparing). At the bottom of the main [README.md](../README.md), you'll also find methods for contacting us(preparing; contact @udzura for Ruby version). Good luck, and happy tracing!
767
769
 
768
770
  ---
769
771
 
@@ -0,0 +1,770 @@
1
+ # RbBCC Ruby Developer Tutorial
2
+
3
+ * オリジナルの Python バージョンは [BCC本家のリポジトリ](https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md) にあります。
4
+ * この Ruby 版チュートリアルは、日本語版も含め BCC のライセンスに従います。
5
+
6
+ ---
7
+
8
+ このチュートリアルは [RbBCC](https://github.com/udzura/rbbcc/) を用いて、Rubyのインタフェースにより bcc のツールを開発するためのチュートリアルです。今回は「可観測性」のパートのみが執筆されています。コードスニペットは [bcc](https://github.com/iovisor/bcc/tree/master/tools) の各所のものを参考にしています: ぜひそれらのライセンスも参照してください。そして、私たちはそれらの Ruby バージョンを [`answers/`](answers/) リポジトリに配置しています。
9
+
10
+ 同時に、 bcc 開発者の [リファレンスガイド](https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#bpf-c) も参照し、 C のインターフェースも理解してください。
11
+
12
+ また、 Python と Lua の bcc インターフェースもあるので、 bcc のオリジナルを参照してください。
13
+
14
+ ## Observability - 可観測性
15
+
16
+ この可観測性のチュートリアルは 17 のレッスンを含んでいます。
17
+
18
+ ### Lesson 1. Hello World
19
+
20
+ [answers/01-hello-world.rb](answers/01-hello-world.rb) を実行しましょう。そして、別のターミナルセッションでいくつかコマンドを(例: `"ls"` )発行しましょう。プロセスを作るたびに「`Hello, World!`」がプリントされるはずです。もし出ない場合、bccのインストールに問題があるでしょう: [BCCの INSTALL.md](https://github.com/iovisor/bcc/blob/master/INSTALL.md) と [rbbcc getting started](getting_started.md) を見てください。
21
+
22
+ ```bash
23
+ ## もし bundler の環境で実行しているのなら、 `bundle exec' をつけてください。
24
+ # ruby answers/01-hello-world.rb
25
+ Found fnc: kprobe__sys_clone
26
+ Attach: p___x64_sys_clone
27
+ <...>-17950 [000] .... 244107.900795: 0: Hello, World!
28
+ bash-17950 [000] .... 244110.775263: 0: Hello, World!
29
+ bash-17950 [000] .... 244114.080360: 0: Hello, World!
30
+ ```
31
+
32
+ 6つの学ぶべきことがあります:
33
+
34
+ 1. ```text: '...'```: これは BPF プログラムをインラインで定義しています。このプログラムは、Cで書きます。
35
+
36
+ 1. ```kprobe__sys_clone()```: これはkprobeによるカーネルの動的トレーシングをするためのショートカット規約です。もし、Cの関数が ``kprobe__`` から開始していたら。残りは計測するカーネルの関数名として扱われます。この場合、 ```sys_clone()``` です。
37
+
38
+ 1. ```void *ctx```: ctx は型があるのですが、今回は使わないので ```void *``` にキャストして捨てています。
39
+
40
+ 1. ```bpf_trace_printk()```: シンプルなカーネルユーティリティで、 ```trace_pipe (/sys/kernel/debug/tracing/trace_pipe)``` に printf() をします。これは単純な例なら問題ないのですが、制限もあります: 引数が3つまで、 `%s` は1つまで、そして `trace_pipe` はマシングローバルであること。なので、並列実行のプログラムではアウトプットがクラッシュするでしょう。より良いインタフェースに `BPF_PERF_OUTPUT()` があり、後述します。
41
+
42
+ 1. ```return 0;```: おまじないです(理由を詳しく知りたければ [bcc#139](https://github.com/iovisor/bcc/issues/139) まで)。
43
+
44
+ 1. ```Table#trace_print```: Ruby側の、trace_pipeを読み込んでアウトプットをプリントするメソッドです。
45
+
46
+ ### Lesson 2. sys_sync()
47
+
48
+ カーネル関数 `sys_sync()` をトレースするプログラムを書きましょう。実行のたびに "sys_sync() called" をプリントします。トレース中別のターミナルで ```sync``` コマンドを打てばテストできます。さきほどの `hello_world.rb` に必要なものが全て書かれています。
49
+
50
+ プログラム起動時に "Tracing sys_sync()... Ctrl-C to end." と出力して、改善しましょう。ヒント: これはただの Ruby プログラムで、Ctrl-Cを押したときに投げられる `Interrupt` 例外を rescue できるはずです。
51
+
52
+ 回答例の一つです: [answers/02-sys_sync.rb](answers/02-sys_sync.rb)
53
+
54
+ Tipsとして、システムコール `sync(2)` を明示的にRubyから呼ぶことも可能です:
55
+
56
+ ```console
57
+ # ausyscall sync
58
+ sync 162
59
+ # ruby -e 'syscall(162)'
60
+ ```
61
+
62
+ ### Lesson 3. hello_fields.rb
63
+
64
+ プログラムは: [answers/03-hello_fields.rb](answers/03-hello_fields.rb). アウトプットのサンプル (コマンドを別のターミナルで打つこと):
65
+
66
+ ```
67
+ # bundle exec ruby ./docs/answers/03-hello_fields.rb
68
+ TIME(s) COMM PID MESSAGE
69
+ 24585001.174885999 sshd 1432 Hello, World!
70
+ 24585001.195710000 sshd 15780 Hello, World!
71
+ 24585001.991976000 systemd-udevd 484 Hello, World!
72
+ 24585002.276147000 bash 15787 Hello, World!
73
+ ```
74
+
75
+ コードは:
76
+
77
+ ```ruby
78
+ require "rbbcc"
79
+ include RbBCC
80
+
81
+ # define BPF program
82
+ prog = <<BPF
83
+ int hello(void *ctx) {
84
+ bpf_trace_printk("Hello, World!\\n");
85
+ return 0;
86
+ }
87
+ BPF
88
+
89
+ # load BPF program
90
+ b = BCC.new(text: prog)
91
+ b.attach_kprobe(event: b.get_syscall_fnname("clone"), fn_name: "hello")
92
+
93
+ # header
94
+ puts("%-18s %-16s %-6s %s" % ["TIME(s)", "COMM", "PID", "MESSAGE"])
95
+
96
+ # format output
97
+ begin
98
+ b.trace_fields do |task, pid, cpu, flags, ts, msg|
99
+ print("%-18.9f %-16s %-6d %s" % [ts, task, pid, msg])
100
+ end
101
+ rescue Interrupt
102
+ puts
103
+ puts "Done"
104
+ rescue => e
105
+ p e
106
+ retry
107
+ end
108
+ ```
109
+
110
+ これは hello_world.rb に近いもので、 sys_clone() 経由で新しいプロセスをトレースします。しかしいくつか新しい要素があります:
111
+
112
+ 1. ```prog:```: 今回私たちは、Cプログラムを変数に格納し、あとで参照しました。これは、 `String#sub!` などで(例えばコマンドラインの入力をもとに)文字列を操作する際に便利でしょう。
113
+
114
+ 1. ```hello()```: いま、私たちはCの関数を ```kprobe__``` ショートカットなしで宣言しました。後ほど説明します。BPFプログラムで宣言されたどのC関数も、probeの際に実行されることを意図されます。そのため ```pt_reg* ctx``` という変数を最初の引数に指定する必要があります。もしprobeでは実行されることのないヘルパー関数を定義する必要があれば、 ```static inline``` を宣言してコンパイラにインライン化をしてもらう必要があるでしょう。場合により ```_always_inline``` という関数attributeを指定する必要もあるでしょう。
115
+
116
+ 1. ```b.attach_kprobe(event: b.get_syscall_fnname("clone"), fn_name: "hello")```: カーネルのcloneシステムコール関数からkprobeをつくり、先ほど定義した hello() を登録、実行させます。 attach_kprobe() を複数回呼び出して、BPF内の関数を複数のカーネル関数とひもづけることも可能です。
117
+
118
+ 1. ```b.trace_fields do |...|```: trace_pipe を一行読みこんだ内容をブロック引数に格納したループを回します(ブロックなしの場合、 readline() のように読み込んだ返り値のセットを返却します)。trace_print() に近いですがこちらの方がアウトプットの加工には便利です。現実的なツールには `BPF_PERF_OUTPUT()` を使いましょう。
119
+
120
+ ### Lesson 4. sync_timing.rb
121
+
122
+ 思い出しましょう、システム管理者が ```reboot``` をする前に、 ```sync``` を3回、遅いコンソールに打ち込んでいた時代を...。それにより最初の非同期なsyncを確実に完了させていたのでしょうかね? しかしそれから、誰かが ```sync;sync;sync``` の方がスマートだと思いついて、一行で全てを実行するプラクティスが普及しました。最初の目的がダメになってしまうにうも関わらず! さらにその後、 sync コマンドは同期的になり、さらに色々な理由でこのプラクティスは馬鹿げたものになりました。やれやれ。
123
+
124
+ この後のサンプルは、どれだけ素早い間隔で ```do_sync``` が呼び出されたかを計測し、もし1秒よりも感覚が小さかったらそれをプリントするものです。```sync;sync;sync``` などと打った場合は2番目と3番目のsyncを表示してくれることでしょう:
125
+
126
+ ```
127
+ # ruby ./answers/04-sync_timing.rb
128
+ Tracing for quick sync's... Ctrl-C to end
129
+ At time 0.00 s: multiple syncs detected, last 95 ms ago
130
+ At time 0.10 s: multiple syncs detected, last 96 ms ago
131
+ ```
132
+
133
+ プログラムは [answers/04-sync_timing.rb](answers/04-sync_timing.rb):
134
+
135
+ ```ruby
136
+ require "rbbcc"
137
+ include RbBCC
138
+
139
+ # load BPF program
140
+ b = BCC.new(text: <<BPF)
141
+ #include <uapi/linux/ptrace.h>
142
+
143
+ BPF_HASH(last);
144
+
145
+ int do_trace(struct pt_regs *ctx) {
146
+ u64 ts, *tsp, delta, key = 0;
147
+
148
+ // attempt to read stored timestamp
149
+ tsp = last.lookup(&key);
150
+ if (tsp != 0) {
151
+ delta = bpf_ktime_get_ns() - *tsp;
152
+ if (delta < 1000000000) {
153
+ // output if time is less than 1 second
154
+ bpf_trace_printk("%d\\n", delta / 1000000);
155
+ }
156
+ last.delete(&key);
157
+ }
158
+
159
+ // update stored timestamp
160
+ ts = bpf_ktime_get_ns();
161
+ last.update(&key, &ts);
162
+ return 0;
163
+ }
164
+ BPF
165
+
166
+ b.attach_kprobe(event: b.get_syscall_fnname("sync"), fn_name: "do_trace")
167
+ puts("Tracing for quick sync's... Ctrl-C to end")
168
+
169
+ # format output
170
+ start = 0
171
+ b.trace_fields do |task, pid, cpu, flags, ts, ms|
172
+ start = ts.to_f if start.zero?
173
+ ts = ts.to_f - start
174
+ puts("At time %.2f s: multiple syncs detected, last %s ms ago" % [ts, ms.chomp])
175
+ end
176
+ ```
177
+
178
+ この回の学びです(全部 C の話です):
179
+
180
+ 1. ```bpf_ktime_get_ns()```: 今のカーネル内時間をナノ秒の解像度で返します。
181
+ 1. ```BPF_HASH(last)```: BPF map を、Hash(連想配列)オブジェクトとして作成します。 ```"last"``` という名前です。今回は追加の引数を指定しませんので、 u64 型のkeyとvalueで定義されます。
182
+ 1. ```key = 0```: このkey/valueストアには一つのペアしか登録しません。なので `0` でハードコーディングします。
183
+ 1. ```last.lookup(&key)```: Hashからkeyを探し、存在したらvalueへのポインタを、なければ `NULL` を返します。keyもポインタとして渡してください。
184
+ 1. ```last.delete(&key)```: Hashから指定したkeyを削除します。現在は [カーネルの `.update()` のバグがあるので](https://git.kernel.org/cgit/linux/kernel/git/davem/net.git/commit/?id=a6ed3ea65d9868fdf9eff84e6fe4f666b8d14b02) 、念のため必要です。
185
+ 1. ```last.update(&key, &ts)```: keyと2番目の引数のvalueを関連づけ、それまでのvalueを上書きします。このレコードはタイムスタンプですね。
186
+
187
+ *Note for RbBCC developers:* `trace_fields` メソッドの返り値がPython版と微妙に違うので直した方がいいです。
188
+
189
+ ### Lesson 5. sync_count.rb
190
+
191
+ 先ほどのレッスンの sync_timing.rb を変更し、すべての sync システムコールの呼び出し回数を保存するようにしましょう(早い遅いに関わらず)。そして出力しましょう。このカウントアップはBPFプログラムの中で、いまあるHashに新しいキーを導入することで記録できるでしょう。
192
+
193
+ 回答例の一つは [answers/05-sync_count.rb](answers/05-sync_count.rb) です。
194
+
195
+ ### Lesson 6. disksnoop.rb
196
+
197
+ [answers/06-disksnoop.rb](answers/06-disksnoop.rb) を見てみましょう。これがサンプル出力です:
198
+
199
+ ```
200
+ # bundle exec answers/06-disksnoop.rb
201
+ TIME(s) T BYTES LAT(ms)
202
+ 16458043.436012 W 4096 3.13
203
+ 16458043.437326 W 4096 4.44
204
+ 16458044.126545 R 4096 42.82
205
+ 16458044.129872 R 4096 3.24
206
+ [...]
207
+ ```
208
+
209
+ コードスニペットです:
210
+
211
+ ```ruby
212
+ require 'rbbcc'
213
+ include RbBCC
214
+
215
+ REQ_WRITE = 1 # from include/linux/blk_types.h
216
+
217
+ # load BPF program
218
+ b = BCC.new(text: <<CLANG)
219
+ #include <uapi/linux/ptrace.h>
220
+ #include <linux/blkdev.h>
221
+
222
+ BPF_HASH(start, struct request *);
223
+
224
+ void trace_start(struct pt_regs *ctx, struct request *req) {
225
+ // stash start timestamp by request ptr
226
+ u64 ts = bpf_ktime_get_ns();
227
+
228
+ start.update(&req, &ts);
229
+ }
230
+
231
+ void trace_completion(struct pt_regs *ctx, struct request *req) {
232
+ u64 *tsp, delta;
233
+
234
+ tsp = start.lookup(&req);
235
+ if (tsp != 0) {
236
+ delta = bpf_ktime_get_ns() - *tsp;
237
+ bpf_trace_printk("%d %x %d\\n", req->__data_len,
238
+ req->cmd_flags, delta / 1000);
239
+ start.delete(&req);
240
+ }
241
+ }
242
+ CLANG
243
+
244
+ b.attach_kprobe(event: "blk_start_request", fn_name: "trace_start")
245
+ b.attach_kprobe(event: "blk_mq_start_request", fn_name: "trace_start")
246
+ b.attach_kprobe(event: "blk_account_io_completion", fn_name: "trace_completion")
247
+ [...]
248
+ ```
249
+
250
+ 今回の学習内容です:
251
+
252
+ 1. ```REQ_WRITE```: カーネル関数をRubyのプログラムの中で定義し、後ろで利用しています。BPFのプログラムの中で REQ_WRITE 定数を使う場合、 `#include` で適切なヘッダを読み込むことで、自分で定義しなくても動作するでしょう。
253
+ 1. ```trace_start(struct pt_regs *ctx, struct request *req)```: この関数はあとでkprobeにアタッチします。このkprobe用の関数の最初の引数は ```struct pt_regs *ctx``` で、BPFのコンテクストを表します。第2引数以降で実際のカーネル関数の引数を列挙します。今回はこれを blk_start_request() に割り当てる予定で、これの最初の引数の型は ```struct request *``` です。
254
+ 1. ```start.update(&req, &ts)```: ```struct request``` へのポインタをHashのkeyに使っています。どういうことか? トレーシングでよく使う技です。構造体のポインタはkeyとしてふさわしいもので、なぜならその値はユニークだからです: 2つの構造体は同じポインタアドレスを持たないため。(freeされてアドレスが再利用される場合にだけは注意しましょう。)なので、私たちがここでしたいのは単にリクエストにタグを打ちたいだけで、それぞれのリクエストはdisk I/Oの詳細を記述しており、それごとにタイムスタンプを発行することで間隔を計測します。ちなみにタイムスタンプを格納する上では2つのkeyが使えます: 構造体のポインタと、Thread ID(特に、関数の開始とreturnまでを計測する場合)です。
255
+ 1. ```req->__data_len```: ```struct request```のメンバをデリファレンスしています。カーネルのソースコードを見てメンバが何か確認しましょう。 bcc は実際にはこれらの表現は ```bpf_probe_read()``` の呼び出しに置換しています。時として、複雑なデリファレンスには対応できないので、 ```bpf_probe_read()``` を直接呼び必要があるでしょう。
256
+
257
+ これは大変面白いプログラムで、このコードの理解ができたのなら、多くの重要な基本を理解したと言えるでしょう。なお、いまだに bpf_trace_printk() を利用していますので、次でそれを修正しましょう。
258
+
259
+ ### Lesson 7. hello_perf_output.rb
260
+
261
+ いよいよ、 `bpf_trace_printk()` の利用をやめ、適切な `BPF_PERF_OUTPUT()` インタフェースを使うようにしましょう。これは、 `trace_field()` がデフォルトで付与してくれるPIDやタイムスタンプなどの情報を自分で直接取得することを意味します。これが別のターミナルでコマンドを実行しながらのサンプルのアウトプットです。
262
+
263
+ ```
264
+ # bundle exec answers/07-hello_perf_output.rb
265
+ TIME(s) COMM PID MESSAGE
266
+ 0.000000000 bash 22986 Hello, perf_output!
267
+ 0.021080275 systemd-udevd 484 Hello, perf_output!
268
+ 0.021359520 systemd-udevd 484 Hello, perf_output!
269
+ 0.021590610 systemd-udevd 484 Hello, perf_output!
270
+ [...]
271
+ ```
272
+
273
+ コードは [answers/07-hello_perf_output.rb](answers/07-hello_perf_output.rb) です:
274
+
275
+ ```ruby
276
+ #!/usr/bin/env ruby
277
+ #
278
+ # This is a Hello World example that uses BPF_PERF_OUTPUT.
279
+ # Ported from hello_perf_output.py
280
+
281
+ require 'rbbcc'
282
+ include RbBCC
283
+
284
+ # define BPF program
285
+ prog = """
286
+ #include <linux/sched.h>
287
+
288
+ // define output data structure in C
289
+ struct data_t {
290
+ u32 pid;
291
+ u64 ts;
292
+ char comm[TASK_COMM_LEN];
293
+ };
294
+ BPF_PERF_OUTPUT(events);
295
+
296
+ int hello(struct pt_regs *ctx) {
297
+ struct data_t data = {};
298
+
299
+ data.pid = bpf_get_current_pid_tgid();
300
+ data.ts = bpf_ktime_get_ns();
301
+ bpf_get_current_comm(&data.comm, sizeof(data.comm));
302
+
303
+ events.perf_submit(ctx, &data, sizeof(data));
304
+
305
+ return 0;
306
+ }
307
+ """
308
+
309
+ # load BPF program
310
+ b = BCC.new(text: prog)
311
+ b.attach_kprobe(event: b.get_syscall_fnname("clone"), fn_name: "hello")
312
+
313
+ # header
314
+ puts("%-18s %-16s %-6s %s" % ["TIME(s)", "COMM", "PID", "MESSAGE"])
315
+
316
+ # process event
317
+ start = 0
318
+ print_event = lambda { |cpu, data, size|
319
+ event = b["events"].event(data)
320
+ if start == 0
321
+ start = event.ts
322
+ end
323
+
324
+ time_s = ((event.ts - start).to_f) / 1000000000
325
+ puts("%-18.9f %-16s %-6d %s" % [time_s, event.comm, event.pid,
326
+ "Hello, perf_output!"])
327
+ }
328
+
329
+ # loop with callback to print_event
330
+ b["events"].open_perf_buffer(&print_event)
331
+
332
+ loop do
333
+ b.perf_buffer_poll()
334
+ end
335
+ ```
336
+
337
+ 学ぶべきこと:
338
+
339
+ 1. ```struct data_t```: これは、カーネルからユーザースペースに渡すデータの構造を宣言しています。
340
+ 1. ```BPF_PERF_OUTPUT(events)```: 私たちが今から使うチャンネルを "events" と名付けています。
341
+ 1. ```struct data_t data = {};```: 空の `data_t` 構造体を作成し、その後中身を埋めます。
342
+ 1. ```bpf_get_current_pid_tgid()```: 下位の32bitで、「プロセスID/PID」を返します(カーネルから見たPIDです。ユーザスペースからは、一般にスレッドIDと呼ばれます)。そしてスレッドグループID/TGIDは上位32bitに含まれています(これは、ユーザスペースで言うところのPIDです)。この関数を返り値をu32型の変数に格納すると、上位の32bit分は破棄されます。PIDとTGIDのどちらを利用すべきでしょうか? マルチスレッドなアプリケーションでは、どのスレッドもTGIDは同じはずです。したがってもし必要であればPIDで区別する必要があります。そしてこれはツールのエンドユーザーが予期しているところでもあるでしょう。
343
+ 1. ```bpf_get_current_comm()```: 最初の引数のポインタアドレスに現在のプロセス名を格納します。
344
+ 1. ```events.perf_submit()```: ここで、perfのリングバッファを経由して、イベントをユーザスペースに送信します。
345
+ 1. ```print_event = lambda { ... }```: ```events``` ストリームから流れてくるイベントをハンドルするRubyのprocオブジェクト(lamnbda)を定義します; ところでPythonと違い、Ruby版の `Table#open_perf_buffer` は直接ブロックを受け取ることもできます :)
346
+ 1. ```b["events"].event(data)```: ここで、Cの定義から自動生成したRubyのオブジェクトとして、イベントデータを受け取ります。
347
+ 1. ```b["events"].open_perf_buffer(&print_event)```: proc ```print_event``` をCで定義した ```events``` と関連づけます。
348
+ 1. ```loop { b.perf_buffer_poll() }```: イベントが来るのを待ち構えます。
349
+
350
+ ### Lesson 8. sync_perf_output.rb
351
+
352
+ 前のレッスンのsync_timing.rbを、 ```BPF_PERF_OUTPUT``` を使うよう書き換えてください。
353
+
354
+ 回答例はこちら: [answers/08-sync_perf_output.rb](answers/08-sync_perf_output.rb).
355
+
356
+ ### Lesson 9. bitehist.rb
357
+
358
+ 次のツールは、disk I/Oのサイズを記録しヒストグラムで可視化します。サンプル出力です:
359
+
360
+ ```
361
+ # bundle exec answers/09-bitehist.rb
362
+ Tracing... Hit Ctrl-C to end.
363
+ ^C
364
+ kbytes : count distribution
365
+ 0 -> 1 : 3 | |
366
+ 2 -> 3 : 0 | |
367
+ 4 -> 7 : 211 |********** |
368
+ 8 -> 15 : 0 | |
369
+ 16 -> 31 : 0 | |
370
+ 32 -> 63 : 0 | |
371
+ 64 -> 127 : 1 | |
372
+ 128 -> 255 : 800 |**************************************|
373
+ ```
374
+
375
+ コードは [answers/09-bitehist.rb](answers/09-bitehist.rb) です:
376
+
377
+ ```ruby
378
+ require 'rbbcc'
379
+ include RbBCC
380
+
381
+ # load BPF program
382
+ b = BCC.new(text: <<BPF)
383
+ #include <uapi/linux/ptrace.h>
384
+ #include <linux/blkdev.h>
385
+
386
+ BPF_HISTOGRAM(dist);
387
+
388
+ int kprobe__blk_account_io_completion(struct pt_regs *ctx, struct request *req)
389
+ {
390
+ dist.increment(bpf_log2l(req->__data_len / 1024));
391
+ return 0;
392
+ }
393
+ BPF
394
+
395
+ # header
396
+ puts("Tracing... Hit Ctrl-C to end.")
397
+
398
+ # trace until Ctrl-C
399
+ begin
400
+ loop { sleep 0.1 }
401
+ rescue Interrupt
402
+ puts
403
+ end
404
+
405
+ # output
406
+ b["dist"].print_log2_hist("kbytes")
407
+ ```
408
+
409
+ ここまでのレッスンのおさらい:
410
+
411
+ - ```kprobe__```: このプレフィックスにより、その後ろの名前のカーネル関数をkprobeでの計測対象にすることを意味します。
412
+ - ```struct pt_regs *ctx, struct request *req```: kprobeへの引数です。 ```ctx``` でBPFのコンテクストを取得し、 ```req``` は計測対象の関数 ```blk_account_io_completion()``` の最初の引数です。
413
+ - ```req->__data_len```: その `req` のメンバをデリファレンスします。
414
+
415
+ 新しい要素です:
416
+
417
+ 1. ```BPF_HISTOGRAM(dist)```: ヒストグラムのためのBPF mapオブジェクトを定義し、 "dist" と名付けます。
418
+ 1. ```dist.increment()```: 第1引数で指定されたインデックスのヒストグラム上の階級を1つインクリメントします。オプションとして。第2引数にインクリメントする度合いを指定することもできます。
419
+ 1. ```bpf_log2l()```: 引数の値の log2() を計算し返します。これがヒストグラムのインデックスになるので、2のべき乗のヒストグラムを作成することになります。
420
+ 1. ```b["dist"].print_log2_hist("kbytes")```: "dist" に加工のしたヒストグラムを、 "kbytes" と言うヘッダで出力します。カーネルからユーザスペースへ送信されるデータは各階級のカウントだけになるので、効率的です。
421
+
422
+ ### Lesson 10. disklatency.rb
423
+
424
+ disk I/O のレイテンシを計測し、ヒストグラムを出力するプログラムを書きましょう。disk I/Oのトレースと時間計測は以前書いたdisksnoop.rbで、ヒストグラムの作り方はさきほどのbitehist.rbを参考にできます。
425
+
426
+ 回答例は: [answers/10-disklatency.rb](answers/10-disklatency.rb).
427
+
428
+ ### Lesson 11. vfsreadlat.rb
429
+
430
+ このサンプルはRubyとCのファイルに分かれています。出力例です:
431
+
432
+ ```
433
+ # bundle exec answers/11-vfsreadlat.rb 1
434
+ Tracing... Hit Ctrl-C to end.
435
+ usecs : count distribution
436
+ 0 -> 1 : 0 | |
437
+ 2 -> 3 : 2 |*********** |
438
+ 4 -> 7 : 7 |****************************************|
439
+ 8 -> 15 : 4 |********************** |
440
+
441
+ usecs : count distribution
442
+ 0 -> 1 : 29 |****************************************|
443
+ 2 -> 3 : 28 |************************************** |
444
+ 4 -> 7 : 4 |***** |
445
+ 8 -> 15 : 8 |*********** |
446
+ 16 -> 31 : 0 | |
447
+ 32 -> 63 : 0 | |
448
+ 64 -> 127 : 0 | |
449
+ 128 -> 255 : 0 | |
450
+ 256 -> 511 : 2 |** |
451
+ 512 -> 1023 : 0 | |
452
+ 1024 -> 2047 : 0 | |
453
+ 2048 -> 4095 : 0 | |
454
+ 4096 -> 8191 : 4 |***** |
455
+ 8192 -> 16383 : 6 |******** |
456
+ 16384 -> 32767 : 9 |************ |
457
+ 32768 -> 65535 : 6 |******** |
458
+ 65536 -> 131071 : 2 |** |
459
+
460
+ usecs : count distribution
461
+ 0 -> 1 : 11 |****************************************|
462
+ 2 -> 3 : 2 |******* |
463
+ 4 -> 7 : 10 |************************************ |
464
+ 8 -> 15 : 8 |***************************** |
465
+ 16 -> 31 : 1 |*** |
466
+ 32 -> 63 : 2 |******* |
467
+ [...]
468
+ ```
469
+
470
+ コードは [answers/11-vfsreadlat.rb](answers/11-vfsreadlat.rb) と [answers/11-vfsreadlat.c](answers/11-vfsreadlat.c) にあるので見てみましょう。学べることは:
471
+
472
+ 1. ```b = BCC.new(src_file: "vfsreadlat.c")```: BPF Cプログラムを別の場所から読み込みます。Rubyファイルと同じディレクトリにあればOKです。
473
+ 1. ```b.attach_kretprobe(event: "vfs_read", fn_name: "do_return")```: BPFのC関数 ```do_return()``` をカーネル関数 ```vfs_read()``` にアタッチします。これは kretprobe と呼ばれるもので、いままでのように関数のエントリーではなく、関数のreturnを計測するものです。
474
+ 1. ```b["dist"].clear()```: ヒストグラムをRuby側からクリアします。定期的なインターバルで計測するためです。
475
+
476
+ ### Lesson 12. urandomread.rb
477
+
478
+ ```dd if=/dev/urandom of=/dev/null bs=8k count=5``` の実行をトレースします:
479
+
480
+ ```
481
+ # bundle exec answers/12-urandomread.rb
482
+ TIME(s) COMM PID GOTBITS
483
+ 24652832.956994001 smtp 24690 384
484
+ 24652837.726500999 dd 24692 65536
485
+ 24652837.727111001 dd 24692 65536
486
+ 24652837.727703001 dd 24692 65536
487
+ 24652837.728294998 dd 24692 65536
488
+ 24652837.728888001 dd 24692 65536
489
+ ```
490
+
491
+ おや!偶然smtpのイベントも捕まえました。コードは [answers/12-urandomread.rb](answers/12-urandomread.rb):
492
+
493
+ ```ruby
494
+ require 'rbbcc'
495
+ include RbBCC
496
+
497
+ b = BCC.new(text: <<BPF)
498
+ TRACEPOINT_PROBE(random, urandom_read) {
499
+ // args is from /sys/kernel/debug/tracing/events/random/urandom_read/format
500
+ bpf_trace_printk("%d\\n", args->got_bits);
501
+ return 0;
502
+ }
503
+ BPF
504
+
505
+ # header
506
+ puts("%-18s %-16s %-6s %s" % ["TIME(s)", "COMM", "PID", "GOTBITS"])
507
+
508
+ # format output
509
+ loop do
510
+ begin
511
+ b.trace_fields do |task, pid, cpu, flags, ts, msg|
512
+ puts("%-18.9f %-16s %-6d %s" % [ts, task, pid, msg])
513
+ end
514
+ rescue Interrupt
515
+ exit
516
+ end
517
+ end
518
+ ```
519
+
520
+ 学ぶことです:
521
+
522
+ 1. ```TRACEPOINT_PROBE(random, urandom_read)```: カーネルの tracepoint(Rubyの `TracePoint` クラスとは名前が同じだけです)である ```random:urandom_read``` を計測します。このtracepointは「安定した(stable)」APIを提供します。カーネル関数のようにバージョンによって変わることがないと言う意味です。そのため、可能な限りkprobeよりもこちらを使うことが推奨されます。 ```perf list``` コマンドを実行すればtracepointのリストが手に入ります。BPFのプログラムをtracepointにアタッチするにはLinuxのバージョン4.7以上が必要です。
523
+ 1. ```args->got_bits```: ```args``` はTRACEPOINT_PROBEマクロが自動定義する変数で、tracepointの引数が格納された構造体です。上にあるコメントはその構造を確認できるLinux上のファイルです。例えば:
524
+
525
+ ```
526
+ # cat /sys/kernel/debug/tracing/events/random/urandom_read/format
527
+ name: urandom_read
528
+ ID: 972
529
+ format:
530
+ field:unsigned short common_type; offset:0; size:2; signed:0;
531
+ field:unsigned char common_flags; offset:2; size:1; signed:0;
532
+ field:unsigned char common_preempt_count; offset:3; size:1; signed:0;
533
+ field:int common_pid; offset:4; size:4; signed:1;
534
+
535
+ field:int got_bits; offset:8; size:4; signed:1;
536
+ field:int pool_left; offset:12; size:4; signed:1;
537
+ field:int input_left; offset:16; size:4; signed:1;
538
+
539
+ print fmt: "got_bits %d nonblocking_pool_entropy_left %d input_entropy_left %d", REC->got_bits, REC->pool_left, REC->input_left
540
+ ```
541
+
542
+ 今回は、 ```got_bits``` メンバを表示しています。
543
+
544
+ ### Lesson 13. disksnoop.rb fixed
545
+
546
+ ここまでのレッスンで作成した disksnoop.rb を、 ```block:block_rq_issue``` と ```block:block_rq_complete``` tracepointを使って書き直しましょう。
547
+
548
+ たとえば、回答例は [answers/13-disksnoop_fixed.rb](answers/13-disksnoop_fixed.rb) です。
549
+
550
+
551
+ ### Lesson 14. strlen_count.rb
552
+
553
+ このプログラムは(カーネルではなく)ユーザレベルの関数をトレースします。今回はライブラリ関数 ```strlen()``` で、その引数の文字列の登場回数をカウントします。こう言う出力です:
554
+
555
+ ```
556
+ # bundle exec answers/14-strlen_count.rb
557
+ Tracing strlen()... Hit Ctrl-C to end.
558
+ ^C COUNT STRING
559
+ 1 " "
560
+ 1 "/bin/ls"
561
+ 1 "."
562
+ 1 "cpudist.py.1"
563
+ 1 ".bashrc"
564
+ 1 "ls --color=auto"
565
+ 1 "key_t"
566
+ [...]
567
+ 10 "a7:~# "
568
+ 10 "/root"
569
+ 12 "LC_ALL"
570
+ 12 "en_US.UTF-8"
571
+ 13 "en_US.UTF-8"
572
+ 20 "~"
573
+ 70 "#%^,~:-=?+/}"
574
+ 340 "\x01\x1b]0;root@bgregg-test: ~\x07\x02root@bgregg-test:~# "
575
+ ```
576
+
577
+ このライブラリ関数で処理される文字列には様々な種類があることが、登場回数のカウントからわかります。たとえば "LC_ALL" と言う文字列は12回 ```strlen()``` の引数になりました。
578
+
579
+ コードは [answers/14-strlen_count.rb](answers/14-strlen_count.rb):
580
+
581
+ ```ruby
582
+ require 'rbbcc'
583
+ include RbBCC
584
+
585
+ # load BPF program
586
+ b = BCC.new(text: <<BPF)
587
+ #include <uapi/linux/ptrace.h>
588
+
589
+ struct key_t {
590
+ char c[80];
591
+ };
592
+ BPF_HASH(counts, struct key_t);
593
+
594
+ int count(struct pt_regs *ctx) {
595
+ if (!PT_REGS_PARM1(ctx))
596
+ return 0;
597
+
598
+ struct key_t key = {};
599
+ u64 zero = 0, *val;
600
+
601
+ bpf_probe_read(&key.c, sizeof(key.c), (void *)PT_REGS_PARM1(ctx));
602
+ // could also use `counts.increment(key)`
603
+ val = counts.lookup_or_try_init(&key, &zero);
604
+ if (val) {
605
+ (*val)++;
606
+ }
607
+ return 0;
608
+ };
609
+ BPF
610
+ b.attach_uprobe(name: "c", sym: "strlen", fn_name: "count")
611
+
612
+ # header
613
+ print("Tracing strlen()... Hit Ctrl-C to end.")
614
+
615
+ # sleep until Ctrl-C
616
+ begin
617
+ sleep(99999999)
618
+ rescue Interrupt
619
+ puts
620
+ end
621
+
622
+ # print output
623
+ puts("%10s %s" % ["COUNT", "STRING"])
624
+ counts = b.get_table("counts")
625
+ counts.items.sort_by{|k, v| v.to_bcc_value }.each do |k, v|
626
+ # unpack following definition of struct key_t above
627
+ puts("%10d %s" % [v.to_bcc_value, k[0, k.size].unpack("Z*")[0]])
628
+ end
629
+ ```
630
+
631
+ 今回の学びです:
632
+
633
+ 1. ```PT_REGS_PARM1(ctx)```: ```strlen()``` の最初の引数をレジスタから取り出します。文字列です。
634
+ 1. ```b.attach_uprobe(name: "c", sym: "strlen", fn_name: "count")```: "c" ライブラリ(もし関数がプログラムの側にあるのなら、バイナリのpathnameを用いてください)にアタッチし、ユーザレベル関数 ```strlen()``` を計測、その実行のたびに私たちが定義したC関数 ```count()``` を呼び出します。
635
+ 1. ```BPF_HASH``` から `Table#items` で取り出した値をRubyで扱うには、面倒ですが ```k/v.to_bcc_value``` をRubyのブロックの中などで呼び出す必要があります。この挙動はRuby版の実装の都合によるものですが、将来変更する可能性があります。
636
+
637
+ ### Lesson 15. nodejs_http_server.rb
638
+
639
+ このプログラムはユーザが静的に定義したトレースポイント(User Statically-Defined Tracing: **USDT**)をprobeとして計測するもので、USDTはユーザランドにおけるカーネルのtracepointに相当するものです。出力例です:
640
+
641
+ ```
642
+ # bundle exec answers/15-nodejs_http_server.rb
643
+ TIME(s) COMM PID ARGS
644
+ 24653324.561322998 node 24728 path:/index.html
645
+ 24653335.343401998 node 24728 path:/images/welcome.png
646
+ 24653340.510164998 node 24728 path:/images/favicon.png
647
+ ```
648
+
649
+ 回答例は [answers/15-nodejs_http_server.rb](answers/15-nodejs_http_server.rb) にあります; 同じディレクトリにあるnode.jsのサーバプログラムを、USDTを有効にした(`--enable-dtrace`)ビルドのnode.jsバイナリで実行して計測する必要があります。
650
+
651
+ ```ruby
652
+ require 'rbbcc'
653
+ include RbBCC
654
+
655
+ if ARGV.size != 1 :
656
+ print("USAGE: #{$0} PID")
657
+ exit()
658
+ end
659
+ pid = ARGV[0]
660
+ debug = !!ENV['DEBUG']
661
+
662
+ # load BPF program
663
+ bpf_text = <<BPF
664
+ #include <uapi/linux/ptrace.h>
665
+ int do_trace(struct pt_regs *ctx) {
666
+ uint64_t addr;
667
+ char path[128]={0};
668
+ bpf_usdt_readarg(6, ctx, &addr);
669
+ bpf_probe_read(&path, sizeof(path), (void *)addr);
670
+ bpf_trace_printk("path:%s\\n", path);
671
+ return 0;
672
+ };
673
+ BPF
674
+
675
+ # enable USDT probe from given PID
676
+ u = USDT.new(pid: pid.to_i)
677
+ u.enable_probe(probe: "http__server__request", fn_name: "do_trace")
678
+ if debug
679
+ puts(u.get_text)
680
+ puts(bpf_text)
681
+ end
682
+
683
+ # initialize BPF
684
+ b = BCC.new(text: bpf_text, usdt_contexts: [u])
685
+ ```
686
+
687
+ 今回の学び:
688
+
689
+ 1. ```bpf_usdt_readarg(6, ctx, &addr)```: USDT probeの6番目の引数が格納されたアドレスを、 ```addr``` に読み込みます。
690
+ 1. ```bpf_probe_read(&path, sizeof(path), (void *)addr)```: ここで、 ```addr``` が示している文字列をBPFプログラム側の ```path``` に格納します。
691
+ 1. ```u = USDT.new(pid: pid.to_i)```: USDTのトレースを与えられたPIDに対してできるよう初期化します。
692
+ 1. ```u.enable_probe(probe: "http__server__request", fn_name: "do_trace")```: 私たちの書いたBPF C関数 ```do_trace()``` をNode.jsのUSDTである ```http__server__request``` probeにアタッチします。
693
+ 1. ```b = BCC.new(text: bpf_text, usdt_contexts: [u])```: BPFのオブジェクトを作る際に、先ほどのUSDTオブジェクト ```u``` を渡す必要があります。
694
+
695
+ もちろん、Ruby自身にもUSDTが存在するので、このレッスンでサンプルを追加する予定です(P/Rは歓迎です)。
696
+
697
+ ### Lesson 16. task_switch.c
698
+
699
+ *これは古いチュートリアルで、ボーナスレッスンとして含めています。ここまでに学んだことの復習と強化のために活用してください。*
700
+
701
+ これはHello Worldよりはもう若干複雑なトレースで、
702
+ このプログラムは実行タスクのスイッチがカーネルで行われるごとに実行されます。
703
+ そして古いPIDと新しいPIDがBPF mapに記録されます。
704
+
705
+ このCプログラムは新しいコンセプト: `prev` 引数を導入しています。
706
+ この引数はBCCのフロントエンドから特別な扱いを受けます。この引数へのアクセスは。kprobeに渡されるセーブ済みのコンテクストから読み込まれます。
707
+ 関数のプロトタイプの2つ目の引数は、kprobeされているカーネル関数の最初の引数と一致する必要があります。
708
+ そのように宣言されれば、プログラムはシームレスにカーネル関数のパラメータにアクセス可能です。
709
+
710
+ ```c
711
+ #include <uapi/linux/ptrace.h>
712
+ #include <linux/sched.h>
713
+
714
+ struct key_t {
715
+ u32 prev_pid;
716
+ u32 curr_pid;
717
+ };
718
+
719
+ BPF_HASH(stats, struct key_t, u64, 1024);
720
+ int count_sched(struct pt_regs *ctx, struct task_struct *prev) {
721
+ struct key_t key = {};
722
+ u64 zero = 0, *val;
723
+
724
+ key.curr_pid = bpf_get_current_pid_tgid();
725
+ key.prev_pid = prev->pid;
726
+
727
+ // could also use `stats.increment(key);`
728
+ val = stats.lookup_or_try_init(&key, &zero);
729
+ if (val) {
730
+ (*val)++;
731
+ }
732
+ return 0;
733
+ }
734
+ ```
735
+
736
+ ユーザスペース側のコンポネントはCファイルを以下のように読み込み、
737
+ `finish_task_switch` カーネル関数にアタッチします。
738
+ BPFオブジェクトは `BCC#[]` を経由して、プログラムの BPF_HASH にそれぞれの名前でアクセスできます。
739
+ これにより、カーネル側に存在する値にパススルーでアクセスが可能になります。
740
+ 取り出したオブジェクトはRubyのHashのように扱えます: each を実装した `Enumerable` で、また読み込み、更新、削除が全て可能です。
741
+
742
+ ```ruby
743
+ require 'rbbcc'
744
+ include RbBCC
745
+
746
+ b = BCC.new(src_file: "16-task_switch.c")
747
+ b.attach_kprobe(event: "finish_task_switch", fn_name: "count_sched")
748
+
749
+ # generate many schedule events
750
+ 100.times { sleep 0.01 }
751
+
752
+ b["stats"].each do |_k, v|
753
+ k = _k[0, 8].unpack("i! i!") # Handling pointer without type!!
754
+ puts("task_switch[%5d->%5d]=%u" % [k[0], k[1], v.to_bcc_value])
755
+ end
756
+ ```
757
+
758
+ これらのプログラムは2つのファイル [answers/16-task_switch.c](answers/16-task_switch.c) と [answers/16-task_switch.rb](answers/16-task_switch.rb) で確認できます。
759
+
760
+ ### Lesson 17. Further Study
761
+
762
+ さらに学習したい場合、 [BCC のオリジナルのドキュメント](https://github.com/iovisor/bcc/tree/master/docs) や、 Sasha Goldshteinの[linux-tracing-workshop](https://github.com/goldshtn/linux-tracing-workshop) に発展的な内容が含まれています。また、 rbbcc/bcc の /tools ディレクトリも参考になるでしょう。今のところは、Pythonのコードの知識も助けになることでしょう :)
763
+
764
+ rbbccにツールを作って貢献したい場合、 [CONTRIBUTING.md](../CONTRIBUTING.md) を読んでください(準備中です)。 [README.md](../README.md) の下にはコンタクトすべきリストもあります(これも準備中; いまいまは @udzura で構いません)。では、良い旅を。Enjoy Tracing!
765
+
766
+ ---
767
+
768
+ ## Networking
769
+
770
+ やる気はある。
@@ -0,0 +1,114 @@
1
+ #include <uapi/linux/ptrace.h>
2
+ #include <net/sock.h>
3
+ #include <bcc/proto.h>
4
+
5
+ #define IP_TCP 6
6
+ #define ETH_HLEN 14
7
+
8
+ /*eBPF program.
9
+ Filter IP and TCP packets, having payload not empty
10
+ and containing "HTTP", "GET", "POST" ... as first bytes of payload
11
+ if the program is loaded as PROG_TYPE_SOCKET_FILTER
12
+ and attached to a socket
13
+ return 0 -> DROP the packet
14
+ return -1 -> KEEP the packet and return it to user space (userspace can read it from the socket_fd )
15
+ */
16
+ int http_filter(struct __sk_buff *skb) {
17
+
18
+ u8 *cursor = 0;
19
+
20
+ struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet));
21
+ //filter IP packets (ethernet type = 0x0800)
22
+ if (!(ethernet->type == 0x0800)) {
23
+ goto DROP;
24
+ }
25
+
26
+ struct ip_t *ip = cursor_advance(cursor, sizeof(*ip));
27
+ //filter TCP packets (ip next protocol = 0x06)
28
+ if (ip->nextp != IP_TCP) {
29
+ goto DROP;
30
+ }
31
+
32
+ u32 tcp_header_length = 0;
33
+ u32 ip_header_length = 0;
34
+ u32 payload_offset = 0;
35
+ u32 payload_length = 0;
36
+
37
+ //calculate ip header length
38
+ //value to multiply * 4
39
+ //e.g. ip->hlen = 5 ; IP Header Length = 5 x 4 byte = 20 byte
40
+ ip_header_length = ip->hlen << 2; //SHL 2 -> *4 multiply
41
+
42
+ //check ip header length against minimum
43
+ if (ip_header_length < sizeof(*ip)) {
44
+ goto DROP;
45
+ }
46
+
47
+ //shift cursor forward for dynamic ip header size
48
+ void *_ = cursor_advance(cursor, (ip_header_length-sizeof(*ip)));
49
+
50
+ struct tcp_t *tcp = cursor_advance(cursor, sizeof(*tcp));
51
+
52
+ //calculate tcp header length
53
+ //value to multiply *4
54
+ //e.g. tcp->offset = 5 ; TCP Header Length = 5 x 4 byte = 20 byte
55
+ tcp_header_length = tcp->offset << 2; //SHL 2 -> *4 multiply
56
+
57
+ //calculate payload offset and length
58
+ payload_offset = ETH_HLEN + ip_header_length + tcp_header_length;
59
+ payload_length = ip->tlen - ip_header_length - tcp_header_length;
60
+
61
+ //http://stackoverflow.com/questions/25047905/http-request-minimum-size-in-bytes
62
+ //minimum length of http request is always geater than 7 bytes
63
+ //avoid invalid access memory
64
+ //include empty payload
65
+ if(payload_length < 7) {
66
+ goto DROP;
67
+ }
68
+
69
+ //load first 7 byte of payload into p (payload_array)
70
+ //direct access to skb not allowed
71
+ unsigned long p[7];
72
+ int i = 0;
73
+ for (i = 0; i < 7; i++) {
74
+ p[i] = load_byte(skb , payload_offset + i);
75
+ }
76
+
77
+ //find a match with an HTTP message
78
+ //HTTP
79
+ if ((p[0] == 'H') && (p[1] == 'T') && (p[2] == 'T') && (p[3] == 'P')) {
80
+ goto KEEP;
81
+ }
82
+ //GET
83
+ if ((p[0] == 'G') && (p[1] == 'E') && (p[2] == 'T')) {
84
+ goto KEEP;
85
+ }
86
+ //POST
87
+ if ((p[0] == 'P') && (p[1] == 'O') && (p[2] == 'S') && (p[3] == 'T')) {
88
+ goto KEEP;
89
+ }
90
+ //PUT
91
+ if ((p[0] == 'P') && (p[1] == 'U') && (p[2] == 'T')) {
92
+ goto KEEP;
93
+ }
94
+ //DELETE
95
+ if ((p[0] == 'D') && (p[1] == 'E') && (p[2] == 'L') && (p[3] == 'E') && (p[4] == 'T') && (p[5] == 'E')) {
96
+ goto KEEP;
97
+ }
98
+ //HEAD
99
+ if ((p[0] == 'H') && (p[1] == 'E') && (p[2] == 'A') && (p[3] == 'D')) {
100
+ goto KEEP;
101
+ }
102
+
103
+ //no HTTP match
104
+ goto DROP;
105
+
106
+ //keep the packet and send it to userspace retruning -1
107
+ KEEP:
108
+ return -1;
109
+
110
+ //drop the packet returning 0
111
+ DROP:
112
+ return 0;
113
+
114
+ }
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ #Original http-parse-simple.py in invisor/bcc
4
+ #Bertrone Matteo - Polytechnic of Turin
5
+ #November 2015
6
+ #Ruby version by Uchio Kondo, License follows
7
+ #
8
+ #eBPF application that parses HTTP packets
9
+ #and extracts (and prints on screen) the URL contained in the GET/POST request.
10
+ #
11
+ #eBPF program http_filter is used as SOCKET_FILTER attached to eth0 interface.
12
+ #only packet of type ip and tcp containing HTTP GET/POST are returned to userspace, others dropped
13
+ #
14
+ #python script uses bcc BPF Compiler Collection by iovisor (https://github.com/iovisor/bcc)
15
+ #and prints on stdout the first line of the HTTP GET/POST request containing the url
16
+
17
+ require 'rbbcc'
18
+ require 'socket'
19
+ require 'io/nonblock'
20
+ include RbBCC
21
+
22
+ def usage
23
+ puts <<-USAGE
24
+ USAGE: #{$0} [-i <if_name>]
25
+ USAGE
26
+ exit
27
+ end
28
+
29
+ interface = "eth0"
30
+
31
+ if ARGV.size == 2
32
+ if ARGV[0] == '-i'
33
+ interface = ARGV[1]
34
+ else
35
+ usage
36
+ end
37
+ elsif ARGV.size != 0
38
+ usage
39
+ end
40
+
41
+ puts("binding socket to '%s'" % interface)
42
+
43
+ bpf = BCC.new(src_file: "http-parse-simple.c")
44
+
45
+ function_http_filter = bpf.load_func("http_filter", BPF::SOCKET_FILTER)
46
+
47
+ BCC.attach_raw_socket(function_http_filter, interface)
48
+
49
+ socket_fd = function_http_filter[:sock]
50
+
51
+ sock = Socket.for_fd socket_fd
52
+ sock.nonblock = false
53
+
54
+ ETH_HLEN = 14
55
+ loop do
56
+ packet_str = sock.sysread(2048)
57
+ packet_bytearray = packet_str.bytes
58
+
59
+ # See original comment...
60
+ #calculate packet total length
61
+ total_length = packet_bytearray[ETH_HLEN + 2] #load MSB
62
+ total_length = total_length << 8 #shift MSB
63
+ total_length = total_length + packet_bytearray[ETH_HLEN + 3] #add LSB
64
+
65
+ #calculate ip header length
66
+ ip_header_length = packet_bytearray[ETH_HLEN] #load Byte
67
+ ip_header_length = ip_header_length & 0x0F #mask bits 0..3
68
+ ip_header_length = ip_header_length << 2 #shift to obtain length
69
+
70
+ tcp_header_length = packet_bytearray[ETH_HLEN + ip_header_length + 12] #load Byte
71
+ tcp_header_length = tcp_header_length & 0xF0 #mask bit 4..7
72
+ tcp_header_length = tcp_header_length >> 2 #SHR 4 ; SHL 2 -> SHR 2
73
+
74
+ payload_offset = ETH_HLEN + ip_header_length + tcp_header_length
75
+
76
+ ((payload_offset-1)..(packet_bytearray.size-1)).each do |i|
77
+ if packet_bytearray[i] == 0x0A
78
+ if packet_bytearray[i-1] == 0x0D
79
+ break
80
+ end
81
+ end
82
+ print(packet_bytearray[i].chr)
83
+ end
84
+ puts
85
+ end
@@ -175,6 +175,23 @@ module RbBCC
175
175
  module_ = (show_module && module_) ? " [#{File.basename.basename(module_)}]" : ""
176
176
  return name + module_
177
177
  end
178
+
179
+ def attach_raw_socket(fn, dev)
180
+ unless fn.is_a?(Hash)
181
+ raise "arg 1 must be of BPF.Function Hash"
182
+ end
183
+ sock = Clib.bpf_open_raw_sock(dev)
184
+ if sock < 0
185
+ raise SystemCallError.new("Failed to open raw device %s" % dev, Fiddle.last_error)
186
+ end
187
+
188
+ res = Clib.bpf_attach_socket(sock, fn[:fd])
189
+ if res < 0
190
+ raise SystemCallError.new("Failed to attach BPF to device %s" % dev, Fiddle.last_error)
191
+ end
192
+ fn[:sock] = sock
193
+ fn
194
+ end
178
195
  end
179
196
 
180
197
  def initialize(text: "", src_file: nil, hdr_file: nil, debug: 0, cflags: [], usdt_contexts: [], allow_rlimit: 0)
@@ -156,6 +156,9 @@ module RbBCC
156
156
  extern 'int perf_reader_poll(int num_readers, struct perf_reader **readers, int timeout)'
157
157
 
158
158
  extern 'void bcc_procutils_free(const char *ptr)'
159
+
160
+ extern 'int bpf_open_raw_sock(const char *name)'
161
+ extern 'int bpf_attach_socket(int sockfd, int progfd)'
159
162
  end
160
163
  end
161
164
 
@@ -1,3 +1,3 @@
1
1
  module RbBCC
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbbcc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Uchio Kondo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-13 00:00:00.000000000 Z
11
+ date: 2020-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -107,6 +107,7 @@ files:
107
107
  - docs/getting_started.md
108
108
  - docs/projects_using_rbbcc.md
109
109
  - docs/tutorial_bcc_ruby_developer.md
110
+ - docs/tutorial_bcc_ruby_developer_japanese.md
110
111
  - examples/bitehist.rb
111
112
  - examples/charty/Gemfile
112
113
  - examples/charty/Gemfile.lock
@@ -120,6 +121,8 @@ files:
120
121
  - examples/hello_world.rb
121
122
  - examples/kvm_hypercall.rb
122
123
  - examples/mallocstack.rb
124
+ - examples/networking/http_filter/http-parse-simple.c
125
+ - examples/networking/http_filter/http-parse-simple.rb
123
126
  - examples/tools/execsnoop.rb
124
127
  - examples/tools/runqlat.rb
125
128
  - examples/urandomread-explicit.rb