rbbcc 0.0.2 → 0.1.0

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: 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: []