Skip to main content

Bpf userspace program example with wasm_bpf plug-in

There is a WasmEdge plug-in called wasm_bpf, which provided APIs to perform operations on eBPF program, such as loading, attaching and polling.

The detailed description can be found at https://github.com/WasmEdge/WasmEdge/blob/master/plugins/wasm_bpf/README.md.

Here we will provide several examples to demonstrate the wasm_bpf plug-in.

Prerequisites

For simplicity, we will reuse the Makefile of wasm-bpf, since wasmEdge_bpfPlugin has the precisely same API as wasm-bpf

  1. Clone the wasm-bpf repo.
  2. Run make install-deps and make /opt/wasi-sdk at the project's root. This will install the build prerequisites.
  3. Install WasmEdge
  4. Build and install the wasm_bpf plug-in. Currently, we have to build wasm_bpf plug-in manually. The building instructions could be found at https://github.com/WasmEdge/WasmEdge/tree/master/plugins/wasm_bpf#build-wasm_bpf-plug-in

The bootstrap example

bootstrap is a simple eBPF program to track the entry and exit of all processes. It will print a line of message when there is an entry of an exiting event of a process.

Run make in wasm-bpf/examples/bootstrap, and you will find the bootstrap.wasm, which can be executed by WasmEdge.

WASMEDGE_PLUGIN_PATH=./build/plugins/wasm_bpf/ wasmedge bootstrap.wasm

WASMEDGE_PLUGIN_PATH should be changed due to your build directory of the plug-in.

Example output:

TIME     EVENT COMM             PID     PPID    FILENAME/EXIT CODE
13:38:00 EXEC bash 121487 40189 /usr/bin/bash
13:38:00 EXEC groups 121489 121487 /usr/bin/groups
13:38:00 EXIT groups 121489 121487 [0] (3ms)
13:38:00 EXEC lesspipe 121490 121487 /usr/bin/lesspipe
13:38:00 EXEC basename 121491 121490 /usr/bin/basename
13:38:00 EXIT basename 121491 121490 [0] (8ms)
13:38:00 EXEC dirname 121493 121492 /usr/bin/dirname
13:38:00 EXIT dirname 121493 121492 [0] (1ms)
13:38:00 EXIT lesspipe 121492 121490 [0]

Details of bootstrap

bootstrap was created in a similar spirit as libbpf-tools from BCC package but is designed to be more stand-alone and with a simpler Makefile to simplify adoption to users' particular needs. It demonstrates the use of typical BPF features:

Cooperating BPF programs (tracepoint handlers for process exec and exit events, in this particular case); BPF map for maintaining the state; BPF ring buffer for sending data to userspace; global variables for application behavior parameterization. It utilizes BPF CO-RE and vmlinux.h to read extra process information from kernel's struct task_struct.

Some code snippets

A bpf program from bootstrap.bpf.c. It tracks the execution of processes, wraps the event in a struct, and sends the struct to the userspace program through ringbuf.

SEC("tp/sched/sched_process_exec")
int handle_exec(struct trace_event_raw_sched_process_exec* ctx) {
struct task_struct* task;
unsigned fname_off;
struct event* e;
pid_t pid;
u64 ts;

/* remember time exec() was executed for this PID */
pid = bpf_get_current_pid_tgid() >> 32;
ts = bpf_ktime_get_ns();
bpf_map_update_elem(&exec_start, &pid, &ts, BPF_ANY);

/* don't emit exec events when minimum duration is specified */
if (min_duration_ns)
return 0;

/* reserve sample from BPF ringbuf */
e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
if (!e)
return 0;

/* fill out the sample with data */
task = (struct task_struct*)bpf_get_current_task();

e->exit_event = false;
e->pid = pid;
e->ppid = BPF_CORE_READ(task, real_parent, tgid);
bpf_get_current_comm(&e->comm, sizeof(e->comm));

fname_off = ctx->__data_loc_filename & 0xFFFF;
bpf_probe_read_str(&e->filename, sizeof(e->filename),
(void*)ctx + fname_off);

/* successfully submit it to user-space for post-processing */
bpf_ringbuf_submit(e, 0);
return 0;
}

The userspace program's core process (compiled to Wasm). It invokes APIs from wasm_bpf to open, load, attach the bpf program, and poll data from the ringbuf.

/* Load and verify BPF application */
skel = bootstrap_bpf__open();
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return 1;
}

/* Parameterize BPF code with minimum duration parameter */
skel->rodata->min_duration_ns = env.min_duration_ms * 1000000ULL;

/* Load & verify BPF programs */
err = bootstrap_bpf__load(skel);
if (err) {
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
goto cleanup;
}

/* Attach tracepoints */
err = bootstrap_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to attach BPF skeleton\n");
goto cleanup;
}

/* Set up ring buffer polling */
rb = bpf_buffer__open(skel->maps.rb, handle_event, NULL);
if (!rb) {
err = -1;
fprintf(stderr, "Failed to create ring buffer\n");
goto cleanup;
}
/* Process events */
printf("%-8s %-5s %-16s %-7s %-7s %s\n", "TIME", "EVENT", "COMM", "PID",
"PPID", "FILENAME/EXIT CODE");
while (!exiting) {
// poll buffer
err = bpf_buffer__poll(rb, 100 /* timeout, ms */);
/* Ctrl-C will cause -EINTR */
if (err == -EINTR) {
err = 0;
break;
}
if (err < 0) {
printf("Error polling perf buffer: %d\n", err);
break;
}
}

Other examples

Each directory under wasm-bpf/examples represents an example able to be run using WasmEdge. You can run make in their directory and run the corresponding WASM with WasmEdge.