class RbBCC::BCC
Attributes
module[R]
perf_buffers[R]
tables[R]
Public Class Methods
_find_file(filename)
click to toggle source
# File lib/rbbcc/bcc.rb, line 18 def _find_file(filename) if filename unless File.exist?(filename) t = File.expand_path "../#{filename}", $0 if File.exist?(t) filename = t else raise "Could not find file #{filename}" end end end return filename end
attach_raw_socket(fn, dev)
click to toggle source
# File lib/rbbcc/bcc.rb, line 205 def attach_raw_socket(fn, dev) unless fn.is_a?(Hash) raise "arg 1 must be of BPF.Function Hash" end sock = Clib.bpf_open_raw_sock(dev) if sock < 0 raise SystemCallError.new("Failed to open raw device %s" % dev, Fiddle.last_error) end res = Clib.bpf_attach_socket(sock, fn[:fd]) if res < 0 raise SystemCallError.new("Failed to attach BPF to device %s" % dev, Fiddle.last_error) end fn[:sock] = sock fn end
check_path_symbol(module_, symname, addr, pid)
click to toggle source
# File lib/rbbcc/bcc.rb, line 110 def check_path_symbol(module_, symname, addr, pid) sym = Clib::BCCSymbol.malloc c_pid = pid == -1 ? 0 : pid if Clib.bcc_resolve_symname(module_, symname, (addr || 0x0), c_pid, nil, sym) < 0 raise("could not determine address of symbol %s" % symname) end module_path = Clib.__extract_char sym.module # XXX: need to free char* in ruby ffi? Clib.bcc_procutils_free(sym.module) return module_path, sym.offset end
decode_table_type(desc)
click to toggle source
# File lib/rbbcc/bcc.rb, line 122 def decode_table_type(desc) return desc if desc.is_a?(String) anon = [] fields = [] # e.g. ["bpf_stacktrace", [["ip", "unsigned long long", [127]]], "struct_packed"] name, typedefs, data_type = desc typedefs.each do |field| case field.size when 2 fields << "#{decode_table_type(field[1])} #{field[0]}" when 3 ftype = field.last if ftype.is_a?(Array) fields << "#{decode_table_type(field[1])}[#{ftype[0]}] #{field[0]}" elsif ftype.is_a?(Integer) warn("Warning: Ruby fiddle does not support bitfield member, ignoring") warn("Adding member `#{field[1]} #{field[0]}:#{ftype}'") fields << "#{decode_table_type(field[1])} #{field[0]}" elsif %w(union struct struct_packed).in?(ftype) name = field[0] if name.empty? name = "__anon%d" % anon.size anon << name end # FIXME: nested struct fields << "#{decode_table_type(field)} #{name}" else raise("Failed to decode type #{field.inspect}") end else raise("Failed to decode type #{field.inspect}") end end c = nil if data_type == "union" c = Fiddle::Importer.union(fields) else c = Fiddle::Importer.struct(fields) end fields.each do |field| md = /^char\[(\d+)\] ([_a-zA-Z0-9]+)/.match(field) if md c.alias_method "__super_#{md[2]}", md[2] c.define_method md[2] do # Split the char[] in the place where the first \0 appears raw = __send__("__super_#{md[2]}") raw = raw[0...raw.index(0)] if raw.index(0) raw.pack("c*") end end end c.define_singleton_method :original_desc do desc end c.define_singleton_method :fields do fields end orig_name = c.inspect c.define_singleton_method :inspect do orig_name.sub /(?=>$)/, " original_desc=#{desc.inspect}" rescue super end c end
get_kprobe_functions(event_re)
click to toggle source
# File lib/rbbcc/bcc.rb, line 46 def get_kprobe_functions(event_re) blacklist = [] fns = [] File.open(File.expand_path("../kprobes/blacklist", TRACEFS), "rb") do |blacklist_f| blacklist = blacklist_f.each_line.map { |line| line.rstrip.split[1] }.uniq end in_init_section = 0 in_irq_section = 0 File.open("/proc/kallsyms", "rb") do |avail_file| avail_file.each_line do |line| t, fn = line.rstrip.split[1,2] # Skip all functions defined between __init_begin and # __init_end if in_init_section == 0 if fn == '__init_begin' in_init_section = 1 next elsif in_init_section == 1 if fn == '__init_end' in_init_section = 2 next end end # Skip all functions defined between __irqentry_text_start and # __irqentry_text_end if in_irq_section == 0 if fn == '__irqentry_text_start' in_irq_section = 1 next elsif in_irq_section == 1 if fn == '__irqentry_text_end' in_irq_section = 2 next end end end # All functions defined as NOKPROBE_SYMBOL() start with the # prefix _kbl_addr_*, blacklisting them by looking at the name # allows to catch also those symbols that are defined in kernel # modules. if fn.start_with?('_kbl_addr_') next # Explicitly blacklist perf-related functions, they are all # non-attachable. elsif fn.start_with?('__perf') || fn.start_with?('perf_') next # Exclude all gcc 8's extra .cold functions elsif fn =~ /^.*\.cold\.\d+$/ next end if %w(t w).include?(t.downcase) \ && /#{event_re}/ =~ fn \ && !blacklist.include?(fn) fns << fn end end end end fns = fns.uniq return fns.empty? ? nil : fns end
ksym(addr, show_module: false, show_offset: false)
click to toggle source
# File lib/rbbcc/bcc.rb, line 32 def ksym(addr, show_module: false, show_offset: false) self.sym(addr, -1, show_module: show_module, show_offset: show_offset, demangle: false) end
ksymname(name)
click to toggle source
# File lib/rbbcc/bcc.rb, line 36 def ksymname(name) SymbolCache.resolve_global(name) end
new(text: "", src_file: nil, hdr_file: nil, debug: 0, cflags: [], usdt_contexts: [], allow_rlimit: 0, dev_name: nil)
click to toggle source
# File lib/rbbcc/bcc.rb, line 223 def initialize(text: "", src_file: nil, hdr_file: nil, debug: 0, cflags: [], usdt_contexts: [], allow_rlimit: 0, dev_name: nil) @kprobe_fds = {} @uprobe_fds = {} @tracepoint_fds = {} @raw_tracepoint_fds = {} if src_file src_file = BCC._find_file(src_file) hdr_file = BCC._find_file(hdr_file) end if src_file && src_file.end_with?(".b") @module = Clib.bpf_module_create_b(src_file, hdr_file, debug, dev_name) else if src_file text = File.read(src_file) end @usdt_contexts = usdt_contexts if code = gen_args_from_usdt text = code + text end cflags_safe = if cflags.empty? or !cflags[-1].nil? cflags + [nil] else cflags end @module = Clib.do_bpf_module_create_c_from_string( text, debug, cflags_safe.pack("p*"), cflags_safe.size, allow_rlimit, dev_name ) end @funcs = {} @tables = {} @perf_buffers = {} unless @module raise "BPF module not created" end trace_autoload! @usdt_contexts.each do |usdt| usdt.enumerate_active_probes.each do |probe| attach_uprobe(name: probe.binpath, fn_name: probe.fn_name, addr: probe.addr, pid: probe.pid) end end at_exit { self.cleanup } end
support_raw_tracepoint()
click to toggle source
# File lib/rbbcc/bcc.rb, line 40 def support_raw_tracepoint # kernel symbol "bpf_find_raw_tracepoint" # indicates raw_tracepint support ksymname("bpf_find_raw_tracepoint") || ksymname("bpf_get_raw_tracepoint") end
sym(addr, pid, show_module: false, show_offset: false, demangle: true)
click to toggle source
# File lib/rbbcc/bcc.rb, line 188 def sym(addr, pid, show_module: false, show_offset: false, demangle: true) # FIXME: case of typeofaddr.find('bpf_stack_build_id') #s = Clib::BCCSymbol.malloc #b = Clib::BCCStacktraceBuildID.malloc #b.status = addr.status #b.build_id = addr.build_id #b.u = addr.offset #Clib.bcc_buildsymcache_resolve(BPF.bsymcache, b, s) name, offset_, module_ = SymbolCache.cache(pid).resolve(addr, demangle) offset = (show_offset && name) ? ("+0x%x" % offset_) : "" name = name || "[unknown]" name = name + offset module_ = (show_module && module_) ? " [#{File.basename.basename(module_)}]" : "" return name + module_ end
Public Instance Methods
[](key)
click to toggle source
# File lib/rbbcc/bcc.rb, line 542 def [](key) self.tables[key] ||= get_table(key) end
[]=(key, value)
click to toggle source
# File lib/rbbcc/bcc.rb, line 546 def []=(key, value) self.tables[key] = value end
attach_kprobe(event:, fn_name:, event_off: 0)
click to toggle source
# File lib/rbbcc/bcc.rb, line 339 def attach_kprobe(event:, fn_name:, event_off: 0) fn = load_func(fn_name, BPF::KPROBE) ev_name = "p_" + event.gsub(/[\+\.]/, "_") fd = Clib.bpf_attach_kprobe(fn[:fd], 0, ev_name, event, event_off, 0) if fd < 0 raise SystemCallError.new("Failed to attach BPF program #{fn_name} to kprobe #{event}", Fiddle.last_error) end Util.debug "Attach: #{ev_name}" @kprobe_fds[ev_name] = fd [ev_name, fd] end
attach_kretprobe(event:, fn_name:, event_re: nil, maxactive: 0)
click to toggle source
# File lib/rbbcc/bcc.rb, line 351 def attach_kretprobe(event:, fn_name:, event_re: nil, maxactive: 0) # TODO: if event_re ... fn = load_func(fn_name, BPF::KPROBE) ev_name = "r_" + event.gsub(/[\+\.]/, "_") fd = Clib.bpf_attach_kprobe(fn[:fd], 1, ev_name, event, 0, maxactive) if fd < 0 raise SystemCallError.new("Failed to attach BPF program #{fn_name} to kretprobe #{event}", Fiddle.last_error) end Util.debug "Attach: #{ev_name}" @kprobe_fds[ev_name] = fd [ev_name, fd] end
attach_raw_tracepoint(tp: "", fn_name: "")
click to toggle source
# File lib/rbbcc/bcc.rb, line 324 def attach_raw_tracepoint(tp: "", fn_name: "") if @raw_tracepoint_fds.keys.include?(tp) raise "Raw tracepoint #{tp} has been attached" end fn = load_func(fn_name, BPF::RAW_TRACEPOINT) fd = Clib.bpf_attach_raw_tracepoint(fn[:fd], tp) if fd < 0 raise SystemCallError.new("Failed to attach BPF program #{fn_name} to raw tracepoint #{tp}", Fiddle.last_error) end Util.debug "Attach: #{tp}" @raw_tracepoint_fds[tp] = fd self end
attach_tracepoint(tp: "", tp_re: "", fn_name: "")
click to toggle source
# File lib/rbbcc/bcc.rb, line 312 def attach_tracepoint(tp: "", tp_re: "", fn_name: "") fn = load_func(fn_name, BPF::TRACEPOINT) tp_category, tp_name = tp.split(':') fd = Clib.bpf_attach_tracepoint(fn[:fd], tp_category, tp_name) if fd < 0 raise SystemCallError.new("Failed to attach BPF program #{fn_name} to tracepoint #{tp}", Fiddle.last_error) end Util.debug "Attach: #{tp}" @tracepoint_fds[tp] = fd self end
attach_uprobe(name: "", sym: "", addr: nil, fn_name: "", pid: -1)
click to toggle source
# File lib/rbbcc/bcc.rb, line 364 def attach_uprobe(name: "", sym: "", addr: nil, fn_name: "", pid: -1) path, addr = BCC.check_path_symbol(name, sym, addr, pid) fn = load_func(fn_name, BPF::KPROBE) ev_name = to_uprobe_evname("p", path, addr, pid) fd = Clib.bpf_attach_uprobe(fn[:fd], 0, ev_name, path, addr, pid) if fd < 0 raise SystemCallError.new(Fiddle.last_error) end Util.debug "Attach: #{ev_name}" @uprobe_fds[ev_name] = fd [ev_name, fd] end
attach_uretprobe(name: "", sym: "", addr: nil, fn_name: "", pid: -1)
click to toggle source
# File lib/rbbcc/bcc.rb, line 379 def attach_uretprobe(name: "", sym: "", addr: nil, fn_name: "", pid: -1) path, addr = BCC.check_path_symbol(name, sym, addr, pid) fn = load_func(fn_name, BPF::KPROBE) ev_name = to_uprobe_evname("r", path, addr, pid) fd = Clib.bpf_attach_uprobe(fn[:fd], 1, ev_name, path, addr, pid) if fd < 0 raise SystemCallError.new(Fiddle.last_error) end Util.debug "Attach: #{ev_name}" @uprobe_fds[ev_name] = fd [ev_name, fd] end
cleanup()
click to toggle source
# File lib/rbbcc/bcc.rb, line 499 def cleanup @kprobe_fds.each do |k, v| detach_kprobe_event(k) end @uprobe_fds.each do |k, v| detach_uprobe_event(k) end @tracepoint_fds.each do |k, v| detach_tracepoint(k) end @raw_tracepoint_fds.each do |k, v| detach_raw_tracepoint(k) end if @module Clib.bpf_module_destroy(@module) end end
detach_kprobe_event(ev_name)
click to toggle source
# File lib/rbbcc/bcc.rb, line 422 def detach_kprobe_event(ev_name) unless @kprobe_fds.keys.include?(ev_name) raise "Event #{ev_name} not registered" end if Clib.bpf_close_perf_event_fd(@kprobe_fds[ev_name]) < 0 raise SystemCallError.new(Fiddle.last_error) end if Clib.bpf_detach_kprobe(ev_name) < 0 raise SystemCallError.new(Fiddle.last_error) end @kprobe_fds.delete(ev_name) end
detach_raw_tracepoint(tp)
click to toggle source
# File lib/rbbcc/bcc.rb, line 410 def detach_raw_tracepoint(tp) unless @raw_tracepoint_fds.keys.include?(tp) raise "Raw tracepoint #{tp} is not attached" end begin File.for_fd(@raw_tracepoint_fds[tp]).close rescue => e warn "Closing fd failed: #{e.inspect}. Ignore and skip" end @tracepoint_fds.delete(tp) end
detach_tracepoint(tp)
click to toggle source
# File lib/rbbcc/bcc.rb, line 394 def detach_tracepoint(tp) unless @tracepoint_fds.keys.include?(tp) raise "Tracepoint #{tp} is not attached" end res = Clib.bpf_close_perf_event_fd(@tracepoint_fds[tp]) if res < 0 raise "Failed to detach BPF from tracepoint" end tp_category, tp_name = tp.split(':') res = Clib.bpf_detach_tracepoint(tp_category, tp_name) if res < 0 raise "Failed to detach BPF from tracepoint" end @tracepoint_fds.delete(tp) end
detach_uprobe_event(ev_name)
click to toggle source
# File lib/rbbcc/bcc.rb, line 435 def detach_uprobe_event(ev_name) unless @uprobe_fds.keys.include?(ev_name) raise "Event #{ev_name} not registered" end if Clib.bpf_close_perf_event_fd(@uprobe_fds[ev_name]) < 0 raise SystemCallError.new(Fiddle.last_error) end if Clib.bpf_detach_uprobe(ev_name) < 0 raise SystemCallError.new(Fiddle.last_error) end @uprobe_fds.delete(ev_name) end
gen_args_from_usdt()
click to toggle source
# File lib/rbbcc/bcc.rb, line 282 def gen_args_from_usdt ptr = Clib.bcc_usdt_genargs(@usdt_contexts.map(&:context).pack('J*'), @usdt_contexts.size) code = "" if !ptr || ptr.null? return nil end Clib.__extract_char ptr end
get_syscall_fnname(name)
click to toggle source
# File lib/rbbcc/bcc.rb, line 570 def get_syscall_fnname(name) get_syscall_prefix + name end
get_syscall_prefix()
click to toggle source
# File lib/rbbcc/bcc.rb, line 561 def get_syscall_prefix SYSCALL_PREFIXES.each do |prefix| if ksymname("%sbpf" % prefix) return prefix end end SYSCALL_PREFIXES[0] end
get_table(name, keytype: nil, leaftype: nil, reducer: nil)
click to toggle source
# File lib/rbbcc/bcc.rb, line 522 def get_table(name, keytype: nil, leaftype: nil, reducer: nil) map_id = Clib.bpf_table_id(@module, name) map_fd = Clib.bpf_table_fd(@module, name) raise KeyError, "map not found" if map_fd < 0 unless keytype key_desc = Clib.bpf_table_key_desc(@module, name) raise("Failed to load BPF Table #{name} key desc") if key_desc.null? keytype = BCC.decode_table_type(eval(key_desc.to_extracted_char_ptr)) end unless leaftype leaf_desc = Clib.bpf_table_leaf_desc(@module, name) raise("Failed to load BPF Table #{name} leaf desc") if leaf_desc.null? leaftype = BCC.decode_table_type(eval(leaf_desc.to_extracted_char_ptr)) end return Table.new(self, map_id, map_fd, keytype, leaftype, name, reducer: reducer) end
ksymname(name)
click to toggle source
# File lib/rbbcc/bcc.rb, line 557 def ksymname(name) SymbolCache.resolve_global(name) end
load_func(func_name, prog_type, device: nil)
click to toggle source
# File lib/rbbcc/bcc.rb, line 292 def load_func(func_name, prog_type, device: nil) if @funcs.keys.include?(func_name) return @funcs[func_name] end log_level = 0 fd = Clib.do_bcc_func_load(@module, prog_type, func_name, Clib.bpf_function_start(@module, func_name), Clib.bpf_function_size(@module, func_name), Clib.bpf_module_license(@module), Clib.bpf_module_kern_version(@module), log_level, nil, 0, device); if fd < 0 raise SystemCallError.new(Fiddle.last_error) end fnobj = {fd: fd, name: func_name} @funcs[func_name] = fnobj return fnobj end
num_open_kprobes()
click to toggle source
# File lib/rbbcc/bcc.rb, line 448 def num_open_kprobes @kprobe_fds.size end
num_open_tracepoints()
click to toggle source
# File lib/rbbcc/bcc.rb, line 456 def num_open_tracepoints @tracepoint_fds.size end
num_open_uprobes()
click to toggle source
# File lib/rbbcc/bcc.rb, line 452 def num_open_uprobes @uprobe_fds.size end
perf_buffer_poll(timeout=-1)
click to toggle source
# File lib/rbbcc/bcc.rb, line 550 def perf_buffer_poll(timeout=-1) readers = self.perf_buffers.values readers.each {|r| r.size = Clib::PerfReader.size } pack = readers.map{|r| r[0, Clib::PerfReader.size] }.pack('p*') Clib.perf_reader_poll(readers.size, pack, timeout) end
trace_fields(&do_each_line)
click to toggle source
# File lib/rbbcc/bcc.rb, line 480 def trace_fields(&do_each_line) ret = [] while buf = trace_readline next if buf.start_with? "CPU:" task = buf[0..15].lstrip() meta, _addr, msg = buf[17..-1].split(": ", 3) pid, cpu, flags, ts = meta.split(" ") cpu = cpu[1..-2] if do_each_line do_each_line.call(task, pid, cpu, flags, ts, msg) else ret = [task, pid, cpu, flags, ts, msg] break end end ret end
trace_print(fmt: nil)
click to toggle source
# File lib/rbbcc/bcc.rb, line 468 def trace_print(fmt: nil) loop do if fmt # TBD else line = trace_readline end puts line $stdout.flush end end
trace_readline()
click to toggle source
# File lib/rbbcc/bcc.rb, line 464 def trace_readline tracefile.readline(1024) end
tracefile()
click to toggle source
# File lib/rbbcc/bcc.rb, line 460 def tracefile @tracefile ||= File.open("#{TRACEFS}/trace_pipe", "rb") end
Private Instance Methods
fix_syscall_fnname(name)
click to toggle source
# File lib/rbbcc/bcc.rb, line 620 def fix_syscall_fnname(name) SYSCALL_PREFIXES.each do |prefix| if name.start_with?(prefix) real = SYSCALL_PREFIXES.find { |candidate| SymbolCache.resolve_global(name.sub(prefix, candidate)) } unless real real = prefix end return name.sub(prefix, real) end end return name end
to_uprobe_evname(prefix, path, addr, pid)
click to toggle source
# File lib/rbbcc/bcc.rb, line 636 def to_uprobe_evname(prefix, path, addr, pid) if pid == -1 return "%s_%s_0x%x" % [prefix, path.gsub(/[^_a-zA-Z0-9]/, "_"), addr] else return "%s_%s_0x%x_%d" % [prefix, path.gsub(/[^_a-zA-Z0-9]/, "_"), addr, pid] end end
trace_autoload!()
click to toggle source
# File lib/rbbcc/bcc.rb, line 575 def trace_autoload! (0..Clib.bpf_num_functions(@module)).each do |i| func_name = "" _func_name = Clib.bpf_function_name(@module, i) if _func_name && !_func_name.null? idx = 0 while _func_name[idx, 1] != "\x00" idx += 1 end _func_name.size = idx + 1 func_name = _func_name.to_s else next end Util.debug "Found fnc: #{func_name}" if func_name.start_with?("kprobe__") fn = load_func(func_name, BPF::KPROBE) attach_kprobe( event: fix_syscall_fnname(func_name[8..-1]), fn_name: fn[:name] ) elsif func_name.start_with?("kretprobe__") fn = load_func(func_name, BPF::KPROBE) attach_kretprobe( event: fix_syscall_fnname(func_name[11..-1]), fn_name: fn[:name] ) elsif func_name.start_with?("tracepoint__") fn = load_func(func_name, BPF::TRACEPOINT) tp = fn[:name].sub(/^tracepoint__/, "").sub(/__/, ":") attach_tracepoint( tp: tp, fn_name: fn[:name] ) elsif func_name.start_with?("raw_tracepoint__") fn = load_func(func_name, BPF::RAW_TRACEPOINT) tp = fn[:name].sub(/^raw_tracepoint__/, "") attach_raw_tracepoint( tp: tp, fn_name: fn[:name] ) end end end