2

I want to pass a variable value specified by the user from the command line from the user space program to the ebpf program. I know how to do it using bpf maps, but i heard there is a more efficient way to do this using bpf global data.

Can anyone give some code sample? (I am using libbpf)

drdot
  • 3,215
  • 9
  • 46
  • 81

1 Answers1

1

Clang will take global data and put it into the .data, .rodata, or .bss section. .data if your value is initialized and can also change value, .rodata for const values and .bss for non-initialized values(will be initialized as 0).

In a normal program these sections would be loaded into the heap, but since eBPF has no heap these sections are loaded as special maps. libbpf calls these internal maps, they are essentially array maps with 1 key/value, the value is the size of the elf section. The generated eBPF then reads data at the appropriate offset in this data blob (using special instructions to improve performance over normal map loads).

Libbpf allows you to access and changes these maps. With the exception of .rodata which can't be modified since libbpf freezes it during loading.

Changing the value of the .bss secion can still be done, I believe you can do it with bpf_map__set_initial_value before calling bpf_object__load. Didn't go that route for the example.

Since a datasection can contain multiple values, we have to figure out how clang laid out the memory. For this we can use BTF, which encodes this data.

Disclaimer, this is likely not working code, slapped this together from examples and header files(I don't use libbpf that often, didn't test/compile it). But this should get you started in the right direction:

int main(int ac, char **argv)
{
    struct bpf_object *obj;
    struct bpf_program *prog;
    struct bpf_map *map;
    struct btf *btf;
    const struct btf_type *datasec;
    struct btf_var_secinfo *infos;
    int map_fd, prog_fd;
    __s32 datasec_id;
    char filename[256];
    int i, err;
    int zero = 0;
    FILE *f;

    snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);

    obj = bpf_object__open_file(filename, NULL);
    if (libbpf_get_error(obj))
        return 1;

    map = bpf_object__find_map_by_name(obj, ".data");
    if (libbpf_get_error(map))
        return 1;

    err = bpf_object__load(obj);
    if (err)
        return 1;

    map_fd = bpf_map__fd(map);
    if (libbpf_get_error(map_fd))
        return 1;

    // Create buffer the size of .data
    buff = malloc(bpf_map__def(map)->value_size);
    if (!buff)
        return 1;
    
    // Read .data into the buffer
    err = bpf_map_lookup_elem(map_fd, &zero, buff, 0);
    if (err)
        return 1;

    // Get BTF, we need it do find out the memory layout of .data
    btf = bpf_object__btf(obj);
    if (libbpf_get_error(btf))
        return 1;

    // Get the type ID of the datasection of .data
    datasec_id = btf__find_by_name(btf, ".data");
    if (libbpf_get_error(datasec_id))
        return 1;

    // Get the actual BTF type from the ID
    datasec = btf__type_by_id(btf, datasec_id)
    if (libbpf_get_error(datasec))
        return 1;

    // Get all secinfos, each of which will be a global variable
    infos = btf_var_secinfos(datasec);
    // Loop over all sections
    for(i = 0; i < btf_vlen(datasec); i++) {
        // Get the BTF type of the current var
        const struct btf_type *t = btf__type_by_id(btf, infos[i]->type);
        // Get the name of the global variable
        const char *name = btf__name_by_offset(btf, t->name_off);
        // If it matches the name of the var we want to change at runtime
        if (!strcmp(name, "global_var")) {
            // Overwrite its value (this code assumes just a single byte)
            // for multibyte values you will obviusly have to edit more bytes.
            // the total size of the var can be gotten from infos[i]->size
            buff[infos[i]->offset] = 0x12;
        }
    }

    // Write the updated datasection to the map
    err = bpf_map_update_elem(map_fd, &zero, buff, 0);
    if (err)
        return 1;

    free(buff);

    // TODO attach program somewhere
}
Dylan Reimerink
  • 5,874
  • 2
  • 15
  • 21
  • Can you pick one approach you suggested and expanded with an example? either modify elf or using bcc – drdot Dec 27 '21 at 06:10
  • For your last point, it is a very interesting statement. But what are the user cases where one want to extract the ebpf instructions from a compiled program? – drdot Dec 27 '21 at 06:17
  • Modifying ELF is complicated, I have never seen anyone do it, just know it is theoretically possible, any example would be pretty large. As for BCC, it is really simple, since the C code is contain in the program it is just a matter of string manipulation, this is the smallest example I could find: https://github.com/iovisor/bcc/blob/master/tools/mysqld_qslower.py. As for dealing with eBPF instructions, I know that tcpdump works in this way. I also use the technique in one of my own projects, so I know it works: https://github.com/dylandreimerink/goxdpfw – Dylan Reimerink Dec 27 '21 at 12:35
  • Dealing with eBPF instructions if difficult, it is essentially assembly programming, but offers the best performance where the alternatives would be using maps with settings decide which code paths to take inside an eBPF program. – Dylan Reimerink Dec 27 '21 at 12:38
  • 1
    @drdot, I have learned some new things about global data loading since writing my initial answer. I believe I have found a way to do what you want with libbpf, so I have updated my answer. Hope this is useful for you – Dylan Reimerink Jan 12 '22 at 19:29
  • Thank you for the ideas. I will check it out over the weekend and let you know what i find – drdot Jan 21 '22 at 05:22