diff mbox series

[bpf-next,v2,12/14] libbpf: Add support for custom exception callbacks

Message ID 20230809114116.3216687-13-memxor@gmail.com (mailing list archive)
State Changes Requested
Delegated to: BPF
Headers show
Series Exceptions - 1/2 | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 9 this patch: 9
netdev/cc_maintainers warning 6 maintainers not CCed: kpsingh@kernel.org john.fastabend@gmail.com sdf@google.com song@kernel.org jolsa@kernel.org haoluo@google.com
netdev/build_clang success Errors and warnings before: 9 this patch: 9
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 9 this patch: 9
netdev/checkpatch warning CHECK: Logical continuations should be on the previous line WARNING: line length of 102 exceeds 80 columns WARNING: line length of 82 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns WARNING: line length of 86 exceeds 80 columns WARNING: line length of 87 exceeds 80 columns WARNING: line length of 88 exceeds 80 columns WARNING: line length of 89 exceeds 80 columns WARNING: line length of 90 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-29 success Logs for veristat
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-0 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-1 success Logs for build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-3 success Logs for build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-5 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-4 success Logs for build for x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-2 success Logs for build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-24 success Logs for test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-26 success Logs for test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for test_verifier on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-28 success Logs for veristat
bpf/vmtest-bpf-next-VM_Test-6 success Logs for test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for test_maps on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-12 success Logs for test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-13 success Logs for test_progs on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-14 success Logs for test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-16 success Logs for test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for test_progs_no_alu32 on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-18 success Logs for test_progs_no_alu32_parallel on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-19 success Logs for test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-20 success Logs for test_progs_no_alu32_parallel on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-21 success Logs for test_progs_parallel on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for test_progs_parallel on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-25 success Logs for test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-15 success Logs for test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for test_progs on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for test_maps on s390x with gcc

Commit Message

Kumar Kartikeya Dwivedi Aug. 9, 2023, 11:41 a.m. UTC
Add support to libbpf to append exception callbacks when loading a
program. The exception callback is found by discovering the declaration
tag 'exception_callback:<value>' and finding the callback in the value
of the tag.

The process is done in two steps. First, for each main program, the
bpf_object__sanitize_and_load_btf function finds and marks its
corresponding exception callback as defined by the declaration tag on
it. Second, bpf_object__reloc_code is modified to append the indicated
exception callback at the end of the instruction iteration (since
exception callback will never be appended in that loop, as it is not
directly referenced).

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 tools/lib/bpf/libbpf.c | 166 +++++++++++++++++++++++++++++++++++------
 1 file changed, 142 insertions(+), 24 deletions(-)

Comments

Alexei Starovoitov Aug. 22, 2023, 4:34 p.m. UTC | #1
On Wed, Aug 9, 2023 at 4:44 AM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>
> Add support to libbpf to append exception callbacks when loading a
> program. The exception callback is found by discovering the declaration
> tag 'exception_callback:<value>' and finding the callback in the value
> of the tag.

...

> +       /* After adding all programs, now pair them with their exception
> +        * callbacks if specified.
> +        */
> +       if (!kernel_supports(obj, FEAT_BTF_DECL_TAG))
> +               goto out;
> +out:

The above looks odd. Accidental leftover?

>         return 0;
>  }
>
> @@ -3137,6 +3148,80 @@ static int bpf_object__sanitize_and_load_btf(struct bpf_object *obj)
>                 }
>         }
>
> +       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;

On the first read of this patch and corresponding kernel support
I started doubting my earlier suggestion to use decl_tag and
reconsidered going back to fake bpf_register_except_cb() call,
but after sleeping on it I think it is a useful extension for both
kernel and libbpf to support such tagging.
We might specify ctors and dtors with decl_tag in the future
and other various callbacks that are never explicitly referenced
in bpf_call, ld_imm64 or other bpf insns.
So having libbpf and kernel support such tagging will help in the long run.
It's not going to be limited to exceptions.
Despite the extra complexity this is a good step forward.

