diff mbox series

[bpf-next,v2,5/8] libbpf: Support opening bpf objects of either endianness

Message ID 3b65982b50a9ca77a13d7a5a07b8b5d37abc477f.1724313164.git.tony.ambardar@gmail.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series libbpf, selftests/bpf: Support cross-endian usage | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
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: 7 this patch: 7
netdev/build_tools success Errors and warnings before: 2 this patch: 2
netdev/cc_maintainers success CCed 13 of 13 maintainers
netdev/build_clang success Errors and warnings before: 7 this patch: 7
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: 7 this patch: 7
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 68 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-15 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-16 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-17 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-18 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17-O2
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18-O2
bpf/vmtest-bpf-next-VM_Test-41 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-40 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-14 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-36 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-37 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-38 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-39 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18

Commit Message

Tony Ambardar Aug. 22, 2024, 9:24 a.m. UTC
From: Tony Ambardar <tony.ambardar@gmail.com>

Allow bpf_object__open() to access files of either endianness, and convert
included BPF programs to native byte-order in-memory for introspection.

Signed-off-by: Tony Ambardar <tony.ambardar@gmail.com>
---
 tools/lib/bpf/libbpf.c          | 21 +++++++++++++++++++--
 tools/lib/bpf/libbpf_internal.h | 11 +++++++++++
 2 files changed, 30 insertions(+), 2 deletions(-)

Comments

Andrii Nakryiko Aug. 23, 2024, 7:47 p.m. UTC | #1
On Thu, Aug 22, 2024 at 2:25 AM Tony Ambardar <tony.ambardar@gmail.com> wrote:
>
> From: Tony Ambardar <tony.ambardar@gmail.com>
>
> Allow bpf_object__open() to access files of either endianness, and convert
> included BPF programs to native byte-order in-memory for introspection.
>
> Signed-off-by: Tony Ambardar <tony.ambardar@gmail.com>
> ---
>  tools/lib/bpf/libbpf.c          | 21 +++++++++++++++++++--
>  tools/lib/bpf/libbpf_internal.h | 11 +++++++++++
>  2 files changed, 30 insertions(+), 2 deletions(-)
>

Instructions are not the only data that would need swapping. We have
user's data sections and stuff like that, which, generally speaking,
isn't that safe to just byteswap.

I do understand the appeal of being endianness-agnostic, but doesn't
extend all the way to actually loading BPF programs. At least I
wouldn't start there.

We need to make open phase endianness agnostic, load should just fail
for swapped endianness case. So let's record the fact that we are not
in native endianness, and fail early in load step.

This will still allow us to generate skeletons and stuff like that, right?

> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 8a0a0c1e37e1..a542031f4f73 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -940,6 +940,21 @@ bpf_object__add_programs(struct bpf_object *obj, Elf_Data *sec_data,
>         return 0;
>  }
>
> +static void bpf_object_bswap_progs(struct bpf_object *obj)
> +{
> +       struct bpf_program *prog = obj->programs;
> +       struct bpf_insn *insn;
> +       int p, i;
> +
> +       for (p = 0; p < obj->nr_programs; p++, prog++) {
> +               insn = prog->insns;
> +               for (i = 0; i < prog->insns_cnt; i++, insn++)
> +                       bpf_insn_bswap(insn);
> +               pr_debug("prog '%s': converted %zu insns to native byteorder\n",

"byte order"?

> +                        prog->name, prog->insns_cnt);
> +       }
> +}
> +
>  static const struct btf_member *
>  find_member_by_offset(const struct btf_type *t, __u32 bit_offset)
>  {
> @@ -1610,7 +1625,6 @@ static int bpf_object__check_endianness(struct bpf_object *obj)
>  #else
>  # error "Unrecognized __BYTE_ORDER__"
>  #endif
> -       pr_warn("elf: endianness mismatch in %s.\n", obj->path);
>         return -LIBBPF_ERRNO__ENDIAN;
>  }
>
> @@ -3953,6 +3967,10 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
>                 return -LIBBPF_ERRNO__FORMAT;
>         }
>
> +       /* change BPF program insns to native endianness for introspection */
> +       if (bpf_object__check_endianness(obj))

let's rename this to "is_native_endianness()" and return true/false.
"check" makes sense as something that errors out, but now it's purely
a query, so "check" naming is confusing.


