struct_ops.c (13512B)
1// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 2/* Copyright (C) 2020 Facebook */ 3 4#include <errno.h> 5#include <stdio.h> 6#include <unistd.h> 7 8#include <linux/err.h> 9 10#include <bpf/bpf.h> 11#include <bpf/btf.h> 12#include <bpf/libbpf.h> 13 14#include "json_writer.h" 15#include "main.h" 16 17#define STRUCT_OPS_VALUE_PREFIX "bpf_struct_ops_" 18 19static const struct btf_type *map_info_type; 20static __u32 map_info_alloc_len; 21static struct btf *btf_vmlinux; 22static __s32 map_info_type_id; 23 24struct res { 25 unsigned int nr_maps; 26 unsigned int nr_errs; 27}; 28 29static const struct btf *get_btf_vmlinux(void) 30{ 31 if (btf_vmlinux) 32 return btf_vmlinux; 33 34 btf_vmlinux = libbpf_find_kernel_btf(); 35 if (libbpf_get_error(btf_vmlinux)) 36 p_err("struct_ops requires kernel CONFIG_DEBUG_INFO_BTF=y"); 37 38 return btf_vmlinux; 39} 40 41static const char *get_kern_struct_ops_name(const struct bpf_map_info *info) 42{ 43 const struct btf *kern_btf; 44 const struct btf_type *t; 45 const char *st_ops_name; 46 47 kern_btf = get_btf_vmlinux(); 48 if (libbpf_get_error(kern_btf)) 49 return "<btf_vmlinux_not_found>"; 50 51 t = btf__type_by_id(kern_btf, info->btf_vmlinux_value_type_id); 52 st_ops_name = btf__name_by_offset(kern_btf, t->name_off); 53 st_ops_name += strlen(STRUCT_OPS_VALUE_PREFIX); 54 55 return st_ops_name; 56} 57 58static __s32 get_map_info_type_id(void) 59{ 60 const struct btf *kern_btf; 61 62 if (map_info_type_id) 63 return map_info_type_id; 64 65 kern_btf = get_btf_vmlinux(); 66 if (libbpf_get_error(kern_btf)) { 67 map_info_type_id = PTR_ERR(kern_btf); 68 return map_info_type_id; 69 } 70 71 map_info_type_id = btf__find_by_name_kind(kern_btf, "bpf_map_info", 72 BTF_KIND_STRUCT); 73 if (map_info_type_id < 0) { 74 p_err("can't find bpf_map_info from btf_vmlinux"); 75 return map_info_type_id; 76 } 77 map_info_type = btf__type_by_id(kern_btf, map_info_type_id); 78 79 /* Ensure map_info_alloc() has at least what the bpftool needs */ 80 map_info_alloc_len = map_info_type->size; 81 if (map_info_alloc_len < sizeof(struct bpf_map_info)) 82 map_info_alloc_len = sizeof(struct bpf_map_info); 83 84 return map_info_type_id; 85} 86 87/* If the subcmd needs to print out the bpf_map_info, 88 * it should always call map_info_alloc to allocate 89 * a bpf_map_info object instead of allocating it 90 * on the stack. 91 * 92 * map_info_alloc() will take the running kernel's btf 93 * into account. i.e. it will consider the 94 * sizeof(struct bpf_map_info) of the running kernel. 95 * 96 * It will enable the "struct_ops" cmd to print the latest 97 * "struct bpf_map_info". 98 * 99 * [ Recall that "struct_ops" requires the kernel's btf to 100 * be available ] 101 */ 102static struct bpf_map_info *map_info_alloc(__u32 *alloc_len) 103{ 104 struct bpf_map_info *info; 105 106 if (get_map_info_type_id() < 0) 107 return NULL; 108 109 info = calloc(1, map_info_alloc_len); 110 if (!info) 111 p_err("mem alloc failed"); 112 else 113 *alloc_len = map_info_alloc_len; 114 115 return info; 116} 117 118/* It iterates all struct_ops maps of the system. 119 * It returns the fd in "*res_fd" and map_info in "*info". 120 * In the very first iteration, info->id should be 0. 121 * An optional map "*name" filter can be specified. 122 * The filter can be made more flexible in the future. 123 * e.g. filter by kernel-struct-ops-name, regex-name, glob-name, ...etc. 124 * 125 * Return value: 126 * 1: A struct_ops map found. It is returned in "*res_fd" and "*info". 127 * The caller can continue to call get_next in the future. 128 * 0: No struct_ops map is returned. 129 * All struct_ops map has been found. 130 * -1: Error and the caller should abort the iteration. 131 */ 132static int get_next_struct_ops_map(const char *name, int *res_fd, 133 struct bpf_map_info *info, __u32 info_len) 134{ 135 __u32 id = info->id; 136 int err, fd; 137 138 while (true) { 139 err = bpf_map_get_next_id(id, &id); 140 if (err) { 141 if (errno == ENOENT) 142 return 0; 143 p_err("can't get next map: %s", strerror(errno)); 144 return -1; 145 } 146 147 fd = bpf_map_get_fd_by_id(id); 148 if (fd < 0) { 149 if (errno == ENOENT) 150 continue; 151 p_err("can't get map by id (%u): %s", 152 id, strerror(errno)); 153 return -1; 154 } 155 156 err = bpf_obj_get_info_by_fd(fd, info, &info_len); 157 if (err) { 158 p_err("can't get map info: %s", strerror(errno)); 159 close(fd); 160 return -1; 161 } 162 163 if (info->type == BPF_MAP_TYPE_STRUCT_OPS && 164 (!name || !strcmp(name, info->name))) { 165 *res_fd = fd; 166 return 1; 167 } 168 close(fd); 169 } 170} 171 172static int cmd_retval(const struct res *res, bool must_have_one_map) 173{ 174 if (res->nr_errs || (!res->nr_maps && must_have_one_map)) 175 return -1; 176 177 return 0; 178} 179 180/* "data" is the work_func private storage */ 181typedef int (*work_func)(int fd, const struct bpf_map_info *info, void *data, 182 struct json_writer *wtr); 183 184/* Find all struct_ops map in the system. 185 * Filter out by "name" (if specified). 186 * Then call "func(fd, info, data, wtr)" on each struct_ops map found. 187 */ 188static struct res do_search(const char *name, work_func func, void *data, 189 struct json_writer *wtr) 190{ 191 struct bpf_map_info *info; 192 struct res res = {}; 193 __u32 info_len; 194 int fd, err; 195 196 info = map_info_alloc(&info_len); 197 if (!info) { 198 res.nr_errs++; 199 return res; 200 } 201 202 if (wtr) 203 jsonw_start_array(wtr); 204 while ((err = get_next_struct_ops_map(name, &fd, info, info_len)) == 1) { 205 res.nr_maps++; 206 err = func(fd, info, data, wtr); 207 if (err) 208 res.nr_errs++; 209 close(fd); 210 } 211 if (wtr) 212 jsonw_end_array(wtr); 213 214 if (err) 215 res.nr_errs++; 216 217 if (!wtr && name && !res.nr_errs && !res.nr_maps) 218 /* It is not printing empty []. 219 * Thus, needs to specifically say nothing found 220 * for "name" here. 221 */ 222 p_err("no struct_ops found for %s", name); 223 else if (!wtr && json_output && !res.nr_errs) 224 /* The "func()" above is not writing any json (i.e. !wtr 225 * test here). 226 * 227 * However, "-j" is enabled and there is no errs here, 228 * so call json_null() as the current convention of 229 * other cmds. 230 */ 231 jsonw_null(json_wtr); 232 233 free(info); 234 return res; 235} 236 237static struct res do_one_id(const char *id_str, work_func func, void *data, 238 struct json_writer *wtr) 239{ 240 struct bpf_map_info *info; 241 struct res res = {}; 242 unsigned long id; 243 __u32 info_len; 244 char *endptr; 245 int fd; 246 247 id = strtoul(id_str, &endptr, 0); 248 if (*endptr || !id || id > UINT32_MAX) { 249 p_err("invalid id %s", id_str); 250 res.nr_errs++; 251 return res; 252 } 253 254 fd = bpf_map_get_fd_by_id(id); 255 if (fd < 0) { 256 p_err("can't get map by id (%lu): %s", id, strerror(errno)); 257 res.nr_errs++; 258 return res; 259 } 260 261 info = map_info_alloc(&info_len); 262 if (!info) { 263 res.nr_errs++; 264 goto done; 265 } 266 267 if (bpf_obj_get_info_by_fd(fd, info, &info_len)) { 268 p_err("can't get map info: %s", strerror(errno)); 269 res.nr_errs++; 270 goto done; 271 } 272 273 if (info->type != BPF_MAP_TYPE_STRUCT_OPS) { 274 p_err("%s id %u is not a struct_ops map", info->name, info->id); 275 res.nr_errs++; 276 goto done; 277 } 278 279 res.nr_maps++; 280 281 if (func(fd, info, data, wtr)) 282 res.nr_errs++; 283 else if (!wtr && json_output) 284 /* The "func()" above is not writing any json (i.e. !wtr 285 * test here). 286 * 287 * However, "-j" is enabled and there is no errs here, 288 * so call json_null() as the current convention of 289 * other cmds. 290 */ 291 jsonw_null(json_wtr); 292 293done: 294 free(info); 295 close(fd); 296 297 return res; 298} 299 300static struct res do_work_on_struct_ops(const char *search_type, 301 const char *search_term, 302 work_func func, void *data, 303 struct json_writer *wtr) 304{ 305 if (search_type) { 306 if (is_prefix(search_type, "id")) 307 return do_one_id(search_term, func, data, wtr); 308 else if (!is_prefix(search_type, "name")) 309 usage(); 310 } 311 312 return do_search(search_term, func, data, wtr); 313} 314 315static int __do_show(int fd, const struct bpf_map_info *info, void *data, 316 struct json_writer *wtr) 317{ 318 if (wtr) { 319 jsonw_start_object(wtr); 320 jsonw_uint_field(wtr, "id", info->id); 321 jsonw_string_field(wtr, "name", info->name); 322 jsonw_string_field(wtr, "kernel_struct_ops", 323 get_kern_struct_ops_name(info)); 324 jsonw_end_object(wtr); 325 } else { 326 printf("%u: %-15s %-32s\n", info->id, info->name, 327 get_kern_struct_ops_name(info)); 328 } 329 330 return 0; 331} 332 333static int do_show(int argc, char **argv) 334{ 335 const char *search_type = NULL, *search_term = NULL; 336 struct res res; 337 338 if (argc && argc != 2) 339 usage(); 340 341 if (argc == 2) { 342 search_type = GET_ARG(); 343 search_term = GET_ARG(); 344 } 345 346 res = do_work_on_struct_ops(search_type, search_term, __do_show, 347 NULL, json_wtr); 348 349 return cmd_retval(&res, !!search_term); 350} 351 352static int __do_dump(int fd, const struct bpf_map_info *info, void *data, 353 struct json_writer *wtr) 354{ 355 struct btf_dumper *d = (struct btf_dumper *)data; 356 const struct btf_type *struct_ops_type; 357 const struct btf *kern_btf = d->btf; 358 const char *struct_ops_name; 359 int zero = 0; 360 void *value; 361 362 /* note: d->jw == wtr */ 363 364 kern_btf = d->btf; 365 366 /* The kernel supporting BPF_MAP_TYPE_STRUCT_OPS must have 367 * btf_vmlinux_value_type_id. 368 */ 369 struct_ops_type = btf__type_by_id(kern_btf, 370 info->btf_vmlinux_value_type_id); 371 struct_ops_name = btf__name_by_offset(kern_btf, 372 struct_ops_type->name_off); 373 value = calloc(1, info->value_size); 374 if (!value) { 375 p_err("mem alloc failed"); 376 return -1; 377 } 378 379 if (bpf_map_lookup_elem(fd, &zero, value)) { 380 p_err("can't lookup struct_ops map %s id %u", 381 info->name, info->id); 382 free(value); 383 return -1; 384 } 385 386 jsonw_start_object(wtr); 387 jsonw_name(wtr, "bpf_map_info"); 388 btf_dumper_type(d, map_info_type_id, (void *)info); 389 jsonw_end_object(wtr); 390 391 jsonw_start_object(wtr); 392 jsonw_name(wtr, struct_ops_name); 393 btf_dumper_type(d, info->btf_vmlinux_value_type_id, value); 394 jsonw_end_object(wtr); 395 396 free(value); 397 398 return 0; 399} 400 401static int do_dump(int argc, char **argv) 402{ 403 const char *search_type = NULL, *search_term = NULL; 404 json_writer_t *wtr = json_wtr; 405 const struct btf *kern_btf; 406 struct btf_dumper d = {}; 407 struct res res; 408 409 if (argc && argc != 2) 410 usage(); 411 412 if (argc == 2) { 413 search_type = GET_ARG(); 414 search_term = GET_ARG(); 415 } 416 417 kern_btf = get_btf_vmlinux(); 418 if (libbpf_get_error(kern_btf)) 419 return -1; 420 421 if (!json_output) { 422 wtr = jsonw_new(stdout); 423 if (!wtr) { 424 p_err("can't create json writer"); 425 return -1; 426 } 427 jsonw_pretty(wtr, true); 428 } 429 430 d.btf = kern_btf; 431 d.jw = wtr; 432 d.is_plain_text = !json_output; 433 d.prog_id_as_func_ptr = true; 434 435 res = do_work_on_struct_ops(search_type, search_term, __do_dump, &d, 436 wtr); 437 438 if (!json_output) 439 jsonw_destroy(&wtr); 440 441 return cmd_retval(&res, !!search_term); 442} 443 444static int __do_unregister(int fd, const struct bpf_map_info *info, void *data, 445 struct json_writer *wtr) 446{ 447 int zero = 0; 448 449 if (bpf_map_delete_elem(fd, &zero)) { 450 p_err("can't unload %s %s id %u: %s", 451 get_kern_struct_ops_name(info), info->name, 452 info->id, strerror(errno)); 453 return -1; 454 } 455 456 p_info("Unregistered %s %s id %u", 457 get_kern_struct_ops_name(info), info->name, 458 info->id); 459 460 return 0; 461} 462 463static int do_unregister(int argc, char **argv) 464{ 465 const char *search_type, *search_term; 466 struct res res; 467 468 if (argc != 2) 469 usage(); 470 471 search_type = GET_ARG(); 472 search_term = GET_ARG(); 473 474 res = do_work_on_struct_ops(search_type, search_term, 475 __do_unregister, NULL, NULL); 476 477 return cmd_retval(&res, true); 478} 479 480static int do_register(int argc, char **argv) 481{ 482 LIBBPF_OPTS(bpf_object_open_opts, open_opts); 483 struct bpf_map_info info = {}; 484 __u32 info_len = sizeof(info); 485 int nr_errs = 0, nr_maps = 0; 486 struct bpf_object *obj; 487 struct bpf_link *link; 488 struct bpf_map *map; 489 const char *file; 490 491 if (argc != 1) 492 usage(); 493 494 file = GET_ARG(); 495 496 if (verifier_logs) 497 /* log_level1 + log_level2 + stats, but not stable UAPI */ 498 open_opts.kernel_log_level = 1 + 2 + 4; 499 500 obj = bpf_object__open_file(file, &open_opts); 501 if (libbpf_get_error(obj)) 502 return -1; 503 504 if (bpf_object__load(obj)) { 505 bpf_object__close(obj); 506 return -1; 507 } 508 509 bpf_object__for_each_map(map, obj) { 510 if (bpf_map__type(map) != BPF_MAP_TYPE_STRUCT_OPS) 511 continue; 512 513 link = bpf_map__attach_struct_ops(map); 514 if (libbpf_get_error(link)) { 515 p_err("can't register struct_ops %s: %s", 516 bpf_map__name(map), 517 strerror(-PTR_ERR(link))); 518 nr_errs++; 519 continue; 520 } 521 nr_maps++; 522 523 bpf_link__disconnect(link); 524 bpf_link__destroy(link); 525 526 if (!bpf_obj_get_info_by_fd(bpf_map__fd(map), &info, 527 &info_len)) 528 p_info("Registered %s %s id %u", 529 get_kern_struct_ops_name(&info), 530 bpf_map__name(map), 531 info.id); 532 else 533 /* Not p_err. The struct_ops was attached 534 * successfully. 535 */ 536 p_info("Registered %s but can't find id: %s", 537 bpf_map__name(map), strerror(errno)); 538 } 539 540 bpf_object__close(obj); 541 542 if (nr_errs) 543 return -1; 544 545 if (!nr_maps) { 546 p_err("no struct_ops found in %s", file); 547 return -1; 548 } 549 550 if (json_output) 551 jsonw_null(json_wtr); 552 553 return 0; 554} 555 556static int do_help(int argc, char **argv) 557{ 558 if (json_output) { 559 jsonw_null(json_wtr); 560 return 0; 561 } 562 563 fprintf(stderr, 564 "Usage: %1$s %2$s { show | list } [STRUCT_OPS_MAP]\n" 565 " %1$s %2$s dump [STRUCT_OPS_MAP]\n" 566 " %1$s %2$s register OBJ\n" 567 " %1$s %2$s unregister STRUCT_OPS_MAP\n" 568 " %1$s %2$s help\n" 569 "\n" 570 " STRUCT_OPS_MAP := [ id STRUCT_OPS_MAP_ID | name STRUCT_OPS_MAP_NAME ]\n" 571 " " HELP_SPEC_OPTIONS " }\n" 572 "", 573 bin_name, argv[-2]); 574 575 return 0; 576} 577 578static const struct cmd cmds[] = { 579 { "show", do_show }, 580 { "list", do_show }, 581 { "register", do_register }, 582 { "unregister", do_unregister }, 583 { "dump", do_dump }, 584 { "help", do_help }, 585 { 0 } 586}; 587 588int do_struct_ops(int argc, char **argv) 589{ 590 int err; 591 592 err = cmd_select(cmds, argc, argv, do_help); 593 594 if (!libbpf_get_error(btf_vmlinux)) 595 btf__free(btf_vmlinux); 596 597 return err; 598}