Skip to content

eBPF

eBPF documentation

bpftrace

homepage

BCC

github project

#!/usr/bin/python3

import sys
import time
import ctypes

from bcc import BPF
# sudo bpftrace -e 'tracepoint:sched:sched_switch /args->prev_comm=="child"/{  printf("%s\n", args->next_comm);@[ustack(1)]=count();}'
# sudo bpftrace -l tracepoint:sched:sched_switch -vv
# tracepoint:sched:sched_switch
#    char prev_comm[16]
#    pid_t prev_pid
#    int prev_prio
#    long prev_state
#    char next_comm[16]
#    pid_t next_pid
#    int next_prio
src = r"""
#define TARGET_PROCESS_NAME "child"


BPF_RINGBUF_OUTPUT(buffer, 1 << 4);
BPF_STACK_TRACE(stacktraces, 1024);

// https://github.com/iovisor/bcc/issues/5137 bpf_strncmp doesn't work

// Returns 0 if lhs and rhs compares equal up to n bytes. Otherwise a non-zero value is returned.
// NOTE #1: Cannot use C standard library's strncmp() because that cannot be compiled by BCC.
// NOTE #2: Different from the C standard library's strncmp(), this does not distinguish order.
// NOTE #3: n must be a literal so that the BCC runtime can unroll the inner loop.
// NOTE #4: Loop unrolling increases instruction code, be aware when BPF verifier complains about
// breaching instruction count limit.
static __inline int bpf_xstrncmp(const char* lhs, size_t n, const char* rhs) {
  for (size_t i = 0; i < n; ++i) {
    if (lhs[i] != rhs[i]) {
      return 1;
    }
    // check we have a NULL we can bail out
    if (lhs[i] == 0) {
        break;
    }
  }
  return 0;
}

struct event {
    u64 timestamp;
    u64 stackid;
};
/*
 * This function serves two roles:
 *
 *  1. if we are entering our process of choice (next_comm) then save the timestamp
 *  2. if we are leaving our process of choiche (prev_comm) we calculate the diff
 *     with the previous timestamp and we save the stack and we send out the data
 */
TRACEPOINT_PROBE(sched, sched_switch) {
    char comm[16];

    bpf_probe_read_kernel_str(comm, sizeof(comm), args->prev_comm);
    bool is_leaving = bpf_xstrncmp(comm, 16, TARGET_PROCESS_NAME) == 0;

    bpf_probe_read_kernel_str(comm, sizeof(comm), args->next_comm);
    bool is_entering = bpf_xstrncmp(TARGET_PROCESS_NAME, 16, comm) == 0;

    // we are interested in other that our process
    if (!(is_entering || is_leaving)) {
        return 0;
    }
    // bpf_trace_printk("evil %s!!!", comm);


    struct event event = {};
    event.timestamp = bpf_ktime_get_ns();

    if (is_leaving) {
        event.stackid = stacktraces.get_stackid(args, BPF_F_USER_STACK);
    }

    buffer.ringbuf_output(&event, sizeof(event), 0);

    return 0;
}
"""

b = BPF(text=src)

pid = int(sys.argv[1])

def get_stacktrace(bpf, stackid: int):
    stack_traces = bpf["stacktraces"]

    output = []

    for addr in stack_traces.walk(stackid):
        sym = bpf.sym(
               addr,
               pid,
                show_module=True,
                show_offset=True)
        output.append(sym.decode('utf-8'))

    return output



def callback(ctx, data, size):
    event = b['buffer'].event(data)
    stacks = get_stacktrace(b, event.stackid) if event.stackid != 0 else [""]
    print("%10d %s" % (event.timestamp, ";".join(stacks)))

b['buffer'].open_ring_buffer(callback)

print("Printing openat() calls, ctrl-c to exit.")

print("%-16s %10s" % ("TIMESTAMP", "stacktrace",))

try:
    while 1:
        b.ring_buffer_poll()
        # or b.ring_buffer_consume()
        time.sleep(0.5)
except KeyboardInterrupt:
    sys.exit()