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 +4 -4
- data/Gemfile.lock +1 -1
- data/docs/tutorial_bcc_ruby_developer.md +12 -10
- data/docs/tutorial_bcc_ruby_developer_japanese.md +770 -0
- data/examples/networking/http_filter/http-parse-simple.c +114 -0
- data/examples/networking/http_filter/http-parse-simple.rb +85 -0
- data/lib/rbbcc/bcc.rb +17 -0
- data/lib/rbbcc/clib.rb +3 -0
- data/lib/rbbcc/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6eda25040fa0dc56bed7b9d672af03ab3f6dbd70a4ebca3e5310331c1f9098c
|
4
|
+
data.tar.gz: ef43f8bd481718f9baf21e2ba79c2a3d35b18d2767fc9572d436338244e83731
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9e19d1eb21813f2aef023ca6bb8c71e51052bf1dfe6b66013e4918129b8f538390098fca5f60f50fbba305482e2f23c2d378ca2f8b7422dc34c31e2d70bf8c3
|
7
|
+
data.tar.gz: 4f506d2a3350304fcad7de75d02b832c11a204588540ca8d210322fa7aef677587b855f2fa64fbeba3a63ba5a35c6a50f480b6d8095f6b081d4e12d383eca0b5
|
data/Gemfile.lock
CHANGED
@@ -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
|
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
|
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.
|
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
|
-
#
|
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.
|
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
|
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.
|
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
|
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
|
data/lib/rbbcc/bcc.rb
CHANGED
@@ -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)
|
data/lib/rbbcc/clib.rb
CHANGED
@@ -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
|
|
data/lib/rbbcc/version.rb
CHANGED
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.
|
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-
|
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
|