Re: [PATCH bpf-next] libbpf: Extend linker API to support in-memory ELF files

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Nov 20, 2024 at 9:02 AM Alastair Robertson <ajor@xxxxxxxx> wrote:
>
> The new_fd, add_fd and finalize_fd functions correspond to the original
> new, add_file and finalize functions, but accept an FD instead of a file
> name. This gives API consumers the option of using anonymous
> files/memfds to avoid writing ELFs to disk.
>
> This new API will be useful for performing linking as part of
> bpftrace's JIT compilation.
>
> The add_buf function is a convenience wrapper that does the work of
> creating a memfd for the caller.
>
> Signed-off-by: Alastair Robertson <ajor@xxxxxxxx>
> ---
>  tools/lib/bpf/libbpf.h   |   9 ++
>  tools/lib/bpf/libbpf.map |   4 +
>  tools/lib/bpf/linker.c   | 229 +++++++++++++++++++++++++++++----------
>  3 files changed, 185 insertions(+), 57 deletions(-)
>

Hey Alastair,

I think adding these new APIs makes sense, but I'll nitpick a bit below.

But overall it would be good to extract preparatory stuff like change
to obj->filename everywhere and stuff like this that's pretty
widespread, but very distracting. I'll point it out as I go through
the patch below.


> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index b2ce3a72b11d..aae8f954c4fc 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -1796,10 +1796,19 @@ struct bpf_linker_file_opts {
>  struct bpf_linker;
>
>  LIBBPF_API struct bpf_linker *bpf_linker__new(const char *filename, struct bpf_linker_opts *opts);
> +LIBBPF_API struct bpf_linker *bpf_linker__new_fd(const char *name, int fd,
> +                                                struct bpf_linker_opts *opts);

API nitpick, given `int fd` is the main thing, let's put it as a first argument.

As for the "name" parameter, it's only going to be used for logging
and error reporting, right? I'm more inclined to add it into
bpf_linker_opts as some sort of name override field. It will keep the
API clean, we can always have some default "fd=%d" name, unless the
user provides a more meaningful (for them) name. And for
filename-based APIs we can still use that name as an override (if it's
provided).

>  LIBBPF_API int bpf_linker__add_file(struct bpf_linker *linker,
>                                     const char *filename,
>                                     const struct bpf_linker_file_opts *opts);
> +LIBBPF_API int bpf_linker__add_fd(struct bpf_linker *linker,
> +                                 const char *name, int fd,

same nit about fd being first param, and question if we need "name" at all

> +                                 const struct bpf_linker_file_opts *opts);
> +LIBBPF_API int bpf_linker__add_buf(struct bpf_linker *linker, const char *name,
> +                                  void *buffer, int buffer_sz,
> +                                  const struct bpf_linker_file_opts *opts);

ditto, buffer + buffer_sz should come right after linker (and let's
shorten to "buf" and "buf_sz"?)

>  LIBBPF_API int bpf_linker__finalize(struct bpf_linker *linker);
> +LIBBPF_API int bpf_linker__finalize_fd(struct bpf_linker *linker);

this I don't think we need, let's see below

>  LIBBPF_API void bpf_linker__free(struct bpf_linker *linker);
>
>  /*
> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> index 54b6f312cfa8..e767f34c1d08 100644
> --- a/tools/lib/bpf/libbpf.map
> +++ b/tools/lib/bpf/libbpf.map
> @@ -432,4 +432,8 @@ LIBBPF_1.5.0 {
>  } LIBBPF_1.4.0;
>
>  LIBBPF_1.6.0 {
> +               bpf_linker__new_fd;
> +               bpf_linker__add_fd;
> +               bpf_linker__add_buf;
> +               bpf_linker__finalize_fd;

this should be sorted

>  } LIBBPF_1.5.0;
> diff --git a/tools/lib/bpf/linker.c b/tools/lib/bpf/linker.c
> index cf71d149fe26..6571ed8b858f 100644
> --- a/tools/lib/bpf/linker.c
> +++ b/tools/lib/bpf/linker.c
> @@ -4,6 +4,8 @@
>   *
>   * Copyright (c) 2021 Facebook
>   */
> +#define _GNU_SOURCE

please add #ifndef guard around, we have/had issues with _GNU_SOURCE
being re-defined
> +
>  #include <stdbool.h>
>  #include <stddef.h>
>  #include <stdio.h>
> @@ -16,6 +18,7 @@
>  #include <elf.h>
>  #include <libelf.h>
>  #include <fcntl.h>
> +#include <sys/mman.h>
>  #include "libbpf.h"
>  #include "btf.h"
>  #include "libbpf_internal.h"
> @@ -157,9 +160,9 @@ struct bpf_linker {
>  #define pr_warn_elf(fmt, ...)                                                                  \
>         libbpf_print(LIBBPF_WARN, "libbpf: " fmt ": %s\n", ##__VA_ARGS__, elf_errmsg(-1))
>
> -static int init_output_elf(struct bpf_linker *linker, const char *file);
> +static int init_output_elf(struct bpf_linker *linker);
>
> -static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
> +static int linker_load_obj_file(struct bpf_linker *linker,
>                                 const struct bpf_linker_file_opts *opts,
>                                 struct src_obj *obj);
>  static int linker_sanity_check_elf(struct src_obj *obj);
> @@ -233,9 +236,56 @@ struct bpf_linker *bpf_linker__new(const char *filename, struct bpf_linker_opts
>         if (!linker)
>                 return errno = ENOMEM, NULL;
>
> -       linker->fd = -1;
> +       linker->filename = strdup(filename);
> +       if (!linker->filename)
> +               return errno = ENOMEM, NULL;
> +
> +       linker->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
> +       if (linker->fd < 0) {
> +               err = -errno;
> +               pr_warn("failed to create '%s': %d\n", filename, err);
> +               goto err_out;
> +       }
> +
> +       err = init_output_elf(linker);
> +       if (err)
> +               goto err_out;
> +
> +       return linker;
> +
> +err_out:
> +       bpf_linker__free(linker);
> +       return errno = -err, NULL;
> +}
> +
> +struct bpf_linker *bpf_linker__new_fd(const char *name, int fd,
> +                                     struct bpf_linker_opts *opts)
> +{
> +       struct bpf_linker *linker;
> +       int err;
> +
> +       if (fd < 0)
> +               return errno = EINVAL, NULL;
> +
> +       if (!OPTS_VALID(opts, bpf_linker_opts))
> +               return errno = EINVAL, NULL;
> +
> +       if (elf_version(EV_CURRENT) == EV_NONE) {
> +               pr_warn_elf("libelf initialization failed");
> +               return errno = EINVAL, NULL;
> +       }
> +
> +       linker = calloc(1, sizeof(*linker));
> +       if (!linker)
> +               return errno = ENOMEM, NULL;
> +
> +       linker->filename = strdup(name);
> +       if (!linker->filename)
> +               return errno = ENOMEM, NULL;
> +
> +       linker->fd = fd;
>
> -       err = init_output_elf(linker, filename);
> +       err = init_output_elf(linker);
>         if (err)
>                 goto err_out;
>
> @@ -294,23 +344,12 @@ static Elf64_Sym *add_new_sym(struct bpf_linker *linker, size_t *sym_idx)
>         return sym;
>  }
>
> -static int init_output_elf(struct bpf_linker *linker, const char *file)
> +static int init_output_elf(struct bpf_linker *linker)
>  {
>         int err, str_off;
>         Elf64_Sym *init_sym;
>         struct dst_sec *sec;
>
> -       linker->filename = strdup(file);
> -       if (!linker->filename)
> -               return -ENOMEM;
> -
> -       linker->fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
> -       if (linker->fd < 0) {
> -               err = -errno;
> -               pr_warn("failed to create '%s': %s\n", file, errstr(err));
> -               return err;
> -       }
> -
>         linker->elf = elf_begin(linker->fd, ELF_C_WRITE, NULL);
>         if (!linker->elf) {
>                 pr_warn_elf("failed to create ELF object");
> @@ -436,10 +475,9 @@ static int init_output_elf(struct bpf_linker *linker, const char *file)
>         return 0;
>  }
>
> -int bpf_linker__add_file(struct bpf_linker *linker, const char *filename,
> -                        const struct bpf_linker_file_opts *opts)
> +static int linker_add_common(struct bpf_linker *linker, struct src_obj *obj,
> +                            const struct bpf_linker_file_opts *opts)

it seems like bpf_linker__add_file() should be implemented on top of
bpf_linker__add_fd() + clean up on error, do we need
"linker_add_common"?

>  {
> -       struct src_obj obj = {};
>         int err = 0;
>
>         if (!OPTS_VALID(opts, bpf_linker_file_opts))
> @@ -448,25 +486,91 @@ int bpf_linker__add_file(struct bpf_linker *linker, const char *filename,
>         if (!linker->elf)
>                 return libbpf_err(-EINVAL);
>
> -       err = err ?: linker_load_obj_file(linker, filename, opts, &obj);
> -       err = err ?: linker_append_sec_data(linker, &obj);
> -       err = err ?: linker_append_elf_syms(linker, &obj);
> -       err = err ?: linker_append_elf_relos(linker, &obj);
> -       err = err ?: linker_append_btf(linker, &obj);
> -       err = err ?: linker_append_btf_ext(linker, &obj);
> +       err = err ?: linker_load_obj_file(linker, opts, obj);
> +       err = err ?: linker_append_sec_data(linker, obj);
> +       err = err ?: linker_append_elf_syms(linker, obj);
> +       err = err ?: linker_append_elf_relos(linker, obj);
> +       err = err ?: linker_append_btf(linker, obj);
> +       err = err ?: linker_append_btf_ext(linker, obj);
>
>         /* free up src_obj resources */
> -       free(obj.btf_type_map);
> -       btf__free(obj.btf);
> -       btf_ext__free(obj.btf_ext);
> -       free(obj.secs);
> -       free(obj.sym_map);
> -       if (obj.elf)
> -               elf_end(obj.elf);
> +       free(obj->btf_type_map);
> +       btf__free(obj->btf);
> +       btf_ext__free(obj->btf_ext);
> +       free(obj->secs);
> +       free(obj->sym_map);
> +       if (obj->elf)
> +               elf_end(obj->elf);
> +       /* leave obj->fd for the caller to clean up if appropriate */
> +
> +       return libbpf_err(err);
> +}
> +
> +int bpf_linker__add_file(struct bpf_linker *linker, const char *filename,
> +                        const struct bpf_linker_file_opts *opts)
> +{
> +       struct src_obj obj = {};
> +       int ret;
> +
> +       obj.filename = filename;
> +       obj.fd = open(filename, O_RDONLY | O_CLOEXEC);
> +       if (obj.fd < 0) {
> +               pr_warn("failed to open file '%s': %s\n", filename, errstr(errno));
> +               return -errno;
> +       }
> +
> +       ret = linker_add_common(linker, &obj, opts);

this can be just bpf_linker__add_fd() call, no?

> +
>         if (obj.fd >= 0)
>                 close(obj.fd);
>
> -       return libbpf_err(err);
> +       return ret;
> +}
> +
> +int bpf_linker__add_fd(struct bpf_linker *linker, const char *name, int fd,
> +                      const struct bpf_linker_file_opts *opts)
> +{
> +       struct src_obj obj = {};
> +
> +       if (fd < 0)
> +               return libbpf_err(-EINVAL);
> +
> +       obj.filename = name;
> +       obj.fd = fd;
> +
> +       return linker_add_common(linker, &obj, opts);
> +}
> +
> +int bpf_linker__add_buf(struct bpf_linker *linker, const char *name,
> +                       void *buffer, int buffer_sz,
> +                       const struct bpf_linker_file_opts *opts)
> +{
> +       struct src_obj obj = {};
> +       int written, ret;
> +
> +       obj.filename = name;
> +       obj.fd = memfd_create(name, 0);
> +       if (obj.fd < 0) {
> +               pr_warn("failed to create memfd '%s': %s\n", name, errstr(errno));
> +               return -errno;
> +       }
> +
> +       written = 0;
> +       while (written < buffer_sz) {
> +               ret = write(obj.fd, buffer, buffer_sz);
> +               if (ret < 0) {
> +                       pr_warn("failed to write '%s': %s\n", name, errstr(errno));
> +                       return -errno;
> +               }
> +               written += ret;
> +       }
> +
> +       ret = linker_add_common(linker, &obj, opts);

ditto, bpf_linker__add_fd() should be fine

> +
> +       if (obj.fd >= 0)
> +               close(obj.fd);
> +
> +       return ret;
>  }
>
>  static bool is_dwarf_sec_name(const char *name)
> @@ -534,7 +638,7 @@ static struct src_sec *add_src_sec(struct src_obj *obj, const char *sec_name)
>         return sec;
>  }
>
> -static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
> +static int linker_load_obj_file(struct bpf_linker *linker,
>                                 const struct bpf_linker_file_opts *opts,
>                                 struct src_obj *obj)
>  {
> @@ -554,20 +658,12 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
>  #error "Unknown __BYTE_ORDER__"
>  #endif
>
> -       pr_debug("linker: adding object file '%s'...\n", filename);
> -
> -       obj->filename = filename;
> +       pr_debug("linker: adding object file '%s'...\n", obj->filename);
>

so it would be nice to refactor this obj->filename as an initial patch
(and whatever other "cosmetic" changes necessary to add new APIs),
that way we can have more focused patch for new APIs

> -       obj->fd = open(filename, O_RDONLY | O_CLOEXEC);
> -       if (obj->fd < 0) {
> -               err = -errno;
> -               pr_warn("failed to open file '%s': %s\n", filename, errstr(err));
> -               return err;
> -       }
>         obj->elf = elf_begin(obj->fd, ELF_C_READ_MMAP, NULL);
>         if (!obj->elf) {
>                 err = -errno;
> -               pr_warn_elf("failed to parse ELF file '%s'", filename);
> +               pr_warn_elf("failed to parse ELF file '%s'", obj->filename);
>                 return err;
>         }
>
> @@ -575,7 +671,7 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
>         ehdr = elf64_getehdr(obj->elf);
>         if (!ehdr) {
>                 err = -errno;
> -               pr_warn_elf("failed to get ELF header for %s", filename);
> +               pr_warn_elf("failed to get ELF header for %s", obj->filename);
>                 return err;
>         }
>
> @@ -583,7 +679,7 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
>         obj_byteorder = ehdr->e_ident[EI_DATA];
>         if (obj_byteorder != ELFDATA2LSB && obj_byteorder != ELFDATA2MSB) {
>                 err = -EOPNOTSUPP;
> -               pr_warn("unknown byte order of ELF file %s\n", filename);
> +               pr_warn("unknown byte order of ELF file %s\n", obj->filename);
>                 return err;
>         }
>         if (link_byteorder == ELFDATANONE) {
> @@ -593,7 +689,7 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
>                          obj_byteorder == ELFDATA2MSB ? "big" : "little");
>         } else if (link_byteorder != obj_byteorder) {
>                 err = -EOPNOTSUPP;
> -               pr_warn("byte order mismatch with ELF file %s\n", filename);
> +               pr_warn("byte order mismatch with ELF file %s\n", obj->filename);
>                 return err;
>         }
>
> @@ -601,13 +697,13 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
>             || ehdr->e_machine != EM_BPF
>             || ehdr->e_ident[EI_CLASS] != ELFCLASS64) {
>                 err = -EOPNOTSUPP;
> -               pr_warn_elf("unsupported kind of ELF file %s", filename);
> +               pr_warn_elf("unsupported kind of ELF file %s", obj->filename);
>                 return err;
>         }
>
>         if (elf_getshdrstrndx(obj->elf, &obj->shstrs_sec_idx)) {
>                 err = -errno;
> -               pr_warn_elf("failed to get SHSTRTAB section index for %s", filename);
> +               pr_warn_elf("failed to get SHSTRTAB section index for %s", obj->filename);
>                 return err;
>         }
>
> @@ -620,7 +716,7 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
>                 if (!shdr) {
>                         err = -errno;
>                         pr_warn_elf("failed to get section #%zu header for %s",
> -                                   sec_idx, filename);
> +                                   sec_idx, obj->filename);
>                         return err;
>                 }
>
> @@ -628,7 +724,7 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
>                 if (!sec_name) {
>                         err = -errno;
>                         pr_warn_elf("failed to get section #%zu name for %s",
> -                                   sec_idx, filename);
> +                                   sec_idx, obj->filename);
>                         return err;
>                 }
>
> @@ -636,7 +732,7 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
>                 if (!data) {
>                         err = -errno;
>                         pr_warn_elf("failed to get section #%zu (%s) data from %s",
> -                                   sec_idx, sec_name, filename);
> +                                   sec_idx, sec_name, obj->filename);
>                         return err;
>                 }
>
> @@ -672,7 +768,7 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
>                                 err = libbpf_get_error(obj->btf);
>                                 if (err) {
>                                         pr_warn("failed to parse .BTF from %s: %s\n",
> -                                               filename, errstr(err));
> +                                               obj->filename, errstr(err));
>                                         return err;
>                                 }
>                                 sec->skipped = true;
> @@ -683,7 +779,7 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
>                                 err = libbpf_get_error(obj->btf_ext);
>                                 if (err) {
>                                         pr_warn("failed to parse .BTF.ext from '%s': %s\n",
> -                                               filename, errstr(err));
> +                                               obj->filename, errstr(err));
>                                         return err;
>                                 }
>                                 sec->skipped = true;
> @@ -700,7 +796,7 @@ static int linker_load_obj_file(struct bpf_linker *linker, const char *filename,
>                         break;
>                 default:
>                         pr_warn("unrecognized section #%zu (%s) in %s\n",
> -                               sec_idx, sec_name, filename);
> +                               sec_idx, sec_name, obj->filename);
>                         err = -EINVAL;
>                         return err;
>                 }

