]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
libbpf: support llvm-generated indirect jumps
authorAnton Protopopov <a.s.protopopov@gmail.com>
Wed, 5 Nov 2025 09:04:08 +0000 (09:04 +0000)
committerAlexei Starovoitov <ast@kernel.org>
Thu, 6 Nov 2025 01:53:23 +0000 (17:53 -0800)
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>
tools/lib/bpf/libbpf.c
tools/lib/bpf/libbpf_internal.h
tools/lib/bpf/linker.c

index 777c848ac0972cab6d289eeff1722f305a85191f..706e7481bdf6b5cf902aa1ca2af1ffff53416255 100644 (file)
@@ -370,6 +370,7 @@ enum reloc_type {
        RELO_EXTERN_CALL,
        RELO_SUBPROG_ADDR,
        RELO_CORE,
+       RELO_INSN_ARRAY,
 };
 
 struct reloc_desc {
@@ -380,7 +381,16 @@ struct reloc_desc {
                struct {
                        int map_idx;
                        int sym_off;
-                       int ext_idx;
+                       /*
+                        * The following two fields can be unionized, as the
+                        * ext_idx field is used for extern symbols, and the
+                        * sym_size is used for jump tables, which are never
+                        * extern
+                        */
+                       union {
+                               int ext_idx;
+                               int sym_size;
+                       };
                };
        };
 };
@@ -422,6 +432,11 @@ struct bpf_sec_def {
        libbpf_prog_attach_fn_t prog_attach_fn;
 };
 
+struct bpf_light_subprog {
+       __u32 sec_insn_off;
+       __u32 sub_insn_off;
+};
+
 /*
  * bpf_prog should be a better name but it has been used in
  * linux/filter.h.
@@ -495,6 +510,9 @@ struct bpf_program {
        __u32 line_info_cnt;
        __u32 prog_flags;
        __u8  hash[SHA256_DIGEST_LENGTH];
+
+       struct bpf_light_subprog *subprogs;
+       __u32 subprog_cnt;
 };
 
 struct bpf_struct_ops {
@@ -668,6 +686,7 @@ struct elf_state {
        int symbols_shndx;
        bool has_st_ops;
        int arena_data_shndx;
+       int jumptables_data_shndx;
 };
 
 struct usdt_manager;
@@ -739,6 +758,16 @@ struct bpf_object {
        void *arena_data;
        size_t arena_data_sz;
 
+       void *jumptables_data;
+       size_t jumptables_data_sz;
+
+       struct {
+               struct bpf_program *prog;
+               int sym_off;
+               int fd;
+       } *jumptable_maps;
+       size_t jumptable_map_cnt;
+
        struct kern_feature_cache *feat_cache;
        char *token_path;
        int token_fd;
@@ -765,6 +794,7 @@ void bpf_program__unload(struct bpf_program *prog)
 
        zfree(&prog->func_info);
        zfree(&prog->line_info);
+       zfree(&prog->subprogs);
 }
 
 static void bpf_program__exit(struct bpf_program *prog)
@@ -3943,6 +3973,13 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
                        } else if (strcmp(name, ARENA_SEC) == 0) {
                                obj->efile.arena_data = data;
                                obj->efile.arena_data_shndx = idx;
+                       } else if (strcmp(name, JUMPTABLES_SEC) == 0) {
+                               obj->jumptables_data = malloc(data->d_size);
+                               if (!obj->jumptables_data)
+                                       return -ENOMEM;
+                               memcpy(obj->jumptables_data, data->d_buf, data->d_size);
+                               obj->jumptables_data_sz = data->d_size;
+                               obj->efile.jumptables_data_shndx = idx;
                        } else {
                                pr_info("elf: skipping unrecognized data section(%d) %s\n",
                                        idx, name);
@@ -4635,6 +4672,16 @@ static int bpf_program__record_reloc(struct bpf_program *prog,
                return 0;
        }
 
+       /* jump table data relocation */
+       if (shdr_idx == obj->efile.jumptables_data_shndx) {
+               reloc_desc->type = RELO_INSN_ARRAY;
+               reloc_desc->insn_idx = insn_idx;
+               reloc_desc->map_idx = -1;
+               reloc_desc->sym_off = sym->st_value;
+               reloc_desc->sym_size = sym->st_size;
+               return 0;
+       }
+
        /* generic map reference relocation */
        if (type == LIBBPF_MAP_UNSPEC) {
                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,
        insn->imm = POISON_CALL_KFUNC_BASE + ext_idx;
 }
 
