diff mbox series

[bpf-next,2/3] libbpf: support BTF loading and raw data output in both endianness

Message ID 20200929043046.1324350-3-andriin@fb.com (mailing list archive)
State Accepted
Commit 3289959b97cac205df006fc79c88f042bf2765ab
Headers show
Series libbpf: support loading/storing any BTF endianness | expand

Commit Message

Andrii Nakryiko Sept. 29, 2020, 4:30 a.m. UTC
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>
---
 tools/lib/bpf/btf.c      | 310 +++++++++++++++++++++++++++++++--------
 tools/lib/bpf/btf.h      |   7 +
 tools/lib/bpf/libbpf.map |   2 +
 3 files changed, 255 insertions(+), 64 deletions(-)
diff mbox series

Patch

diff --git a/tools/lib/bpf/btf.c b/tools/lib/bpf/btf.c
index c25f49fad5a6..e1dbd766c698 100644
--- a/tools/lib/bpf/btf.c
+++ b/tools/lib/bpf/btf.c
@@ -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>
@@ -27,8 +28,13 @@ 
 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 @@  struct btf *btf__new(const void *data, __u32 size)
 	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 @@  int btf__load(struct btf *btf)
 		*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;
 	}
diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h
index f7dec0144c3c..57247240a20a 100644
--- a/tools/lib/bpf/btf.h
+++ b/tools/lib/bpf/btf.h
@@ -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);
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index 6b10ebad69c6..f7a8ff37ef04 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -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;