libbpf: Handle BTF pointer sizes more carefully
authorAndrii Nakryiko <andriin@fb.com>
Thu, 13 Aug 2020 20:49:40 +0000 (13:49 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Thu, 13 Aug 2020 23:45:41 +0000 (16:45 -0700)
With libbpf and BTF it is pretty common to have libbpf built for one
architecture, while BTF information was generated for a different architecture
(typically, but not always, BPF). In such case, the size of a pointer might
differ betweem architectures. libbpf previously was always making an
assumption that pointer size for BTF is the same as native architecture
pointer size, but that breaks for cases where libbpf is built as 32-bit
library, while BTF is for 64-bit architecture.

To solve this, add heuristic to determine pointer size by searching for `long`
or `unsigned long` integer type and using its size as a pointer size. Also,
allow to override the pointer size with a new API btf__set_pointer_size(), for
cases where application knows which pointer size should be used. User
application can check what libbpf "guessed" by looking at the result of
btf__pointer_size(). If it's not 0, then libbpf successfully determined a
pointer size, otherwise native arch pointer size will be used.

For cases where BTF is parsed from ELF file, use ELF's class (32-bit or
64-bit) to determine pointer size.

Fixes: 8a138aed4a80 ("bpf: btf: Add BTF support to libbpf")
Fixes: 351131b51c7a ("libbpf: add btf_dump API for BTF-to-C conversion")
Signed-off-by: Andrii Nakryiko <andriin@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20200813204945.1020225-5-andriin@fb.com
tools/lib/bpf/btf.c
tools/lib/bpf/btf.h
tools/lib/bpf/btf_dump.c
tools/lib/bpf/libbpf.map

index 4843e44916f7c454939ba0c32b174ac3ab077efa..7dfca7016aaa8d9597e0708592dfa6d410d9e3a5 100644 (file)
@@ -41,6 +41,7 @@ struct btf {
        __u32 types_size;
        __u32 data_size;
        int fd;
+       int ptr_sz;
 };
 
 static inline __u64 ptr_to_u64(const void *ptr)
@@ -221,6 +222,70 @@ const struct btf_type *btf__type_by_id(const struct btf *btf, __u32 type_id)
        return btf->types[type_id];
 }
 