+static int find_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym_off)
+{
+       size_t i;
+
+       for (i = 0; i < obj->jumptable_map_cnt; i++) {
+               /*
+                * This might happen that same offset is used for two different
+                * programs (as jump tables can be the same). However, for
+                * different programs different maps should be created.
+                */
+               if (obj->jumptable_maps[i].sym_off == sym_off &&
+                   obj->jumptable_maps[i].prog == prog)
+                       return obj->jumptable_maps[i].fd;
+       }
+
+       return -ENOENT;
+}
+
+static int add_jt_map(struct bpf_object *obj, struct bpf_program *prog, int sym_off, int map_fd)
+{
+       size_t cnt = obj->jumptable_map_cnt;
+       size_t size = sizeof(obj->jumptable_maps[0]);
+       void *tmp;
+
+       tmp = libbpf_reallocarray(obj->jumptable_maps, cnt + 1, size);
+       if (!tmp)
+               return -ENOMEM;
+
+       obj->jumptable_maps = tmp;
+       obj->jumptable_maps[cnt].prog = prog;
+       obj->jumptable_maps[cnt].sym_off = sym_off;
+       obj->jumptable_maps[cnt].fd = map_fd;
+       obj->jumptable_map_cnt++;
+
+       return 0;
+}
+
+static int find_subprog_idx(struct bpf_program *prog, int insn_idx)
+{
+       int i;
+
+       for (i = prog->subprog_cnt - 1; i >= 0; i--) {
+               if (insn_idx >= prog->subprogs[i].sub_insn_off)
+                       return i;
+       }
+
+       return -1;
+}
+
+static int create_jt_map(struct bpf_object *obj, struct bpf_program *prog, struct reloc_desc *relo)
+{
+       const __u32 jt_entry_size = 8;
+       int sym_off = relo->sym_off;
+       int jt_size = relo->sym_size;
+       __u32 max_entries = jt_size / jt_entry_size;
+       __u32 value_size = sizeof(struct bpf_insn_array_value);
+       struct bpf_insn_array_value val = {};
+       int subprog_idx;
+       int map_fd, err;
+       __u64 insn_off;
+       __u64 *jt;
+       __u32 i;
+
+       map_fd = find_jt_map(obj, prog, sym_off);
+       if (map_fd >= 0)
+               return map_fd;
+
+       if (sym_off % jt_entry_size) {
+               pr_warn("map '.jumptables': jumptable start %d should be multiple of %u\n",
+                       sym_off, jt_entry_size);
+               return -EINVAL;
+       }
+
+       if (jt_size % jt_entry_size) {
+               pr_warn("map '.jumptables': jumptable size %d should be multiple of %u\n",
+                       jt_size, jt_entry_size);
+               return -EINVAL;
+       }
+
+       map_fd = bpf_map_create(BPF_MAP_TYPE_INSN_ARRAY, ".jumptables",
+                               4, value_size, max_entries, NULL);
+       if (map_fd < 0)
+               return map_fd;
+
+       if (!obj->jumptables_data) {
+               pr_warn("map '.jumptables': ELF file is missing jump table data\n");
+               err = -EINVAL;
+               goto err_close;
+       }
+       if (sym_off + jt_size > obj->jumptables_data_sz) {
+               pr_warn("map '.jumptables': jumptables_data size is %zd, trying to access %d\n",
+                       obj->jumptables_data_sz, sym_off + jt_size);
+               err = -EINVAL;
+               goto err_close;
+       }
+
+       subprog_idx = -1; /* main program */
+       if (relo->insn_idx < 0 || relo->insn_idx >= prog->insns_cnt) {
+               pr_warn("map '.jumptables': invalid instruction index %d\n", relo->insn_idx);
+               err = -EINVAL;
+               goto err_close;
+       }
+       if (prog->subprogs)
+               subprog_idx = find_subprog_idx(prog, relo->insn_idx);
+
+       jt = (__u64 *)(obj->jumptables_data + sym_off);
+       for (i = 0; i < max_entries; i++) {
+               /*
+                * The offset should be made to be relative to the beginning of
+                * the main function, not the subfunction.
+                */
+               insn_off = jt[i]/sizeof(struct bpf_insn);
+               if (subprog_idx >= 0) {
+                       insn_off -= prog->subprogs[subprog_idx].sec_insn_off;
+                       insn_off += prog->subprogs[subprog_idx].sub_insn_off;
+               } else {
+                       insn_off -= prog->sec_insn_off;
+               }
+
+               /*
+                * LLVM-generated jump tables contain u64 records, however
+                * should contain values that fit in u32.
+                */
+               if (insn_off > UINT32_MAX) {
+                       pr_warn("map '.jumptables': invalid jump table value 0x%llx at offset %d\n",
+                               (long long)jt[i], sym_off + i * jt_entry_size);
+                       err = -EINVAL;
+                       goto err_close;
+               }
+
+               val.orig_off = insn_off;
+               err = bpf_map_update_elem(map_fd, &i, &val, 0);
+               if (err)
+                       goto err_close;
+       }
+
+       err = bpf_map_freeze(map_fd);
+       if (err)
+               goto err_close;
+
+       err = add_jt_map(obj, prog, sym_off, map_fd);
+       if (err)
+               goto err_close;
+
+       return map_fd;
+
+err_close:
+       close(map_fd);
+       return err;
+}
+
 /* Relocate data references within program code:
  *  - map references;
  *  - global variable references;
@@ -6236,6 +6434,20 @@ bpf_object__relocate_data(struct bpf_object *obj, struct bpf_program *prog)
                case RELO_CORE:
                        /* will be handled by bpf_program_record_relos() */
                        break;
