Operations 9 min read

Capture Linux unlink Calls Using eBPF fentry/fexit – A Step‑by‑Step Guide

This article explains how to use eBPF fentry and fexit probes to monitor the Linux unlink system call, compares them with kprobes, provides complete C and Go examples, shows compilation with eunomia‑bpf, and discusses the low‑overhead advantages of fentry for kernel tracing.

BirdNest Tech Talk
BirdNest Tech Talk
BirdNest Tech Talk
Capture Linux unlink Calls Using eBPF fentry/fexit – A Step‑by‑Step Guide

Problem

Capture the unlink system call in Linux using eBPF fentry (function entry) and fexit (function exit) probes. These probes attach to a kernel function at its entry and exit points, allowing direct access to the function’s arguments and return value without the helper‑read overhead required by traditional kprobe and kretprobe mechanisms.

Since Linux 5.5, fentry / fexit are available to eBPF programs and provide lower latency because they execute in‑line with the target function, avoiding the extra trap handling of kprobes.

Solution

Below is a minimal eBPF program that traces do_unlinkat, the kernel implementation of the unlink syscall. Two probes are defined: one at entry ( fentry) and one at exit ( fexit).

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_endian.h>

char LICENSE[] SEC("license") = "Dual BSD/GPL";

SEC("fentry/do_unlinkat")
int BPF_PROG(do_unlinkat, int dfd, struct filename *name)
{
    pid_t pid = bpf_get_current_pid_tgid() >> 32;
    bpf_printk("fentry: pid = %d, filename = %s", pid, name->name);
    return 0;
}

SEC("fexit/do_unlinkat")
int BPF_PROG(do_unlinkat_exit, int dfd, struct filename *name, long ret)
{
    pid_t pid = bpf_get_current_pid_tgid() >> 32;
    bpf_printk("fexit: pid = %d, filename = %s, ret = %ld", pid, name->name, ret);
    return 0;
}

Key components:

Headers : vmlinux.h provides kernel data‑structure definitions; bpf/bpf_helpers.h supplies eBPF helper functions; bpf/bpf_tracing.h contains tracing‑specific APIs.

License : The verifier requires a LICENSE string; "Dual BSD/GPL" is a common choice.

fentry probe : Declared with SEC("fentry/do_unlinkat"). It runs before do_unlinkat executes, reads the current PID via bpf_get_current_pid_tgid(), and prints the PID together with the filename argument.

fexit probe : Declared with SEC("fexit/do_unlinkat"). It runs after the function returns, obtains the same PID, and prints the PID, filename, and the function’s return value.

Compile and run the program with the eunomia‑bpf toolchain:

root@host:~/ebpf-study$ ecc fentry-link.bpf.c
INFO [ecc_rs::bpf_compiler] Compiling bpf object...
INFO [ecc_rs::bpf_compiler] Generating package json..
INFO [ecc_rs::bpf_compiler] Packing ebpf object and config into package.json...
root@host:~/ebpf-study$ ecli run package.json
INFO [faerie::elf] strtab: 0x4cb symtab 0x508 relocs 0x550 sh_offset 0x550
INFO [bpf_loader_lib::skeleton::poller] Running ebpf program...

For a Go‑based loader, the cilium/ebpf library can be used. The bpf2go command generates Go bindings from the C source, then the program attaches the probes and waits for a termination signal.

//go:generate bpf2go -cc clang -cflags "-O2 -g -target bpf -D__TARGET_ARCH_x86" \
    --go-package main bpf fentry-link.bpf.c -- -I/usr/include/bpf -I/usr/include/linux

package main

import (
    "fmt"
    "log"
    "os"
    "os/signal"
    "syscall"

    "github.com/cilium/ebpf"
    "github.com/cilium/ebpf/link"
    "github.com/cilium/ebpf/rlimit"
)

func main() {
    if err := rlimit.RemoveMemlock(); err != nil {
        log.Fatal(err)
    }
    objs := bpfObjects{}
    if err := loadBpfObjects(&objs, nil); err != nil {
        log.Fatalf("Loading objects failed: %v", err)
    }
    defer objs.Close()

    // Attach fentry to the entry point of do_unlinkat
    fentry, err := link.AttachTracing(link.TracingOptions{Program: objs.DoUnlinkat, AttachType: ebpf.AttachTraceFEntry})
    if err != nil {
        log.Fatalf("Attaching fentry program failed: %v", err)
    }
    defer fentry.Close()

    // Attach fexit to the exit point of do_unlinkat
    fexit, err := link.AttachTracing(link.TracingOptions{Program: objs.DoUnlinkatExit, AttachType: ebpf.AttachTraceFExit})
    if err != nil {
        log.Fatalf("Attaching fexit program failed: %v", err)
    }
    defer fexit.Close()

    fmt.Println("eBPF programs successfully attached.")

    // Wait for SIGINT or SIGTERM
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
    <-sig
    fmt.Println("Exiting program...")
}

Running go generate creates the Go bindings, then go run . starts the loader. Each time an unlink occurs, the kernel logs lines such as:

fentry: pid = 1234, filename = /tmp/file.txt
fexit: pid = 1234, filename = /tmp/file.txt, ret = 0

Discussion

How fentry works – The eBPF program is injected at the exact entry point of the target kernel function. Because the probe runs before any of the function’s own instructions, it avoids the context‑switch and breakpoint handling required by kprobes, resulting in lower overhead.

fexit complementarity – The fexit probe runs after the function returns, giving access to both the original arguments (still available on the stack) and the return value. Using both probes together yields a full picture of the syscall’s lifecycle.

Advantages over kprobe/kretprobe

Reduced latency: Direct injection eliminates the extra trap that kprobes incur.

Simplified code: Arguments can be read directly as C variables; no need for helper functions like bpf_probe_read.

Kernel compatibility: Requires only CONFIG_BPF and Linux 5.5+; older kprobe techniques work on earlier kernels but with higher overhead.

fentry vs fexit diagram
fentry vs fexit diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

GoCLinuxeBPFkernel tracingfentry
BirdNest Tech Talk
Written by

BirdNest Tech Talk

Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.