rbbcc 0.1.0 → 0.1.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 +2 -2
- data/docs/README.md +5 -0
- data/docs/getting_started.md +154 -0
- data/examples/disksnoop.rb +73 -0
- data/examples/hello_fields.rb +32 -0
- data/examples/kvm_hypercall.rb +69 -0
- data/examples/mallocstack.rb +63 -0
- data/lib/rbbcc/bcc.rb +167 -6
- data/lib/rbbcc/clib.rb +19 -1
- data/lib/rbbcc/symbol_cache.rb +5 -5
- data/lib/rbbcc/table.rb +38 -2
- data/lib/rbbcc/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccce892b1f35442a6a076717e95ff3d0dd1f3ea9b7454042ea1ed6edcafdf3ef
|
4
|
+
data.tar.gz: 928a6c4f96aec375d0d736122fefbbcfcabaafcb5be6ee0945facfcc59c8521a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b9aefc4bec5f184ae268fe4191bb5de1271e52ce8a6198fe38bae9e558dd273f4c893675581c9d0700d67a3e55d5b5a961d145431ed9ca01785ed4e9183af19
|
7
|
+
data.tar.gz: 9b5dabad56e3ee0453473aeca770251ba3453cb0e822503c0ad86fc8016a2768a62444b05191303e6a11bdd88aaf353097d691dc1e59d12cb41bf4d694b68f47
|
data/Gemfile.lock
CHANGED
data/docs/README.md
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# Getting Started
|
2
|
+
|
3
|
+
RbBCC is a project to utilize the power of eBPF/BCC from Ruby.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
RbBCC requires `libbcc.so` version **0.10.0**(we plan to support newer version of libbcc, but it may be done after rbbcc 1.0...).
|
8
|
+
|
9
|
+
BTW we do not need to install header files, becuase current version of RbBCC uses the functionality of libbcc via ffi(We use MRI's standard library **fiddle**, not external gems).
|
10
|
+
|
11
|
+
We can install this shared library via package `libbcc` from official iovisor project repo:
|
12
|
+
|
13
|
+
```console
|
14
|
+
# e.g. In Ubuntu:
|
15
|
+
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
|
16
|
+
$ echo "deb https://repo.iovisor.org/apt/$(lsb_release -cs) $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/iovisor.list
|
17
|
+
$ sudo apt-get update
|
18
|
+
$ sudo apt-get install libbcc
|
19
|
+
```
|
20
|
+
|
21
|
+
For more imformation, see [bcc's official doc](https://github.com/iovisor/bcc/blob/master/INSTALL.md).
|
22
|
+
|
23
|
+
After installed libbcc, you can create project hierarchy like:
|
24
|
+
|
25
|
+
```
|
26
|
+
.
|
27
|
+
├── Gemfile
|
28
|
+
└── tools
|
29
|
+
└── hello_world.rb
|
30
|
+
```
|
31
|
+
|
32
|
+
with `Gemfile` below:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
source "https://rubygems.org"
|
36
|
+
|
37
|
+
gem "rbbcc"
|
38
|
+
```
|
39
|
+
|
40
|
+
Then run `bundle install`.
|
41
|
+
|
42
|
+
### With docker
|
43
|
+
|
44
|
+
_TBD_
|
45
|
+
|
46
|
+
## Hello world from Linux kernel!
|
47
|
+
|
48
|
+
Creating `tools/hello_world.rb` as:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
#!/usr/bin/env ruby
|
52
|
+
|
53
|
+
require 'rbbcc'
|
54
|
+
include RbBCC
|
55
|
+
|
56
|
+
text = <<CLANG
|
57
|
+
int kprobe__sys_clone(void *ctx)
|
58
|
+
{
|
59
|
+
bpf_trace_printk("Hello, World!\\n");
|
60
|
+
return 0;
|
61
|
+
}
|
62
|
+
CLANG
|
63
|
+
|
64
|
+
b = BCC.new(text: text)
|
65
|
+
printf("%-18s %-16s %-6s %s\n", "TIME(s)", "COMM", "PID", "value")
|
66
|
+
|
67
|
+
b.trace_fields do |task, pid, cpu, flags, ts, msg|
|
68
|
+
printf("%-18.9f %-16s %-6d %s", ts, task, pid, msg)
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
Then invoke this Ruby script. BCC requires to run as a priveleged user.
|
73
|
+
|
74
|
+
```console
|
75
|
+
$ sudo bundle exec ruby tools/hello_world.rb
|
76
|
+
Found fnc: kprobe__sys_clone
|
77
|
+
Attach: p___x64_sys_clone
|
78
|
+
TIME(s) COMM PID value
|
79
|
+
```
|
80
|
+
|
81
|
+
Open another terminal and hit commands, like `curl https://www.ruby-lang.org/`.
|
82
|
+
|
83
|
+
Then RbBCC process displays what kind of program invokes another program with message `Hello, World`.
|
84
|
+
|
85
|
+
```console
|
86
|
+
TIME(s) COMM PID value
|
87
|
+
109979.625639000 bash 7591 Hello, World!
|
88
|
+
109979.632267000 curl 29098 Hello, World!
|
89
|
+
109981.467914000 bash 7591 Hello, World!
|
90
|
+
109981.474290000 curl 29100 Hello, World!
|
91
|
+
109984.913358000 bash 7591 Hello, World!
|
92
|
+
109984.921165000 curl 29102 Hello, World!
|
93
|
+
...
|
94
|
+
```
|
95
|
+
|
96
|
+
These lines are displayed by a kernel hook of `clone(2)(internally: sys_clone)` call. The bash command like `curl, grep, ping...` internally call `sys_clone` to fork new processes, and RbBCC traces these kernel calls with a very small cost.
|
97
|
+
|
98
|
+
Then, change the Ruby(and internal C) codes into this like:
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
#!/usr/bin/env ruby
|
102
|
+
|
103
|
+
require 'rbbcc'
|
104
|
+
include RbBCC
|
105
|
+
|
106
|
+
text = <<CLANG
|
107
|
+
#include <uapi/linux/ptrace.h>
|
108
|
+
|
109
|
+
int printret(struct pt_regs *ctx) {
|
110
|
+
char str[80] = {};
|
111
|
+
u32 pid;
|
112
|
+
if (!PT_REGS_RC(ctx))
|
113
|
+
return 0;
|
114
|
+
pid = bpf_get_current_pid_tgid();
|
115
|
+
bpf_probe_read(&str, sizeof(str), (void *)PT_REGS_RC(ctx));
|
116
|
+
bpf_trace_printk("[%d]input: %s\\n", pid, str);
|
117
|
+
|
118
|
+
return 0;
|
119
|
+
};
|
120
|
+
CLANG
|
121
|
+
|
122
|
+
b = BCC.new(text: text)
|
123
|
+
b.attach_uretprobe(name: "/bin/bash", sym: "readline", fn_name: "printret")
|
124
|
+
|
125
|
+
printf("%-18s %-16s %s\n", "TIME(s)", "COMM", "value")
|
126
|
+
b.trace_fields do |task, pid, cpu, flags, ts, msg|
|
127
|
+
printf("%-18.9f %-16s %s", ts, task, msg)
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
Run again:
|
132
|
+
|
133
|
+
```console
|
134
|
+
$ sudo bundle exec ruby tools/hello_world.rb
|
135
|
+
Found fnc: printret
|
136
|
+
Attach: p__bin_bash_0xad900
|
137
|
+
TIME(s) COMM value
|
138
|
+
```
|
139
|
+
|
140
|
+
And open another "bash" terminal again. When you hit a command to this bash in any session, any input strings are snooped and shown in the tracing process. This is all of the trace result of return in `readline()` function from user program "bash". RbBCC can detect where and how it occurs.
|
141
|
+
|
142
|
+
```console
|
143
|
+
TIME(s) COMM value
|
144
|
+
1554.284390000 bash [5457]input: curl localhost
|
145
|
+
1559.425699000 bash [5457]input: ping 8.8.8.8
|
146
|
+
1565.805870000 bash [5457]input: sudo cat /etc/passwd
|
147
|
+
...
|
148
|
+
```
|
149
|
+
|
150
|
+
----
|
151
|
+
|
152
|
+
For more use case and information. Please see [`examples`](../examples/) directory and (especially for C API) BCC's official document.
|
153
|
+
|
154
|
+
* [bcc Reference Guide](https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# disksnoop.rb Trace block device I/O: basic version of iosnoop.
|
4
|
+
# For Linux, uses BCC, eBPF. Embedded C.
|
5
|
+
# This is ported from original disksnoop.py
|
6
|
+
#
|
7
|
+
# Written as a basic example of tracing latency.
|
8
|
+
#
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License")
|
10
|
+
#
|
11
|
+
# 11-Aug-2015 Brendan Gregg Created disksnoop.py
|
12
|
+
|
13
|
+
require 'rbbcc'
|
14
|
+
include RbBCC
|
15
|
+
|
16
|
+
REQ_WRITE = 1 # from include/linux/blk_types.h
|
17
|
+
|
18
|
+
# load BPF program
|
19
|
+
b = BCC.new(text: <<CLANG)
|
20
|
+
#include <uapi/linux/ptrace.h>
|
21
|
+
#include <linux/blkdev.h>
|
22
|
+
|
23
|
+
BPF_HASH(start, struct request *);
|
24
|
+
|
25
|
+
void trace_start(struct pt_regs *ctx, struct request *req) {
|
26
|
+
// stash start timestamp by request ptr
|
27
|
+
u64 ts = bpf_ktime_get_ns();
|
28
|
+
|
29
|
+
start.update(&req, &ts);
|
30
|
+
}
|
31
|
+
|
32
|
+
void trace_completion(struct pt_regs *ctx, struct request *req) {
|
33
|
+
u64 *tsp, delta;
|
34
|
+
|
35
|
+
tsp = start.lookup(&req);
|
36
|
+
if (tsp != 0) {
|
37
|
+
delta = bpf_ktime_get_ns() - *tsp;
|
38
|
+
bpf_trace_printk("%d %x %d\\n", req->__data_len,
|
39
|
+
req->cmd_flags, delta / 1000);
|
40
|
+
start.delete(&req);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
CLANG
|
44
|
+
|
45
|
+
if BCC.get_kprobe_functions('blk_start_request')
|
46
|
+
b.attach_kprobe(event: "blk_start_request", fn_name: "trace_start")
|
47
|
+
end
|
48
|
+
b.attach_kprobe(event: "blk_mq_start_request", fn_name: "trace_start")
|
49
|
+
b.attach_kprobe(event: "blk_account_io_completion", fn_name: "trace_completion")
|
50
|
+
|
51
|
+
# header
|
52
|
+
puts("%-18s %-2s %-7s %8s" % ["TIME(s)", "T", "BYTES", "LAT(ms)"])
|
53
|
+
|
54
|
+
# format output
|
55
|
+
loop do
|
56
|
+
begin
|
57
|
+
task, pid, cpu, flags, ts, msg = b.trace_fields
|
58
|
+
bytes_s, bflags_s, us_s = msg.split
|
59
|
+
|
60
|
+
if (bflags_s.to_i(16) & REQ_WRITE).nonzero?
|
61
|
+
type_s = "W"
|
62
|
+
elsif bytes_s == "0" # see blk_fill_rwbs() for logic
|
63
|
+
type_s = "M"
|
64
|
+
else
|
65
|
+
type_s = "R"
|
66
|
+
end
|
67
|
+
ms = us_s.to_i.to_f / 1000
|
68
|
+
|
69
|
+
puts("%-18.9f %-2s %-7s %8.2f" % [ts, type_s, bytes_s, ms])
|
70
|
+
rescue Interrupt
|
71
|
+
exit
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This is a Hello World example that formats output as fields.
|
4
|
+
|
5
|
+
require 'rbbcc'
|
6
|
+
include RbBCC
|
7
|
+
|
8
|
+
# define BPF program
|
9
|
+
prog = <<CLANG
|
10
|
+
int hello(void *ctx) {
|
11
|
+
bpf_trace_printk("Hello, World!\\n");
|
12
|
+
return 0;
|
13
|
+
}
|
14
|
+
CLANG
|
15
|
+
|
16
|
+
# load BPF program
|
17
|
+
b = BCC.new(text: prog)
|
18
|
+
b.attach_kprobe(event: b.get_syscall_fnname("clone"), fn_name: "hello")
|
19
|
+
|
20
|
+
# header
|
21
|
+
puts("%-18s %-16s %-6s %s" % ["TIME(s)", "COMM", "PID", "MESSAGE"])
|
22
|
+
|
23
|
+
# format output
|
24
|
+
loop do
|
25
|
+
begin
|
26
|
+
b.trace_fields do |task, pid, cpu, flags, ts, msg|
|
27
|
+
puts("%-18.9f %-16s %-6d %s" % [ts, task, pid, msg])
|
28
|
+
end
|
29
|
+
rescue Interrupt
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/python
|
2
|
+
#
|
3
|
+
# kvm_hypercall.rb - TODO: it is still not tested
|
4
|
+
# See original kvm_hypercall.py and txt
|
5
|
+
#
|
6
|
+
# Demonstrates stateful kvm_entry and kvm_exit recording along with the
|
7
|
+
# associated hypercall when exit_reason is VMCALL. See kvm_hypercall.txt
|
8
|
+
# for usage
|
9
|
+
#
|
10
|
+
# REQUIRES: Linux 4.7+ (BPF_PROG_TYPE_TRACEPOINT support)
|
11
|
+
#
|
12
|
+
# Copyright (c) 2017 ShiftLeft Inc.
|
13
|
+
#
|
14
|
+
# Author(s):
|
15
|
+
# Suchakrapani Sharma <suchakra@shiftleft.io>
|
16
|
+
|
17
|
+
require 'rbbcc'
|
18
|
+
include RbBCC
|
19
|
+
|
20
|
+
# load BPF program
|
21
|
+
b = BCC.new(text: <<CLANG)
|
22
|
+
#define EXIT_REASON 18
|
23
|
+
BPF_HASH(start, u8, u8);
|
24
|
+
|
25
|
+
TRACEPOINT_PROBE(kvm, kvm_exit) {
|
26
|
+
u8 e = EXIT_REASON;
|
27
|
+
u8 one = 1;
|
28
|
+
if (args->exit_reason == EXIT_REASON) {
|
29
|
+
bpf_trace_printk("KVM_EXIT exit_reason : %d\\n", args->exit_reason);
|
30
|
+
start.update(&e, &one);
|
31
|
+
}
|
32
|
+
return 0;
|
33
|
+
}
|
34
|
+
|
35
|
+
TRACEPOINT_PROBE(kvm, kvm_entry) {
|
36
|
+
u8 e = EXIT_REASON;
|
37
|
+
u8 zero = 0;
|
38
|
+
u8 *s = start.lookup(&e);
|
39
|
+
if (s != NULL && *s == 1) {
|
40
|
+
bpf_trace_printk("KVM_ENTRY vcpu_id : %u\\n", args->vcpu_id);
|
41
|
+
start.update(&e, &zero);
|
42
|
+
}
|
43
|
+
return 0;
|
44
|
+
}
|
45
|
+
|
46
|
+
TRACEPOINT_PROBE(kvm, kvm_hypercall) {
|
47
|
+
u8 e = EXIT_REASON;
|
48
|
+
u8 zero = 0;
|
49
|
+
u8 *s = start.lookup(&e);
|
50
|
+
if (s != NULL && *s == 1) {
|
51
|
+
bpf_trace_printk("HYPERCALL nr : %d\\n", args->nr);
|
52
|
+
}
|
53
|
+
return 0;
|
54
|
+
};
|
55
|
+
CLANG
|
56
|
+
|
57
|
+
# header
|
58
|
+
puts("%-18s %-16s %-6s %s" % ["TIME(s)", "COMM", "PID", "EVENT"])
|
59
|
+
|
60
|
+
# format output
|
61
|
+
loop do
|
62
|
+
begin
|
63
|
+
b.trace_fields do |task, pid, cpu, flags, ts, msg|
|
64
|
+
puts("%-18.9f %-16s %-6d %s" % [ts, task, pid, msg])
|
65
|
+
end
|
66
|
+
rescue Interrupt
|
67
|
+
exit
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# mallocstacks Trace malloc() calls in a process and print the full
|
4
|
+
# stack trace for all callsites.
|
5
|
+
# For Linux, uses BCC, eBPF. Embedded C.
|
6
|
+
#
|
7
|
+
# This script is a basic example of the new Linux 4.6+ BPF_STACK_TRACE
|
8
|
+
# table API.
|
9
|
+
#
|
10
|
+
# Copyright 2016 GitHub, Inc.
|
11
|
+
# Licensed under the Apache License, Version 2.0 (the "License")
|
12
|
+
|
13
|
+
require 'rbbcc'
|
14
|
+
include RbBCC
|
15
|
+
|
16
|
+
if ARGV.size == 0
|
17
|
+
$stderr.puts("USAGE: mallocstacks PID")
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
pid = ARGV[0].to_i
|
21
|
+
|
22
|
+
# load BPF program
|
23
|
+
b = BCC.new(text: <<CLANG)
|
24
|
+
#include <uapi/linux/ptrace.h>
|
25
|
+
|
26
|
+
BPF_HASH(calls, int);
|
27
|
+
BPF_STACK_TRACE(stack_traces, 1024);
|
28
|
+
|
29
|
+
int alloc_enter(struct pt_regs *ctx, size_t size) {
|
30
|
+
int key = stack_traces.get_stackid(ctx,
|
31
|
+
BPF_F_USER_STACK|BPF_F_REUSE_STACKID);
|
32
|
+
if (key < 0)
|
33
|
+
return 0;
|
34
|
+
|
35
|
+
// could also use `calls.increment(key, size);`
|
36
|
+
u64 zero = 0, *val;
|
37
|
+
val = calls.lookup_or_init(&key, &zero);
|
38
|
+
if (val)
|
39
|
+
(*val) += size;
|
40
|
+
return 0;
|
41
|
+
};
|
42
|
+
CLANG
|
43
|
+
|
44
|
+
b.attach_uprobe(name: "c", sym: "malloc", fn_name: "alloc_enter", pid: pid)
|
45
|
+
puts("Attaching to malloc in pid %d, Ctrl+C to quit." % pid)
|
46
|
+
|
47
|
+
# sleep until Ctrl-C
|
48
|
+
loop do
|
49
|
+
begin
|
50
|
+
sleep 1
|
51
|
+
rescue Interrupt
|
52
|
+
break # pass
|
53
|
+
end
|
54
|
+
end
|
55
|
+
calls = b.get_table("calls")
|
56
|
+
stack_traces = b.get_table("stack_traces")
|
57
|
+
|
58
|
+
calls.items.sort_by{|k, v| v.to_bcc_value }.reverse.each do |(k, v)|
|
59
|
+
puts("%d bytes allocated at:" % v.to_bcc_value)
|
60
|
+
stack_traces.walk(k) do |addr|
|
61
|
+
puts("\t%s" % BCC.sym(addr, pid, show_offset: true))
|
62
|
+
end
|
63
|
+
end
|
data/lib/rbbcc/bcc.rb
CHANGED
@@ -13,6 +13,142 @@ module RbBCC
|
|
13
13
|
TRACEFS = "/sys/kernel/debug/tracing"
|
14
14
|
|
15
15
|
class BCC
|
16
|
+
class << self
|
17
|
+
def get_kprobe_functions(event_re)
|
18
|
+
blacklist = []
|
19
|
+
fns = []
|
20
|
+
File.open(File.expand_path("../kprobes/blacklist", TRACEFS), "rb") do |blacklist_f|
|
21
|
+
blacklist = blacklist_f.each_line.map { |line|
|
22
|
+
line.rstrip.split[1]
|
23
|
+
}.uniq
|
24
|
+
end
|
25
|
+
in_init_section = 0
|
26
|
+
in_irq_section = 0
|
27
|
+
File.open("/proc/kallsyms", "rb") do |avail_file|
|
28
|
+
avail_file.each_line do |line|
|
29
|
+
t, fn = line.rstrip.split[1,2]
|
30
|
+
# Skip all functions defined between __init_begin and
|
31
|
+
# __init_end
|
32
|
+
if in_init_section == 0
|
33
|
+
if fn == '__init_begin'
|
34
|
+
in_init_section = 1
|
35
|
+
next
|
36
|
+
elsif in_init_section == 1
|
37
|
+
if fn == '__init_end'
|
38
|
+
in_init_section = 2
|
39
|
+
next
|
40
|
+
end
|
41
|
+
end
|
42
|
+
# Skip all functions defined between __irqentry_text_start and
|
43
|
+
# __irqentry_text_end
|
44
|
+
if in_irq_section == 0
|
45
|
+
if fn == '__irqentry_text_start'
|
46
|
+
in_irq_section = 1
|
47
|
+
next
|
48
|
+
elsif in_irq_section == 1
|
49
|
+
if fn == '__irqentry_text_end'
|
50
|
+
in_irq_section = 2
|
51
|
+
next
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
# All functions defined as NOKPROBE_SYMBOL() start with the
|
56
|
+
# prefix _kbl_addr_*, blacklisting them by looking at the name
|
57
|
+
# allows to catch also those symbols that are defined in kernel
|
58
|
+
# modules.
|
59
|
+
if fn.start_with?('_kbl_addr_')
|
60
|
+
next
|
61
|
+
# Explicitly blacklist perf-related functions, they are all
|
62
|
+
# non-attachable.
|
63
|
+
elsif fn.start_with?('__perf') || fn.start_with?('perf_')
|
64
|
+
next
|
65
|
+
# Exclude all gcc 8's extra .cold functions
|
66
|
+
elsif fn =~ /^.*\.cold\.\d+$/
|
67
|
+
next
|
68
|
+
end
|
69
|
+
if %w(t w).include?(t.downcase) \
|
70
|
+
&& /#{event_re}/ =~ fn \
|
71
|
+
&& !blacklist.include?(fn)
|
72
|
+
fns << fn
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
fns = fns.uniq
|
78
|
+
return fns.empty? ? nil : fns
|
79
|
+
end
|
80
|
+
|
81
|
+
def check_path_symbol(module_, symname, addr, pid)
|
82
|
+
sym = Clib::BCCSymbol.malloc
|
83
|
+
c_pid = pid == -1 ? 0 : pid
|
84
|
+
if Clib.bcc_resolve_symname(module_, symname, (addr || 0x0), c_pid, nil, sym) < 0
|
85
|
+
raise("could not determine address of symbol %s" % symname)
|
86
|
+
end
|
87
|
+
module_path = Clib.__extract_char sym.module
|
88
|
+
# XXX: need to free char* in ruby ffi?
|
89
|
+
Clib.bcc_procutils_free(sym.module)
|
90
|
+
return module_path, sym.offset
|
91
|
+
end
|
92
|
+
|
93
|
+
def decode_table_type(desc)
|
94
|
+
return desc if desc.is_a?(String)
|
95
|
+
|
96
|
+
anon = []
|
97
|
+
fields = []
|
98
|
+
# e.g. ["bpf_stacktrace", [["ip", "unsigned long long", [127]]], "struct_packed"]
|
99
|
+
name, typedefs, data_type = desc
|
100
|
+
typedefs.each do |field|
|
101
|
+
case field.size
|
102
|
+
when 2
|
103
|
+
fields << "#{decode_table_type(field[1])} #{field[0]}"
|
104
|
+
when 3
|
105
|
+
ftype = field.last
|
106
|
+
if ftype.is_a?(Array)
|
107
|
+
fields << "#{decode_table_type(field[1])}[#{ftype[0]}] #{field[0]}"
|
108
|
+
elsif ftype.is_a?(Integer)
|
109
|
+
warn("Warning: Ruby fiddle does not support bitfield member, ignoring")
|
110
|
+
warn("Adding member `#{field[1]} #{field[0]}:#{ftype}'")
|
111
|
+
fields << "#{decode_table_type(field[1])} #{field[0]}"
|
112
|
+
elsif %w(union struct struct_packed).in?(ftype)
|
113
|
+
name = field[0]
|
114
|
+
if name.empty?
|
115
|
+
name = "__anon%d" % anon.size
|
116
|
+
anon << name
|
117
|
+
end
|
118
|
+
# FIXME: nested struct
|
119
|
+
fields << "#{decode_table_type(field)} #{name}"
|
120
|
+
else
|
121
|
+
raise("Failed to decode type #{field.inspect}")
|
122
|
+
end
|
123
|
+
else
|
124
|
+
raise("Failed to decode type #{field.inspect}")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
if data_type == "union"
|
128
|
+
return Fiddle::Importer.union(fields)
|
129
|
+
else
|
130
|
+
return Fiddle::Importer.struct(fields)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def sym(addr, pid, show_module: false, show_offset: false, demangle: true)
|
135
|
+
# FIXME: case of typeofaddr.find('bpf_stack_build_id')
|
136
|
+
#s = Clib::BCCSymbol.malloc
|
137
|
+
#b = Clib::BCCStacktraceBuildID.malloc
|
138
|
+
#b.status = addr.status
|
139
|
+
#b.build_id = addr.build_id
|
140
|
+
#b.u = addr.offset
|
141
|
+
#Clib.bcc_buildsymcache_resolve(BPF.bsymcache, b, s)
|
142
|
+
|
143
|
+
name, offset_, module_ = SymbolCache.cache(pid).resolve(addr, demangle)
|
144
|
+
offset = (show_offset && name) ? ("+0x%x" % offset_) : ""
|
145
|
+
name = name || "[unknown]"
|
146
|
+
name = name + offset
|
147
|
+
module_ = (show_module && module_) ? " [#{File.basename.basename(module_)}]" : ""
|
148
|
+
return name + module_
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
16
152
|
def initialize(text:, debug: 0, cflags: [], usdt_contexts: [], allow_rlimit: 0)
|
17
153
|
@kprobe_fds = {}
|
18
154
|
@uprobe_fds = {}
|
@@ -96,9 +232,26 @@ module RbBCC
|
|
96
232
|
end
|
97
233
|
|
98
234
|
def attach_uprobe(name: "", sym: "", addr: nil, fn_name: "", pid: -1)
|
235
|
+
path, addr = BCC.check_path_symbol(name, sym, addr, pid)
|
236
|
+
|
237
|
+
fn = load_func(fn_name, BPF::KPROBE)
|
238
|
+
ev_name = to_uprobe_evname("p", path, addr, pid)
|
239
|
+
fd = Clib.bpf_attach_uprobe(fn[:fd], 0, ev_name, path, addr, pid)
|
240
|
+
if fd < 0
|
241
|
+
raise SystemCallError.new(Fiddle.last_error)
|
242
|
+
end
|
243
|
+
puts "Attach: #{ev_name}"
|
244
|
+
|
245
|
+
@uprobe_fds[ev_name] = fd
|
246
|
+
[ev_name, fd]
|
247
|
+
end
|
248
|
+
|
249
|
+
def attach_uretprobe(name: "", sym: "", addr: nil, fn_name: "", pid: -1)
|
250
|
+
path, addr = BCC.check_path_symbol(name, sym, addr, pid)
|
251
|
+
|
99
252
|
fn = load_func(fn_name, BPF::KPROBE)
|
100
|
-
ev_name = to_uprobe_evname("
|
101
|
-
fd = Clib.bpf_attach_uprobe(fn[:fd],
|
253
|
+
ev_name = to_uprobe_evname("r", path, addr, pid)
|
254
|
+
fd = Clib.bpf_attach_uprobe(fn[:fd], 1, ev_name, path, addr, pid)
|
102
255
|
if fd < 0
|
103
256
|
raise SystemCallError.new(Fiddle.last_error)
|
104
257
|
end
|
@@ -155,15 +308,22 @@ module RbBCC
|
|
155
308
|
end
|
156
309
|
|
157
310
|
def trace_fields(&do_each_line)
|
311
|
+
ret = []
|
158
312
|
while buf = trace_readline
|
159
313
|
next if buf.start_with? "CPU:"
|
160
314
|
task = buf[0..15].lstrip()
|
161
|
-
meta, _addr, msg = buf[17..-1].split(": ")
|
315
|
+
meta, _addr, msg = buf[17..-1].split(": ", 3)
|
162
316
|
pid, cpu, flags, ts = meta.split(" ")
|
163
317
|
cpu = cpu[1..-2]
|
164
318
|
|
165
|
-
do_each_line
|
319
|
+
if do_each_line
|
320
|
+
do_each_line.call(task, pid, cpu, flags, ts, msg)
|
321
|
+
else
|
322
|
+
ret = [task, pid, cpu, flags, ts, msg]
|
323
|
+
break
|
324
|
+
end
|
166
325
|
end
|
326
|
+
ret
|
167
327
|
end
|
168
328
|
|
169
329
|
def cleanup
|
@@ -189,13 +349,14 @@ module RbBCC
|
|
189
349
|
unless keytype
|
190
350
|
key_desc = Clib.bpf_table_key_desc(@module, name)
|
191
351
|
raise("Failed to load BPF Table #{name} key desc") if key_desc.null?
|
192
|
-
keytype = eval(key_desc.to_extracted_char_ptr)
|
352
|
+
keytype = BCC.decode_table_type(eval(key_desc.to_extracted_char_ptr))
|
193
353
|
end
|
194
354
|
|
195
355
|
unless leaftype
|
196
356
|
leaf_desc = Clib.bpf_table_leaf_desc(@module, name)
|
197
357
|
raise("Failed to load BPF Table #{name} leaf desc") if leaf_desc.null?
|
198
|
-
|
358
|
+
|
359
|
+
leaftype = BCC.decode_table_type(eval(leaf_desc.to_extracted_char_ptr))
|
199
360
|
end
|
200
361
|
return Table.new(self, map_id, map_fd, keytype, leaftype, name, reducer: reducer)
|
201
362
|
end
|
data/lib/rbbcc/clib.rb
CHANGED
@@ -81,14 +81,32 @@ module RbBCC
|
|
81
81
|
"const char *module",
|
82
82
|
"unsigned long offset"
|
83
83
|
])
|
84
|
-
|
84
|
+
BCCSymbolOption = struct([
|
85
|
+
'int use_debug_file',
|
86
|
+
'int check_debug_file_crc',
|
87
|
+
'unsigned int use_symbol_type'
|
88
|
+
])
|
89
|
+
BCCIPOffsetUnion = union(['unsigned long long offset', 'unsigned long long ip'])
|
90
|
+
BCCStacktraceBuildID = \
|
91
|
+
struct([
|
92
|
+
'unsigned int status',
|
93
|
+
'unsigned char[20] build_id',
|
94
|
+
'unsigned long long u' # truly union
|
95
|
+
])
|
96
|
+
extern 'int bcc_resolve_symname(char *module, char *symname,
|
97
|
+
unsigned long long addr, int pid,
|
98
|
+
struct bcc_symbol_option* option,
|
99
|
+
struct bcc_symbol *sym)'
|
85
100
|
extern 'void * bcc_symcache_new(int, void *)'
|
86
101
|
extern 'void bcc_free_symcache(void *, int)'
|
87
102
|
extern 'int bcc_symcache_resolve(void *, unsigned long, void *)'
|
88
103
|
extern 'int bcc_symcache_resolve_no_demangle(void *, unsigned long, void *)'
|
89
104
|
extern 'int bcc_symcache_resolve_name(void *, char *, char *, unsigned long long *)'
|
105
|
+
extern 'void bcc_symbol_free_demangle_name(struct bcc_symbol *sym)'
|
90
106
|
|
91
107
|
extern 'int perf_reader_poll(int num_readers, struct perf_reader **readers, int timeout)'
|
108
|
+
|
109
|
+
extern 'void bcc_procutils_free(const char *ptr)'
|
92
110
|
end
|
93
111
|
end
|
94
112
|
|
data/lib/rbbcc/symbol_cache.rb
CHANGED
@@ -24,21 +24,21 @@ module RbBCC
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def resolve(addr, demangle)
|
27
|
-
sym = Clib::
|
27
|
+
sym = Clib::BCCSymbol.malloc
|
28
28
|
ret = if demangle
|
29
29
|
Clib.bcc_symcache_resolve(@cache, addr, sym)
|
30
30
|
else
|
31
31
|
Clib.bcc_symcache_resolve_no_demangle(@cache, addr, sym)
|
32
32
|
end
|
33
|
-
if
|
33
|
+
if ret < 0
|
34
34
|
return [nil, addr, nil]
|
35
35
|
end
|
36
36
|
|
37
37
|
if demangle
|
38
|
-
name_res = sym.demangle_name
|
39
|
-
|
38
|
+
name_res = Clib.__extract_char(sym.demangle_name)
|
39
|
+
Clib.bcc_symbol_free_demangle_name(sym)
|
40
40
|
else
|
41
|
-
name_res = sym.name
|
41
|
+
name_res = Clib.__extract_char(sym.name)
|
42
42
|
end
|
43
43
|
|
44
44
|
return [name_res, sym.offset, Clib.__extract_char(sym.module)]
|
data/lib/rbbcc/table.rb
CHANGED
@@ -29,10 +29,13 @@ module RbBCC
|
|
29
29
|
ttype = Clib.bpf_table_type_id(bpf.module, map_id)
|
30
30
|
case ttype
|
31
31
|
when BPF_MAP_TYPE_HASH
|
32
|
+
HashTable.new(bpf, map_id, map_fd, keytype, leaftype)
|
32
33
|
when BPF_MAP_TYPE_ARRAY
|
33
34
|
ArrayTable.new(bpf, map_id, map_fd, keytype, leaftype)
|
34
35
|
when BPF_MAP_TYPE_PERF_EVENT_ARRAY
|
35
36
|
PerfEventArray.new(bpf, map_id, map_fd, keytype, leaftype, name: name)
|
37
|
+
when BPF_MAP_TYPE_STACK_TRACE
|
38
|
+
StackTrace.new(bpf, map_id, map_fd, keytype, leaftype)
|
36
39
|
else
|
37
40
|
raise "Unknown table type #{ttype}"
|
38
41
|
end
|
@@ -46,12 +49,13 @@ module RbBCC
|
|
46
49
|
|
47
50
|
def initialize(bpf, map_id, map_fd, keytype, leaftype, name: nil)
|
48
51
|
@bpf, @map_id, @map_fd, @keysize, @leafsize = \
|
49
|
-
|
52
|
+
bpf, map_id, map_fd, sizeof(keytype), sizeof(leaftype)
|
53
|
+
@leaftype = leaftype
|
50
54
|
@ttype = Clib.bpf_table_type_id(self.bpf.module, self.map_id)
|
51
55
|
@flags = Clib.bpf_table_flags_id(self.bpf.module, self.map_id)
|
52
56
|
@name = name
|
53
57
|
end
|
54
|
-
attr_reader :bpf, :map_id, :map_fd, :keysize, :leafsize, :ttype, :flags, :name
|
58
|
+
attr_reader :bpf, :map_id, :map_fd, :keysize, :leafsize, :leaftype, :ttype, :flags, :name
|
55
59
|
|
56
60
|
def next(key)
|
57
61
|
next_key = Fiddle::Pointer.malloc(self.keysize)
|
@@ -175,6 +179,9 @@ module RbBCC
|
|
175
179
|
end
|
176
180
|
end
|
177
181
|
|
182
|
+
class HashTable < TableBase
|
183
|
+
end
|
184
|
+
|
178
185
|
class ArrayTable < TableBase
|
179
186
|
def initialize(bpf, map_id, map_fd, keytype, leaftype, name: nil)
|
180
187
|
super
|
@@ -326,4 +333,33 @@ module RbBCC
|
|
326
333
|
@_open_key_fds[cpu] = -1
|
327
334
|
end
|
328
335
|
end
|
336
|
+
|
337
|
+
class StackTrace < TableBase
|
338
|
+
MAX_DEPTH = 127
|
339
|
+
BPF_F_STACK_BUILD_ID = (1<<5)
|
340
|
+
BPF_STACK_BUILD_ID_EMPTY = 0 #can't get stacktrace
|
341
|
+
BPF_STACK_BUILD_ID_VALID = 1 #valid build-id,ip
|
342
|
+
BPF_STACK_BUILD_ID_IP = 2 #fallback to ip
|
343
|
+
|
344
|
+
def get_stack(stack_id)
|
345
|
+
key = stack_id.is_a?(Fiddle::Pointer) ? stack_id : byref(stack_id, @keysize)
|
346
|
+
leaftype.new(self[stack_id])
|
347
|
+
end
|
348
|
+
|
349
|
+
def walk(stack_id, resolve: nil, &blk)
|
350
|
+
addrs = if (flags & BPF_F_STACK_BUILD_ID).nonzero?
|
351
|
+
get_stack(stack_id).trace[0..MAX_DEPTH]
|
352
|
+
else
|
353
|
+
get_stack(stack_id).ip[0..MAX_DEPTH]
|
354
|
+
end
|
355
|
+
addrs.each do |addr|
|
356
|
+
break if addr.zero?
|
357
|
+
if resolve
|
358
|
+
blk.call(resolve.call(addr))
|
359
|
+
else
|
360
|
+
blk.call(addr)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
329
365
|
end
|
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.1.
|
4
|
+
version: 0.1.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: 2019-12-
|
11
|
+
date: 2019-12-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -69,12 +69,18 @@ files:
|
|
69
69
|
- Rakefile
|
70
70
|
- bin/console
|
71
71
|
- bin/setup
|
72
|
+
- docs/README.md
|
73
|
+
- docs/getting_started.md
|
72
74
|
- examples/bitehist.rb
|
73
75
|
- examples/dddos.rb
|
76
|
+
- examples/disksnoop.rb
|
74
77
|
- examples/example.gif
|
75
78
|
- examples/extract_arg.rb
|
79
|
+
- examples/hello_fields.rb
|
76
80
|
- examples/hello_perf_output.rb
|
77
81
|
- examples/hello_world.rb
|
82
|
+
- examples/kvm_hypercall.rb
|
83
|
+
- examples/mallocstack.rb
|
78
84
|
- examples/usdt-test.rb
|
79
85
|
- examples/usdt.rb
|
80
86
|
- lib/rbbcc.rb
|