+static int determine_ptr_size(const struct btf *btf)
+{
+       const struct btf_type *t;
+       const char *name;
+       int i;
+
+       for (i = 1; i <= btf->nr_types; i++) {
+               t = btf__type_by_id(btf, i);
+               if (!btf_is_int(t))
+                       continue;
+
+               name = btf__name_by_offset(btf, t->name_off);
+               if (!name)
+                       continue;
+
+               if (strcmp(name, "long int") == 0 ||
+                   strcmp(name, "long unsigned int") == 0) {
+                       if (t->size != 4 && t->size != 8)
+                               continue;
+                       return t->size;
+               }
+       }
+
+       return -1;
+}
+
+static size_t btf_ptr_sz(const struct btf *btf)
+{
+       if (!btf->ptr_sz)
+               ((struct btf *)btf)->ptr_sz = determine_ptr_size(btf);
+       return btf->ptr_sz < 0 ? sizeof(void *) : btf->ptr_sz;
+}
+
+/* Return pointer size this BTF instance assumes. The size is heuristically
+ * determined by looking for 'long' or 'unsigned long' integer type and
+ * recording its size in bytes. If BTF type information doesn't have any such
+ * type, this function returns 0. In the latter case, native architecture's
+ * pointer size is assumed, so will be either 4 or 8, depending on
+ * architecture that libbpf was compiled for. It's possible to override
+ * guessed value by using btf__set_pointer_size() API.
+ */
+size_t btf__pointer_size(const struct btf *btf)
+{
+       if (!btf->ptr_sz)
+               ((struct btf *)btf)->ptr_sz = determine_ptr_size(btf);
+
+       if (btf->ptr_sz < 0)
+               /* not enough BTF type info to guess */
+               return 0;
+
+       return btf->ptr_sz;
+}
+
+/* Override or set pointer size in bytes. Only values of 4 and 8 are
+ * supported.
+ */
+int btf__set_pointer_size(struct btf *btf, size_t ptr_sz)
+{
+       if (ptr_sz != 4 && ptr_sz != 8)
+               return -EINVAL;
+       btf->ptr_sz = ptr_sz;
+       return 0;
+}
+
 static bool btf_type_is_void(const struct btf_type *t)
 {
        return t == &btf_void || btf_is_fwd(t);
@@ -253,7 +318,7 @@ __s64 btf__resolve_size(const struct btf *btf, __u32 type_id)
                        size = t->size;
                        goto done;
                case BTF_KIND_PTR:
-                       size = sizeof(void *);
+                       size = btf_ptr_sz(btf);
                        goto done;
                case BTF_KIND_TYPEDEF:
                case BTF_KIND_VOLATILE:
@@ -293,9 +358,9 @@ int btf__align_of(const struct btf *btf, __u32 id)
        switch (kind) {
        case BTF_KIND_INT:
        case BTF_KIND_ENUM:
-               return min(sizeof(void *), (size_t)t->size);
+               return min(btf_ptr_sz(btf), (size_t)t->size);
        case BTF_KIND_PTR:
-               return sizeof(void *);
+               return btf_ptr_sz(btf);
        case BTF_KIND_TYPEDEF:
        case BTF_KIND_VOLATILE:
        case BTF_KIND_CONST:
@@ -533,6 +598,18 @@ struct btf *btf__parse_elf(const char *path, struct btf_ext **btf_ext)
        if (IS_ERR(btf))
                goto done;
 
+       switch (gelf_getclass(elf)) {
+       case ELFCLASS32:
+               btf__set_pointer_size(btf, 4);
+               break;
+       case ELFCLASS64:
+               btf__set_pointer_size(btf, 8);
+               break;
+       default:
+               pr_warn("failed to get ELF class (bitness) for %s\n", path);
+               break;
+       }
+
        if (btf_ext && btf_ext_data) {
                *btf_ext = btf_ext__new(btf_ext_data->d_buf,
                                        btf_ext_data->d_size);
index f4a1a1d2b9a3fc7c4c50a7fb2b6cf66cbf26a75c..1ca14448df4cd02bfd7be8a369ae0f02cb464ae6 100644 (file)
@@ -76,6 +76,8 @@ LIBBPF_API __s32 btf__find_by_name_kind(const struct btf *btf,
 LIBBPF_API __u32 btf__get_nr_types(const struct btf *btf);
 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 __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 ac81f3f8957ad94b83e346d6268f0fb8a3d8d77b..fe39bd7746976890707a252f49466026cc6861db 100644 (file)
@@ -61,6 +61,7 @@ struct btf_dump {
        const struct btf_ext *btf_ext;
        btf_dump_printf_fn_t printf_fn;
        struct btf_dump_opts opts;
+       int ptr_sz;
        bool strip_mods;
 
        /* per-type auxiliary state */
@@ -139,6 +140,7 @@ struct btf_dump *btf_dump__new(const struct btf *btf,
        d->btf_ext = btf_ext;
        d->printf_fn = printf_fn;
        d->opts.ctx = opts ? opts->ctx : NULL;
+       d->ptr_sz = btf__pointer_size(btf) ? : sizeof(void *);
 
        d->type_names = hashmap__new(str_hash_fn, str_equal_fn, NULL);
        if (IS_ERR(d->type_names)) {
@@ -804,7 +806,7 @@ static void btf_dump_emit_bit_padding(const struct btf_dump *d,
                                      int align, int lvl)
 {
        int off_diff = m_off - cur_off;
-       int ptr_bits = sizeof(void *) * 8;
+       int ptr_bits = d->ptr_sz * 8;
 
        if (off_diff <= 0)
                /* no gap */
index 0c4722bfdd0ac22a87d33162fbdc44df69e3d064..e35bd6cdbdbf85eca0fd7afb4a25c993e1846020 100644 (file)
@@ -295,5 +295,7 @@ LIBBPF_0.1.0 {
                bpf_program__set_sk_lookup;
                btf__parse;
                btf__parse_raw;
+               btf__pointer_size;
                btf__set_fd;
+               btf__set_pointer_size;
 } LIBBPF_0.0.9;