+               case RELO_INSN_ARRAY: {
+                       int map_fd;
+
+                       map_fd = create_jt_map(obj, prog, relo);
+                       if (map_fd < 0) {
+                               pr_warn("prog '%s': relo #%d: can't create jump table: sym_off %u\n",
+                                       prog->name, i, relo->sym_off);
+                               return map_fd;
+                       }
+                       insn[0].src_reg = BPF_PSEUDO_MAP_VALUE;
+                       insn->imm = map_fd;
+                       insn->off = 0;
+               }
+                       break;
                default:
                        pr_warn("prog '%s': relo #%d: bad relo type %d\n",
                                prog->name, i, relo->type);
@@ -6433,6 +6645,24 @@ static int append_subprog_relos(struct bpf_program *main_prog, struct bpf_progra
        return 0;
 }
 
+static int save_subprog_offsets(struct bpf_program *main_prog, struct bpf_program *subprog)
+{
+       size_t size = sizeof(main_prog->subprogs[0]);
+       int cnt = main_prog->subprog_cnt;
+       void *tmp;
+
+       tmp = libbpf_reallocarray(main_prog->subprogs, cnt + 1, size);
+       if (!tmp)
+               return -ENOMEM;
+
+       main_prog->subprogs = tmp;
+       main_prog->subprogs[cnt].sec_insn_off = subprog->sec_insn_off;
+       main_prog->subprogs[cnt].sub_insn_off = subprog->sub_insn_off;
+       main_prog->subprog_cnt++;
+
+       return 0;
+}
+
 static int
 bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main_prog,
                                struct bpf_program *subprog)
@@ -6462,6 +6692,14 @@ bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main
        err = append_subprog_relos(main_prog, subprog);
        if (err)
                return err;
+
+       err = save_subprog_offsets(main_prog, subprog);
+       if (err) {
+               pr_warn("prog '%s': failed to add subprog offsets: %s\n",
+                       main_prog->name, errstr(err));
+               return err;
+       }
+
        return 0;
 }
 
@@ -9229,6 +9467,13 @@ void bpf_object__close(struct bpf_object *obj)
 
        zfree(&obj->arena_data);
 
+       zfree(&obj->jumptables_data);
+       obj->jumptables_data_sz = 0;
+
+       for (i = 0; i < obj->jumptable_map_cnt; i++)
+               close(obj->jumptable_maps[i].fd);
+       zfree(&obj->jumptable_maps);
+
        free(obj);
 }
 
index 35b2527bedecbc68d68c82189c6181f906f2c153..fc59b21b51b5e5353f25349b0f225ec32c944255 100644 (file)
@@ -74,6 +74,8 @@
 #define ELF64_ST_VISIBILITY(o) ((o) & 0x03)
 #endif
 
+#define JUMPTABLES_SEC ".jumptables"
+
 #define BTF_INFO_ENC(kind, kind_flag, vlen) \
        ((!!(kind_flag) << 31) | ((kind) << 24) | ((vlen) & BTF_MAX_VLEN))
 #define BTF_TYPE_ENC(name, info, size_or_type) (name), (info), (size_or_type)
index 56ae77047bc3676e567213deae8e9ef2ca09ea12..f4403e3cf99464b0c0883b76a5eeb0b9020d3ed5 100644 (file)
@@ -2025,6 +2025,9 @@ static int linker_append_elf_sym(struct bpf_linker *linker, struct src_obj *obj,
                        obj->sym_map[src_sym_idx] = dst_sec->sec_sym_idx;
                        return 0;
                }
+
+               if (strcmp(src_sec->sec_name, JUMPTABLES_SEC) == 0)
+                       goto add_sym;
        }
 
        if (sym_bind == STB_LOCAL)