OSDN Git Service

libbpf: Support BTF loading and raw data output in both endianness
authorAndrii Nakryiko <andriin@fb.com>
Tue, 29 Sep 2020 04:30:45 +0000 (21:30 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Tue, 29 Sep 2020 19:21:23 +0000 (12:21 -0700)
Teach BTF to recognized wrong endianness and transparently convert it
internally to host endianness. Original endianness of BTF will be preserved
and used during btf__get_raw_data() to convert resulting raw data to the same
endianness and a source raw_data. This means that little-endian host can parse
big-endian BTF with no issues, all the type data will be presented to the
client application in native endianness, but when it's time for emitting BTF
to persist it in a file (e.g., after BTF deduplication), original non-native
endianness will be preserved and stored.

It's possible to query original endianness of BTF data with new
btf__endianness() API. It's also possible to override desired output
endianness with btf__set_endianness(), so that if application needs to load,
say, big-endian BTF and store it as little-endian BTF, it's possible to
manually override this. If btf__set_endianness() was used to change
endianness, btf__endianness() will reflect overridden endianness.

Given there are no known use cases for supporting cross-endianness for
.BTF.ext, loading .BTF.ext in non-native endianness is not supported.

Signed-off-by: Andrii Nakryiko <andriin@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20200929043046.1324350-3-andriin@fb.com
tools/lib/bpf/btf.c
tools/lib/bpf/btf.h
tools/lib/bpf/libbpf.map

index c25f49f..e1dbd76 100644 (file)
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
 /* Copyright (c) 2018 Facebook */
 
+#include <byteswap.h>
 #include <endian.h>
 #include <stdio.h>
 #include <stdlib.h>
 static struct btf_type btf_void;
 
 struct btf {
+       /* raw BTF data in native endianness */
        void *raw_data;
+       /* raw BTF data in non-native endianness */
+       void *raw_data_swapped;
        __u32 raw_size;
+       /* whether target endianness differs from the native one */
+       bool swapped_endian;
 
        /*
         * When BTF is loaded from an ELF or raw memory it is stored
@@ -153,9 +159,19 @@ static int btf_add_type_idx_entry(struct btf *btf, __u32 type_off)
        return 0;
 }
 
+static void btf_bswap_hdr(struct btf_header *h)
+{
+       h->magic = bswap_16(h->magic);
+       h->hdr_len = bswap_32(h->hdr_len);
+       h->type_off = bswap_32(h->type_off);
+       h->type_len = bswap_32(h->type_len);
+       h->str_off = bswap_32(h->str_off);
+       h->str_len = bswap_32(h->str_len);
+}
+
 static int btf_parse_hdr(struct btf *btf)
 {
-       const struct btf_header *hdr = btf->hdr;
+       struct btf_header *hdr = btf->hdr;
        __u32 meta_left;
 
        if (btf->raw_size < sizeof(struct btf_header)) {
@@ -163,21 +179,19 @@ static int btf_parse_hdr(struct btf *btf)
                return -EINVAL;
        }
 
-       if (hdr->magic != BTF_MAGIC) {
+       if (hdr->magic == bswap_16(BTF_MAGIC)) {
+               btf->swapped_endian = true;
+               if (bswap_32(hdr->hdr_len) != sizeof(struct btf_header)) {
+                       pr_warn("Can't load BTF with non-native endianness due to unsupported header length %u\n",
+                               bswap_32(hdr->hdr_len));
+                       return -ENOTSUP;
+               }
+               btf_bswap_hdr(hdr);
+       } else if (hdr->magic != BTF_MAGIC) {
                pr_debug("Invalid BTF magic:%x\n", hdr->magic);
                return -EINVAL;
        }
 
-       if (hdr->version != BTF_VERSION) {
-               pr_debug("Unsupported BTF version:%u\n", hdr->version);
-               return -ENOTSUP;
-       }
-
-       if (hdr->flags) {
-               pr_debug("Unsupported BTF flags:%x\n", hdr->flags);
-               return -ENOTSUP;
-       }
-
        meta_left = btf->raw_size - sizeof(*hdr);
        if (!meta_left) {
                pr_debug("BTF has no data\n");
@@ -224,7 +238,7 @@ static int btf_parse_str_sec(struct btf *btf)
 
 static int btf_type_size(const struct btf_type *t)
 {
-       int base_size = sizeof(struct btf_type);
+       const int base_size = sizeof(struct btf_type);
        __u16 vlen = btf_vlen(t);
 
        switch (btf_kind(t)) {
@@ -257,12 +271,83 @@ static int btf_type_size(const struct btf_type *t)
        }
 }
 
+static void btf_bswap_type_base(struct btf_type *t)
+{
+       t->name_off = bswap_32(t->name_off);
+       t->info = bswap_32(t->info);
+       t->type = bswap_32(t->type);
+}
+
+static int btf_bswap_type_rest(struct btf_type *t)
+{
+       struct btf_var_secinfo *v;
+       struct btf_member *m;
+       struct btf_array *a;
+       struct btf_param *p;
+       struct btf_enum *e;
+       __u16 vlen = btf_vlen(t);
+       int i;
+
+       switch (btf_kind(t)) {
+       case BTF_KIND_FWD:
+       case BTF_KIND_CONST:
+       case BTF_KIND_VOLATILE:
+       case BTF_KIND_RESTRICT:
+       case BTF_KIND_PTR:
+       case BTF_KIND_TYPEDEF:
+       case BTF_KIND_FUNC:
+               return 0;
+       case BTF_KIND_INT:
+               *(__u32 *)(t + 1) = bswap_32(*(__u32 *)(t + 1));
+               return 0;
+       case BTF_KIND_ENUM:
+               for (i = 0, e = btf_enum(t); i < vlen; i++, e++) {
+                       e->name_off = bswap_32(e->name_off);
+                       e->val = bswap_32(e->val);
+               }
+               return 0;
+       case BTF_KIND_ARRAY:
+               a = btf_array(t);
+               a->type = bswap_32(a->type);
+               a->index_type = bswap_32(a->index_type);
+               a->nelems = bswap_32(a->nelems);
+               return 0;
+       case BTF_KIND_STRUCT:
+       case BTF_KIND_UNION:
+               for (i = 0, m = btf_members(t); i < vlen; i++, m++) {
+                       m->name_off = bswap_32(m->name_off);
+                       m->type = bswap_32(m->type);
+                       m->offset = bswap_32(m->offset);
+               }
+               return 0;
+       case BTF_KIND_FUNC_PROTO:
+               for (i = 0, p = btf_params(t); i < vlen; i++, p++) {
+                       p->name_off = bswap_32(p->name_off);
+                       p->type = bswap_32(p->type);
+               }
+               return 0;
+       case BTF_KIND_VAR:
+               btf_var(t)->linkage = bswap_32(btf_var(t)->linkage);
+               return 0;
+       case BTF_KIND_DATASEC:
+               for (i = 0, v = btf_var_secinfos(t); i < vlen; i++, v++) {
+                       v->type = bswap_32(v->type);
+                       v->offset = bswap_32(v->offset);
+                       v->size = bswap_32(v->size);
+               }
+               return 0;
+       default:
+               pr_debug("Unsupported BTF_KIND:%u\n", btf_kind(t));
+               return -EINVAL;
+       }
+}
+
 static int btf_parse_type_sec(struct btf *btf)
 {
        struct btf_header *hdr = btf->hdr;
        void *next_type = btf->types_data;
        void *end_type = next_type + hdr->type_len;
-       int err, type_size;
+       int err, i, type_size;
 
        /* VOID (type_id == 0) is specially handled by btf__get_type_by_id(),
         * so ensure we can never properly use its offset from index by
@@ -272,19 +357,36 @@ static int btf_parse_type_sec(struct btf *btf)
        if (err)
                return err;
 
-       while (next_type < end_type) {
-               err = btf_add_type_idx_entry(btf, next_type - btf->types_data);
-               if (err)
-                       return err;
+       while (next_type + sizeof(struct btf_type) <= end_type) {
+               i++;
+
+               if (btf->swapped_endian)
+                       btf_bswap_type_base(next_type);
 
                type_size = btf_type_size(next_type);
                if (type_size < 0)
                        return type_size;
+               if (next_type + type_size > end_type) {
+                       pr_warn("BTF type [%d] is malformed\n", i);
+                       return -EINVAL;
+               }
+
+               if (btf->swapped_endian && btf_bswap_type_rest(next_type))
+                       return -EINVAL;
+
+               err = btf_add_type_idx_entry(btf, next_type - btf->types_data);
+               if (err)
+                       return err;
 
                next_type += type_size;
                btf->nr_types++;
        }
 
+       if (next_type != end_type) {
+               pr_warn("BTF types data is malformed\n");
+               return -EINVAL;
+       }
+
        return 0;
 }
 
@@ -373,6 +475,38 @@ int btf__set_pointer_size(struct btf *btf, size_t ptr_sz)
        return 0;
 }
 
+static bool is_host_big_endian(void)
+{
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+       return false;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+       return true;
+#else
+# error "Unrecognized __BYTE_ORDER__"
+#endif
+}
+
+enum btf_endianness btf__endianness(const struct btf *btf)
+{
+       if (is_host_big_endian())
+               return btf->swapped_endian ? BTF_LITTLE_ENDIAN : BTF_BIG_ENDIAN;
+       else
+               return btf->swapped_endian ? BTF_BIG_ENDIAN : BTF_LITTLE_ENDIAN;
+}
+
+int btf__set_endianness(struct btf *btf, enum btf_endianness endian)
+{
+       if (endian != BTF_LITTLE_ENDIAN && endian != BTF_BIG_ENDIAN)
+               return -EINVAL;
+
+       btf->swapped_endian = is_host_big_endian() != (endian == BTF_BIG_ENDIAN);
+       if (!btf->swapped_endian) {
+               free(btf->raw_data_swapped);
+               btf->raw_data_swapped = NULL;
+       }
+       return 0;
+}
+
 static bool btf_type_is_void(const struct btf_type *t)
 {
        return t == &btf_void || btf_is_fwd(t);
@@ -561,6 +695,7 @@ void btf__free(struct btf *btf)
                free(btf->strs_data);
        }
        free(btf->raw_data);
+       free(btf->raw_data_swapped);
        free(btf->type_offs);
        free(btf);
 }
@@ -572,8 +707,10 @@ struct btf *btf__new_empty(void)
        btf = calloc(1, sizeof(*btf));
        if (!btf)
                return ERR_PTR(-ENOMEM);
+
        btf->fd = -1;
        btf->ptr_sz = sizeof(void *);
+       btf->swapped_endian = false;
 
        /* +1 for empty string at offset 0 */
        btf->raw_size = sizeof(struct btf_header) + 1;
@@ -604,8 +741,6 @@ struct btf *btf__new(const void *data, __u32 size)
        if (!btf)
                return ERR_PTR(-ENOMEM);
 
-       btf->fd = -1;
-
        btf->raw_data = malloc(size);
        if (!btf->raw_data) {
                err = -ENOMEM;
@@ -624,6 +759,10 @@ struct btf *btf__new(const void *data, __u32 size)
 
        err = btf_parse_str_sec(btf);
        err = err ?: btf_parse_type_sec(btf);
+       if (err)
+               goto done;
+
+       btf->fd = -1;
 
 done:
        if (err) {
@@ -634,17 +773,6 @@ done:
        return btf;
 }
 
-static bool btf_check_endianness(const GElf_Ehdr *ehdr)
-{
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-       return ehdr->e_ident[EI_DATA] == ELFDATA2LSB;
-#elif __BYTE_ORDER == __BIG_ENDIAN
-       return ehdr->e_ident[EI_DATA] == ELFDATA2MSB;
-#else
-# error "Unrecognized __BYTE_ORDER__"
-#endif
-}
-
 struct btf *btf__parse_elf(const char *path, struct btf_ext **btf_ext)
 {
        Elf_Data *btf_data = NULL, *btf_ext_data = NULL;
@@ -677,10 +805,6 @@ struct btf *btf__parse_elf(const char *path, struct btf_ext **btf_ext)
                pr_warn("failed to get EHDR from %s\n", path);
                goto done;
        }
-       if (!btf_check_endianness(&ehdr)) {
-               pr_warn("non-native ELF endianness is not supported\n");
-               goto done;
-       }
        if (!elf_rawdata(elf_getscn(elf, ehdr.e_shstrndx), NULL)) {
                pr_warn("failed to get e_shstrndx from %s\n", path);
                goto done;
@@ -792,7 +916,7 @@ struct btf *btf__parse_raw(const char *path)
                err = -EIO;
                goto err_out;
        }
-       if (magic != BTF_MAGIC) {
+       if (magic != BTF_MAGIC && magic != bswap_16(BTF_MAGIC)) {
                /* definitely not a raw BTF */
                err = -EPROTO;
                goto err_out;
@@ -942,11 +1066,13 @@ int btf__finalize_data(struct bpf_object *obj, struct btf *btf)
        return err;
 }
 
+static void *btf_get_raw_data(const struct btf *btf, __u32 *size, bool swap_endian);
+
 int btf__load(struct btf *btf)
 {
        __u32 log_buf_size = 0, raw_size;
        char *log_buf = NULL;
-       const void *raw_data;
+       void *raw_data;
        int err = 0;
 
        if (btf->fd >= 0)
@@ -961,11 +1087,14 @@ retry_load:
                *log_buf = 0;
        }
 
-       raw_data = btf__get_raw_data(btf, &raw_size);
+       raw_data = btf_get_raw_data(btf, &raw_size, false);
        if (!raw_data) {
                err = -ENOMEM;
                goto done;
        }
+       /* cache native raw data representation */
+       btf->raw_size = raw_size;
+       btf->raw_data = raw_data;
 
        btf->fd = bpf_load_btf(raw_data, raw_size, log_buf, log_buf_size, false);
        if (btf->fd < 0) {
@@ -998,31 +1127,73 @@ void btf__set_fd(struct btf *btf, int fd)
        btf->fd = fd;
 }
 
-const void *btf__get_raw_data(const struct btf *btf_ro, __u32 *size)
+static void *btf_get_raw_data(const struct btf *btf, __u32 *size, bool swap_endian)
 {
-       struct btf *btf = (struct btf *)btf_ro;
+       struct btf_header *hdr = btf->hdr;
+       struct btf_type *t;
+       void *data, *p;
+       __u32 data_sz;
+       int i;
 
-       if (!btf->raw_data) {
-               struct btf_header *hdr = btf->hdr;
-               void *data;
+       data = swap_endian ? btf->raw_data_swapped : btf->raw_data;
+       if (data) {
+               *size = btf->raw_size;
+               return data;
+       }
+
+       data_sz = hdr->hdr_len + hdr->type_len + hdr->str_len;
+       data = calloc(1, data_sz);
+       if (!data)
+               return NULL;
+       p = data;
+
+       memcpy(p, hdr, hdr->hdr_len);
+       if (swap_endian)
+               btf_bswap_hdr(p);
+       p += hdr->hdr_len;
+
+       memcpy(p, btf->types_data, hdr->type_len);
+       if (swap_endian) {
+               for (i = 1; i <= btf->nr_types; i++) {
+                       t = p  + btf->type_offs[i];
+                       /* btf_bswap_type_rest() relies on native t->info, so
+                        * we swap base type info after we swapped all the
+                        * additional information
+                        */
+                       if (btf_bswap_type_rest(t))
+                               goto err_out;
+                       btf_bswap_type_base(t);
+               }
+       }
+       p += hdr->type_len;
+
+       memcpy(p, btf->strs_data, hdr->str_len);
+       p += hdr->str_len;
 
-               btf->raw_size = hdr->hdr_len + hdr->type_len + hdr->str_len;
-               btf->raw_data = calloc(1, btf->raw_size);
-               if (!btf->raw_data)
-                       return NULL;
-               data = btf->raw_data;
+       *size = data_sz;
+       return data;
+err_out:
+       free(data);
+       return NULL;
+}
 
-               memcpy(data, hdr, hdr->hdr_len);
-               data += hdr->hdr_len;
+const void *btf__get_raw_data(const struct btf *btf_ro, __u32 *size)
+{
+       struct btf *btf = (struct btf *)btf_ro;
+       __u32 data_sz;
+       void *data;
 
-               memcpy(data, btf->types_data, hdr->type_len);
-               data += hdr->type_len;
+       data = btf_get_raw_data(btf, &data_sz, btf->swapped_endian);
+       if (!data)
+               return NULL;
 
-               memcpy(data, btf->strs_data, hdr->str_len);
-               data += hdr->str_len;
-       }
-       *size = btf->raw_size;
-       return btf->raw_data;
+       btf->raw_size = data_sz;
+       if (btf->swapped_endian)
+               btf->raw_data_swapped = data;
+       else
+               btf->raw_data = data;
+       *size = data_sz;
+       return data;
 }
 
 const char *btf__str_by_offset(const struct btf *btf, __u32 offset)
@@ -1190,6 +1361,18 @@ static bool strs_hash_equal_fn(const void *key1, const void *key2, void *ctx)
        return strcmp(str1, str2) == 0;
 }
 
+static void btf_invalidate_raw_data(struct btf *btf)
+{
+       if (btf->raw_data) {
+               free(btf->raw_data);
+               btf->raw_data = NULL;
+       }
+       if (btf->raw_data_swapped) {
+               free(btf->raw_data_swapped);
+               btf->raw_data_swapped = NULL;
+       }
+}
+
 /* Ensure BTF is ready to be modified (by splitting into a three memory
  * regions for header, types, and strings). Also invalidate cached
  * raw_data, if any.
@@ -1203,10 +1386,7 @@ static int btf_ensure_modifiable(struct btf *btf)
 
        if (btf_is_modifiable(btf)) {
                /* any BTF modification invalidates raw_data */
-               if (btf->raw_data) {
-                       free(btf->raw_data);
-                       btf->raw_data = NULL;
-               }
+               btf_invalidate_raw_data(btf);
                return 0;
        }
 
@@ -1254,8 +1434,7 @@ static int btf_ensure_modifiable(struct btf *btf)
        btf->strs_deduped = btf->hdr->str_len <= 1;
 
        /* invalidate raw_data representation */
-       free(btf->raw_data);
-       btf->raw_data = NULL;
+       btf_invalidate_raw_data(btf);
 
        return 0;
 
@@ -2275,7 +2454,10 @@ static int btf_ext_parse_hdr(__u8 *data, __u32 data_size)
                return -EINVAL;
        }
 
-       if (hdr->magic != BTF_MAGIC) {
+       if (hdr->magic == bswap_16(BTF_MAGIC)) {
+               pr_warn("BTF.ext in non-native endianness is not supported\n");
+               return -ENOTSUP;
+       } else if (hdr->magic != BTF_MAGIC) {
                pr_debug("Invalid BTF.ext magic:%x\n", hdr->magic);
                return -EINVAL;
        }
index f7dec01..5724724 100644 (file)
@@ -25,6 +25,11 @@ struct btf_type;
 
 struct bpf_object;
 
+enum btf_endianness {
+       BTF_LITTLE_ENDIAN = 0,
+       BTF_BIG_ENDIAN = 1,
+};
+
 LIBBPF_API void btf__free(struct btf *btf);
 LIBBPF_API struct btf *btf__new(const void *data, __u32 size);
 LIBBPF_API struct btf *btf__new_empty(void);
@@ -42,6 +47,8 @@ LIBBPF_API const struct btf_type *btf__type_by_id(const struct btf *btf,
                                                  __u32 id);
 LIBBPF_API size_t btf__pointer_size(const struct btf *btf);
 LIBBPF_API int btf__set_pointer_size(struct btf *btf, size_t ptr_sz);
+LIBBPF_API enum btf_endianness btf__endianness(const struct btf *btf);
+LIBBPF_API int btf__set_endianness(struct btf *btf, enum btf_endianness endian);
 LIBBPF_API __s64 btf__resolve_size(const struct btf *btf, __u32 type_id);
 LIBBPF_API int btf__resolve_type(const struct btf *btf, __u32 type_id);
 LIBBPF_API int btf__align_of(const struct btf *btf, __u32 id);
index 6b10eba..f7a8ff3 100644 (file)
@@ -325,8 +325,10 @@ LIBBPF_0.2.0 {
                btf__add_union;
                btf__add_var;
                btf__add_volatile;
+               btf__endianness;
                btf__find_str;
                btf__new_empty;
+               btf__set_endianness;
                btf__str_by_offset;
                perf_buffer__buffer_cnt;
                perf_buffer__buffer_fd;