Skip to content

Commit c54dc09

Browse files
aspskKernel Patches Daemon
authored andcommitted
libbpf: support llvm-generated indirect jumps
For v5 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]>
1 parent dcf3f3f commit c54dc09

File tree

3 files changed

+232
-3
lines changed

3 files changed

+232
-3
lines changed

tools/lib/bpf/libbpf.c

Lines changed: 220 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ static const char * const map_type_name[] = {
192192
[BPF_MAP_TYPE_USER_RINGBUF] = "user_ringbuf",
193193
[BPF_MAP_TYPE_CGRP_STORAGE] = "cgrp_storage",
194194
[BPF_MAP_TYPE_ARENA] = "arena",
195+
[BPF_MAP_TYPE_INSN_ARRAY] = "insn_array",
195196
};
196197

197198
static const char * const prog_type_name[] = {
@@ -373,6 +374,7 @@ enum reloc_type {
373374
RELO_EXTERN_CALL,
374375
RELO_SUBPROG_ADDR,
375376
RELO_CORE,
377+
RELO_INSN_ARRAY,
376378
};
377379

378380
struct reloc_desc {
@@ -383,7 +385,10 @@ struct reloc_desc {
383385
struct {
384386
int map_idx;
385387
int sym_off;
386-
int ext_idx;
388+
union {
389+
int ext_idx;
390+
int sym_size;
391+
};
387392
};
388393
};
389394
};
@@ -425,6 +430,11 @@ struct bpf_sec_def {
425430
libbpf_prog_attach_fn_t prog_attach_fn;
426431
};
427432

433+
struct bpf_light_subprog {
434+
__u32 sec_insn_off;
435+
__u32 sub_insn_off;
436+
};
437+
428438
/*
429439
* bpf_prog should be a better name but it has been used in
430440
* linux/filter.h.
@@ -498,6 +508,9 @@ struct bpf_program {
498508
__u32 line_info_cnt;
499509
__u32 prog_flags;
500510
__u8 hash[SHA256_DIGEST_LENGTH];
511+
512+
struct bpf_light_subprog *subprogs;
513+
__u32 subprog_cnt;
501514
};
502515

503516
struct bpf_struct_ops {
@@ -527,6 +540,7 @@ struct bpf_struct_ops {
527540
#define STRUCT_OPS_SEC ".struct_ops"
528541
#define STRUCT_OPS_LINK_SEC ".struct_ops.link"
529542
#define ARENA_SEC ".addr_space.1"
543+
#define JUMPTABLES_SEC ".jumptables"
530544

531545
enum libbpf_map_type {
532546
LIBBPF_MAP_UNSPEC,
@@ -671,6 +685,7 @@ struct elf_state {
671685
int symbols_shndx;
672686
bool has_st_ops;
673687
int arena_data_shndx;
688+
int jumptables_data_shndx;
674689
};
675690

676691
struct usdt_manager;
@@ -742,6 +757,16 @@ struct bpf_object {
742757
void *arena_data;
743758
size_t arena_data_sz;
744759

760+
void *jumptables_data;
761+
size_t jumptables_data_sz;
762+
763+
struct {
764+
struct bpf_program *prog;
765+
int off;
766+
int fd;
767+
} *jumptable_maps;
768+
size_t jumptable_map_cnt;
769+
745770
struct kern_feature_cache *feat_cache;
746771
char *token_path;
747772
int token_fd;
@@ -768,6 +793,7 @@ void bpf_program__unload(struct bpf_program *prog)
768793

769794
zfree(&prog->func_info);
770795
zfree(&prog->line_info);
796+
zfree(&prog->subprogs);
771797
}
772798

773799
static void bpf_program__exit(struct bpf_program *prog)
@@ -3946,6 +3972,13 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
39463972
} else if (strcmp(name, ARENA_SEC) == 0) {
39473973
obj->efile.arena_data = data;
39483974
obj->efile.arena_data_shndx = idx;
3975+
} else if (strcmp(name, JUMPTABLES_SEC) == 0) {
3976+
obj->jumptables_data = malloc(data->d_size);
3977+
if (!obj->jumptables_data)
3978+
return -ENOMEM;
3979+
memcpy(obj->jumptables_data, data->d_buf, data->d_size);
3980+
obj->jumptables_data_sz = data->d_size;
3981+
obj->efile.jumptables_data_shndx = idx;
39493982
} else {
39503983
pr_info("elf: skipping unrecognized data section(%d) %s\n",
39513984
idx, name);
@@ -4638,6 +4671,16 @@ static int bpf_program__record_reloc(struct bpf_program *prog,
46384671
return 0;
46394672
}
46404673

4674+
/* jump table data relocation */
4675+
if (shdr_idx == obj->efile.jumptables_data_shndx) {
4676+
reloc_desc->type = RELO_INSN_ARRAY;
4677+
reloc_desc->insn_idx = insn_idx;
4678+
reloc_desc->map_idx = -1;
4679+
reloc_desc->sym_off = sym->st_value;
4680+
reloc_desc->sym_size = sym->st_size;
4681+
return 0;
4682+
}
4683+
46414684
/* generic map reference relocation */
46424685
if (type == LIBBPF_MAP_UNSPEC) {
46434686
if (!bpf_object__shndx_is_maps(obj, shdr_idx)) {
@@ -6148,6 +6191,131 @@ static void poison_kfunc_call(struct bpf_program *prog, int relo_idx,
61486191
insn->imm = POISON_CALL_KFUNC_BASE + ext_idx;
61496192
}
61506193

6194+
static int find_jt_map(struct bpf_object *obj, struct bpf_program *prog, int off)
6195+
{
6196+
size_t i;
6197+
6198+
for (i = 0; i < obj->jumptable_map_cnt; i++) {
6199+
/*
6200+
* This might happen that same offset is used for two different
6201+
* programs (as jump tables can be the same). However, for
6202+
* different programs different maps should be created.
6203+
*/
6204+
if (obj->jumptable_maps[i].off == off &&
6205+
obj->jumptable_maps[i].prog == prog)
6206+
return obj->jumptable_maps[i].fd;
6207+
}
6208+
6209+
return -ENOENT;
6210+
}
6211+
6212+
static int add_jt_map(struct bpf_object *obj, struct bpf_program *prog, int off, int map_fd)
6213+
{
6214+
size_t new_cnt = obj->jumptable_map_cnt + 1;
6215+
size_t size = sizeof(obj->jumptable_maps[0]);
6216+
void *tmp;
6217+
6218+
tmp = libbpf_reallocarray(obj->jumptable_maps, new_cnt, size);
6219+
if (!tmp)
6220+
return -ENOMEM;
6221+
6222+
obj->jumptable_maps = tmp;
6223+
obj->jumptable_maps[new_cnt - 1].prog = prog;
6224+
obj->jumptable_maps[new_cnt - 1].off = off;
6225+
obj->jumptable_maps[new_cnt - 1].fd = map_fd;
6226+
obj->jumptable_map_cnt = new_cnt;
6227+
6228+
return 0;
6229+
}
6230+
6231+
static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog,
6232+
int off, int size, int adjust_off)
6233+
{
6234+
const __u32 value_size = sizeof(struct bpf_insn_array_value);
6235+
const __u32 max_entries = size / value_size;
6236+
struct bpf_insn_array_value val = {};
6237+
int map_fd, err;
6238+
__u64 xlated_off;
6239+
__u64 *jt;
6240+
__u32 i;
6241+
6242+
map_fd = find_jt_map(obj, prog, off);
6243+
if (map_fd >= 0)
6244+
return map_fd;
6245+
6246+
map_fd = bpf_map_create(BPF_MAP_TYPE_INSN_ARRAY, ".jumptables",
6247+
4, value_size, max_entries, NULL);
6248+
if (map_fd < 0)
6249+
return map_fd;
6250+
6251+
if (!obj->jumptables_data) {
6252+
pr_warn("map '.jumptables': ELF file is missing jump table data\n");
6253+
err = -EINVAL;
6254+
goto err_close;
6255+
}
6256+
if (off + size > obj->jumptables_data_sz) {
6257+
pr_warn("jumptables_data size is %zd, trying to access %d\n",
6258+
obj->jumptables_data_sz, off + size);
6259+
err = -EINVAL;
6260+
goto err_close;
6261+
}
6262+
6263+
jt = (__u64 *)(obj->jumptables_data + off);
6264+
for (i = 0; i < max_entries; i++) {
6265+
/*
6266+
* LLVM-generated jump tables contain u64 records, however
6267+
* should contain values that fit in u32.
6268+
* The adjust_off provided by the caller adjusts the offset to
6269+
* be relative to the beginning of the main function
6270+
*/
6271+
xlated_off = jt[i]/sizeof(struct bpf_insn) + adjust_off;
6272+
if (xlated_off > UINT32_MAX) {
6273+
pr_warn("invalid jump table value %llx at offset %d (adjust_off %d)\n",
6274+
jt[i], off + i, adjust_off);
6275+
err = -EINVAL;
6276+
goto err_close;
6277+
}
6278+
6279+
val.xlated_off = xlated_off;
6280+
err = bpf_map_update_elem(map_fd, &i, &val, 0);
6281+
if (err)
6282+
goto err_close;
6283+
}
6284+
6285+
err = bpf_map_freeze(map_fd);
6286+
if (err)
6287+
goto err_close;
6288+
6289+
err = add_jt_map(obj, prog, off, map_fd);
6290+
if (err)
6291+
goto err_close;
6292+
6293+
return map_fd;
6294+
6295+
err_close:
6296+
close(map_fd);
6297+
return err;
6298+
}
6299+
6300+
/*
6301+
* In LLVM the .jumptables section contains jump tables entries relative to the
6302+
* section start. The BPF kernel-side code expects jump table offsets relative
6303+
* to the beginning of the program (passed in bpf(BPF_PROG_LOAD)). This helper
6304+
* computes a delta to be added when creating a map.
6305+
*/
6306+
static int jt_adjust_off(struct bpf_program *prog, int insn_idx)
6307+
{
6308+
int i;
6309+
6310+
for (i = prog->subprog_cnt - 1; i >= 0; i--) {
6311+
if (insn_idx >= prog->subprogs[i].sub_insn_off)
6312+
return prog->subprogs[i].sub_insn_off - prog->subprogs[i].sec_insn_off;
6313+
}
6314+
6315+
return -prog->sec_insn_off;
6316+
}
6317+
6318+
61516319
/* Relocate data references within program code:
61526320
* - map references;
61536321
* - global variable references;
@@ -6239,6 +6407,21 @@ bpf_object__relocate_data(struct bpf_object *obj, struct bpf_program *prog)
62396407
case RELO_CORE:
62406408
/* will be handled by bpf_program_record_relos() */
62416409
break;
6410+
case RELO_INSN_ARRAY: {
6411+
int map_fd;
6412+
6413+
map_fd = create_jt_map(obj, prog, relo->sym_off, relo->sym_size,
6414+
jt_adjust_off(prog, relo->insn_idx));
6415+
if (map_fd < 0) {
6416+
pr_warn("prog '%s': relo #%d: can't create jump table: sym_off %u\n",
6417+
prog->name, i, relo->sym_off);
6418+
return map_fd;
6419+
}
6420+
insn[0].src_reg = BPF_PSEUDO_MAP_VALUE;
6421+
insn->imm = map_fd;
6422+
insn->off = 0;
6423+
}
6424+
break;
62426425
default:
62436426
pr_warn("prog '%s': relo #%d: bad relo type %d\n",
62446427
prog->name, i, relo->type);
@@ -6436,6 +6619,24 @@ static int append_subprog_relos(struct bpf_program *main_prog, struct bpf_progra
64366619
return 0;
64376620
}
64386621

6622+
static int save_subprog_offsets(struct bpf_program *main_prog, struct bpf_program *subprog)
6623+
{
6624+
size_t size = sizeof(main_prog->subprogs[0]);
6625+
int new_cnt = main_prog->subprog_cnt + 1;
6626+
void *tmp;
6627+
6628+
tmp = libbpf_reallocarray(main_prog->subprogs, new_cnt, size);
6629+
if (!tmp)
6630+
return -ENOMEM;
6631+
6632+
main_prog->subprogs = tmp;
6633+
main_prog->subprogs[new_cnt - 1].sec_insn_off = subprog->sec_insn_off;
6634+
main_prog->subprogs[new_cnt - 1].sub_insn_off = subprog->sub_insn_off;
6635+
main_prog->subprog_cnt = new_cnt;
6636+
6637+
return 0;
6638+
}
6639+
64396640
static int
64406641
bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main_prog,
64416642
struct bpf_program *subprog)
@@ -6465,6 +6666,15 @@ bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main
64656666
err = append_subprog_relos(main_prog, subprog);
64666667
if (err)
64676668
return err;
6669+
6670+
/* Save subprogram offsets */
6671+
err = save_subprog_offsets(main_prog, subprog);
6672+
if (err) {
6673+
pr_warn("prog '%s': failed to add subprog offsets: %s\n",
6674+
main_prog->name, errstr(err));
6675+
return err;
6676+
}
6677+
64686678
return 0;
64696679
}
64706680