> +static int
> +bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main_prog,
> +                               struct bpf_program *subprog)
> +{

Please split this refactoring into a separate patch for ease of review.
Kumar Kartikeya Dwivedi Aug. 22, 2023, 4:58 p.m. UTC | #2
On Tue, 22 Aug 2023 at 22:05, Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Wed, Aug 9, 2023 at 4:44 AM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
> >
> > Add support to libbpf to append exception callbacks when loading a
> > program. The exception callback is found by discovering the declaration
> > tag 'exception_callback:<value>' and finding the callback in the value
> > of the tag.
>
> ...
>
> > +       /* After adding all programs, now pair them with their exception
> > +        * callbacks if specified.
> > +        */
> > +       if (!kernel_supports(obj, FEAT_BTF_DECL_TAG))
> > +               goto out;
> > +out:
>
> The above looks odd. Accidental leftover?
>

Oops, yes. I have dropped it now.

> >         return 0;
> >  }
> >
> > @@ -3137,6 +3148,80 @@ static int bpf_object__sanitize_and_load_btf(struct bpf_object *obj)
> >                 }
> >         }
> >
> > +       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;
>
> On the first read of this patch and corresponding kernel support
> I started doubting my earlier suggestion to use decl_tag and
> reconsidered going back to fake bpf_register_except_cb() call,

This is exactly what I thought when I realised it's not that simple
when implementing it :).

> but after sleeping on it I think it is a useful extension for both
> kernel and libbpf to support such tagging.
> We might specify ctors and dtors with decl_tag in the future
> and other various callbacks that are never explicitly referenced
> in bpf_call, ld_imm64 or other bpf insns.
> So having libbpf and kernel support such tagging will help in the long run.
> It's not going to be limited to exceptions.
> Despite the extra complexity this is a good step forward.
>

I agree. This same code can also be reused to establish an edge from
one BTF type to some other BTF type (by name).
function -> exception_cb. struct -> ctor, struct -> dtor etc.

I did have some questions though.
First of all this is explicitly an unstable feature. How do we feel
about putting related support for it in libbpf and making breaking
changes later?
Will the expectation be to pair the libbpf with its corresponding
kernel release to use such features? Or do we have to make the changes
in a backwards compatible fashion?

Secondly, due to proliferation of BTF tag usage, do you think it's
time we reserve a namespace for all tags that would be recognized by
the verifier? E.g. require all of them to be prefixed with "bpf."
similar to "llvm." etc.? Since they are simply attributes for a
specific BTF type (or component of a type).
It may be too late for some BTF tags, but we could do better going forward.

It may also allow us to indicate that a tag is experimental until its
effect on the program becomes stabilized. E.g.
bpf.experimental.exception_callback instead of exception_callback? Or
do you feel it's unnecessary?

> > +static int
> > +bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main_prog,
> > +                               struct bpf_program *subprog)
> > +{
>
> Please split this refactoring into a separate patch for ease of review.

Ack, will do.
Alexei Starovoitov Aug. 22, 2023, 7:20 p.m. UTC | #3
On Tue, Aug 22, 2023 at 9:58 AM Kumar Kartikeya Dwivedi
<memxor@gmail.com> wrote:
>
> On Tue, 22 Aug 2023 at 22:05, Alexei Starovoitov
> <alexei.starovoitov@gmail.com> wrote:
> >
> > On Wed, Aug 9, 2023 at 4:44 AM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
> > >
> > > Add support to libbpf to append exception callbacks when loading a
> > > program. The exception callback is found by discovering the declaration
> > > tag 'exception_callback:<value>' and finding the callback in the value
> > > of the tag.
> >
> > ...
> >
> > > +       /* After adding all programs, now pair them with their exception
> > > +        * callbacks if specified.
> > > +        */
> > > +       if (!kernel_supports(obj, FEAT_BTF_DECL_TAG))
> > > +               goto out;
> > > +out:
> >
> > The above looks odd. Accidental leftover?
> >
>
> Oops, yes. I have dropped it now.
>
> > >         return 0;
> > >  }
> > >
> > > @@ -3137,6 +3148,80 @@ static int bpf_object__sanitize_and_load_btf(struct bpf_object *obj)
> > >                 }
> > >         }
> > >
> > > +       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;
> >
> > On the first read of this patch and corresponding kernel support
> > I started doubting my earlier suggestion to use decl_tag and
> > reconsidered going back to fake bpf_register_except_cb() call,
>
> This is exactly what I thought when I realised it's not that simple
> when implementing it :).
>
> > but after sleeping on it I think it is a useful extension for both
> > kernel and libbpf to support such tagging.
> > We might specify ctors and dtors with decl_tag in the future
> > and other various callbacks that are never explicitly referenced
> > in bpf_call, ld_imm64 or other bpf insns.
> > So having libbpf and kernel support such tagging will help in the long run.
> > It's not going to be limited to exceptions.
> > Despite the extra complexity this is a good step forward.
> >
>
> I agree. This same code can also be reused to establish an edge from
> one BTF type to some other BTF type (by name).
> function -> exception_cb. struct -> ctor, struct -> dtor etc.
>
> I did have some questions though.
> First of all this is explicitly an unstable feature. How do we feel
> about putting related support for it in libbpf and making breaking
> changes later?
> Will the expectation be to pair the libbpf with its corresponding
> kernel release to use such features? Or do we have to make the changes
> in a backwards compatible fashion?

