Skip to main content
  1. Posts/

eBPF for Linux Admins: Part IX

·5 mins· loading · loading ·
Ansil H
ebpf kprobes ebpf maps
Author
Ansil H
DevOps Guy
eBPF - This article is part of a series.
Part 9: This Article

In this chapter, we will look at how to use eBPF maps to pass information to user space programs.

Also we will write an ebpf loader program instead of using bpftool this time 😃

BPF Loader
#

The ebpf loader program will perform below with the help of libbpf library.

  • Loads the bpf object code
  • Attaches the bpf program to an event
  • Create maps
  • Attach map to the ebpf program
  • Poll information from map

eBPF maps
#

Maps are a generic data structure for storage of different types of data. They allow sharing of data between eBPF kernel programs, and also between kernel and user-space applications.

Each map type has the following attributes: • type • maximum number of elements • key size in bytes • value size in bytes

You can read more about eBPF maps in the man pages. https://man7.org/linux/man-pages/man2/bpf.2.html

There are many types of maps supported by eBPF https://elixir.bootlin.com/linux/v6.7.4/source/include/uapi/linux/bpf.h#L906

But in this article we will be using a new type of map called, BPF_MAP_TYPE_RINGBUF.

You can read more about this map in below link. https://www.kernel.org/doc/html/next/bpf/ringbuf.html

The loader code
#

Please read the inline-comments in the code to understand what is happening in the loader.

vi kprobe_openat_user.c
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <unistd.h>

#define TASK_COMM_LEN 16
#define FILE_NAME_LEN 256

// Placeholder for type casting ring-buffer data
struct event
{
  __u32 e_pid;
  char e_filename[FILE_NAME_LEN];
  char e_comm[TASK_COMM_LEN];
};

// Function that translates the ring-buffer data and prints to console
static int
event_logger (void *ctx, void *data, size_t len)
{
  struct event *evt = (struct event *) data;
  printf ("do_sys_openat2 called by:%s file:%s pid:%d\n", evt->e_comm,
	  evt->e_filename, evt->e_pid);
  return 0;
}

// Main 
int
main ()
{
  const char *file = "kprobe_openat.ebpf.o";
  struct bpf_object *ebpf_object;
  struct bpf_program *ebpf_prog;
  struct bpf_link *ebpf_link = NULL;
  int err, map_fd;
  struct ring_buffer *rb;
  
  // Open the ebpf object code 
  ebpf_object = bpf_object__open_file (file, NULL);
  if (!ebpf_object)
    {
      printf ("Unable to open ebpf program");
      return 1;
    }
 
  // Load the ebpf object code
  err = bpf_object__load (ebpf_object);
  if (err)
    {
      printf ("Failed to load ebpf program");
      return 1;
    }

  // The find the relevant ebpf function from object code
  ebpf_prog =
    bpf_object__find_program_by_name (ebpf_object, "kprobe__do_sys_openat2");
  if (!ebpf_prog)
    {
      printf ("Failed to find program name");
      return 1;
    }

  // Auto-attach the ebpf program to the kprobe
  ebpf_link = bpf_program__attach (ebpf_prog);
  if (!ebpf_link)
    {
      printf ("Failed to attach ebpf");
      return 1;
    }

  // Get the file descriptor to the map inside the ebpf object
  map_fd = bpf_object__find_map_fd_by_name (ebpf_object, "rb");

  // Create new ring-buffer and map the fd to the new rb and attach event_logger for polling
  rb = ring_buffer__new (map_fd, event_logger, NULL, NULL);
  if (!rb)
    {
      puts ("Failed to create rb");
      return 1;
    }
  while (1)
    {
      // Read the data from ring-buffer (polling by event_logger)
      ring_buffer__consume (rb);
      sleep (1);
    }
  return 0;
}

eBPF program
#

vi kprobe_openat.ebpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <string.h>

#define TARGET_NAME "sample_write"
#define MAX_ENTRIES 10
#define FILE_NAME_LEN 256

// Structure to store the data that we want to pass to user
struct event
{
  u32 e_pid;
  char e_filename[FILE_NAME_LEN];
  char e_comm[TASK_COMM_LEN];
};

// eBPF map reference
struct
{
  __uint (type, BPF_MAP_TYPE_RINGBUF);
  __uint (max_entries, 256 * 1024);
} rb SEC (".maps");

