Skip to content

Commit 0d9c6f7

Browse files
aspskKernel Patches Daemon
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]>
1 parent 5c2a8f7 commit 0d9c6f7

File tree

4 files changed

+263
-3
lines changed

4 files changed

+263
-3
lines changed

tools/lib/bpf/libbpf.c

Lines changed: 248 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ static const char * const map_type_name[] = {
190190
[BPF_MAP_TYPE_USER_RINGBUF] = "user_ringbuf",
191191
[BPF_MAP_TYPE_CGRP_STORAGE] = "cgrp_storage",
192192
[BPF_MAP_TYPE_ARENA] = "arena",
193+
[BPF_MAP_TYPE_INSN_ARRAY] = "insn_array",
193194
};
194195

195196
static const char * const prog_type_name[] = {
@@ -369,6 +370,7 @@ enum reloc_type {
369370
RELO_EXTERN_CALL,
370371
RELO_SUBPROG_ADDR,
371372
RELO_CORE,
373+
RELO_INSN_ARRAY,
372374
};
373375

374376
struct reloc_desc {
@@ -379,7 +381,16 @@ struct reloc_desc {
379381
struct {
380382
int map_idx;
381383
int sym_off;
382-
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+
};
383394
};
384395
};
385396
};
@@ -421,6 +432,11 @@ struct bpf_sec_def {
421432
libbpf_prog_attach_fn_t prog_attach_fn;
422433
};
423434

