• Andrii Nakryiko's avatar
    libbpf: Recognize __arena global variables. · 2e7ba4f8
    Andrii Nakryiko authored
    LLVM automatically places __arena variables into ".arena.1" ELF section.
    In order to use such global variables bpf program must include definition
    of arena map in ".maps" section, like:
    struct {
           __uint(type, BPF_MAP_TYPE_ARENA);
           __uint(map_flags, BPF_F_MMAPABLE);
           __uint(max_entries, 1000);         /* number of pages */
           __ulong(map_extra, 2ull << 44);    /* start of mmap() region */
    } arena SEC(".maps");
    
    libbpf recognizes both uses of arena and creates single `struct bpf_map *`
    instance in libbpf APIs.
    ".arena.1" ELF section data is used as initial data image, which is exposed
    through skeleton and bpf_map__initial_value() to the user, if they need to tune
    it before the load phase. During load phase, this initial image is copied over
    into mmap()'ed region corresponding to arena, and discarded.
    
    Few small checks here and there had to be added to make sure this
    approach works with bpf_map__initial_value(), mostly due to hard-coded
    assumption that map->mmaped is set up with mmap() syscall and should be
    munmap()'ed. For arena, .arena.1 can be (much) smaller than maximum
    arena size, so this smaller data size has to be tracked separately.
    Given it is enforced that there is only one arena for entire bpf_object
    instance, we just keep it in a separate field. This can be generalized
    if necessary later.
    
    All global variables from ".arena.1" section are accessible from user space
    via skel->arena->name_of_var.
    
    For bss/data/rodata the skeleton/libbpf perform the following sequence:
    1. addr = mmap(MAP_ANONYMOUS)
    2. user space optionally modifies global vars
    3. map_fd = bpf_create_map()
    4. bpf_update_map_elem(map_fd, addr) // to store values into the kernel
    5. mmap(addr, MAP_FIXED, map_fd)
    after step 5 user spaces see the values it wrote at step 2 at the same addresses
    
    arena doesn't support update_map_elem. Hence skeleton/libbpf do:
    1. addr = malloc(sizeof SEC ".arena.1")
    2. user space optionally modifies global vars
    3. map_fd = bpf_create_map(MAP_TYPE_ARENA)
    4. real_addr = mmap(map->map_extra, MAP_SHARED | MAP_FIXED, map_fd)
    5. memcpy(real_addr, addr) // this will fault-in and allocate pages
    
    At the end look and feel of global data vs __arena global data is the same from
    bpf prog pov.
    
    Another complication is:
    struct {
      __uint(type, BPF_MAP_TYPE_ARENA);
    } arena SEC(".maps");
    
    int __arena foo;
    int bar;
    
      ptr1 = &foo;   // relocation against ".arena.1" section
      ptr2 = &arena; // relocation against ".maps" section
      ptr3 = &bar;   // relocation against ".bss" section
    
    Fo the kernel ptr1 and ptr2 has point to the same arena's map_fd
    while ptr3 points to a different global array's map_fd.
    For the verifier:
    ptr1->type == unknown_scalar
    ptr2->type == const_ptr_to_map
    ptr3->type == ptr_to_map_value
    
    After verification, from JIT pov all 3 ptr-s are normal ld_imm64 insns.
    Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
    Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
    Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
    Acked-by: default avatarQuentin Monnet <quentin@isovalent.com>
    Link: https://lore.kernel.org/bpf/20240308010812.89848-11-alexei.starovoitov@gmail.com
    2e7ba4f8
libbpf.c 368 KB