I'm trying to implement the killsnoop.py program in bcc in C. When executing the program, I'm getting a failed to load: -13
error. Can someone help me to debug this?
Note: For compilation, I've taken the libbpf-bootstrap example from Andrii Nakryiko
's blog post.
Below is the program that I've used.
killsnoop.bpf.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "killsnoop.h"
struct val_t {
__u32 uid;
__u32 pid;
int sig;
int tpid;
char comm[TASK_COMM_LEN];
};
struct data_t {
__u32 uid;
__u32 pid;
int tpid;
int sig;
int ret;
char comm[TASK_COMM_LEN];
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __u32);
__type(value, struct val_t);
__uint(max_entries, 10240);
} info_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __u32);
__type(value, struct data_t);
__uint(max_entries, 10240);
} event SEC(".maps");
SEC("kprobe/__x64_sys_kill")
int entry_probe(struct pt_regs *ctx, int tpid, int sig) {
__u64 pid_tgid = bpf_get_current_pid_tgid();
__u32 pid = pid_tgid >> 32;
__u32 tid = (__u32) pid_tgid;
__u64 uid_gid = bpf_get_current_uid_gid();
__u32 uid = (__u32) uid_gid;
struct val_t val = {
.uid = uid,
.pid = pid
};
if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) == 0) {
val.tpid = tpid;
val.sig = sig;
bpf_map_update_elem(&info_map, &tid, &val, BPF_ANY);
}
return 0;
}
SEC("kretprobe/__x64_sys_kill")
int return_probe(struct pt_regs *ctx) {
struct data_t data = {};
struct val_t *valp;
__u64 pid_tgid = bpf_get_current_pid_tgid();
__u32 pid = pid_tgid >> 32;
__u32 tid = (__u32) pid_tgid;
valp = bpf_map_lookup_elem(&info_map, &tid);
if (!valp) {
return 0; // missed entry
}
bpf_core_read(&data.comm, sizeof(data.comm), valp->comm);
data.pid = pid;
data.tpid = valp->tpid;
data.ret = PT_REGS_RC_CORE(ctx);
data.sig = valp->sig;
data.uid = valp->uid;
bpf_perf_event_output(ctx, &event, BPF_F_CURRENT_CPU, &data, sizeof(data));
bpf_map_delete_elem(&info_map, &tid);
return 0;
}
killsnoop.c
#include <signal.h>
#include "killsnoop.skel.h"
#include "killsnoop.h"
#define OUTPUT_FORMAT "%lld %d %d %s %d %d"
#define PERF_POLL_TIMEOUT_MS 10
#define PERF_BUFFER_PAGES 64
static volatile int shutdown = 0;
static void sig_int(int signal) {
shutdown = 1;
}
void handle_event(void *ctx, int cpu, void *data, u32 data_size) {
const struct event *e = data;
time_t curr_time;
time(&curr_time);
printf(OUTPUT_FORMAT, (long long) curr_time, e->pid, e->tpid, e->comm, e->sig, e->uid);
}
void handle_lost_event(void *ctx, int cpu, u64 lost_cnt) {
fprintf(stderr, "Lost %llu events on CPU #%d!\n", lost_cnt, cpu);
}
int main(int argc, char **argv) {
int err;
struct perf_buffer *perfBuffer;
struct killsnoop_bpf *obj;
obj = killsnoop_bpf__open_and_load();
if (!obj) {
fprintf(stderr, "failed to open/load BPF skeleton!");
goto cleanup;
}
err = killsnoop_bpf__attach(obj);
if (err) {
fprintf(stderr, "failed to attach BPF programs\n");
goto cleanup;
}
perfBuffer = perf_buffer__new(bpf_map__fd(obj->maps.info_map), PERF_BUFFER_PAGES, handle_event, handle_lost_event,
NULL, NULL);
if (!perfBuffer) {
err = -errno;
fprintf(stderr, "failed to open perf buffer: %d\n", err);
goto cleanup;
}
if (signal(SIGINT, sig_int) == SIG_ERR) {
fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
err = 1;
goto cleanup;
}
while (!shutdown) {
err = perf_buffer__poll(perfBuffer, PERF_POLL_TIMEOUT_MS);
if (err < 0 && err != -EINTR) {
fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err));
goto cleanup;
}
/* reset err to return 0 if exiting */
err = 0;
}
cleanup:
perf_buffer__free(perfBuffer);
killsnoop_bpf__destroy(obj);
return err != 0;
}
killsnoop.h
#define TASK_COMM_LEN 16
typedef unsigned int u32;
typedef unsigned long long u64;
struct event {
u32 uid;
u32 pid;
int tpid;
int sig;
int ret;
char comm[TASK_COMM_LEN];
};
BCC tends to do a little bit of background magic which allows you to use the arguments of the attachment point directly in your eBPF program like you are doing:
entry_probe(struct pt_regs *ctx, int tpid, int sig)
However, this is BCC specific. With libbpf you have to use the BPF_KPROBE_SYSCALL
macro to get similar behavior. For example: https://elixir.bootlin.com/linux/v5.18.14/source/tools/testing/selftests/bpf/progs/bpf_syscall_macro.c#L68
In your case:
SEC("kprobe/" SYS_PREFIX "sys_kill")
int BPF_KPROBE_SYSCALL(entry_probe, int tpid, int sig) {
...
The reason you are getting this error is because all arguments are saves to the stack once the BPF program is entered. Thus the program attempts to write R3 which is int sig
in this case to the stack. But R3 is not defined, only R1 is which is the ctx. And you are not allowed to read from uninitialized registers hence the R3 !read_ok
error.