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 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