rbbcc 0.0.2 → 0.1.0

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: 83335da3fa30d52d38210f68efa6cf19cb5d44143ecc58ecdc6d4df30560303e
4
- data.tar.gz: 75d0c97a8624a72928b4195bd830257c79e6a5e883058d9ae5c6c53512fccb1b
3
+ metadata.gz: d39196324559279beb78acb224f5d1e4b8d327bec531a9f0fc821787c89703cf
4
+ data.tar.gz: 932e49ca5f9225eb7eaca7beea106efb7a54279a9c4570a12b4d2cc7da0fd1e5
5
5
  SHA512:
6
- metadata.gz: a634d67b7b8a322ac433825bdbf376e7ef3ebe30f2e43dc477eb6f52c14fbc812bf04a286a6e9af30ad3ffc999fe370c2bdfe0fee56608e501618973873f74a7
7
- data.tar.gz: 9b9cc3a877ab56ec8ceb516b0fc576fe981784e8689ec026974e429cecbe6e82d6da06e74d7473d9e38819b50e2d430c01801d1deecaee3bc197900a91d93684
6
+ metadata.gz: 6ab268845839b5b566b66a75bf0fefeea5fc6bda9cc0b451d5617f09d7a03d389be2075901924ef3bfaf05af840afb295721d878547a58d5d2d5404c1fbbe880
7
+ data.tar.gz: e387413acd3d1720e990f0cb11d69f4f5629559d37d46c784bd1e454a38badcae2997def0807dd512e486de0988fc1750dbffcba87a0d5a1b819380a64d066bf
data/.gitignore CHANGED
@@ -7,3 +7,7 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+
11
+ .ruby-version
12
+
13
+ /examples/work/*
data/Dockerfile ADDED
@@ -0,0 +1,15 @@
1
+ FROM rubylang/ruby:2.6.3-bionic
2
+
3
+ RUN set -ex; \
4
+ echo "deb [trusted=yes] http://repo.iovisor.org/apt/bionic bionic-nightly main" > /etc/apt/sources.list.d/iovisor.list; \
5
+ apt-get update -y; \
6
+ deps="auditd bcc-tools curl gcc git libelf1 libbcc-examples"; \
7
+ DEBIAN_FRONTEND=noninteractive apt-get install -y $deps; \
8
+ apt-get clean; \
9
+ rm -rf /var/lib/apt/lists/*;
10
+
11
+ RUN gem install rbbcc
12
+ COPY ./misc/rbbcc-dfm-ruby /usr/bin/rbbcc-dfm-ruby
13
+ RUN chmod a+x /usr/bin/rbbcc-dfm-ruby
14
+
15
+ ENTRYPOINT ["ruby"]
@@ -0,0 +1,9 @@
1
+ # Please follow your Docker for Mac kernel build
2
+ FROM linuxkit/kernel:4.9.184 AS ksrc
3
+
4
+ FROM udzura/rbbcc:0.0.2
5
+
6
+ COPY --from=ksrc /kernel-dev.tar /
7
+ RUN tar xf kernel-dev.tar && rm -f kernel-dev.tar
8
+
9
+ ENTRYPOINT ["rbbcc-dfm-ruby"]
data/Gemfile.lock CHANGED
@@ -1,11 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbbcc (0.0.1)
4
+ rbbcc (0.0.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ coderay (1.1.2)
10
+ method_source (0.9.2)
11
+ pry (0.12.2)
12
+ coderay (~> 1.1.0)
13
+ method_source (~> 0.9.0)
9
14
  rake (10.5.0)
10
15
 
11
16
  PLATFORMS
@@ -13,6 +18,7 @@ PLATFORMS
13
18
 
14
19
  DEPENDENCIES
15
20
  bundler (~> 2.0)
21
+ pry
16
22
  rake (~> 10.0)
17
23
  rbbcc!
18
24
 
data/README.md CHANGED
@@ -41,6 +41,59 @@ RbBCC::BCC.new(text: code).trace_print
41
41
 
42
42
  See examples (both in rbbcc and BCC)
43
43
 
44
+ ## Trying with docker
45
+
46
+ [`udzura/rbbcc:${version}` is available.](https://hub.docker.com/r/udzura/rbbcc)
47
+
48
+ * On generic linux:
49
+
50
+ ```console
51
+ $ docker run --privileged \
52
+ -v/lib/modules:/lib/modules \
53
+ -v/sys:/sys \
54
+ -v/usr/src:/usr/src \
55
+ -v`pwd`:/opt \
56
+ -ti \
57
+ udzura/rbbcc:0.0.2 /opt/hello_world.rb
58
+ Found fnc: kprobe__sys_clone
59
+ Attach: p_sys_clone
60
+
61
+ bash-20113 [000] .... 377.602655: 0x00000001: Hello, World!
62
+ bash-20113 [000] .... 385.367309: 0x00000001: Hello, World!
63
+ bash-20113 [000] .... 391.203779: 0x00000001: Hello, World!
64
+ curl-20316 [001] .... 391.218226: 0x00000001: Hello, World!
65
+ bash-20113 [000] .... 402.528271: 0x00000001: Hello, World!
66
+ docker-containe-20236 [000] .... 403.090058: 0x00000001: Hello, World!
67
+ ...
68
+ ```
69
+
70
+ * On Docker for Mac:
71
+ * Docker for Mac host does not have heders file in `/usr/src`, so you should extract headers in each container inside.
72
+
73
+ ```console
74
+ ### I have prepared an example Dockerfile
75
+ $ docker build -t udzura/rbbcc:0.0.2-dfm -f Dockerfile.dfm-example .
76
+ $ docker run --privileged \
77
+ -v/lib/modules:/lib/modules \
78
+ -v`pwd`/examples:/opt \
79
+ -ti \
80
+ udzura/rbbcc:0.0.2-dfm /opt/hello_world.rb
81
+ ...
82
+ containerd-shim-4932 [002] d... 1237.795421: : Hello, World!
83
+ httpd-4958 [000] d... 1237.795666: : Hello, World!
84
+ httpd-4958 [000] d... 1237.795794: : Hello, World!
85
+ httpd-4967 [000] d... 1237.795988: : Hello, World!
86
+ httpd-4967 [000] d... 1237.796211: : Hello, World!
87
+ httpd-4967 [000] d... 1237.796289: : Hello, World!
88
+ httpd-4967 [000] d... 1237.796325: : Hello, World!
89
+ <...>-5025 [003] d... 1237.796956: : Hello, World!
90
+ runc-5025 [003] d... 1237.797133: : Hello, World!
91
+ httpd-4967 [000] d... 1237.797156: : Hello, World!
92
+ httpd-4967 [000] d... 1237.797198: : Hello, World!
93
+ httpd-4967 [000] d... 1237.797340: : Hello, World!
94
+ runc-5025 [003] d... 1237.797464: : Hello, World!
95
+ ```
96
+
44
97
  ## Development
45
98
 
46
99
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # bitehist.rb, ported from bitehist.py. See license on that file
4
+ #
5
+ # Written as a basic example of using histograms to show a distribution.
6
+ #
7
+ # A Ctrl-C will print the gathered histogram then exit.
8
+ #
9
+
10
+ require 'rbbcc'
11
+ include RbBCC
12
+
13
+ b = BCC.new(text: <<CLANG)
14
+ #include <uapi/linux/ptrace.h>
15
+ #include <linux/blkdev.h>
16
+
17
+ BPF_HISTOGRAM(dist);
18
+ BPF_HISTOGRAM(dist_linear);
19
+
20
+ int kprobe__blk_account_io_completion(struct pt_regs *ctx, struct request *req)
21
+ {
22
+ dist.increment(bpf_log2l(req->__data_len / 1024));
23
+ dist_linear.increment(req->__data_len / 1024);
24
+ return 0;
25
+ }
26
+ CLANG
27
+
28
+ puts "Tracing... Hit Ctrl-C to end."
29
+
30
+ loop do
31
+ begin
32
+ sleep 0.1
33
+ rescue Interrupt
34
+ puts
35
+ break
36
+ end
37
+ end
38
+
39
+ puts "log2 histogram"
40
+ puts "~~~~~~~~~~~~~~"
41
+ b["dist"].print_log2_hist("kbytes")
42
+
43
+ puts
44
+ puts "linear histogram"
45
+ puts "~~~~~~~~~~~~~~~~"
46
+ b["dist_linear"].print_linear_hist("kbytes")
data/examples/dddos.rb ADDED
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # dddos.rb DDOS dectection system, ported from python code.
4
+ #
5
+ # Written as a basic tracing example of using ePBF
6
+ # to detect a potential DDOS attack against a system.
7
+ #
8
+ # See Copyright and License on bcc project:
9
+ # examples/tracing/dddos.py
10
+ #
11
+ # 14-Jan-2019 Jugurtha BELKALEM Created dddos.py.
12
+
13
+ require 'rbbcc'
14
+ include RbBCC
15
+
16
+ prog = """
17
+ #include <linux/skbuff.h>
18
+ #include <uapi/linux/ip.h>
19
+
20
+ #define MAX_NB_PACKETS 1000
21
+ #define LEGAL_DIFF_TIMESTAMP_PACKETS 1000000
22
+
23
+ BPF_HASH(rcv_packets);
24
+
25
+ struct detectionPackets {
26
+ u64 nb_ddos_packets;
27
+ };
28
+
29
+ BPF_PERF_OUTPUT(events);
30
+
31
+ int detect_ddos(struct pt_regs *ctx, void *skb){
32
+ struct detectionPackets detectionPacket = {};
33
+
34
+ // Used to count number of received packets
35
+ u64 rcv_packets_nb_index = 0, rcv_packets_nb_inter=1, *rcv_packets_nb_ptr;
36
+
37
+ // Used to measure elapsed time between 2 successive received packets
38
+ u64 rcv_packets_ts_index = 1, rcv_packets_ts_inter=0, *rcv_packets_ts_ptr;
39
+
40
+ /* The algorithm analyses packets received by ip_rcv function
41
+ * and measures the difference in reception time between each packet.
42
+ * DDOS flooders send millions of packets such that difference of
43
+ * timestamp between 2 successive packets is so small
44
+ * (which is not like regular applications behaviour).
45
+ * This script looks for this difference in time and if it sees
46
+ * more than MAX_NB_PACKETS succesive packets with a difference
47
+ * of timestamp between each one of them less than
48
+ * LEGAL_DIFF_TIMESTAMP_PACKETS ns,
49
+ * ------------------ It Triggers an ALERT -----------------
50
+ * Those settings must be adapted depending on regular network traffic
51
+ * -------------------------------------------------------------------
52
+ * Important: this is a rudimentary intrusion detection system, one can
53
+ * test a real case attack using hping3. However; if regular network
54
+ * traffic increases above predefined detection settings, a false
55
+ * positive alert will be triggered (an example would be the
56
+ case of large file downloads).
57
+ */
58
+ rcv_packets_nb_ptr = rcv_packets.lookup(&rcv_packets_nb_index);
59
+ rcv_packets_ts_ptr = rcv_packets.lookup(&rcv_packets_ts_index);
60
+ if(rcv_packets_nb_ptr != 0 && rcv_packets_ts_ptr != 0){
61
+ rcv_packets_nb_inter = *rcv_packets_nb_ptr;
62
+ rcv_packets_ts_inter = bpf_ktime_get_ns() - *rcv_packets_ts_ptr;
63
+ if(rcv_packets_ts_inter < LEGAL_DIFF_TIMESTAMP_PACKETS){
64
+ rcv_packets_nb_inter++;
65
+ } else {
66
+ rcv_packets_nb_inter = 0;
67
+ }
68
+ if(rcv_packets_nb_inter > MAX_NB_PACKETS){
69
+ detectionPacket.nb_ddos_packets = rcv_packets_nb_inter;
70
+ events.perf_submit(ctx, &detectionPacket, sizeof(detectionPacket));
71
+ }
72
+ }
73
+ rcv_packets_ts_inter = bpf_ktime_get_ns();
74
+ rcv_packets.update(&rcv_packets_nb_index, &rcv_packets_nb_inter);
75
+ rcv_packets.update(&rcv_packets_ts_index, &rcv_packets_ts_inter);
76
+ return 0;
77
+ }
78
+ """
79
+
80
+ # Loads eBPF program
81
+ b = BCC.new(text: prog)
82
+
83
+ # Attach kprobe to kernel function and sets detect_ddos as kprobe handler
84
+ b.attach_kprobe(event: "ip_rcv", fn_name: "detect_ddos")
85
+
86
+ DetectionTimestamp = \
87
+ Fiddle::Importer.struct(["unsigned long long nb_ddos_packets"])
88
+
89
+ # Show message when ePBF stats
90
+ puts("DDOS detector started ... Hit Ctrl-C to end!")
91
+
92
+ puts("%-26s %-10s" % ["TIME(s)", "MESSAGE"])
93
+
94
+ trigger_alert_event = lambda { |cpu, data, size|
95
+ # data is raw Fiddle::Pointer instance
96
+ event = DetectionTimestamp.malloc
97
+ Fiddle::Pointer.new(event.to_ptr)[0, DetectionTimestamp.size] = data[0, DetectionTimestamp.size]
98
+ puts("%-26s %s %d" % [
99
+ Time.now,
100
+ "DDOS Attack => nb of packets up to now : ",
101
+ event.nb_ddos_packets])
102
+ }
103
+
104
+ # loop with callback to trigger_alert_event
105
+ b["events"].open_perf_buffer(&trigger_alert_event)
106
+ loop do
107
+ begin
108
+ b.perf_buffer_poll
109
+ rescue Interrupt
110
+ exit
111
+ end
112
+ end
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This is a Hello World example that uses BPF_PERF_OUTPUT.
4
+ # Ported from hello_perf_output.py
5
+
6
+ require 'rbbcc'
7
+ include RbBCC
8
+
9
+ # define BPF program
10
+ prog = """
11
+ #include <linux/sched.h>
12
+
13
+ // define output data structure in C
14
+ struct data_t {
15
+ u32 pid;
16
+ u64 ts;
17
+ char comm[TASK_COMM_LEN];
18
+ };
19
+ BPF_PERF_OUTPUT(events);
20
+
21
+ int hello(struct pt_regs *ctx) {
22
+ struct data_t data = {};
23
+
24
+ data.pid = bpf_get_current_pid_tgid();
25
+ data.ts = bpf_ktime_get_ns();
26
+ bpf_get_current_comm(&data.comm, sizeof(data.comm));
27
+
28
+ events.perf_submit(ctx, &data, sizeof(data));
29
+
30
+ return 0;
31
+ }
32
+ """
33
+
34
+ # load BPF program
35
+ b = BCC.new(text: prog)
36
+ b.attach_kprobe(event: b.get_syscall_fnname("clone"), fn_name: "hello")
37
+
38
+ # header
39
+ puts("%-18s %-16s %-6s %s" % ["TIME(s)", "COMM", "PID", "MESSAGE"])
40
+
41
+ # process event
42
+ start = 0
43
+ print_event = lambda { |cpu, data, size|
44
+ event = b["events"].event(data)
45
+ if start == 0
46
+ start = event.ts
47
+ end
48
+
49
+ time_s = ((event.ts - start).to_f) / 1000000000
50
+ # event.comm.pack("c*").sprit
51
+ puts("%-18.9f %-16s %-6d %s" % [time_s, event.comm, event.pid,
52
+ "Hello, perf_output!"])
53
+ }
54
+
55
+ # loop with callback to print_event
56
+ b["events"].open_perf_buffer(&print_event)
57
+
58
+ loop do
59
+ begin
60
+ b.perf_buffer_poll()
61
+ rescue Interrupt
62
+ exit()
63
+ end
64
+ end
data/lib/rbbcc/bcc.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'rbbcc/consts'
2
+ require 'rbbcc/table'
2
3
  require 'rbbcc/symbol_cache'
3
4
 
4
5
  module RbBCC
@@ -28,6 +29,8 @@ module RbBCC
28
29
  allow_rlimit
29
30
  )
30
31
  @funcs = {}
32
+ @tables = {}
33
+ @perf_buffers = {}
31
34
 
32
35
  unless @module
33
36
  raise "BPF module not created"
@@ -43,7 +46,7 @@ module RbBCC
43
46
 
44
47
  at_exit { self.cleanup }
45
48
  end
46
- attr_reader :module
49
+ attr_reader :module, :perf_buffers
47
50
 
48
51
  def gen_args_from_usdt
49
52
  ptr = Clib.bcc_usdt_genargs(@usdt_contexts.map(&:context).pack('J*'), @usdt_contexts.size)
@@ -177,6 +180,58 @@ module RbBCC
177
180
  end
178
181
  end
179
182
 
183
+ attr_reader :tables
184
+ def get_table(name, keytype: nil, leaftype: nil, reducer: nil)
185
+ map_id = Clib.bpf_table_id(@module, name)
186
+ map_fd = Clib.bpf_table_fd(@module, name)
187
+
188
+ raise KeyError, "map not found" if map_fd < 0
189
+ unless keytype
190
+ key_desc = Clib.bpf_table_key_desc(@module, name)
191
+ 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?
193
+ end
194
+
195
+ unless leaftype
196
+ leaf_desc = Clib.bpf_table_leaf_desc(@module, name)
197
+ raise("Failed to load BPF Table #{name} leaf desc") if leaf_desc.null?
198
+ leaftype = eval(leaf_desc.to_extracted_char_ptr)
199
+ end
200
+ return Table.new(self, map_id, map_fd, keytype, leaftype, name, reducer: reducer)
201
+ end
202
+
203
+ def [](key)
204
+ self.tables[key] ||= get_table(key)
205
+ end
206
+
207
+ def []=(key, value)
208
+ self.tables[key] = value
209
+ end
210
+
211
+ def perf_buffer_poll(timeout=-1)
212
+ readers = self.perf_buffers.values
213
+ readers.each {|r| r.size = Clib::PerfReader.size }
214
+ pack = readers.map{|r| r[0, Clib::PerfReader.size] }.pack('p*')
215
+ Clib.perf_reader_poll(readers.size, pack, timeout)
216
+ end
217
+
218
+ def ksymname(name)
219
+ SymbolCache.resolve_global(name)
220
+ end
221
+
222
+ def get_syscall_prefix
223
+ SYSCALL_PREFIXES.each do |prefix|
224
+ if ksymname("%sbpf" % prefix)
225
+ return prefix
226
+ end
227
+ end
228
+ SYSCALL_PREFIXES[0]
229
+ end
230
+
231
+ def get_syscall_fnname(name)
232
+ get_syscall_prefix + name
233
+ end
234
+
180
235
  private
181
236
  def trace_autoload!
182
237
  (0..Clib.bpf_num_functions(@module)).each do |i|
data/lib/rbbcc/clib.rb CHANGED
@@ -13,7 +13,8 @@ module RbBCC
13
13
  end
14
14
 
15
15
  extend Fiddle::Importer
16
- dlload "libbcc.so.0"
16
+ dlload "libbcc.so.0.10.0"
17
+ typealias "size_t", "int"
17
18
 
18
19
  extern 'void * bpf_module_create_c_from_string(char *, unsigned int, char **, int, long)'
19
20
  extern 'int bpf_num_functions(void *)'
@@ -25,6 +26,13 @@ module RbBCC
25
26
  extern 'int bpf_function_size(void *, char *)'
26
27
  extern 'char * bpf_module_license(void *)'
27
28
  extern 'unsigned int bpf_module_kern_version(void *)'
29
+ extern 'int bpf_table_fd(void *, char *)'
30
+ extern 'int bpf_table_id(void *, char *)'
31
+ extern 'int bpf_table_type_id(void *, int)'
32
+ extern 'int bpf_table_flags_id(void *, int)'
33
+ extern 'char * bpf_table_key_desc(void *, char *)'
34
+ extern 'char * bpf_table_leaf_desc(void *, char *)'
35
+ extern 'int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags)'
28
36
 
29
37
  extern 'int bpf_attach_kprobe(int, int, char *, char *, unsigned long, int)'
30
38
  extern 'int bpf_detach_kprobe(char *)'
@@ -32,6 +40,35 @@ module RbBCC
32
40
  extern 'int bpf_detach_uprobe(char *)'
33
41
  extern 'int bpf_open_perf_event(unsigned int, unsigned long, int, int)'
34
42
  extern 'int bpf_close_perf_event_fd(int)'
43
+ extern 'int bpf_get_first_key(int, void *, int)'
44
+ extern 'int bpf_get_next_key(int, void *, void *)'
45
+ extern 'int bpf_lookup_elem(int fd, void *key, void *value)'
46
+ extern 'size_t bpf_table_max_entries_id(void *program, size_t id)'
47
+
48
+ # FIXME: This size of struct will change in future version
49
+ # and no struct member info in header. This is hacky
50
+ PerfReader = struct(
51
+ [
52
+ "void * raw_cb",
53
+ "void * lost_cb",
54
+ "void * cb_cookie",
55
+ "void * buf",
56
+ "int buf_size",
57
+ "void * base",
58
+ "int rb_use_state",
59
+ "int rb_read_tid",
60
+ "int page_size",
61
+ "int page_cnt",
62
+ "int fd"
63
+ ]
64
+ )
65
+
66
+ #typedef void (*perf_reader_raw_cb)(void *cb_cookie, void *raw, int raw_size);
67
+ #typedef void (*perf_reader_lost_cb)(void *cb_cookie, uint64_t lost);
68
+ extern 'void * bpf_open_perf_buffer(void *raw_cb, void *lost_cb, void *cb_cookie, int pid, int cpu, int page_cnt)'
69
+ extern 'int perf_reader_fd(void *reader)'
70
+ extern 'size_t bpf_perf_event_fields(void *program, const char *event)'
71
+ extern 'char * bpf_perf_event_field(void *program, const char *event, size_t i)'
35
72
 
36
73
  extern 'void * bcc_usdt_new_frompid(int, char *)'
37
74
  extern 'int bcc_usdt_enable_probe(void *, char *, char *)'
@@ -50,5 +87,13 @@ module RbBCC
50
87
  extern 'int bcc_symcache_resolve(void *, unsigned long, void *)'
51
88
  extern 'int bcc_symcache_resolve_no_demangle(void *, unsigned long, void *)'
52
89
  extern 'int bcc_symcache_resolve_name(void *, char *, char *, unsigned long long *)'
90
+
91
+ extern 'int perf_reader_poll(int num_readers, struct perf_reader **readers, int timeout)'
92
+ end
93
+ end
94
+
95
+ class Fiddle::Pointer
96
+ def to_extracted_char_ptr
97
+ RbBCC::Clib.__extract_char(self)
53
98
  end
54
99
  end
@@ -0,0 +1,29 @@
1
+ module RbBCC
2
+ module CPUHelper
3
+ module_function
4
+ def get_online_cpus
5
+ return _read_cpu_range('/sys/devices/system/cpu/online')
6
+ end
7
+
8
+ def get_possible_cpus
9
+ return _read_cpu_range('/sys/devices/system/cpu/possible')
10
+ end
11
+
12
+ # formatted like: '0,2-4,7-10'
13
+ def _read_cpu_range(path)
14
+ cpus = nil
15
+ File.open(path, 'r') do |f|
16
+ tmp = f.read.split(',').map do |range|
17
+ if range.include?('-')
18
+ start, end_ = *range.split('-')
19
+ (start.to_i..end_.to_i).to_a
20
+ else
21
+ range.to_i
22
+ end
23
+ end
24
+ cpus = tmp.flatten
25
+ end
26
+ cpus
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,98 @@
1
+ # FIXME: These should be class attributes?
2
+ $stars_max = 40
3
+ $log2_index_max = 65
4
+ $linear_index_max = 1025
5
+
6
+ module RbBCC
7
+ # They're directly ported from table.py
8
+ # They might be more looked like Ruby code
9
+ module DisplayHelper
10
+ def stars(val, val_max, width)
11
+ i = 0
12
+ text = ""
13
+ while true
14
+ break if (i > (width * val.to_f / val_max) - 1) || (i > width - 1)
15
+ text += "*"
16
+ i += 1
17
+ end
18
+ if val > val_max
19
+ text = text[0...-1] + "+"
20
+ end
21
+ return text
22
+ end
23
+
24
+ def print_log2_hist(vals, val_type, strip_leading_zero)
25
+ stars_max = $stars_max
26
+ log2_dist_max = 64
27
+ idx_max = -1
28
+ val_max = 0
29
+
30
+ vals.each_with_index do |v, i|
31
+ idx_max = i if v > 0
32
+ val_max = v if v > val_max
33
+ end
34
+
35
+ if idx_max <= 32
36
+ header = " %-19s : count distribution"
37
+ body = "%10d -> %-10d : %-8d |%-*s|"
38
+ stars = stars_max
39
+ else
40
+ header = " %-29s : count distribution"
41
+ body = "%20d -> %-20d : %-8d |%-*s|"
42
+ stars = stars_max / 2
43
+ end
44
+
45
+ if idx_max > 0
46
+ puts(header % val_type)
47
+ end
48
+
49
+ (1...(idx_max + 1)).each do |i|
50
+ low = (1 << i) >> 1
51
+ high = (1 << i) - 1
52
+ if (low == high)
53
+ low -= 1
54
+ end
55
+ val = vals[i]
56
+
57
+ if strip_leading_zero
58
+ if val
59
+ puts(body % [low, high, val, stars,
60
+ stars(val, val_max, stars)])
61
+ strip_leading_zero = false
62
+ end
63
+ else
64
+ puts(body % [low, high, val, stars,
65
+ stars(val, val_max, stars)])
66
+ end
67
+ end
68
+ end
69
+
70
+ def print_linear_hist(vals, val_type)
71
+ stars_max = $stars_max
72
+ log2_dist_max = 64
73
+ idx_max = -1
74
+ val_max = 0
75
+
76
+ vals.each_with_index do |v, i|
77
+ idx_max = i if v > 0
78
+ val_max = v if v > val_max
79
+ end
80
+
81
+ header = " %-13s : count distribution"
82
+ body = " %-10d : %-8d |%-*s|"
83
+ stars = stars_max
84
+
85
+ if idx_max >= 0
86
+ puts(header % val_type);
87
+ end
88
+
89
+ (0...(idx_max + 1)).each do |i|
90
+ val = vals[i]
91
+ puts(body % [i, val, stars,
92
+ stars(val, val_max, stars)])
93
+ end
94
+ end
95
+ end
96
+
97
+ extend DisplayHelper
98
+ end
@@ -0,0 +1,15 @@
1
+ require 'fiddle'
2
+ require 'fiddle/import'
3
+
4
+ class Fiddle::Pointer
5
+ def to_bcc_value
6
+ case self.size
7
+ when Fiddle::Importer.sizeof("int")
8
+ self[0, self.size].unpack("i!").first
9
+ when Fiddle::Importer.sizeof("long")
10
+ self[0, self.size].unpack("l!").first
11
+ else
12
+ self[0, self.size].unpack("Z*").first
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,329 @@
1
+ require 'rbbcc/clib'
2
+ require 'fiddle'
3
+ require 'enumerator'
4
+ require 'rbbcc/disp_helper'
5
+ require 'rbbcc/cpu_helper'
6
+
7
+ module RbBCC
8
+ module Table
9
+ BPF_MAP_TYPE_HASH = 1
10
+ BPF_MAP_TYPE_ARRAY = 2
11
+ BPF_MAP_TYPE_PROG_ARRAY = 3
12
+ BPF_MAP_TYPE_PERF_EVENT_ARRAY = 4
13
+ BPF_MAP_TYPE_PERCPU_HASH = 5
14
+ BPF_MAP_TYPE_PERCPU_ARRAY = 6
15
+ BPF_MAP_TYPE_STACK_TRACE = 7
16
+ BPF_MAP_TYPE_CGROUP_ARRAY = 8
17
+ BPF_MAP_TYPE_LRU_HASH = 9
18
+ BPF_MAP_TYPE_LRU_PERCPU_HASH = 10
19
+ BPF_MAP_TYPE_LPM_TRIE = 11
20
+ BPF_MAP_TYPE_ARRAY_OF_MAPS = 12
21
+ BPF_MAP_TYPE_HASH_OF_MAPS = 13
22
+ BPF_MAP_TYPE_DEVMAP = 14
23
+ BPF_MAP_TYPE_SOCKMAP = 15
24
+ BPF_MAP_TYPE_CPUMAP = 16
25
+ BPF_MAP_TYPE_XSKMAP = 17
26
+ BPF_MAP_TYPE_SOCKHASH = 18
27
+
28
+ def self.new(bpf, map_id, map_fd, keytype, leaftype, name, **kwargs)
29
+ ttype = Clib.bpf_table_type_id(bpf.module, map_id)
30
+ case ttype
31
+ when BPF_MAP_TYPE_HASH
32
+ when BPF_MAP_TYPE_ARRAY
33
+ ArrayTable.new(bpf, map_id, map_fd, keytype, leaftype)
34
+ when BPF_MAP_TYPE_PERF_EVENT_ARRAY
35
+ PerfEventArray.new(bpf, map_id, map_fd, keytype, leaftype, name: name)
36
+ else
37
+ raise "Unknown table type #{ttype}"
38
+ end
39
+ end
40
+ end
41
+
42
+ class TableBase
43
+ include Fiddle::Importer
44
+ include CPUHelper
45
+ include Enumerable
46
+
47
+ def initialize(bpf, map_id, map_fd, keytype, leaftype, name: nil)
48
+ @bpf, @map_id, @map_fd, @keysize, @leafsize = \
49
+ bpf, map_id, map_fd, sizeof(keytype), sizeof(leaftype)
50
+ @ttype = Clib.bpf_table_type_id(self.bpf.module, self.map_id)
51
+ @flags = Clib.bpf_table_flags_id(self.bpf.module, self.map_id)
52
+ @name = name
53
+ end
54
+ attr_reader :bpf, :map_id, :map_fd, :keysize, :leafsize, :ttype, :flags, :name
55
+
56
+ def next(key)
57
+ next_key = Fiddle::Pointer.malloc(self.keysize)
58
+
59
+ if !key
60
+ res = Clib.bpf_get_first_key(self.map_fd, next_key,
61
+ next_key.size)
62
+ else
63
+ unless key.is_a?(Fiddle::Pointer)
64
+ raise TypeError, key.inspect
65
+ end
66
+ res = Clib.bpf_get_next_key(self.map_fd, key,
67
+ next_key)
68
+ end
69
+
70
+ if res < 0
71
+ raise StopIteration
72
+ end
73
+
74
+ return next_key
75
+ end
76
+
77
+ def [](key)
78
+ leaf = Fiddle::Pointer.malloc(self.leafsize)
79
+ res = Clib.bpf_lookup_elem(self.map_fd, key, leaf)
80
+ if res < 0
81
+ nil
82
+ end
83
+ return leaf
84
+ end
85
+
86
+ def fetch(key)
87
+ self[key] || raise(KeyError, "key not found")
88
+ end
89
+
90
+ def []=(key, leaf)
91
+ res = Clib.bpf_update_elem(self.map_fd, key, leaf, 0)
92
+ if res < 0
93
+ raise SystemCallError.new("Could not update table", Fiddle.last_error)
94
+ end
95
+ res
96
+ end
97
+
98
+ def each_key
99
+ k = nil
100
+ keys = []
101
+ loop do
102
+ k = self.next(k)
103
+ keys << k
104
+ yield k
105
+ end
106
+ keys
107
+ end
108
+
109
+ def each_value
110
+ each_key {|key| yield(self[key]) if self[key] }
111
+ end
112
+
113
+ def each_pair
114
+ each_key {|key| yield(key, self[key]) if self[key] }
115
+ end
116
+ alias each each_pair
117
+
118
+ def values
119
+ enum_for(:each_value).to_a
120
+ end
121
+
122
+ def items
123
+ enum_for(:each_pair).to_a
124
+ end
125
+
126
+ def print_log2_hist(val_type="value",
127
+ section_header: "Bucket ptr",
128
+ section_print_fn: nil,
129
+ bucket_fn: nil,
130
+ strip_leading_zero: false,
131
+ bucket_sort_fn: nil)
132
+ if structured_key?
133
+ raise NotImplementedError
134
+ else
135
+ vals = Array.new($log2_index_max) { 0 }
136
+ each_pair do |k, v|
137
+ vals[k.to_bcc_value] = v.to_bcc_value
138
+ end
139
+ RbBCC.print_log2_hist(vals, val_type, strip_leading_zero)
140
+ end
141
+ nil
142
+ end
143
+
144
+ def print_linear_hist(val_type="value",
145
+ section_header: "Bucket ptr",
146
+ section_print_fn: nil,
147
+ bucket_fn: nil,
148
+ bucket_sort_fn: nil)
149
+ if structured_key?
150
+ raise NotImplementedError
151
+ else
152
+ vals = Array.new($linear_index_max) { 0 }
153
+ each_pair do |k, v|
154
+ vals[k.to_bcc_value] = v.to_bcc_value
155
+ end
156
+ RbBCC.print_linear_hist(vals, val_type)
157
+ end
158
+ nil
159
+ end
160
+
161
+ def structured_key?
162
+ false # TODO: implement me in the future
163
+ end
164
+
165
+ private
166
+ def byref(value, size=sizeof("int"))
167
+ pack_fmt = case size
168
+ when sizeof("int") ; "i!"
169
+ when sizeof("long"); "l!"
170
+ else ; "Z*"
171
+ end
172
+ ptr = Fiddle::Pointer.malloc(size)
173
+ ptr[0, size] = [value].pack(pack_fmt)
174
+ ptr
175
+ end
176
+ end
177
+
178
+ class ArrayTable < TableBase
179
+ def initialize(bpf, map_id, map_fd, keytype, leaftype, name: nil)
180
+ super
181
+ @max_entries = Clib.bpf_table_max_entries_id(bpf.module, map_id)
182
+ end
183
+
184
+ # We now emulate the Array class of Ruby
185
+ def size
186
+ @max_entries
187
+ end
188
+ alias length size
189
+
190
+ def [](key)
191
+ super(normalize_key(key))
192
+ end
193
+
194
+ def each(&b)
195
+ each_value do |v|
196
+ b.call(v.to_bcc_value)
197
+ end
198
+ end
199
+
200
+ private
201
+ def normalize_key(key)
202
+ case key
203
+ when Fiddle::Pointer
204
+ key
205
+ when Integer
206
+ byref(key, keysize)
207
+ else
208
+ raise KeyError, "#{key.inspect} must be integer or pointor"
209
+ end
210
+ end
211
+ end
212
+
213
+ class PerfEventArray < TableBase
214
+ def initialize(bpf, map_id, map_fd, keytype, leaftype, name: nil)
215
+ super
216
+ @open_key_fds = {}
217
+ @event_class = nil
218
+ @_cbs = {}
219
+ @_open_key_fds = {}
220
+ end
221
+
222
+ def event(data)
223
+ @event_class ||= get_event_class
224
+ ev = @event_class.malloc
225
+ Fiddle::Pointer.new(ev.to_ptr)[0, @event_class.size] = data[0, @event_class.size]
226
+ return ev
227
+ end
228
+
229
+ def open_perf_buffer(page_cnt: 8, lost_cb: nil, &callback)
230
+ if page_cnt & (page_cnt - 1) != 0
231
+ raise "Perf buffer page_cnt must be a power of two"
232
+ end
233
+
234
+ get_online_cpus.each do |i|
235
+ _open_perf_buffer(i, callback, page_cnt, lost_cb)
236
+ end
237
+ end
238
+
239
+ private
240
+ def get_event_class
241
+ ct_mapping = {
242
+ 's8': 'char',
243
+ 'u8': 'unsined char',
244
+ 's8 *': 'char *',
245
+ 's16': 'short',
246
+ 'u16': 'unsigned short',
247
+ 's32': 'int',
248
+ 'u32': 'unsigned int',
249
+ 's64': 'long long',
250
+ 'u64': 'unsigned long long'
251
+ }
252
+
253
+ array_type = /(.+) \[([0-9]+)\]$/
254
+ fields = []
255
+ num_fields = Clib.bpf_perf_event_fields(self.bpf.module, @name)
256
+ num_fields.times do |i|
257
+ field = Clib.__extract_char(Clib.bpf_perf_event_field(self.bpf.module, @name, i))
258
+ field_name, field_type = *field.split('#')
259
+ if field_type =~ /enum .*/
260
+ field_type = "int" #it is indeed enum...
261
+ end
262
+ if _field_type = ct_mapping[field_type.to_sym]
263
+ field_type = _field_type
264
+ end
265
+
266
+ m = array_type.match(field_type)
267
+ if m
268
+ field_type = "#{m[1]}[#{m[2]}]"
269
+ fields << [field_type, field_name].join(" ")
270
+ else
271
+ fields << [field_type, field_name].join(" ")
272
+ end
273
+ end
274
+ klass = Fiddle::Importer.struct(fields)
275
+ if fields.find {|f| f =~ /^char\[(\d+)\] ([_a-zA-Z0-9]+)/ }
276
+ size = $1
277
+ m = Module.new do
278
+ define_method $2 do
279
+ super().pack("c#{size}").sub(/\0+$/, "")
280
+ end
281
+ end
282
+ klass.prepend m
283
+ end
284
+ klass
285
+ end
286
+
287
+ def _open_perf_buffer(cpu, callback, page_cnt, lost_cb)
288
+ # bind("void raw_cb_callback(void *, void *, int)")
289
+ fn = Fiddle::Closure::BlockCaller.new(
290
+ Fiddle::TYPE_VOID,
291
+ [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT]
292
+ ) do |_dummy, data, size|
293
+ begin
294
+ callback.call(cpu, data, size)
295
+ rescue => e
296
+ if Fiddle.last_error == 32 # EPIPE
297
+ exit
298
+ else
299
+ raise e
300
+ end
301
+ end
302
+ end
303
+ lost_fn = Fiddle::Closure::BlockCaller.new(
304
+ Fiddle::TYPE_VOID,
305
+ [Fiddle::TYPE_VOIDP, Fiddle::TYPE_ULONG]
306
+ ) do |_dummy, lost|
307
+ begin
308
+ lost_cb(lost)
309
+ rescue => e
310
+ if Fiddle.last_error == 32 # EPIPE
311
+ exit
312
+ else
313
+ raise e
314
+ end
315
+ end
316
+ end if lost_cb
317
+ reader = Clib.bpf_open_perf_buffer(fn, lost_fn, nil, -1, cpu, page_cnt)
318
+ if !reader || reader.null?
319
+ raise "Could not open perf buffer"
320
+ end
321
+ fd = Clib.perf_reader_fd(reader)
322
+
323
+ self[byref(cpu, @keysize)] = byref(fd, @leafsize)
324
+ self.bpf.perf_buffers[[object_id, cpu]] = reader
325
+ @_cbs[cpu] = [fn, lost_fn]
326
+ @_open_key_fds[cpu] = -1
327
+ end
328
+ end
329
+ end
data/lib/rbbcc/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RbBCC
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/rbbcc.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require "rbbcc/fiddle_ext"
1
2
  require "rbbcc/bcc"
2
3
  require "rbbcc/clib"
3
4
  require "rbbcc/usdt"
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env sh
2
+ if ! mount | grep debug ; then
3
+ mount -t debugfs none /sys/kernel/debug
4
+ fi
5
+ exec ruby "$@"
data/rbbcc.gemspec CHANGED
@@ -23,4 +23,5 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 2.0"
25
25
  spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "pry"
26
27
  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.0.2
4
+ version: 0.1.0
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-08-02 00:00:00.000000000 Z
11
+ date: 2019-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  description: BCC port for MRI. See https://github.com/iovisor/bcc
42
56
  email:
43
57
  - udzura@udzura.jp
@@ -46,6 +60,8 @@ extensions: []
46
60
  extra_rdoc_files: []
47
61
  files:
48
62
  - ".gitignore"
63
+ - Dockerfile
64
+ - Dockerfile.dfm-example
49
65
  - Gemfile
50
66
  - Gemfile.lock
51
67
  - LICENSE
@@ -53,8 +69,11 @@ files:
53
69
  - Rakefile
54
70
  - bin/console
55
71
  - bin/setup
72
+ - examples/bitehist.rb
73
+ - examples/dddos.rb
56
74
  - examples/example.gif
57
75
  - examples/extract_arg.rb
76
+ - examples/hello_perf_output.rb
58
77
  - examples/hello_world.rb
59
78
  - examples/usdt-test.rb
60
79
  - examples/usdt.rb
@@ -62,9 +81,14 @@ files:
62
81
  - lib/rbbcc/bcc.rb
63
82
  - lib/rbbcc/clib.rb
64
83
  - lib/rbbcc/consts.rb
84
+ - lib/rbbcc/cpu_helper.rb
85
+ - lib/rbbcc/disp_helper.rb
86
+ - lib/rbbcc/fiddle_ext.rb
65
87
  - lib/rbbcc/symbol_cache.rb
88
+ - lib/rbbcc/table.rb
66
89
  - lib/rbbcc/usdt.rb
67
90
  - lib/rbbcc/version.rb
91
+ - misc/rbbcc-dfm-ruby
68
92
  - rbbcc.gemspec
69
93
  homepage: https://github.com/udzura/rbbcc
70
94
  licenses: []