We should always do backwards compatible changes in both kernel and libbpf,
but sooner or later we might hit an issue where we would have to break things.
At that time the special prefix won't save us, so...

>
> Secondly, due to proliferation of BTF tag usage, do you think it's
> time we reserve a namespace for all tags that would be recognized by
> the verifier? E.g. require all of them to be prefixed with "bpf."
> similar to "llvm." etc.? Since they are simply attributes for a
> specific BTF type (or component of a type).
> It may be too late for some BTF tags, but we could do better going forward.
>
> It may also allow us to indicate that a tag is experimental until its
> effect on the program becomes stabilized. E.g.
> bpf.experimental.exception_callback instead of exception_callback? Or
> do you feel it's unnecessary?

... I don't think any of that is necessary.
Whether btf tag is prefixed with "exception_callback:" or
"bpf.experimental.debug.exception_callback:" we will be doing the same thing.
We'll keep backward compat if trade-offs allow.
Andrii Nakryiko Aug. 25, 2023, 6:43 p.m. UTC | #4
On Wed, Aug 9, 2023 at 4:44 AM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>
> Add support to libbpf to append exception callbacks when loading a
> program. The exception callback is found by discovering the declaration
> tag 'exception_callback:<value>' and finding the callback in the value
> of the tag.
>
> The process is done in two steps. First, for each main program, the
> bpf_object__sanitize_and_load_btf function finds and marks its
> corresponding exception callback as defined by the declaration tag on
> it. Second, bpf_object__reloc_code is modified to append the indicated
> exception callback at the end of the instruction iteration (since
> exception callback will never be appended in that loop, as it is not
> directly referenced).
>
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> ---

Just two point before you send next version:

a) it seems like this appending of exception callback logically fits
bpf_object__relocate() step, where other subprogs are appended. Any
reason we can't do it there?

b) all the callbacks are static functions, right? Which means in the
case of static linking, we can have multiple subprogs with the same
name. So this whole look up by name thing doesn't guarantee unique
match. At the very least libbpf should check that the match is unique
and error out otherwise.

Ideally though, would be great if something like this would be
supported instead (but I know it's way more complex, Alexei already
mentioned that in person and on the list):

try (my_callback) {
    ... code that throws ...
}

try (my_other_callback) {
    ... some other code that throws ...
}


This try() macro can be implemented in a form similar to bpf_for() by
using fancy for() loop. It would look and feel way more like
try/catch.

