Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

libbpf: support llvm-generated indirect jumps

For v4 instruction set LLVM is allowed to generate indirect jumps for
switch statements and for 'goto *rX' assembly. Every such a jump will
be accompanied by necessary metadata, e.g. (`llvm-objdump -Sr ...`):

0: r2 = 0x0 ll
0000000000000030: R_BPF_64_64 BPF.JT.0.0

Here BPF.JT.1.0 is a symbol residing in the .jumptables section:

Symbol table:
4: 0000000000000000 240 OBJECT GLOBAL DEFAULT 4 BPF.JT.0.0

The -bpf-min-jump-table-entries llvm option may be used to control the
minimal size of a switch which will be converted to an indirect jumps.

Signed-off-by: Anton Protopopov <a.s.protopopov@gmail.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20251105090410.1250500-11-a.s.protopopov@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Anton Protopopov and committed by
Alexei Starovoitov
dd3fd3c9 bc414d35

+251 -1
+246 -1
tools/lib/bpf/libbpf.c
··· 370 370 RELO_EXTERN_CALL, 371 371 RELO_SUBPROG_ADDR, 372 372 RELO_CORE, 373 + RELO_INSN_ARRAY, 373 374 }; 374 375 375 376 struct reloc_desc { ··· 381 380 struct { 382 381 int map_idx; 383 382 int sym_off; 384 - int ext_idx; 383 + /* 384 + * The following two fields can be unionized, as the 385 + * ext_idx field is used for extern symbols, and the 386 + * sym_size is used for jump tables, which are never 387 + * extern 388 + */ 389 + union { 390 + int ext_idx; 391 + int sym_size; 392 + }; 385 393 }; 386 394 }; 387 395 }; ··· 430 420 libbpf_prog_setup_fn_t prog_setup_fn; 431 421 libbpf_prog_prepare_load_fn_t prog_prepare_load_fn; 432 422 libbpf_prog_attach_fn_t prog_attach_fn; 423 + }; 424 + 425 + struct bpf_light_subprog { 426 + __u32 sec_insn_off; 427 + __u32 sub_insn_off; 433 428 }; 434 429 435 430 /* ··· 510 495 __u32 line_info_cnt; 511 496 __u32 prog_flags; 512 497 __u8 hash[SHA256_DIGEST_LENGTH]; 498 + 499 + struct bpf_light_subprog *subprogs; 500 + __u32 subprog_cnt; 513 501 }; 514 502 515 503 struct bpf_struct_ops { ··· 686 668 int symbols_shndx; 687 669 bool has_st_ops; 688 670 int arena_data_shndx; 671 + int jumptables_data_shndx; 689 672 }; 690 673 691 674 struct usdt_manager; ··· 758 739 void *arena_data; 759 740 size_t arena_data_sz; 760 741 742 + void *jumptables_data; 743 + size_t jumptables_data_sz; 744 + 745 + struct { 746 + struct bpf_program *prog; 747 + int sym_off; 748 + int fd; 749 + } *jumptable_maps; 750 + size_t jumptable_map_cnt; 751 + 761 752 struct kern_feature_cache *feat_cache; 762 753 char *token_path; 763 754 int token_fd; ··· 794 765 795 766 zfree(&prog->func_info); 796 767 zfree(&prog->line_info); 768 + zfree(&prog->subprogs); 797 769 } 798 770 799 771 static void bpf_program__exit(struct bpf_program *prog) ··· 3973 3943 } else if (strcmp(name, ARENA_SEC) == 0) { 3974 3944 obj->efile.arena_data = data; 3975 3945 obj->efile.arena_data_shndx = idx; 3946 + } else if (strcmp(name, JUMPTABLES_SEC) == 0) { 3947 + obj->jumptables_data = malloc(data->d_size); 3948 + if (!obj->jumptables_data) 3949 + return -ENOMEM; 3950 + memcpy(obj->jumptables_data, data->d_buf, data->d_size); 3951 + obj->jumptables_data_sz = data->d_size; 3952 + obj->efile.jumptables_data_shndx = idx; 3976 3953 } else { 3977 3954 pr_info("elf: skipping unrecognized data section(%d) %s\n", 3978 3955 idx, name); ··· 4669 4632 pr_debug("prog '%s': found arena map %d (%s, sec %d, off %zu) for insn %u\n", 4670 4633 prog->name, obj->arena_map_idx, map->name, map->sec_idx, 4671 4634 map->sec_offset, insn_idx); 4635 + return 0; 4636 + } 4637 + 4638 + /* jump table data relocation */ 4639 + if (shdr_idx == obj->efile.jumptables_data_shndx) { 4640 + reloc_desc->type = RELO_INSN_ARRAY; 4641 + reloc_desc->insn_idx = insn_idx; 4642 + reloc_desc->map_idx = -1; 4643 + reloc_desc->sym_off = sym->st_value; 4644 + reloc_desc->sym_size = sym->st_size; 4672 4645 return 0; 4673 4646 } 4674 4647 ··· 6192 6145 insn->imm = POISON_CALL_KFUNC_BASE + ext_idx; 6193 6146 } 6194 6147 6148 + static int find_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym_off) 6149 + { 6150 + size_t i; 6151 + 6152 + for (i = 0; i < obj->jumptable_map_cnt; i++) { 6153 + /* 6154 + * This might happen that same offset is used for two different 6155 + * programs (as jump tables can be the same). However, for 6156 + * different programs different maps should be created. 6157 + */ 6158 + if (obj->jumptable_maps[i].sym_off == sym_off && 6159 + obj->jumptable_maps[i].prog == prog) 6160 + return obj->jumptable_maps[i].fd; 6161 + } 6162 + 6163 + return -ENOENT; 6164 + } 6165 + 6166 + static int add_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym_off, int map_fd) 6167 + { 6168 + size_t cnt = obj->jumptable_map_cnt; 6169 + size_t size = sizeof(obj->jumptable_maps[0]); 6170 + void *tmp; 6171 + 6172 + tmp = libbpf_reallocarray(obj->jumptable_maps, cnt + 1, size); 6173 + if (!tmp) 6174 + return -ENOMEM; 6175 + 6176 + obj->jumptable_maps = tmp; 6177 + obj->jumptable_maps[cnt].prog = prog; 6178 + obj->jumptable_maps[cnt].sym_off = sym_off; 6179 + obj->jumptable_maps[cnt].fd = map_fd; 6180 + obj->jumptable_map_cnt++; 6181 + 6182 + return 0; 6183 + } 6184 + 6185 + static int find_subprog_idx(struct bpf_program *prog, int insn_idx) 6186 + { 6187 + int i; 6188 + 6189 + for (i = prog->subprog_cnt - 1; i >= 0; i--) { 6190 + if (insn_idx >= prog->subprogs[i].sub_insn_off) 6191 + return i; 6192 + } 6193 + 6194 + return -1; 6195 + } 6196 + 6197 + static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog, struct reloc_desc *relo) 6198 + { 6199 + const __u32 jt_entry_size = 8; 6200 + int sym_off = relo->sym_off; 6201 + int jt_size = relo->sym_size; 6202 + __u32 max_entries = jt_size / jt_entry_size; 6203 + __u32 value_size = sizeof(struct bpf_insn_array_value); 6204 + struct bpf_insn_array_value val = {}; 6205 + int subprog_idx; 6206 + int map_fd, err; 6207 + __u64 insn_off; 6208 + __u64 *jt; 6209 + __u32 i; 6210 + 6211 + map_fd = find_jt_map(obj, prog, sym_off); 6212 + if (map_fd >= 0) 6213 + return map_fd; 6214 + 6215 + if (sym_off % jt_entry_size) { 6216 + pr_warn("map '.jumptables': jumptable start %d should be multiple of %u\n", 6217 + sym_off, jt_entry_size); 6218 + return -EINVAL; 6219 + } 6220 + 6221 + if (jt_size % jt_entry_size) { 6222 + pr_warn("map '.jumptables': jumptable size %d should be multiple of %u\n", 6223 + jt_size, jt_entry_size); 6224 + return -EINVAL; 6225 + } 6226 + 6227 + map_fd = bpf_map_create(BPF_MAP_TYPE_INSN_ARRAY, ".jumptables", 6228 + 4, value_size, max_entries, NULL); 6229 + if (map_fd < 0) 6230 + return map_fd; 6231 + 6232 + if (!obj->jumptables_data) { 6233 + pr_warn("map '.jumptables': ELF file is missing jump table data\n"); 6234 + err = -EINVAL; 6235 + goto err_close; 6236 + } 6237 + if (sym_off + jt_size > obj->jumptables_data_sz) { 6238 + pr_warn("map '.jumptables': jumptables_data size is %zd, trying to access %d\n", 6239 + obj->jumptables_data_sz, sym_off + jt_size); 6240 + err = -EINVAL; 6241 + goto err_close; 6242 + } 6243 + 6244 + subprog_idx = -1; /* main program */ 6245 + if (relo->insn_idx < 0 || relo->insn_idx >= prog->insns_cnt) { 6246 + pr_warn("map '.jumptables': invalid instruction index %d\n", relo->insn_idx); 6247 + err = -EINVAL; 6248 + goto err_close; 6249 + } 6250 + if (prog->subprogs) 6251 + subprog_idx = find_subprog_idx(prog, relo->insn_idx); 6252 + 6253 + jt = (__u64 *)(obj->jumptables_data + sym_off); 6254 + for (i = 0; i < max_entries; i++) { 6255 + /* 6256 + * The offset should be made to be relative to the beginning of 6257 + * the main function, not the subfunction. 6258 + */ 6259 + insn_off = jt[i]/sizeof(struct bpf_insn); 6260 + if (subprog_idx >= 0) { 6261 + insn_off -= prog->subprogs[subprog_idx].sec_insn_off; 6262 + insn_off += prog->subprogs[subprog_idx].sub_insn_off; 6263 + } else { 6264 + insn_off -= prog->sec_insn_off; 6265 + } 6266 + 6267 + /* 6268 + * LLVM-generated jump tables contain u64 records, however 6269 + * should contain values that fit in u32. 6270 + */ 6271 + if (insn_off > UINT32_MAX) { 6272 + pr_warn("map '.jumptables': invalid jump table value 0x%llx at offset %d\n", 6273 + (long long)jt[i], sym_off + i * jt_entry_size); 6274 + err = -EINVAL; 6275 + goto err_close; 6276 + } 6277 + 6278 + val.orig_off = insn_off; 6279 + err = bpf_map_update_elem(map_fd, &i, &val, 0); 6280 + if (err) 6281 + goto err_close; 6282 + } 6283 + 6284 + err = bpf_map_freeze(map_fd); 6285 + if (err) 6286 + goto err_close; 6287 + 6288 + err = add_jt_map(obj, prog, sym_off, map_fd); 6289 + if (err) 6290 + goto err_close; 6291 + 6292 + return map_fd; 6293 + 6294 + err_close: 6295 + close(map_fd); 6296 + return err; 6297 + } 6298 + 6195 6299 /* Relocate data references within program code: 6196 6300 * - map references; 6197 6301 * - global variable references; ··· 6433 6235 break; 6434 6236 case RELO_CORE: 6435 6237 /* will be handled by bpf_program_record_relos() */ 6238 + break; 6239 + case RELO_INSN_ARRAY: { 6240 + int map_fd; 6241 + 6242 + map_fd = create_jt_map(obj, prog, relo); 6243 + if (map_fd < 0) { 6244 + pr_warn("prog '%s': relo #%d: can't create jump table: sym_off %u\n", 6245 + prog->name, i, relo->sym_off); 6246 + return map_fd; 6247 + } 6248 + insn[0].src_reg = BPF_PSEUDO_MAP_VALUE; 6249 + insn->imm = map_fd; 6250 + insn->off = 0; 6251 + } 6436 6252 break; 6437 6253 default: 6438 6254 pr_warn("prog '%s': relo #%d: bad relo type %d\n", ··· 6645 6433 return 0; 6646 6434 } 6647 6435 6436 + static int save_subprog_offsets(struct bpf_program *main_prog, struct bpf_program *subprog) 6437 + { 6438 + size_t size = sizeof(main_prog->subprogs[0]); 6439 + int cnt = main_prog->subprog_cnt; 6440 + void *tmp; 6441 + 6442 + tmp = libbpf_reallocarray(main_prog->subprogs, cnt + 1, size); 6443 + if (!tmp) 6444 + return -ENOMEM; 6445 + 6446 + main_prog->subprogs = tmp; 6447 + main_prog->subprogs[cnt].sec_insn_off = subprog->sec_insn_off; 6448 + main_prog->subprogs[cnt].sub_insn_off = subprog->sub_insn_off; 6449 + main_prog->subprog_cnt++; 6450 + 6451 + return 0; 6452 + } 6453 + 6648 6454 static int 6649 6455 bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main_prog, 6650 6456 struct bpf_program *subprog) ··· 6692 6462 err = append_subprog_relos(main_prog, subprog); 6693 6463 if (err) 6694 6464 return err; 6465 + 6466 + err = save_subprog_offsets(main_prog, subprog); 6467 + if (err) { 6468 + pr_warn("prog '%s': failed to add subprog offsets: %s\n", 6469 + main_prog->name, errstr(err)); 6470 + return err; 6471 + } 6472 + 6695 6473 return 0; 6696 6474 } 6697 6475 ··· 9466 9228 close(obj->token_fd); 9467 9229 9468 9230 zfree(&obj->arena_data); 9231 + 9232 + zfree(&obj->jumptables_data); 9233 + obj->jumptables_data_sz = 0; 9234 + 9235 + for (i = 0; i < obj->jumptable_map_cnt; i++) 9236 + close(obj->jumptable_maps[i].fd); 9237 + zfree(&obj->jumptable_maps); 9469 9238 9470 9239 free(obj); 9471 9240 }
+2
tools/lib/bpf/libbpf_internal.h
··· 74 74 #define ELF64_ST_VISIBILITY(o) ((o) & 0x03) 75 75 #endif 76 76 77 + #define JUMPTABLES_SEC ".jumptables" 78 + 77 79 #define BTF_INFO_ENC(kind, kind_flag, vlen) \ 78 80 ((!!(kind_flag) << 31) | ((kind) << 24) | ((vlen) & BTF_MAX_VLEN)) 79 81 #define BTF_TYPE_ENC(name, info, size_or_type) (name), (info), (size_or_type)
+3
tools/lib/bpf/linker.c
··· 2025 2025 obj->sym_map[src_sym_idx] = dst_sec->sec_sym_idx; 2026 2026 return 0; 2027 2027 } 2028 + 2029 + if (strcmp(src_sec->sec_name, JUMPTABLES_SEC) == 0) 2030 + goto add_sym; 2028 2031 } 2029 2032 2030 2033 if (sym_bind == STB_LOCAL)