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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d39196324559279beb78acb224f5d1e4b8d327bec531a9f0fc821787c89703cf
4
- data.tar.gz: 932e49ca5f9225eb7eaca7beea106efb7a54279a9c4570a12b4d2cc7da0fd1e5
3
+ metadata.gz: ccce892b1f35442a6a076717e95ff3d0dd1f3ea9b7454042ea1ed6edcafdf3ef
4
+ data.tar.gz: 928a6c4f96aec375d0d736122fefbbcfcabaafcb5be6ee0945facfcc59c8521a
5
5
  SHA512:
6
- metadata.gz: 6ab268845839b5b566b66a75bf0fefeea5fc6bda9cc0b451d5617f09d7a03d389be2075901924ef3bfaf05af840afb295721d878547a58d5d2d5404c1fbbe880
7
- data.tar.gz: e387413acd3d1720e990f0cb11d69f4f5629559d37d46c784bd1e454a38badcae2997def0807dd512e486de0988fc1750dbffcba87a0d5a1b819380a64d066bf
6
+ metadata.gz: 0b9aefc4bec5f184ae268fe4191bb5de1271e52ce8a6198fe38bae9e558dd273f4c893675581c9d0700d67a3e55d5b5a961d145431ed9ca01785ed4e9183af19
7
+ data.tar.gz: 9b5dabad56e3ee0453473aeca770251ba3453cb0e822503c0ad86fc8016a2768a62444b05191303e6a11bdd88aaf353097d691dc1e59d12cb41bf4d694b68f47
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbbcc (0.0.2)
4
+ rbbcc (0.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -23,4 +23,4 @@ DEPENDENCIES
23
23
  rbbcc!
24
24
 
25
25
  BUNDLED WITH
26
- 2.0.2
26
+ 2.1.0
data/docs/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # RbBCC Docs
2
+
3
+ * [Getting Started](getting_started.md)
4
+
5
+ **Please see also [BCC itselfe's README](https://github.com/iovisor/bcc/#readme) and [docs](https://github.com/iovisor/bcc/tree/master/docs).**
@@ -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("p", name, addr, pid)
101
- fd = Clib.bpf_attach_uprobe(fn[:fd], 0, ev_name, name, addr, pid)
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.call(task, pid, cpu, flags, ts, msg)
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) # XXX: parse as JSON?
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
- leaftype = eval(leaf_desc.to_extracted_char_ptr)
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
 
@@ -24,21 +24,21 @@ module RbBCC
24
24
  end
25
25
 
26
26
  def resolve(addr, demangle)
27
- sym = Clib::BCCSymcache.malloc
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 res < 0
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
- # Clib.bcc_symbol_free_demangle_name(sym)
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
- bpf, map_id, map_fd, sizeof(keytype), sizeof(leaftype)
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
@@ -1,3 +1,3 @@
1
1
  module RbBCC
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbbcc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
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-17 00:00:00.000000000 Z
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