>  tools/lib/bpf/libbpf.c | 166 +++++++++++++++++++++++++++++++++++------
>  1 file changed, 142 insertions(+), 24 deletions(-)
>
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 17883f5a44b9..7c607bac8204 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -432,9 +432,11 @@ struct bpf_program {
>         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;

[...]
Kumar Kartikeya Dwivedi Aug. 26, 2023, 10:41 p.m. UTC | #5
On Sat, 26 Aug 2023 at 00:13, Andrii Nakryiko <andrii.nakryiko@gmail.com> wrote:
>
> On Wed, Aug 9, 2023 at 4:44 AM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
> >
> > Add support to libbpf to append exception callbacks when loading a
> > program. The exception callback is found by discovering the declaration
> > tag 'exception_callback:<value>' and finding the callback in the value
> > of the tag.
> >
> > The process is done in two steps. First, for each main program, the
> > bpf_object__sanitize_and_load_btf function finds and marks its
> > corresponding exception callback as defined by the declaration tag on
> > it. Second, bpf_object__reloc_code is modified to append the indicated
> > exception callback at the end of the instruction iteration (since
> > exception callback will never be appended in that loop, as it is not
> > directly referenced).
> >
> > Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> > ---
>
> Just two point before you send next version:
>
> a) it seems like this appending of exception callback logically fits
> bpf_object__relocate() step, where other subprogs are appended. Any
> reason we can't do it there?
>

We should be able to do it there as well. But I felt it is better to
do it in bpf_object__reloc_code as the logic is similar to the
handling of bpf_pseudo_func/bpf_pseudo_call insns. And then we need to
recurse using bpf_object__reloc_code for exception cb again.

> b) all the callbacks are static functions, right? Which means in the
> case of static linking, we can have multiple subprogs with the same
> name. So this whole look up by name thing doesn't guarantee unique
> match. At the very least libbpf should check that the match is unique
> and error out otherwise.

Ack, will fix this in v3.

>
> Ideally though, would be great if something like this would be
> supported instead (but I know it's way more complex, Alexei already
> mentioned that in person and on the list):
>
> try (my_callback) {
>     ... code that throws ...
> }
>
> try (my_other_callback) {
>     ... some other code that throws ...
> }
>
>
> This try() macro can be implemented in a form similar to bpf_for() by
> using fancy for() loop. It would look and feel way more like
> try/catch.
>

These try blocks are easier than having a try/catch block which the
execution jumps to when the exception is thrown. I think the latter
will involve some form of compiler support, because otherwise there is
no control flow that is seen by the compiler into the catch block,
which will mess up things, and I plan to atleast explore that approach
(already looking at LLVM) once I am done with the second part of this
feature.

Having just these try (callback) {} blocks is easier as we can record
which subprog corresponds to [begin_ip, end_ip] (per frame) and stop
unwinding when we find a suitable handler for the ip of a parent
frame. The callback will be invoked and will return to the parent
frame (or kernel if it's the main frame). So if this seems like a more
useful thing, I can make this work and send it out as a follow up to
this set.

> [...]
Alexei Starovoitov Aug. 27, 2023, 10:27 p.m. UTC | #6
On Sat, Aug 26, 2023 at 3:42 PM Kumar Kartikeya Dwivedi
<memxor@gmail.com> wrote:
>
> >
> > Ideally though, would be great if something like this would be
> > supported instead (but I know it's way more complex, Alexei already
> > mentioned that in person and on the list):
> >
> > try (my_callback) {
> >     ... code that throws ...
> > }
> >
> > try (my_other_callback) {
> >     ... some other code that throws ...
> > }
> >
> >
> > This try() macro can be implemented in a form similar to bpf_for() by
> > using fancy for() loop. It would look and feel way more like
> > try/catch.
> >
>
> These try blocks are easier than having a try/catch block which the
> execution jumps to when the exception is thrown. I think the latter
> will involve some form of compiler support, because otherwise there is
> no control flow that is seen by the compiler into the catch block,
> which will mess up things, and I plan to atleast explore that approach
> (already looking at LLVM) once I am done with the second part of this
> feature.
>
> Having just these try (callback) {} blocks is easier as we can record
> which subprog corresponds to [begin_ip, end_ip] (per frame) and stop
> unwinding when we find a suitable handler for the ip of a parent
> frame. The callback will be invoked and will return to the parent
> frame (or kernel if it's the main frame). So if this seems like a more
> useful thing, I can make this work and send it out as a follow up to
> this set.

I suspect even "just try {}" will not work without compiler support.
{} is a semantic structure from compiler pov. It doesn't have
any delineation in LLVM IR. They don't exist to optimization passes.
diff mbox series

Patch

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 17883f5a44b9..7c607bac8204 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -432,9 +432,11 @@  struct bpf_program {
 	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;
@@ -760,6 +762,7 @@  bpf_object__init_prog(struct bpf_object *obj, struct bpf_program *prog,
 
 	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
@@ -866,20 +869,28 @@  bpf_object__add_programs(struct bpf_object *obj, Elf_Data *sec_data,
 		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++;
 		obj->nr_programs = nr_progs;
 	}
 
+	/* After adding all programs, now pair them with their exception
+	 * callbacks if specified.
+	 */
+	if (!kernel_supports(obj, FEAT_BTF_DECL_TAG))
+		goto out;
+out:
 	return 0;
 }
 
@@ -3137,6 +3148,80 @@  static int bpf_object__sanitize_and_load_btf(struct bpf_object *obj)
 		}
 	}
 
+	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;
+				}
+				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;
@@ -6184,14 +6269,46 @@  static int append_subprog_relos(struct bpf_program *main_prog, struct bpf_progra
 	return 0;
 }
 
