Skip to content

Libbpf eBPF macro bpf_printk

v0.0.6

The bpf_printk macro is used to make printing to the kernel trace log easier.

Definition

The short version (left out the implementation)

/* Helper macro to print out debug messages */
#define bpf_printk(fmt, args...) ___bpf_pick_printk(args)(fmt, ##args)

The full version
#ifdef BPF_NO_GLOBAL_DATA
#define BPF_PRINTK_FMT_MOD
#else
#define BPF_PRINTK_FMT_MOD static const
#endif

#define __bpf_printk(fmt, ...)              \
({                          \
    BPF_PRINTK_FMT_MOD char ____fmt[] = fmt;    \
    bpf_trace_printk(____fmt, sizeof(____fmt),  \
            ##__VA_ARGS__);     \
})

/*
* __bpf_vprintk wraps the bpf_trace_vprintk helper with variadic arguments
* instead of an array of u64.
*/
#define __bpf_vprintk(fmt, args...)             \
({                              \
    static const char ___fmt[] = fmt;           \
    unsigned long long ___param[___bpf_narg(args)];     \
                                \
    _Pragma("GCC diagnostic push")              \
    _Pragma("GCC diagnostic ignored \"-Wint-conversion\"")  \
    ___bpf_fill(___param, args);                \
    _Pragma("GCC diagnostic pop")               \
                                \
    bpf_trace_vprintk(___fmt, sizeof(___fmt),       \
            ___param, sizeof(___param));        \
})

/* Use __bpf_printk when bpf_printk call has 3 or fewer fmt args
* Otherwise use __bpf_vprintk
*/
#define ___bpf_pick_printk(...) \
    ___bpf_nth(_, ##__VA_ARGS__, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk,   \
        __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk,     \
        __bpf_vprintk, __bpf_vprintk, __bpf_printk /*3*/, __bpf_printk /*2*/,\
        __bpf_printk /*1*/, __bpf_printk /*0*/)

/* Helper macro to print out debug messages */
#define bpf_printk(fmt, args...) ___bpf_pick_printk(args)(fmt, ##args)

Usage

This is a macro makes writing to the kernel trace log easier. It does two things, the first is it picks between the bpf_trace_printk and bpf_trace_vprintk helper functions. The bpf_trace_printk helper only supports up to 3 arguments besides the format string, but bpf_trace_vprintk supports any number of arguments (the macro supports up to 12 arguments).

Note

While the bpf_trace_printk helper is supported on most kernels, the bpf_trace_vprintk helper is only supported on kernel versions v5.16 and later.

So using this macro with more than 3 arguments on older kernels might cause the verifier to reject your program.

The second thing it does is it turns the literal format string into a char array. If BPF_NO_GLOBAL_DATA is defined the char array will live on the stack, otherwise it will be turned into a global variable (this only works up to 4 arguments, the bpf_trace_vprintk variant always make the format string into a global variable). Without this char array the compiler will place strings in a dedicated string ELF section unrecognized by loaders and not emit the proper global variable relocations.

Example

SEC("tc")
int example_prog(struct __sk_buff *ctx)
{
    // Will use bpf_trace_printk
    bpf_printk(
        "Got a packet from interface %d, src: %pi4, dst: %pi4\\n", 
        ctx->ingress_ifindex, 
        ctx->remote_ip4, 
        ctx->local_ip4,
    );

    // Will use bpf_trace_vprintk
    bpf_printk(
        "Got a packet from interface %d, src: %pi4, dst: %pi4, src port: %d, dst port: %d\\n", 
        ctx->ingress_ifindex, 
        ctx->remote_ip4, 
        ctx->local_ip4,
        ctx->remote_port,
        ctx->local_port,
    );

    return TC_ACT_OK;
}