rbbcc 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|