// The ebpf auto-attach logic needs the SEC
SEC ("kprobe/do_sys_openat2")
     int kprobe__do_sys_openat2 (struct pt_regs *ctx)
{
  char filename[256];
  char comm[TASK_COMM_LEN] = { };
  struct event *evt;
  const char fmt_str[] = "do_sys_openat2 called by:%s file:%s pid:%d";

  // Reserve the ring-buffer
  evt = bpf_ringbuf_reserve (&rb, sizeof (struct event), 0);
  if (!evt)
    {
      return 0;
    }
  
  // Get the PID of the process.
  evt->e_pid = bpf_get_current_pid_tgid ();	// Get current process PID

  // Read the filename from the second argument
  // The x86 arch/ABI have first argument in di and second in si registers (man syscall) 
  bpf_probe_read (evt->e_filename, sizeof (filename), (char *) ctx->si);

  // Read the current process name
  bpf_get_current_comm (evt->e_comm, sizeof (comm));

  // Compare process name with our "sample_write" name
  if (memcmp (evt->e_comm, TARGET_NAME, 12) == 0)
    {
      // Print a message with filename, process name, and PID
      bpf_trace_printk (fmt_str, sizeof (fmt_str), evt->e_comm,
			evt->e_filename, evt->e_pid);
      // Also send the same message to the ring-buffer
      bpf_ringbuf_submit (evt, 0);
      return 0;
    }
  // If the program name is not matching with TARGET_NAME, then discard the data
  bpf_ringbuf_discard (evt, 0);
  return 0;
}

char _license[] SEC ("license") = "GPL";

Compilation steps
#

  • Generate the types header from running kernel’s BTF.
bpftool btf dump file /sys/kernel/btf/vmlinux format c >vmlinux.h
  • Compile the user-space program
clang -O2 -g -Wall kprobe_openat_user.c -o kprobe_openat_user -lbpf
  • Compile the kernel-space eBPF re-locatable object code
clang -v -O2 -g -Wall -target bpf -c kprobe_openat.ebpf.c -o kprobe_openat.ebpf.o

Execution and monitoring
#

  • Open another terminal to watch the trace pipe so that we can compare that with the ring-buffer data.
sudo cat /sys/kernel/debug/tracing/trace_pipe
  • Execute the user-space program and the program will not show any output this time.
sudo  ./kprobe_openat_user
  • Now, execute the sample_write that we created in Chapter 6.
./sample_write
  • Output of the user-space program.
do_sys_openat2 called by:sample_write file:/etc/ld.so.cache pid:2361
do_sys_openat2 called by:sample_write file:/lib/x86_64-linux-gnu/libc.so.6 pid:2361
do_sys_openat2 called by:sample_write file:./sample.txt pid:2361
  • Output of trace pipe
           <...>-2361    [002] ...21 96607.710040: bpf_trace_printk: do_sys_openat2 called by:sample_write file:/etc/ld.so.cache pid:2361
           <...>-2361    [002] ...21 96607.710082: bpf_trace_printk: do_sys_openat2 called by:sample_write file:/lib/x86_64-linux-gnu/libc.so.6 pid:2361
    sample_write-2361    [002] ...21 96607.710316: bpf_trace_printk: do_sys_openat2 called by:sample_write file:./sample.txt pid:2361

This conclude the primer to ebpf for linux Admin series.

Foot Note:-

I’m neither an expert in eBPF nor a Kernel developer. I’m an ops guy with an enthusiasm to learn new technologies. My intention was to give an intro for eBPF which can easily connect with Linux admins. The programs given in this series are used to showcase some of the capabilities of eBPF and should not be used for production use. There are a tone of information/details to be discussed to uncover eBPF Please refer the eBPF/Linux Kernel docs for further reading

Some parts of the codes in this article were inspired by the Falco Blog

eBPF - This article is part of a series.
Part 9: This Article

Related

eBPF for Linux Admins: Part VIII
·3 mins· loading · loading
Ansil H
ebpf kprobes
eBPF for Linux Admins: Part VII
·6 mins· loading · loading
Ansil H
ebpf kernel kprobes
eBPF for Linux Admins: Part VI
·4 mins· loading · loading
Ansil H
ebpf kernel kprobes