as I mentioned all of the above logging changes don't have to be in this patch

> @@ -2634,7 +2730,7 @@ static int linker_append_btf_ext(struct bpf_linker *linker, struct src_obj *obj)
>         return 0;
>  }
>
> -int bpf_linker__finalize(struct bpf_linker *linker)
> +int linker_finalize_common(struct bpf_linker *linker)
>  {
>         struct dst_sec *sec;
>         size_t strs_sz;
> @@ -2693,9 +2789,28 @@ int bpf_linker__finalize(struct bpf_linker *linker)
>         }
>
>         elf_end(linker->elf);
> +       linker->elf = NULL;
> +
> +       /* leave linker->fd for the caller to close if appropriate */
> +
> +       return 0;
> +}
> +
> +int bpf_linker__finalize(struct bpf_linker *linker)
> +{
> +       linker_finalize_common(linker);
> +
>         close(linker->fd);
> +       linker->fd = -1;
>
> -       linker->elf = NULL;
> +       return 0;
> +}
> +
> +int bpf_linker__finalize_fd(struct bpf_linker *linker)
> +{


so as I mentioned, I don't think we need an extra finalize variant. We
can just remember in linker struct whether we own fd or it was just
passed to use, and then existing bpf_linker__finalize() will just
check that and skip close, if necessary

This will also make it harder to mismatch new and finalize APIs.

pw-bot: cr

> +       linker_finalize_common(linker);
> +
> +       /* linker->fd was opened by the caller, so do not close it here */
>         linker->fd = -1;
>
>         return 0;
> --
> 2.43.5
>





[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux