Skip to content

Commit dd3fd3c

Browse files
aspskAlexei Starovoitov
authored andcommitted
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 <[email protected]> Acked-by: Eduard Zingerman <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent bc414d3 commit dd3fd3c

File tree

3 files changed

+251
-1
lines changed

3 files changed

+251
-1
lines changed

tools/lib/bpf/libbpf.c

Lines changed: 246 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ enum reloc_type {
370370
RELO_EXTERN_CALL,
371371
RELO_SUBPROG_ADDR,
372372
RELO_CORE,
373+
RELO_INSN_ARRAY,
373374
};
374375

375376
struct reloc_desc {
@@ -380,7 +381,16 @@ struct reloc_desc {
380381
struct {
381382
int map_idx;
382383
int sym_off;
383-
int ext_idx;
384+
/*
385+
* The following two fields can be unionized, as the
386+
* ext_idx field is used for extern symbols, and the
387+
* sym_size is used for jump tables, which are never
388+
* extern
389+
*/
390+
union {
391+
int ext_idx;
392+
int sym_size;
393+
};
384394
};
385395
};
386396
};
@@ -422,6 +432,11 @@ struct bpf_sec_def {
422432
libbpf_prog_attach_fn_t prog_attach_fn;
423433
};
424434

435+
struct bpf_light_subprog {
436+
__u32 sec_insn_off;
437+
__u32 sub_insn_off;
438+
};
439+
425440
/*
426441
* bpf_prog should be a better name but it has been used in
427442
* linux/filter.h.
@@ -495,6 +510,9 @@ struct bpf_program {
495510
__u32 line_info_cnt;
496511
__u32 prog_flags;
497512
__u8 hash[SHA256_DIGEST_LENGTH];
513+
514+
struct bpf_light_subprog *subprogs;
515+
__u32 subprog_cnt;
498516
};
499517

500518
struct bpf_struct_ops {
@@ -668,6 +686,7 @@ struct elf_state {
668686
int symbols_shndx;
669687
bool has_st_ops;
670688
int arena_data_shndx;
689+
int jumptables_data_shndx;
671690
};
672691

673692
struct usdt_manager;
@@ -739,6 +758,16 @@ struct bpf_object {
739758
void *arena_data;
740759
size_t arena_data_sz;
741760

761+
void *jumptables_data;
762+
size_t jumptables_data_sz;
763+
764+
struct {
765+
struct bpf_program *prog;
766+
int sym_off;
767+
int fd;
768+
} *jumptable_maps;
769+
size_t jumptable_map_cnt;
770+
742771
struct kern_feature_cache *feat_cache;
743772
char *token_path;
744773
int token_fd;
@@ -765,6 +794,7 @@ void bpf_program__unload(struct bpf_program *prog)
765794

766795
zfree(&prog->func_info);
767796
zfree(&prog->line_info);
797+
zfree(&prog->subprogs);
768798
}
769799

770800
static void bpf_program__exit(struct bpf_program *prog)
@@ -3943,6 +3973,13 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
39433973
} else if (strcmp(name, ARENA_SEC) == 0) {
39443974
obj->efile.arena_data = data;
39453975
obj->efile.arena_data_shndx = idx;
3976+
} else if (strcmp(name, JUMPTABLES_SEC) == 0) {
3977+
obj->jumptables_data = malloc(data->d_size);
3978+
if (!obj->jumptables_data)
3979+
return -ENOMEM;
3980+
memcpy(obj->jumptables_data, data->d_buf, data->d_size);
3981+
obj->jumptables_data_sz = data->d_size;
3982+
obj->efile.jumptables_data_shndx = idx;
39463983
} else {
39473984
pr_info("elf: skipping unrecognized data section(%d) %s\n",
39483985
idx, name);
@@ -4635,6 +4672,16 @@ static int bpf_program__record_reloc(struct bpf_program *prog,
46354672
return 0;
46364673
}
46374674

4675+
/* jump table data relocation */
4676+
if (shdr_idx == obj->efile.jumptables_data_shndx) {
4677+
reloc_desc->type = RELO_INSN_ARRAY;
4678+
reloc_desc->insn_idx = insn_idx;
4679+
reloc_desc->map_idx = -1;
4680+
reloc_desc->sym_off = sym->st_value;
4681+
reloc_desc->sym_size = sym->st_size;
4682+
return 0;
4683+
}
4684+
46384685
/* generic map reference relocation */
46394686
if (type == LIBBPF_MAP_UNSPEC) {
46404687
if (!bpf_object__shndx_is_maps(obj, shdr_idx)) {
@@ -6145,6 +6192,157 @@ static void poison_kfunc_call(struct bpf_program *prog, int relo_idx,
61456192
insn->imm = POISON_CALL_KFUNC_BASE + ext_idx;
61466193
}
61476194

6195+
static int find_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym_off)
6196+
{
6197+
size_t i;
6198+
6199+
for (i = 0; i < obj->jumptable_map_cnt; i++) {
6200+
/*
6201+
* This might happen that same offset is used for two different
6202+
* programs (as jump tables can be the same). However, for
6203+
* different programs different maps should be created.
6204+
*/
6205+
if (obj->jumptable_maps[i].sym_off == sym_off &&
6206+
obj->jumptable_maps[i].prog == prog)
6207+
return obj->jumptable_maps[i].fd;
6208+
}
6209+
6210+
return -ENOENT;
6211+
}
6212+
6213+
static int add_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym_off, int map_fd)
6214+
{
6215+
size_t cnt = obj->jumptable_map_cnt;
6216+
size_t size = sizeof(obj->jumptable_maps[0]);
6217+
void *tmp;
6218+
6219+
tmp = libbpf_reallocarray(obj->jumptable_maps, cnt + 1, size);
6220+
if (!tmp)
6221+
return -ENOMEM;
6222+
6223+
obj->jumptable_maps = tmp;
6224+
obj->jumptable_maps[cnt].prog = prog;
6225+
obj->jumptable_maps[cnt].sym_off = sym_off;
6226+
obj->jumptable_maps[cnt].fd = map_fd;
6227+
obj->jumptable_map_cnt++;
6228+
6229+
return 0;
6230+
}
6231+
6232+
static int find_subprog_idx(struct bpf_program *prog, int insn_idx)
6233+
{
6234+
int i;
6235+
6236+
for (i = prog->subprog_cnt - 1; i >= 0; i--) {
6237+
if (insn_idx >= prog->subprogs[i].sub_insn_off)
6238+
return i;
6239+
}
6240+
6241+
return -1;
6242+
}
6243+
6244+
static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog, struct reloc_desc *relo)
6245+
{
6246+
const __u32 jt_entry_size = 8;
6247+
int sym_off = relo->sym_off;
6248+
int jt_size = relo->sym_size;
6249+
__u32 max_entries = jt_size / jt_entry_size;
6250+
__u32 value_size = sizeof(struct bpf_insn_array_value);
6251+
struct bpf_insn_array_value val = {};
6252+
int subprog_idx;
6253+
int map_fd, err;
6254+
__u64 insn_off;
6255+
__u64 *jt;
6256+
__u32 i;
6257+
6258+
map_fd = find_jt_map(obj, prog, sym_off);
6259+
if (map_fd >= 0)
6260+
return map_fd;
6261+
6262+
if (sym_off % jt_entry_size) {
6263+
pr_warn("map '.jumptables': jumptable start %d should be multiple of %u\n",
6264+
sym_off, jt_entry_size);
6265+
return -EINVAL;
6266+
}
6267+
6268+
if (jt_size % jt_entry_size) {
6269+
pr_warn("map '.jumptables': jumptable size %d should be multiple of %u\n",
6270+
jt_size, jt_entry_size);
6271+
return -EINVAL;
6272+
}
6273+
6274+
map_fd = bpf_map_create(BPF_MAP_TYPE_INSN_ARRAY, ".jumptables",
6275+
4, value_size, max_entries, NULL);
6276+
if (map_fd < 0)
6277+
return map_fd;
6278+
6279+
if (!obj->jumptables_data) {
6280+
pr_warn("map '.jumptables': ELF file is missing jump table data\n");
6281+
err = -EINVAL;
6282+
goto err_close;
6283+
}
6284+
if (sym_off + jt_size > obj->jumptables_data_sz) {
6285+
pr_warn("map '.jumptables': jumptables_data size is %zd, trying to access %d\n",
6286+
obj->jumptables_data_sz, sym_off + jt_size);
6287+
err = -EINVAL;
6288+
goto err_close;
6289+
}
6290+
6291+
subprog_idx = -1; /* main program */
6292+
if (relo->insn_idx < 0 || relo->insn_idx >= prog->insns_cnt) {
6293+
pr_warn("map '.jumptables': invalid instruction index %d\n", relo->insn_idx);
6294+
err = -EINVAL;
6295+
goto err_close;
6296+
}
6297+
if (prog->subprogs)
6298+
subprog_idx = find_subprog_idx(prog, relo->insn_idx);
6299+
6300+
jt = (__u64 *)(obj->jumptables_data + sym_off);
6301+
for (i = 0; i < max_entries; i++) {
6302+
/*
6303+
* The offset should be made to be relative to the beginning of
6304+
* the main function, not the subfunction.
6305+
*/
6306+
insn_off = jt[i]/sizeof(struct bpf_insn);
6307+
if (subprog_idx >= 0) {
6308+
insn_off -= prog->subprogs[subprog_idx].sec_insn_off;
6309+
insn_off += prog->subprogs[subprog_idx].sub_insn_off;
6310+
} else {
6311+
insn_off -= prog->sec_insn_off;
6312+
}
6313+
6314+
/*
6315+
* LLVM-generated jump tables contain u64 records, however
6316+
* should contain values that fit in u32.
6317+
*/
6318+
if (insn_off > UINT32_MAX) {
6319+
pr_warn("map '.jumptables': invalid jump table value 0x%llx at offset %d\n",
6320+
(long long)jt[i], sym_off + i * jt_entry_size);
6321+
err = -EINVAL;
6322+
goto err_close;
6323+
}
6324+
6325+
val.orig_off = insn_off;
6326+
err = bpf_map_update_elem(map_fd, &i, &val, 0);
6327+
if (err)
6328+
goto err_close;
6329+
}
6330+
6331+
err = bpf_map_freeze(map_fd);
6332+
if (err)
6333+
goto err_close;
6334+
6335+
err = add_jt_map(obj, prog, sym_off, map_fd);
6336+
if (err)
6337+
goto err_close;
6338+
6339+
return map_fd;
6340+
6341+
err_close:
6342+
close(map_fd);
6343+
return err;
6344+
}
6345+
61486346
/* Relocate data references within program code:
61496347
* - map references;
61506348
* - global variable references;
@@ -6236,6 +6434,20 @@ bpf_object__relocate_data(struct bpf_object *obj, struct bpf_program *prog)
62366434
case RELO_CORE:
62376435
/* will be handled by bpf_program_record_relos() */
62386436
break;
6437+
case RELO_INSN_ARRAY: {
6438+
int map_fd;
6439+
6440+
map_fd = create_jt_map(obj, prog, relo);
6441+
if (map_fd < 0) {
6442+
pr_warn("prog '%s': relo #%d: can't create jump table: sym_off %u\n",
6443+
prog->name, i, relo->sym_off);
6444+
return map_fd;
6445+
}
6446+
insn[0].src_reg = BPF_PSEUDO_MAP_VALUE;
6447+
insn->imm = map_fd;
6448+
insn->off = 0;
6449+
}
6450+
break;
62396451
default:
62406452
pr_warn("prog '%s': relo #%d: bad relo type %d\n",
62416453
prog->name, i, relo->type);
@@ -6433,6 +6645,24 @@ static int append_subprog_relos(struct bpf_program *main_prog, struct bpf_progra
64336645
return 0;
64346646
}
64356647

