Commit 8fa8c0e8 authored by Mark Drayton's avatar Mark Drayton

Store kprobes with string keys, fix num_open_kprobes

Prior to this diff we used inconsistent types for keys in `open_kprobes`. The
results from the regex match (`attach_kprobe(event_re=..)`) and the automatic
`kprobe__` features were passed through `str.decode()`, yielding unicode keys,
but specific matches (i.e. from `attach_kprobe(event=..)`) were stored with
string keys passed down from the caller. Only probes under string keys were
released in `cleanup_kprobes`, leaving attached probes on program exit.

This diff makes all the keys regular strings. I erred on the side of using
regular strings over `str.decode()`ing them because a) this data isn't passed
outside of Python, b) it's more Python 3 compatible (there is no `.decode()` on
a regular string object in Python 3 so such a change would ultimately need
removing again).

I also cleaned up a few other things:

* removed the call to `awk` for getting probable functions

* removed the `isinstance` checks when cleaning uprobes/tracepoints -- we
  should only have string keys in these dicts

* made `num_open_kprobes` skip the perf_events buffers. People likely use this
  to check that the right number of probes have been placed so counting
  perf_events buffers doesn't make sense here
parent a1333bcd
...@@ -20,7 +20,6 @@ import json ...@@ -20,7 +20,6 @@ import json
import multiprocessing import multiprocessing
import os import os
import re import re
from subprocess import Popen, PIPE, STDOUT
import struct import struct
import sys import sys
basestring = (unicode if sys.version_info[0] < 3 else str) basestring = (unicode if sys.version_info[0] < 3 else str)
...@@ -46,19 +45,18 @@ DEBUG_PREPROCESSOR = 0x4 ...@@ -46,19 +45,18 @@ DEBUG_PREPROCESSOR = 0x4
def cleanup_kprobes(): def cleanup_kprobes():
for k, v in open_kprobes.items(): for k, v in open_kprobes.items():
lib.perf_reader_free(v) lib.perf_reader_free(v)
# non-string keys here include the perf_events reader
if isinstance(k, str): if isinstance(k, str):
desc = "-:kprobes/%s" % k desc = "-:kprobes/%s" % k
lib.bpf_detach_kprobe(desc.encode("ascii")) lib.bpf_detach_kprobe(desc.encode("ascii"))
for k, v in open_uprobes.items(): for k, v in open_uprobes.items():
lib.perf_reader_free(v) lib.perf_reader_free(v)
if isinstance(k, str): desc = "-:uprobes/%s" % k
desc = "-:uprobes/%s" % k lib.bpf_detach_uprobe(desc.encode("ascii"))
lib.bpf_detach_uprobe(desc.encode("ascii"))
for k, v in open_tracepoints.items(): for k, v in open_tracepoints.items():
lib.perf_reader_free(v) lib.perf_reader_free(v)
if isinstance(k, str): (tp_category, tp_name) = k.split(':')
(tp_category, tp_name) = k.split(':') lib.bpf_detach_tracepoint(tp_category, tp_name)
lib.bpf_detach_tracepoint(tp_category, tp_name)
open_kprobes.clear() open_kprobes.clear()
open_uprobes.clear() open_uprobes.clear()
open_tracepoints.clear() open_tracepoints.clear()
...@@ -350,19 +348,21 @@ class BPF(object): ...@@ -350,19 +348,21 @@ class BPF(object):
@staticmethod @staticmethod
def _get_kprobe_functions(event_re): def _get_kprobe_functions(event_re):
p = Popen(["awk", "$1 ~ /%s/ { print $1 }" % event_re, blacklist = set([line.rstrip().split()[1] for line in
"%s/available_filter_functions" % TRACEFS], stdout=PIPE) open("%s/../kprobes/blacklist" % TRACEFS)])
lines = p.communicate()[0].decode().split() fns = []
with open("%s/../kprobes/blacklist" % TRACEFS) as f: with open("%s/available_filter_functions" % TRACEFS) as f:
blacklist = [line.split()[1] for line in f.readlines()] for line in f:
fns = [line.rstrip() for line in lines if fn = line.rstrip().split()[0]
(line != "\n" and line not in blacklist)] if re.match(event_re, fn) and fn not in blacklist:
fns.append(fn)
_check_probe_quota(len(fns)) _check_probe_quota(len(fns))
return fns return fns
def attach_kprobe(self, event="", fn_name="", event_re="", def attach_kprobe(self, event="", fn_name="", event_re="",
pid=-1, cpu=0, group_fd=-1): pid=-1, cpu=0, group_fd=-1):
assert isinstance(event, str), "event must be a string"
# allow the caller to glob multiple functions together # allow the caller to glob multiple functions together
if event_re: if event_re:
for line in BPF._get_kprobe_functions(event_re): for line in BPF._get_kprobe_functions(event_re):
...@@ -403,6 +403,7 @@ class BPF(object): ...@@ -403,6 +403,7 @@ class BPF(object):
@staticmethod @staticmethod
def detach_kprobe(event): def detach_kprobe(event):
assert isinstance(event, str), "event must be a string"
ev_name = "p_" + event.replace("+", "_").replace(".", "_") ev_name = "p_" + event.replace("+", "_").replace(".", "_")
if ev_name not in open_kprobes: if ev_name not in open_kprobes:
raise Exception("Kprobe %s is not attached" % event) raise Exception("Kprobe %s is not attached" % event)
...@@ -416,6 +417,7 @@ class BPF(object): ...@@ -416,6 +417,7 @@ class BPF(object):
def attach_kretprobe(self, event="", fn_name="", event_re="", def attach_kretprobe(self, event="", fn_name="", event_re="",
pid=-1, cpu=0, group_fd=-1): pid=-1, cpu=0, group_fd=-1):
assert isinstance(event, str), "event must be a string"
# allow the caller to glob multiple functions together # allow the caller to glob multiple functions together
if event_re: if event_re:
for line in BPF._get_kprobe_functions(event_re): for line in BPF._get_kprobe_functions(event_re):
...@@ -441,6 +443,7 @@ class BPF(object): ...@@ -441,6 +443,7 @@ class BPF(object):
@staticmethod @staticmethod
def detach_kretprobe(event): def detach_kretprobe(event):
assert isinstance(event, str), "event must be a string"
ev_name = "r_" + event.replace("+", "_").replace(".", "_") ev_name = "r_" + event.replace("+", "_").replace(".", "_")
if ev_name not in open_kprobes: if ev_name not in open_kprobes:
raise Exception("Kretprobe %s is not attached" % event) raise Exception("Kretprobe %s is not attached" % event)
...@@ -609,7 +612,7 @@ class BPF(object): ...@@ -609,7 +612,7 @@ class BPF(object):
def _trace_autoload(self): def _trace_autoload(self):
for i in range(0, lib.bpf_num_functions(self.module)): for i in range(0, lib.bpf_num_functions(self.module)):
func_name = lib.bpf_function_name(self.module, i).decode() func_name = lib.bpf_function_name(self.module, i)
if len(open_kprobes) == 0 and func_name.startswith("kprobe__"): if len(open_kprobes) == 0 and func_name.startswith("kprobe__"):
fn = self.load_func(func_name, BPF.KPROBE) fn = self.load_func(func_name, BPF.KPROBE)
self.attach_kprobe(event=fn.name[8:], fn_name=fn.name) self.attach_kprobe(event=fn.name[8:], fn_name=fn.name)
...@@ -755,9 +758,10 @@ class BPF(object): ...@@ -755,9 +758,10 @@ class BPF(object):
"""num_open_kprobes() """num_open_kprobes()
Get the number of open K[ret]probes. Can be useful for scenarios where Get the number of open K[ret]probes. Can be useful for scenarios where
event_re is used while attaching and detaching probes event_re is used while attaching and detaching probes. Excludes
perf_events readers.
""" """
return len(open_kprobes) return len([k for k in open_kprobes.keys() if isinstance(k, str)])
def kprobe_poll(self, timeout = -1): def kprobe_poll(self, timeout = -1):
"""kprobe_poll(self) """kprobe_poll(self)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment