Skip to content

SCX eBPF macro MEMBER_VPTR

v6.12

The MEMBER_VPTR macro obtains a verified pointer to a struct or array member

Definition

#define MEMBER_VPTR(base, member) (typeof((base) member) *)             \
({                                                                      \
    u64 __base = (u64)&(base);                                          \
    u64 __addr = (u64)&((base) member) - __base;                        \
    _Static_assert(sizeof(base) >= sizeof((base) member),               \
               "@base is smaller than @member, is @base a pointer?");   \
    asm volatile (                                                      \
        "if %0 <= %[max] goto +2\n"                                     \
        "%0 = 0\n"                                                      \
        "goto +1\n"                                                     \
        "%0 += %1\n"                                                    \
        : "+r"(__addr)                                                  \
        : "r"(__base),                                                  \
          [max]"i"(sizeof(base) - sizeof((base) member)));              \
    __addr;                                                             \
})

Usage

The verifier often gets confused by the instruction sequence the compiler generates for indexing struct fields or arrays. This macro forces the compiler to generate a code sequence which first calculates the byte offset, checks it against the struct or array size and add that byte offset to generate the pointer to the member to help the verifier.

Ideally, we want to abort if the calculated offset is out-of-bounds. However, BPF currently doesn't support abort, so evaluate to NULL instead. The caller must check for NULL and take appropriate action to appease the verifier. To avoid confusing the verifier, it's best to check for NULL and dereference immediately.

vptr = MEMBER_VPTR(my_array, [i][j]);
if (!vptr)
    return error;
*vptr = new_value;

sizeof(base) should encompass the memory area to be accessed and thus can't be a pointer to the area. Use MEMBER_VPTR(*ptr, .member) instead of MEMBER_VPTR(ptr, ->member).

Parameters

  • @base: struct or array to index
  • @member: dereferenced member (e.g. .field, [idx0][idx1], .field[idx0] ...)

Example

/* SPDX-License-Identifier: GPL-2.0 */
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
 * Copyright (c) 2022 Tejun Heo <tj@kernel.org>
 * Copyright (c) 2022 David Vernet <dvernet@meta.com>
 */

/*
 * Print out the online and possible CPU map using bpf_printk() as a
 * demonstration of using the cpumask kfuncs and ops.cpu_on/offline().
 */
static void print_cpus(void)
{
    const struct cpumask *possible, *online;
    s32 cpu;
    char buf[128] = "", *p;
    int idx;

    possible = scx_bpf_get_possible_cpumask();
    online = scx_bpf_get_online_cpumask();

    idx = 0;
    bpf_for(cpu, 0, scx_bpf_nr_cpu_ids()) {
        if (!(p = MEMBER_VPTR(buf, [idx++])))
            break;
        if (bpf_cpumask_test_cpu(cpu, online))
            *p++ = 'O';
        else if (bpf_cpumask_test_cpu(cpu, possible))
            *p++ = 'X';
        else
            *p++ = ' ';

        if ((cpu & 7) == 7) {
            if (!(p = MEMBER_VPTR(buf, [idx++])))
                break;
            *p++ = '|';
        }
    }
    buf[sizeof(buf) - 1] = '\0';

    scx_bpf_put_cpumask(online);
    scx_bpf_put_cpumask(possible);

    bpf_printk("CPUS: |%s", buf);
}

void BPF_STRUCT_OPS(qmap_cpu_online, s32 cpu)
{
    bpf_printk("CPU %d coming online", cpu);
    /* @cpu is already online at this point */
    print_cpus();
}