6648+
static int save_subprog_offsets(struct bpf_program *main_prog, struct bpf_program *subprog)
6649+
{
6650+
size_t size = sizeof(main_prog->subprogs[0]);
6651+
int cnt = main_prog->subprog_cnt;
6652+
void *tmp;
6653+
6654+
tmp = libbpf_reallocarray(main_prog->subprogs, cnt + 1, size);
6655+
if (!tmp)
6656+
return -ENOMEM;
6657+
6658+
main_prog->subprogs = tmp;
6659+
main_prog->subprogs[cnt].sec_insn_off = subprog->sec_insn_off;
6660+
main_prog->subprogs[cnt].sub_insn_off = subprog->sub_insn_off;
6661+
main_prog->subprog_cnt++;
6662+
6663+
return 0;
6664+
}
6665+
64366666
static int
64376667
bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main_prog,
64386668
struct bpf_program *subprog)
@@ -6462,6 +6692,14 @@ bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main
64626692
err = append_subprog_relos(main_prog, subprog);
64636693
if (err)
64646694
return err;
6695+
6696+
err = save_subprog_offsets(main_prog, subprog);
6697+
if (err) {
6698+
pr_warn("prog '%s': failed to add subprog offsets: %s\n",
6699+
main_prog->name, errstr(err));
6700+
return err;
6701+
}
6702+
64656703
return 0;
64666704
}
64676705