@@ -9232,6 +9442,15 @@ void bpf_object__close(struct bpf_object *obj)
92329442

92339443
zfree(&obj->arena_data);
92349444

9445+
zfree(&obj->jumptables_data);
9446+
obj->jumptables_data_sz = 0;
9447+
9448+
if (obj->jumptable_maps && obj->jumptable_map_cnt) {
9449+
for (i = 0; i < obj->jumptable_map_cnt; i++)
9450+
close(obj->jumptable_maps[i].fd);
9451+
}
9452+
zfree(&obj->jumptable_maps);
9453+
92359454
free(obj);
92369455
}
92379456

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;

tools/lib/bpf/linker.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include "str_error.h"
2929

3030
#define BTF_EXTERN_SEC ".extern"
31+
#define JUMPTABLES_SEC ".jumptables"
32+
#define JUMPTABLES_REL_SEC ".rel.jumptables"
3133

3234
struct src_sec {
3335
const char *sec_name;
@@ -2026,6 +2028,9 @@ static int linker_append_elf_sym(struct bpf_linker *linker, struct src_obj *obj,
20262028
obj->sym_map[src_sym_idx] = dst_sec->sec_sym_idx;
20272029
return 0;
20282030
}
2031+
2032+
if (strcmp(src_sec->sec_name, JUMPTABLES_SEC) == 0)
2033+
goto add_sym;
20292034
}
20302035

20312036
if (sym_bind == STB_LOCAL)
@@ -2272,8 +2277,9 @@ static int linker_append_elf_relos(struct bpf_linker *linker, struct src_obj *ob
22722277
insn->imm += sec->dst_off / sizeof(struct bpf_insn);
22732278
else
22742279
insn->imm += sec->dst_off;
2275-
} else {
2276-
pr_warn("relocation against STT_SECTION in non-exec section is not supported!\n");
2280+
} else if (strcmp(src_sec->sec_name, JUMPTABLES_REL_SEC) != 0) {
2281+
pr_warn("relocation against STT_SECTION in section %s is not supported!\n",
2282+
src_sec->sec_name);
22772283
return -EINVAL;
22782284
}
22792285
}

0 commit comments

Comments
 (0)