insn[0].imm = ext->ksym.kernel_btf_id;
                        break;
                case RELO_SUBPROG_ADDR:
-                       insn[0].src_reg = BPF_PSEUDO_FUNC;
-                       /* will be handled as a follow up pass */
+                       if (insn[0].src_reg != BPF_PSEUDO_FUNC) {
+                               pr_warn("prog '%s': relo #%d: bad insn\n",
+                                       prog->name, i);
+                               return -EINVAL;
+                       }
+                       /* handled already */
                        break;
                case RELO_CALL:
-                       /* will be handled as a follow up pass */
+                       /* handled already */
                        break;
                default:
                        pr_warn("prog '%s': relo #%d: bad relo type %d\n",
                       sizeof(*prog->reloc_desc), cmp_relo_by_insn_idx);
 }
 
+static int append_subprog_relos(struct bpf_program *main_prog, struct bpf_program *subprog)
+{
+       int new_cnt = main_prog->nr_reloc + subprog->nr_reloc;
+       struct reloc_desc *relos;
+       int i;
+
+       if (main_prog == subprog)
+               return 0;
+       relos = libbpf_reallocarray(main_prog->reloc_desc, new_cnt, sizeof(*relos));
+       if (!relos)
+               return -ENOMEM;
+       memcpy(relos + main_prog->nr_reloc, subprog->reloc_desc,
+              sizeof(*relos) * subprog->nr_reloc);
+
+       for (i = main_prog->nr_reloc; i < new_cnt; i++)
+               relos[i].insn_idx += subprog->sub_insn_off;
+       /* After insn_idx adjustment the 'relos' array is still sorted
+        * by insn_idx and doesn't break bsearch.
+        */
+       main_prog->reloc_desc = relos;
+       main_prog->nr_reloc = new_cnt;
+       return 0;
+}
+
 static int
 bpf_object__reloc_code(struct bpf_object *obj, struct bpf_program *main_prog,
                       struct bpf_program *prog)
                        continue;
 
                relo = find_prog_insn_relo(prog, insn_idx);
+               if (relo && relo->type == RELO_EXTERN_FUNC)
+                       /* kfunc relocations will be handled later
+                        * in bpf_object__relocate_data()
+                        */
+                       continue;
                if (relo && relo->type != RELO_CALL && relo->type != RELO_SUBPROG_ADDR) {
                        pr_warn("prog '%s': unexpected relo for insn #%zu, type %d\n",
                                prog->name, insn_idx, relo->type);
                        pr_debug("prog '%s': added %zu insns from sub-prog '%s'\n",
                                 main_prog->name, subprog->insns_cnt, subprog->name);
 
+                       /* The subprog insns are now appended. Append its relos too. */
+                       err = append_subprog_relos(main_prog, subprog);
+                       if (err)
+                               return err;
                        err = bpf_object__reloc_code(obj, main_prog, subprog);
                        if (err)
                                return err;
 bpf_object__relocate(struct bpf_object *obj, const char *targ_btf_path)
 {
        struct bpf_program *prog;
-       size_t i;
+       size_t i, j;
        int err;
 
        if (obj->btf_ext) {
                        return err;
                }
        }
-       /* relocate data references first for all programs and sub-programs,
-        * as they don't change relative to code locations, so subsequent
-        * subprogram processing won't need to re-calculate any of them
+
+       /* Before relocating calls pre-process relocations and mark
+        * few ld_imm64 instructions that points to subprogs.
+        * Otherwise bpf_object__reloc_code() later would have to consider
+        * all ld_imm64 insns as relocation candidates. That would
+        * reduce relocation speed, since amount of find_prog_insn_relo()
+        * would increase and most of them will fail to find a relo.
         */
        for (i = 0; i < obj->nr_programs; i++) {
                prog = &obj->programs[i];
-               err = bpf_object__relocate_data(obj, prog);
-               if (err) {
-                       pr_warn("prog '%s': failed to relocate data references: %d\n",
-                               prog->name, err);
-                       return err;
+               for (j = 0; j < prog->nr_reloc; j++) {
+                       struct reloc_desc *relo = &prog->reloc_desc[j];
+                       struct bpf_insn *insn = &prog->insns[relo->insn_idx];
+
+                       /* mark the insn, so it's recognized by insn_is_pseudo_func() */
+                       if (relo->type == RELO_SUBPROG_ADDR)
+                               insn[0].src_reg = BPF_PSEUDO_FUNC;
                }
        }
-       /* now relocate subprogram calls and append used subprograms to main
+
+       /* relocate subprogram calls and append used subprograms to main
         * programs; each copy of subprogram code needs to be relocated
         * differently for each main program, because its code location might
-        * have changed
+        * have changed.
+        * Append subprog relos to main programs to allow data relos to be
+        * processed after text is completely relocated.
         */
        for (i = 0; i < obj->nr_programs; i++) {
                prog = &obj->programs[i];
                        return err;
                }
        }
+       /* Process data relos for main programs */
+       for (i = 0; i < obj->nr_programs; i++) {
+               prog = &obj->programs[i];
+               if (prog_is_subprog(obj, prog))
+                       continue;
+               err = bpf_object__relocate_data(obj, prog);
+               if (err) {
+                       pr_warn("prog '%s': failed to relocate data references: %d\n",
+                               prog->name, err);
+                       return err;
+               }
+       }
        /* free up relocation descriptors */
        for (i = 0; i < obj->nr_programs; i++) {
                prog = &obj->programs[i];