435+
struct bpf_light_subprog {
436+
__u32 sec_insn_off;
437+
__u32 sub_insn_off;
438+
};
439+
424440
/*
425441
* bpf_prog should be a better name but it has been used in
426442
* linux/filter.h.
@@ -494,6 +510,9 @@ struct bpf_program {
494510
__u32 line_info_cnt;
495511
__u32 prog_flags;
496512
__u8 hash[SHA256_DIGEST_LENGTH];
513+
514+
struct bpf_light_subprog *subprogs;
515+
__u32 subprog_cnt;
497516
};
498517

499518
struct bpf_struct_ops {
@@ -667,6 +686,7 @@ struct elf_state {
667686
int symbols_shndx;
668687
bool has_st_ops;
669688
int arena_data_shndx;
689+
int jumptables_data_shndx;
670690
};
671691

672692
struct usdt_manager;
@@ -738,6 +758,16 @@ struct bpf_object {
738758
void *arena_data;
739759
size_t arena_data_sz;
740760

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+
741771
struct kern_feature_cache *feat_cache;
742772
char *token_path;
743773
int token_fd;
@@ -764,6 +794,7 @@ void bpf_program__unload(struct bpf_program *prog)
764794

765795
zfree(&prog->func_info);
766796
zfree(&prog->line_info);
797+
zfree(&prog->subprogs);
767798
}
768799

769800
static void bpf_program__exit(struct bpf_program *prog)
@@ -3942,6 +3973,13 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
39423973
} else if (strcmp(name, ARENA_SEC) == 0) {
39433974
obj->efile.arena_data = data;
39443975
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;
39453983
} else {
39463984
pr_info("elf: skipping unrecognized data section(%d) %s\n",
39473985
idx, name);
@@ -4634,6 +4672,16 @@ static int bpf_program__record_reloc(struct bpf_program *prog,
46344672
return 0;
46354673
}
46364674

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+
46374685
/* generic map reference relocation */
46384686
if (type == LIBBPF_MAP_UNSPEC) {
46394687
if (!bpf_object__shndx_is_maps(obj, shdr_idx)) {
@@ -6144,6 +6192,157 @@ static void poison_kfunc_call(struct bpf_program *prog, int relo_idx,
61446192
insn->imm = POISON_CALL_KFUNC_BASE + ext_idx;
61456193
}
61466194

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 new_cnt = obj->jumptable_map_cnt + 1;
6216+
size_t size = sizeof(obj->jumptable_maps[0]);
6217+
void *tmp;
6218+
6219+
tmp = libbpf_reallocarray(obj->jumptable_maps, new_cnt, size);
6220+
if (!tmp)
6221+
return -ENOMEM;
6222+
6223+
obj->jumptable_maps = tmp;
6224+
obj->jumptable_maps[new_cnt - 1].prog = prog;
6225+
obj->jumptable_maps[new_cnt - 1].sym_off = sym_off;
6226+
obj->jumptable_maps[new_cnt - 1].fd = map_fd;
6227+
obj->jumptable_map_cnt = new_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("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("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("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("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("invalid jump table value 0x%llx at offset %d\n",
6320+
jt[i], sym_off + i);
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+
61476346
/* Relocate data references within program code:
61486347
* - map references;
61496348
* - global variable references;
@@ -6235,6 +6434,20 @@ bpf_object__relocate_data(struct bpf_object *obj, struct bpf_program *prog)
62356434
case RELO_CORE:
62366435
/* will be handled by bpf_program_record_relos() */
62376436
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;
62386451
default:
62396452
pr_warn("prog '%s': relo #%d: bad relo type %d\n",
62406453
prog->name, i, relo->type);
@@ -6432,6 +6645,24 @@ static int append_subprog_relos(struct bpf_program *main_prog, struct bpf_progra
64326645
return 0;
64336646
}
64346647

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 new_cnt = main_prog->subprog_cnt + 1;
6652+
void *tmp;
6653+
6654+
tmp = libbpf_reallocarray(main_prog->subprogs, new_cnt, size);
6655+
if (!tmp)
6656+
return -ENOMEM;
6657+
6658+
main_prog->subprogs = tmp;
6659+
main_prog->subprogs[new_cnt - 1].sec_insn_off = subprog->sec_insn_off;
6660+
main_prog->subprogs[new_cnt - 1].sub_insn_off = subprog->sub_insn_off;
6661+
main_prog->subprog_cnt = new_cnt;
6662+
6663+
return 0;
6664+
}
6665+
64356666
static int
64366667
bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main_prog,
64376668
struct bpf_program *subprog)
@@ -6461,6 +6692,15 @@ bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main
64616692
err = append_subprog_relos(main_prog, subprog);
64626693
if (err)
64636694
return err;
6695+
6696+
/* Save subprogram offsets */
6697+
err = save_subprog_offsets(main_prog, subprog);
6698+
if (err) {
6699+
pr_warn("prog '%s': failed to add subprog offsets: %s\n",
6700+
main_prog->name, errstr(err));
6701+
return err;
6702+
}
6703+
64646704
return 0;
64656705
}
64666706

@@ -9228,6 +9468,13 @@ void bpf_object__close(struct bpf_object *obj)
92289468

92299469
zfree(&obj->arena_data);
92309470

9471+
zfree(&obj->jumptables_data);
9472+
obj->jumptables_data_sz = 0;
9473+
9474+
for (i = 0; i < obj->jumptable_map_cnt; i++)
9475+
close(obj->jumptable_maps[i].fd);
9476+
zfree(&obj->jumptable_maps);
9477+
92319478
free(obj);
92329479
}
92339480

tools/lib/bpf/libbpf_internal.h

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

77+
#ifndef JUMPTABLES_SEC
78+
#define JUMPTABLES_SEC ".jumptables"
79+
#endif
80+
7781
#define BTF_INFO_ENC(kind, kind_flag, vlen) \
7882
((!!(kind_flag) << 31) | ((kind) << 24) | ((vlen) & BTF_MAX_VLEN))
7983
#define BTF_TYPE_ENC(name, info, size_or_type) (name), (info), (size_or_type)

tools/lib/bpf/libbpf_probes.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,10 @@ static int probe_map_create(enum bpf_map_type map_type)
364364
case BPF_MAP_TYPE_SOCKHASH:
365365
case BPF_MAP_TYPE_REUSEPORT_SOCKARRAY:
366366
break;
367+
case BPF_MAP_TYPE_INSN_ARRAY:
368+
key_size = sizeof(__u32);
369+
value_size = sizeof(struct bpf_insn_array_value);
370+
break;
367371
case BPF_MAP_TYPE_UNSPEC:
368372
default:
369373
return -EOPNOTSUPP;

0 commit comments

Comments
 (0)