int fd;
        bool autoload;
        bool autoattach;
+       bool sym_global;
        bool mark_btf_static;
        enum bpf_prog_type type;
        enum bpf_attach_type expected_attach_type;
+       int exception_cb_idx;
 
        int prog_ifindex;
        __u32 attach_btf_obj_fd;
 
        prog->type = BPF_PROG_TYPE_UNSPEC;
        prog->fd = -1;
+       prog->exception_cb_idx = -1;
 
        /* libbpf's convention for SEC("?abc...") is that it's just like
         * SEC("abc...") but the corresponding bpf_program starts out with
                if (err)
                        return err;
 
+               if (ELF64_ST_BIND(sym->st_info) != STB_LOCAL)
+                       prog->sym_global = true;
+
                /* if function is a global/weak symbol, but has restricted
                 * (STV_HIDDEN or STV_INTERNAL) visibility, mark its BTF FUNC
                 * as static to enable more permissive BPF verification mode
                 * with more outside context available to BPF verifier
                 */
-               if (ELF64_ST_BIND(sym->st_info) != STB_LOCAL
-                   && (ELF64_ST_VISIBILITY(sym->st_other) == STV_HIDDEN
-                       || ELF64_ST_VISIBILITY(sym->st_other) == STV_INTERNAL))
+               if (prog->sym_global && (ELF64_ST_VISIBILITY(sym->st_other) == STV_HIDDEN
+                   || ELF64_ST_VISIBILITY(sym->st_other) == STV_INTERNAL))
                        prog->mark_btf_static = true;
 
                nr_progs++;
                }
        }
 
+       if (!kernel_supports(obj, FEAT_BTF_DECL_TAG))
+               goto skip_exception_cb;
+       for (i = 0; i < obj->nr_programs; i++) {
+               struct bpf_program *prog = &obj->programs[i];
+               int j, k, n;
+
+               if (prog_is_subprog(obj, prog))
+                       continue;
+               n = btf__type_cnt(obj->btf);
+               for (j = 1; j < n; j++) {
+                       const char *str = "exception_callback:", *name;
+                       size_t len = strlen(str);
+                       struct btf_type *t;
+
+                       t = btf_type_by_id(obj->btf, j);
+                       if (!btf_is_decl_tag(t) || btf_decl_tag(t)->component_idx != -1)
+                               continue;
+
+                       name = btf__str_by_offset(obj->btf, t->name_off);
+                       if (strncmp(name, str, len))
+                               continue;
+
+                       t = btf_type_by_id(obj->btf, t->type);
+                       if (!btf_is_func(t) || btf_func_linkage(t) != BTF_FUNC_GLOBAL) {
+                               pr_warn("prog '%s': exception_callback:<value> decl tag not applied to the main program\n",
+                                       prog->name);
+                               return -EINVAL;
+                       }
+                       if (strcmp(prog->name, btf__str_by_offset(obj->btf, t->name_off)))
+                               continue;
+                       /* Multiple callbacks are specified for the same prog,
+                        * the verifier will eventually return an error for this
+                        * case, hence simply skip appending a subprog.
+                        */
+                       if (prog->exception_cb_idx >= 0) {
+                               prog->exception_cb_idx = -1;
+                               break;
+                       }
+
+                       name += len;
+                       if (str_is_empty(name)) {
+                               pr_warn("prog '%s': exception_callback:<value> decl tag contains empty value\n",
+                                       prog->name);
+                               return -EINVAL;
+                       }
+
+                       for (k = 0; k < obj->nr_programs; k++) {
+                               struct bpf_program *subprog = &obj->programs[k];
+
+                               if (!prog_is_subprog(obj, subprog))
+                                       continue;
+                               if (strcmp(name, subprog->name))
+                                       continue;
+                               /* Enforce non-hidden, as from verifier point of
+                                * view it expects global functions, whereas the
+                                * mark_btf_static fixes up linkage as static.
+                                */
+                               if (!subprog->sym_global || subprog->mark_btf_static) {
+                                       pr_warn("prog '%s': exception callback %s must be a global non-hidden function\n",
+                                               prog->name, subprog->name);
+                                       return -EINVAL;
+                               }
+                               /* Let's see if we already saw a static exception callback with the same name */
+                               if (prog->exception_cb_idx >= 0) {
+                                       pr_warn("prog '%s': multiple subprogs with same name as exception callback '%s'\n",
+                                               prog->name, subprog->name);
+                                       return -EINVAL;
+                               }
+                               prog->exception_cb_idx = k;
+                               break;
+                       }
+
+                       if (prog->exception_cb_idx >= 0)
+                               continue;
+                       pr_warn("prog '%s': cannot find exception callback '%s'\n", prog->name, name);
+                       return -ENOENT;
+               }
+       }
+skip_exception_cb:
+
        sanitize = btf_needs_sanitization(obj);
        if (sanitize) {
                const void *raw_data;
 bpf_object__reloc_code(struct bpf_object *obj, struct bpf_program *main_prog,
                       struct bpf_program *prog)
 {
-       size_t sub_insn_idx, insn_idx, new_cnt;
+       size_t sub_insn_idx, insn_idx;
        struct bpf_program *subprog;
-       struct bpf_insn *insns, *insn;
        struct reloc_desc *relo;
+       struct bpf_insn *insn;
        int err;
 
        err = reloc_prog_func_and_line_info(obj, main_prog, prog);
                                prog->name, err);
                        return err;
                }
+
+               /* Now, also append exception callback if it has not been done already. */
+               if (prog->exception_cb_idx >= 0) {
+                       struct bpf_program *subprog = &obj->programs[prog->exception_cb_idx];
+
+                       /* Calling exception callback directly is disallowed, which the
+                        * verifier will reject later. In case it was processed already,
+                        * we can skip this step, otherwise for all other valid cases we
+                        * have to append exception callback now.
+                        */
+                       if (subprog->sub_insn_off == 0) {
+                               err = bpf_object__append_subprog_code(obj, prog, subprog);
+                               if (err)
+                                       return err;
+                               err = bpf_object__reloc_code(obj, prog, subprog);
+                               if (err)
+                                       return err;
+                       }
+               }
        }
        /* Process data relos for main programs */
        for (i = 0; i < obj->nr_programs; i++) {