Reputation: 3347
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)
Upvotes: 2
Views: 1505
Reputation: 7978
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
}
Upvotes: 2