BTW, so libelf will transparently byte-swap relocations and stuff like
that to native endianness, is that right?

> +               bpf_object_bswap_progs(obj);
> +
>         /* sort BPF programs by section name and in-section instruction offset
>          * for faster search
>          */
> @@ -7993,7 +8011,6 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
>         }
>
>         err = bpf_object__elf_init(obj);
> -       err = err ? : bpf_object__check_endianness(obj);
>         err = err ? : bpf_object__elf_collect(obj);
>         err = err ? : bpf_object__collect_externs(obj);
>         err = err ? : bpf_object_fixup_btf(obj);
> diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
> index 6b0270c83537..f53daa601c6f 100644
> --- a/tools/lib/bpf/libbpf_internal.h
> +++ b/tools/lib/bpf/libbpf_internal.h
> @@ -11,6 +11,7 @@
>
>  #include <stdlib.h>
>  #include <limits.h>
> +#include <byteswap.h>
>  #include <errno.h>
>  #include <linux/err.h>
>  #include <fcntl.h>
> @@ -590,6 +591,16 @@ static inline bool is_ldimm64_insn(struct bpf_insn *insn)
>         return insn->code == (BPF_LD | BPF_IMM | BPF_DW);
>  }
>
> +static inline void bpf_insn_bswap(struct bpf_insn *insn)
> +{
> +       /* dst_reg & src_reg nibbles */
> +       __u8 *regs = (__u8 *)insn + offsetofend(struct bpf_insn, code);
> +
> +       *regs = (*regs >> 4) | (*regs << 4);

hm... we have fields, just do a brain-dead swap instead of all this
mucking with offsetofend(

__u8 tmp_reg = insn->dst_reg;

insn->dst_reg = insn->src_reg;
insn->src_reg = tmp_reg;

?


> +       insn->off = bswap_16(insn->off);
> +       insn->imm = bswap_32(insn->imm);
> +}
> +
>  /* Unconditionally dup FD, ensuring it doesn't use [0, 2] range.
>   * Original FD is not closed or altered in any other way.
>   * Preserves original FD value, if it's invalid (negative).
> --
> 2.34.1
>
Tony Ambardar Aug. 26, 2024, 10:53 a.m. UTC | #2
On Fri, Aug 23, 2024 at 12:47:47PM -0700, Andrii Nakryiko wrote:
> On Thu, Aug 22, 2024 at 2:25 AM Tony Ambardar <tony.ambardar@gmail.com> wrote:
> >
> > From: Tony Ambardar <tony.ambardar@gmail.com>
> >
> > Allow bpf_object__open() to access files of either endianness, and convert
> > included BPF programs to native byte-order in-memory for introspection.
> >
> > Signed-off-by: Tony Ambardar <tony.ambardar@gmail.com>
> > ---
> >  tools/lib/bpf/libbpf.c          | 21 +++++++++++++++++++--
> >  tools/lib/bpf/libbpf_internal.h | 11 +++++++++++
> >  2 files changed, 30 insertions(+), 2 deletions(-)
> >
> 
> Instructions are not the only data that would need swapping. We have
> user's data sections and stuff like that, which, generally speaking,
> isn't that safe to just byteswap.
> 
> I do understand the appeal of being endianness-agnostic, but doesn't
> extend all the way to actually loading BPF programs. At least I
> wouldn't start there.

Yes, absolutely. I first planned to move the endianness check from "open"
to "load" functions but got waylaid tracing skeleton code into the latter
and left it to continue progress. Let me figure out the best place to put
a check without breaking things.

> 
> We need to make open phase endianness agnostic, load should just fail
> for swapped endianness case. So let's record the fact that we are not
> in native endianness, and fail early in load step.
> 
> This will still allow us to generate skeletons and stuff like that, right?
> 
> > diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> > index 8a0a0c1e37e1..a542031f4f73 100644
> > --- a/tools/lib/bpf/libbpf.c
> > +++ b/tools/lib/bpf/libbpf.c
> > @@ -940,6 +940,21 @@ bpf_object__add_programs(struct bpf_object *obj, Elf_Data *sec_data,
> >         return 0;
> >  }
> >
> > +static void bpf_object_bswap_progs(struct bpf_object *obj)
> > +{
> > +       struct bpf_program *prog = obj->programs;
> > +       struct bpf_insn *insn;
> > +       int p, i;
> > +
> > +       for (p = 0; p < obj->nr_programs; p++, prog++) {
> > +               insn = prog->insns;
> > +               for (i = 0; i < prog->insns_cnt; i++, insn++)
> > +                       bpf_insn_bswap(insn);
> > +               pr_debug("prog '%s': converted %zu insns to native byteorder\n",
> 
> "byte order"?
> 

Good catch. Fixed.

> > +                        prog->name, prog->insns_cnt);
> > +       }
> > +}
> > +
> >  static const struct btf_member *
> >  find_member_by_offset(const struct btf_type *t, __u32 bit_offset)
> >  {
> > @@ -1610,7 +1625,6 @@ static int bpf_object__check_endianness(struct bpf_object *obj)
> >  #else
> >  # error "Unrecognized __BYTE_ORDER__"
> >  #endif
> > -       pr_warn("elf: endianness mismatch in %s.\n", obj->path);
> >         return -LIBBPF_ERRNO__ENDIAN;
> >  }
> >
> > @@ -3953,6 +3967,10 @@ static int bpf_object__elf_collect(struct bpf_object *obj)
> >                 return -LIBBPF_ERRNO__FORMAT;
> >         }
> >
> > +       /* change BPF program insns to native endianness for introspection */
> > +       if (bpf_object__check_endianness(obj))
> 
> let's rename this to "is_native_endianness()" and return true/false.
> "check" makes sense as something that errors out, but now it's purely
> a query, so "check" naming is confusing.
> 

Right, I mistook this as exported before and left it.

> 
> BTW, so libelf will transparently byte-swap relocations and stuff like
> that to native endianness, is that right?

Correct. Sections with types like ELF_T_REL (.rel) and ELF_T_SYM (.symtab)
get translated automagically. See patch #3 for example.

> 
> > +               bpf_object_bswap_progs(obj);
> > +
> >         /* sort BPF programs by section name and in-section instruction offset
> >          * for faster search
> >          */
> > @@ -7993,7 +8011,6 @@ static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
> >         }
> >
> >         err = bpf_object__elf_init(obj);
> > -       err = err ? : bpf_object__check_endianness(obj);
> >         err = err ? : bpf_object__elf_collect(obj);
> >         err = err ? : bpf_object__collect_externs(obj);
> >         err = err ? : bpf_object_fixup_btf(obj);
> > diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
> > index 6b0270c83537..f53daa601c6f 100644
> > --- a/tools/lib/bpf/libbpf_internal.h
> > +++ b/tools/lib/bpf/libbpf_internal.h
> > @@ -11,6 +11,7 @@
> >
> >  #include <stdlib.h>
> >  #include <limits.h>
> > +#include <byteswap.h>
> >  #include <errno.h>
> >  #include <linux/err.h>
> >  #include <fcntl.h>
> > @@ -590,6 +591,16 @@ static inline bool is_ldimm64_insn(struct bpf_insn *insn)
> >         return insn->code == (BPF_LD | BPF_IMM | BPF_DW);
> >  }
> >
> > +static inline void bpf_insn_bswap(struct bpf_insn *insn)
> > +{
> > +       /* dst_reg & src_reg nibbles */
> > +       __u8 *regs = (__u8 *)insn + offsetofend(struct bpf_insn, code);
> > +
> > +       *regs = (*regs >> 4) | (*regs << 4);
> 
> hm... we have fields, just do a brain-dead swap instead of all this
> mucking with offsetofend(
> 
> __u8 tmp_reg = insn->dst_reg;
> 
> insn->dst_reg = insn->src_reg;
> insn->src_reg = tmp_reg;
> 
> ?

Main reason for this is most compilers recognize the shift/or statement
pattern and emit a rotate op as I recall. And the offsetofend() seemed
clearest at documenting "the byte after opcode" while not obscuring these
are nibble fields. So would prefer to leave it unless you have strong
objections or I'm off the mark somehow. Let me know either way? Thanks!

> 
> 
> > +       insn->off = bswap_16(insn->off);
> > +       insn->imm = bswap_32(insn->imm);
> > +}
> > +
> >  /* Unconditionally dup FD, ensuring it doesn't use [0, 2] range.
> >   * Original FD is not closed or altered in any other way.
> >   * Preserves original FD value, if it's invalid (negative).
> > --
> > 2.34.1
> >
Andrii Nakryiko Aug. 26, 2024, 9:28 p.m. UTC | #3
On Mon, Aug 26, 2024 at 3:53 AM Tony Ambardar <tony.ambardar@gmail.com> wrote:
>
> On Fri, Aug 23, 2024 at 12:47:47PM -0700, Andrii Nakryiko wrote:
> > On Thu, Aug 22, 2024 at 2:25 AM Tony Ambardar <tony.ambardar@gmail.com> wrote:
> > >
> > > From: Tony Ambardar <tony.ambardar@gmail.com>
> > >
> > > Allow bpf_object__open() to access files of either endianness, and convert
> > > included BPF programs to native byte-order in-memory for introspection.
> > >
> > > Signed-off-by: Tony Ambardar <tony.ambardar@gmail.com>
> > > ---
> > >  tools/lib/bpf/libbpf.c          | 21 +++++++++++++++++++--
> > >  tools/lib/bpf/libbpf_internal.h | 11 +++++++++++
> > >  2 files changed, 30 insertions(+), 2 deletions(-)
> > >
> >
> > Instructions are not the only data that would need swapping. We have
> > user's data sections and stuff like that, which, generally speaking,
> > isn't that safe to just byteswap.
> >
> > I do understand the appeal of being endianness-agnostic, but doesn't
> > extend all the way to actually loading BPF programs. At least I
> > wouldn't start there.
>
> Yes, absolutely. I first planned to move the endianness check from "open"
> to "load" functions but got waylaid tracing skeleton code into the latter
> and left it to continue progress. Let me figure out the best place to put
> a check without breaking things.
>

checking early during load should work just fine, I don't expect any problems

> >
> > We need to make open phase endianness agnostic, load should just fail
> > for swapped endianness case. So let's record the fact that we are not
> > in native endianness, and fail early in load step.
> >
> > This will still allow us to generate skeletons and stuff like that, right?
> >

[...]

> > >
> > > +       /* change BPF program insns to native endianness for introspection */
> > > +       if (bpf_object__check_endianness(obj))
> >
> > let's rename this to "is_native_endianness()" and return true/false.
> > "check" makes sense as something that errors out, but now it's purely
> > a query, so "check" naming is confusing.
> >
>
> Right, I mistook this as exported before and left it.

yeah, that double underscore is very misleading and I'd like to get
rid of it, but my last attempt failed, so we are stuck with that for
now

>
> >
> > BTW, so libelf will transparently byte-swap relocations and stuff like
> > that to native endianness, is that right?
>
> Correct. Sections with types like ELF_T_REL (.rel) and ELF_T_SYM (.symtab)
> get translated automagically. See patch #3 for example.
>

ok, thanks for confirming

[...]

> > >
> > > +static inline void bpf_insn_bswap(struct bpf_insn *insn)
> > > +{
> > > +       /* dst_reg & src_reg nibbles */
> > > +       __u8 *regs = (__u8 *)insn + offsetofend(struct bpf_insn, code);
> > > +
> > > +       *regs = (*regs >> 4) | (*regs << 4);
> >
> > hm... we have fields, just do a brain-dead swap instead of all this
> > mucking with offsetofend(
> >
> > __u8 tmp_reg = insn->dst_reg;
> >
> > insn->dst_reg = insn->src_reg;
> > insn->src_reg = tmp_reg;
> >
> > ?
>
> Main reason for this is most compilers recognize the shift/or statement
> pattern and emit a rotate op as I recall. And the offsetofend() seemed
> clearest at documenting "the byte after opcode" while not obscuring these
> are nibble fields. So would prefer to leave it unless you have strong
> objections or I'm off the mark somehow. Let me know either way? Thanks!
>

I do strongly prefer not having to use offsetofend() and pointer
manipulations. Whatever tiny performance difference is completely
irrelevant here. Let's go with a cleaner approach, please.


> >
> >
> > > +       insn->off = bswap_16(insn->off);
> > > +       insn->imm = bswap_32(insn->imm);
> > > +}
> > > +
> > >  /* Unconditionally dup FD, ensuring it doesn't use [0, 2] range.
> > >   * Original FD is not closed or altered in any other way.
> > >   * Preserves original FD value, if it's invalid (negative).
> > > --
> > > 2.34.1
> > >
Tony Ambardar Aug. 27, 2024, 8:40 a.m. UTC | #4
On Mon, Aug 26, 2024 at 02:28:17PM -0700, Andrii Nakryiko wrote:
> On Mon, Aug 26, 2024 at 3:53 AM Tony Ambardar <tony.ambardar@gmail.com> wrote:
> >
> > On Fri, Aug 23, 2024 at 12:47:47PM -0700, Andrii Nakryiko wrote:
> > > On Thu, Aug 22, 2024 at 2:25 AM Tony Ambardar <tony.ambardar@gmail.com> wrote:
> > > >
> > > > From: Tony Ambardar <tony.ambardar@gmail.com>
> > > >
> > > > Allow bpf_object__open() to access files of either endianness, and convert
> > > > included BPF programs to native byte-order in-memory for introspection.
> > > >
> > > > Signed-off-by: Tony Ambardar <tony.ambardar@gmail.com>
> > > > ---
> > > >  tools/lib/bpf/libbpf.c          | 21 +++++++++++++++++++--
> > > >  tools/lib/bpf/libbpf_internal.h | 11 +++++++++++
> > > >  2 files changed, 30 insertions(+), 2 deletions(-)
> > > >
> > >
> > > Instructions are not the only data that would need swapping. We have
> > > user's data sections and stuff like that, which, generally speaking,
> > > isn't that safe to just byteswap.
> > >
> > > I do understand the appeal of being endianness-agnostic, but doesn't
> > > extend all the way to actually loading BPF programs. At least I
> > > wouldn't start there.
> >
> > Yes, absolutely. I first planned to move the endianness check from "open"
> > to "load" functions but got waylaid tracing skeleton code into the latter
> > and left it to continue progress. Let me figure out the best place to put
> > a check without breaking things.
> >
> 
> checking early during load should work just fine, I don't expect any problems

Right, I believe I have this working now without impacting skeleton.

> 
> > >
> > > We need to make open phase endianness agnostic, load should just fail
> > > for swapped endianness case. So let's record the fact that we are not
> > > in native endianness, and fail early in load step.
> > >
> > > This will still allow us to generate skeletons and stuff like that, right?
> > >
> 
> [...]
> 
> > > >
> > > > +       /* change BPF program insns to native endianness for introspection */
> > > > +       if (bpf_object__check_endianness(obj))
> > >
> > > let's rename this to "is_native_endianness()" and return true/false.
> > > "check" makes sense as something that errors out, but now it's purely
> > > a query, so "check" naming is confusing.
> > >
> >
> > Right, I mistook this as exported before and left it.
> 
> yeah, that double underscore is very misleading and I'd like to get
> rid of it, but my last attempt failed, so we are stuck with that for
> now
> 
> >
> > >
> > > BTW, so libelf will transparently byte-swap relocations and stuff like
> > > that to native endianness, is that right?
> >
> > Correct. Sections with types like ELF_T_REL (.rel) and ELF_T_SYM (.symtab)
> > get translated automagically. See patch #3 for example.
> >
> 
> ok, thanks for confirming
> 
> [...]
> 
> > > >
> > > > +static inline void bpf_insn_bswap(struct bpf_insn *insn)
> > > > +{
> > > > +       /* dst_reg & src_reg nibbles */
> > > > +       __u8 *regs = (__u8 *)insn + offsetofend(struct bpf_insn, code);
> > > > +
> > > > +       *regs = (*regs >> 4) | (*regs << 4);
> > >
> > > hm... we have fields, just do a brain-dead swap instead of all this
> > > mucking with offsetofend(
> > >
> > > __u8 tmp_reg = insn->dst_reg;
> > >
> > > insn->dst_reg = insn->src_reg;
> > > insn->src_reg = tmp_reg;
> > >
> > > ?
> >
> > Main reason for this is most compilers recognize the shift/or statement
> > pattern and emit a rotate op as I recall. And the offsetofend() seemed
> > clearest at documenting "the byte after opcode" while not obscuring these
> > are nibble fields. So would prefer to leave it unless you have strong
> > objections or I'm off the mark somehow. Let me know either way? Thanks!
> >
> 
> I do strongly prefer not having to use offsetofend() and pointer
> manipulations. Whatever tiny performance difference is completely
> irrelevant here. Let's go with a cleaner approach, please.

OK, will do for next revision.

> 
> 
> > >
> > >
> > > > +       insn->off = bswap_16(insn->off);
> > > > +       insn->imm = bswap_32(insn->imm);
> > > > +}
> > > > +
> > > >  /* Unconditionally dup FD, ensuring it doesn't use [0, 2] range.
> > > >   * Original FD is not closed or altered in any other way.
> > > >   * Preserves original FD value, if it's invalid (negative).
> > > > --
> > > > 2.34.1
> > > >
diff mbox series

Patch

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 8a0a0c1e37e1..a542031f4f73 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -940,6 +940,21 @@  bpf_object__add_programs(struct bpf_object *obj, Elf_Data *sec_data,
 	return 0;
 }
 
+static void bpf_object_bswap_progs(struct bpf_object *obj)
+{
+	struct bpf_program *prog = obj->programs;
+	struct bpf_insn *insn;
+	int p, i;
+
+	for (p = 0; p < obj->nr_programs; p++, prog++) {
+		insn = prog->insns;
+		for (i = 0; i < prog->insns_cnt; i++, insn++)
+			bpf_insn_bswap(insn);
+		pr_debug("prog '%s': converted %zu insns to native byteorder\n",
+			 prog->name, prog->insns_cnt);
+	}
+}
+
 static const struct btf_member *
 find_member_by_offset(const struct btf_type *t, __u32 bit_offset)
 {
@@ -1610,7 +1625,6 @@  static int bpf_object__check_endianness(struct bpf_object *obj)
 #else
 # error "Unrecognized __BYTE_ORDER__"
 #endif
-	pr_warn("elf: endianness mismatch in %s.\n", obj->path);
 	return -LIBBPF_ERRNO__ENDIAN;
 }
 
@@ -3953,6 +3967,10 @@  static int bpf_object__elf_collect(struct bpf_object *obj)
 		return -LIBBPF_ERRNO__FORMAT;
 	}
 
+	/* change BPF program insns to native endianness for introspection */
+	if (bpf_object__check_endianness(obj))
+		bpf_object_bswap_progs(obj);
+
 	/* sort BPF programs by section name and in-section instruction offset
 	 * for faster search
 	 */
@@ -7993,7 +8011,6 @@  static struct bpf_object *bpf_object_open(const char *path, const void *obj_buf,
 	}
 
 	err = bpf_object__elf_init(obj);
-	err = err ? : bpf_object__check_endianness(obj);
 	err = err ? : bpf_object__elf_collect(obj);
 	err = err ? : bpf_object__collect_externs(obj);
 	err = err ? : bpf_object_fixup_btf(obj);
diff --git a/tools/lib/bpf/libbpf_internal.h b/tools/lib/bpf/libbpf_internal.h
index 6b0270c83537..f53daa601c6f 100644
--- a/tools/lib/bpf/libbpf_internal.h
+++ b/tools/lib/bpf/libbpf_internal.h
@@ -11,6 +11,7 @@ 
 
 #include <stdlib.h>
 #include <limits.h>
+#include <byteswap.h>
 #include <errno.h>
 #include <linux/err.h>
 #include <fcntl.h>
@@ -590,6 +591,16 @@  static inline bool is_ldimm64_insn(struct bpf_insn *insn)
 	return insn->code == (BPF_LD | BPF_IMM | BPF_DW);
 }
 
+static inline void bpf_insn_bswap(struct bpf_insn *insn)
+{
+	/* dst_reg & src_reg nibbles */
+	__u8 *regs = (__u8 *)insn + offsetofend(struct bpf_insn, code);
+
+	*regs = (*regs >> 4) | (*regs << 4);
+	insn->off = bswap_16(insn->off);
+	insn->imm = bswap_32(insn->imm);
+}
+
 /* Unconditionally dup FD, ensuring it doesn't use [0, 2] range.
  * Original FD is not closed or altered in any other way.
  * Preserves original FD value, if it's invalid (negative).