@@ -9229,6 +9467,13 @@ void bpf_object__close(struct bpf_object *obj)
92299467

92309468
zfree(&obj->arena_data);
92319469

9470+
zfree(&obj->jumptables_data);
9471+
obj->jumptables_data_sz = 0;
9472+
9473+
for (i = 0; i < obj->jumptable_map_cnt; i++)
9474+
close(obj->jumptable_maps[i].fd);
9475+
zfree(&obj->jumptable_maps);
9476+
92329477
free(obj);
92339478
}
92349479

tools/lib/bpf/libbpf_internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@
7474
#define ELF64_ST_VISIBILITY(o) ((o) & 0x03)
7575
#endif
7676

77+
#define JUMPTABLES_SEC ".jumptables"
78+
7779
#define BTF_INFO_ENC(kind, kind_flag, vlen) \
7880
((!!(kind_flag) << 31) | ((kind) << 24) | ((vlen) & BTF_MAX_VLEN))
7981
#define BTF_TYPE_ENC(name, info, size_or_type) (name), (info), (size_or_type)

tools/lib/bpf/linker.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,6 +2025,9 @@ static int linker_append_elf_sym(struct bpf_linker *linker, struct src_obj *obj,
20252025
obj->sym_map[src_sym_idx] = dst_sec->sec_sym_idx;
20262026
return 0;
20272027
}
2028+
2029+
if (strcmp(src_sec->sec_name, JUMPTABLES_SEC) == 0)
2030+
goto add_sym;
20282031
}
20292032

20302033
if (sym_bind == STB_LOCAL)

0 commit comments

Comments
 (0)