+static int
+bpf_object__append_subprog_code(struct bpf_object *obj, struct bpf_program *main_prog,
+				struct bpf_program *subprog)
+{
+	struct bpf_insn *insns;
+	size_t new_cnt;
+	int err;
+
+	subprog->sub_insn_off = main_prog->insns_cnt;
+
+	new_cnt = main_prog->insns_cnt + subprog->insns_cnt;
+	insns = libbpf_reallocarray(main_prog->insns, new_cnt, sizeof(*insns));
+	if (!insns) {
+		pr_warn("prog '%s': failed to realloc prog code\n", main_prog->name);
+		return -ENOMEM;
+	}
+	main_prog->insns = insns;
+	main_prog->insns_cnt = new_cnt;
+
+	memcpy(main_prog->insns + subprog->sub_insn_off, subprog->insns,
+	       subprog->insns_cnt * sizeof(*insns));
+
+	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;
+	return 0;
+}
+
 static int
 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);
@@ -6266,25 +6383,7 @@  bpf_object__reloc_code(struct bpf_object *obj, struct bpf_program *main_prog,
 		 *   and relocate.
 		 */
 		if (subprog->sub_insn_off == 0) {
-			subprog->sub_insn_off = main_prog->insns_cnt;
-
-			new_cnt = main_prog->insns_cnt + subprog->insns_cnt;
-			insns = libbpf_reallocarray(main_prog->insns, new_cnt, sizeof(*insns));
-			if (!insns) {
-				pr_warn("prog '%s': failed to realloc prog code\n", main_prog->name);
-				return -ENOMEM;
-			}
-			main_prog->insns = insns;
-			main_prog->insns_cnt = new_cnt;
-
-			memcpy(main_prog->insns + subprog->sub_insn_off, subprog->insns,
-			       subprog->insns_cnt * sizeof(*insns));
-
-			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);
+			err = bpf_object__append_subprog_code(obj, main_prog, subprog);
 			if (err)
 				return err;
 			err = bpf_object__reloc_code(obj, main_prog, subprog);
@@ -6308,6 +6407,25 @@  bpf_object__reloc_code(struct bpf_object *obj, struct bpf_program *main_prog,
 			 prog->name, insn_idx, insn->imm, subprog->name, subprog->sub_insn_off);
 	}
 
+	/* Now, also append exception callback if it has not been done already. */
+	if (main_prog == prog && main_prog->exception_cb_idx >= 0) {
+		subprog = &obj->programs[main_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, main_prog, subprog);
+			if (err)
+				return err;
+			err = bpf_object__reloc_code(obj, main_prog, subprog);
+			if (err)
+				return err;
+		}
+	}
+
 	return 0;
 }