diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h
index 0c48f2e..7937124 100644
--- a/tools/lib/bpf/btf.h
+++ b/tools/lib/bpf/btf.h
@@ -180,6 +180,23 @@ struct btf_dump_emit_type_decl_opts {
 btf_dump__emit_type_decl(struct btf_dump *d, __u32 id,
 			 const struct btf_dump_emit_type_decl_opts *opts);
 
+
+struct btf_dump_emit_type_data_opts {
+	/* size of this struct, for forward/backward compatibility */
+	size_t sz;
+	int indent_level;
+	/* below match "show" flags for bpf_show_snprintf() */
+	bool compact;
+	bool noname;
+	bool zero;
+};
+#define btf_dump_emit_type_data_opts__last_field zero
+
+LIBBPF_API int
+btf_dump__emit_type_data(struct btf_dump *d, __u32 id,
+			 const struct btf_dump_emit_type_data_opts *opts,
+			 void *data);
+
 /*
  * A set of helpers for easier BTF types handling
  */
diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c
index 2f9d685..04d604f 100644
--- a/tools/lib/bpf/btf_dump.c
+++ b/tools/lib/bpf/btf_dump.c
@@ -10,6 +10,8 @@
 #include <stddef.h>
 #include <stdlib.h>
 #include <string.h>
+#include <ctype.h>
+#include <endian.h>
 #include <errno.h>
 #include <linux/err.h>
 #include <linux/btf.h>
@@ -19,14 +21,31 @@
 #include "libbpf.h"
 #include "libbpf_internal.h"
 
+#define BITS_PER_BYTE			8
+#define BITS_PER_U128			(sizeof(__u64) * BITS_PER_BYTE * 2)
+#define BITS_PER_BYTE_MASK		(BITS_PER_BYTE - 1)
+#define BITS_PER_BYTE_MASKED(bits)	((bits) & BITS_PER_BYTE_MASK)
+#define BITS_ROUNDDOWN_BYTES(bits)	((bits) >> 3)
+#define BITS_ROUNDUP_BYTES(bits) \
+	(BITS_ROUNDDOWN_BYTES(bits) + !!BITS_PER_BYTE_MASKED(bits))
+
 static const char PREFIXES[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t";
 static const size_t PREFIX_CNT = sizeof(PREFIXES) - 1;
 
+
 static const char *pfx(int lvl)
 {
 	return lvl >= PREFIX_CNT ? PREFIXES : &PREFIXES[PREFIX_CNT - lvl];
 }
 
+static const char SPREFIXES[] = "                         ";
+static const size_t SPREFIX_CNT = sizeof(SPREFIXES) - 1;
+
+static const char *spfx(int lvl)
+{
+	return lvl >= SPREFIX_CNT ? SPREFIXES : &SPREFIXES[SPREFIX_CNT - lvl];
+}
+
 enum btf_dump_type_order_state {
 	NOT_ORDERED,
 	ORDERING,
@@ -53,6 +72,49 @@ struct btf_dump_type_aux_state {
 	__u8 referenced: 1;
 };
 
+#define BTF_DUMP_DATA_MAX_NAME_LEN	256
+
+/*
+ * Common internal data for BTF type data dump operations.
+ *
+ * The implementation here is similar to that in kernel/bpf/btf.c
+ * that supports the bpf_snprintf_btf() helper, so any bugs in
+ * type data dumping here are likely in that code also.
+ *
+ * One challenge with showing nested data is we want to skip 0-valued
+ * data, but in order to figure out whether a nested object is all zeros
+ * we need to walk through it.  As a result, we need to make two passes
+ * when handling structs, unions and arrays; the first path simply looks
+ * for nonzero data, while the second actually does the display.  The first
+ * pass is signalled by state.depth_check being set, and if we
+ * encounter a non-zero value we set state.depth_to_show to the depth
+ * at which we encountered it.  When we have completed the first pass,
+ * we will know if anything needs to be displayed if
+ * state.depth_to_show > state.depth.  See btf_dump_emit_[struct,array]_data()
+ * for the implementation of this.
+ *
+ */
+struct btf_dump_data {
+	bool compact;
+	bool noname;
+	bool zero;
+	__u8 indent_lvl;	/* base indent level */
+	/* below are used during iteration */
+	struct {
+		__u8 depth;
+		__u8 depth_to_show;
+		__u8 depth_check;
+		__u8 array_member:1,
+		     array_terminated:1;
+		__u16 array_encoding;
+		__u32 type_id;
+		const struct btf_type *type;
+		const struct btf_member *member;
+		char name[BTF_DUMP_DATA_MAX_NAME_LEN];
+		int err;
+	} state;
+};
+
 struct btf_dump {
 	const struct btf *btf;
 	const struct btf_ext *btf_ext;
@@ -89,6 +151,10 @@ struct btf_dump {
 	 * name occurrences
 	 */
 	struct hashmap *ident_names;
+	/*
+	 * data for typed display.
+	 */
+	struct btf_dump_data data;
 };
 
 static size_t str_hash_fn(const void *key, void *ctx)
@@ -1438,3 +1504,911 @@ static const char *btf_dump_ident_name(struct btf_dump *d, __u32 id)
 {
 	return btf_dump_resolve_name(d, id, d->ident_names);
 }
+
+static void __btf_dump_emit_type_data(struct btf_dump *d,
+				      const struct btf_type *t,
+				      __u32 id,
+				      void *data,
+				      __u8 bits_offset);
+
+static const struct btf_type *skip_mods(const struct btf *btf,
+					__u32 id, __u32 *res_id)
+{
+	const struct btf_type *t = btf__type_by_id(btf, id);
+
+	while (btf_is_mod(t)) {
+		id = t->type;
+		t = btf__type_by_id(btf, t->type);
+	}
+
+	if (res_id)
+		*res_id = id;
+
+	return t;
+}
+
+#define BTF_MAX_ITER		10
+#define BTF_KIND_BIT(kind)	(1ULL << kind)
+
+/*
+ * Populate dump->data.state.name with type name information.
+ * Format of type name is
+ *
+ *	[.member_name = ] (type_name)
+ */
+static const char *btf_dump_data_name(struct btf_dump *d)
+{
+	/* BTF_MAX_ITER array suffixes "[]" */
+	const char *array_suffixes = "[][][][][][][][][][]";
+	const char *array_suffix = &array_suffixes[strlen(array_suffixes)];
+	/* BTF_MAX_ITER pointer suffixes "*" */
+	const char *ptr_suffixes = "**********";
+	const char *ptr_suffix = &ptr_suffixes[strlen(ptr_suffixes)];
+	const char *name = NULL, *prefix = "", *parens = "";
+	const struct btf_member *m = d->data.state.member;
+	const struct btf_type *t = d->data.state.type;
+	const struct btf_array *array;
+	__u32 id = d->data.state.type_id;
+	const char *member = NULL;
+	bool show_member = false;
+	__u64 kinds = 0;
+	int i;
+
+	d->data.state.name[0] = '\0';
+
+	/*
+	 * Don't show type name if we're showing an array member;
+	 * in that case we show the array type so don't need to repeat
+	 * ourselves for each member.
+	 */
+	if (d->data.state.array_member)
+		return "";
+
+	/* Retrieve member name, if any. */
+	if (m) {
+		member = btf_name_of(d, m->name_off);
+		show_member = strlen(member) > 0;
+		id = m->type;
+	}
+
+	/*
+	 * Start with type_id, as we have resolved the struct btf_type *
+	 * via btf_dump_emit_modifier_data() past the parent typedef to the
+	 * child struct, int etc it is defined as.  In such cases, the type_id
+	 * still represents the starting type while the struct btf_type *
+	 * in our d->data.state points at the resolved type of the typedef.
+	 */
+	t = btf__type_by_id(d->btf, id);
+	if (!t)
+		return "";
+
+       /*
+	* The goal here is to build up the right number of pointer and
+	* array suffixes while ensuring the type name for a typedef
+	* is represented.  Along the way we accumulate a list of
+	* BTF kinds we have encountered, since these will inform later
+	* display; for example, pointer types will not require an
+	* opening "{" for struct, we will just display the pointer value.
+	*
+	* We also want to accumulate the right number of pointer or array
+	* indices in the format string while iterating until we get to
+	* the typedef/pointee/array member target type.
+	*
+	* We start by pointing at the end of pointer and array suffix
+	* strings; as we accumulate pointers and arrays we move the pointer
+	* or array string backwards so it will show the expected number of
+	* '*' or '[]' for the type.  BTF_SHOW_MAX_ITER of nesting of pointers
+	* and/or arrays and typedefs are supported as a precaution.
+	*
+	* We also want to get typedef name while proceeding to resolve
+	* type it points to so that we can add parentheses if it is a
+	* "typedef struct" etc.
+	*
+	* Qualifiers ("const", "volatile", "restrict") are simply skipped
+	* as they complicate simple type name display without adding much
+	* in the case of displaying a cast in front of the data to be
+	* displayed.
+	*/
+	for (i = 0; i < BTF_MAX_ITER; i++) {
+
+		switch (BTF_INFO_KIND(t->info)) {
+		case BTF_KIND_TYPEDEF:
+			if (!name)
+				name = btf_name_of(d, t->name_off);
+			kinds |= BTF_KIND_BIT(BTF_KIND_TYPEDEF);
+			id = t->type;
+			break;
+		case BTF_KIND_ARRAY:
+			kinds |= BTF_KIND_BIT(BTF_KIND_ARRAY);
+			parens = "[";
+			if (!t)
+				return "";
+			array = btf_array(t);
+			if (array_suffix > array_suffixes)
+				array_suffix -= 2;
+			id = array->type;
+			break;
+		case BTF_KIND_PTR:
+			kinds |= BTF_KIND_BIT(BTF_KIND_PTR);
+			if (ptr_suffix > ptr_suffixes)
+				ptr_suffix -= 1;
+			id = t->type;
+			break;
+		default:
+			id = 0;
+			break;
+		}
+		if (!id)
+			break;
+		t = skip_mods(d->btf, id, NULL);
+	}
+	/* We may not be able to represent this type; bail to be safe */
+	if (i == BTF_MAX_ITER) {
+		pr_warn("iters %d exceeded %d when displaying type name:[%u]\n",
+			i, BTF_MAX_ITER, id);
+		return "";
+	}
+
+	if (!name)
+		name = btf_name_of(d, t->name_off);
+
+	switch (BTF_INFO_KIND(t->info)) {
+	case BTF_KIND_STRUCT:
+	case BTF_KIND_UNION:
+		prefix = BTF_INFO_KIND(t->info) == BTF_KIND_STRUCT ?
+						   "struct" : "union";
+		/* if it's an array of struct/union, parens is already set */
+		if (!(kinds & (BTF_KIND_BIT(BTF_KIND_ARRAY))))
+			parens = "{";
+		break;
+	case BTF_KIND_ENUM:
+		prefix = "enum";
+		break;
+	default:
+		break;
+	}
+
+	/* pointer does not require parens */
+	if (kinds & BTF_KIND_BIT(BTF_KIND_PTR))
+		parens = "";
+	/* typedef does not require struct/union/enum prefix */
+	if (kinds & BTF_KIND_BIT(BTF_KIND_TYPEDEF))
+		prefix = "";
+
+	if (!name)
+		name = "";
+
+	/* Even if we don't want type name info, we want parentheses etc */
+	if (d->data.noname)
+		snprintf(d->data.state.name, sizeof(d->data.state.name), "%s",
+			 parens);
+	else
+		snprintf(d->data.state.name, sizeof(d->data.state.name),
+			 "%s%s%s(%s%s%s%s%s%s)%s",
+			 /* first 3 strings comprise ".member = " */
+			 show_member ? "." : "",
+			 show_member ? member : "",
+			 show_member ? " = " : "",
+			 /* ...next is our prefix (struct, enum, etc) */
+			 prefix,
+			 strlen(prefix) > 0 && strlen(name) > 0 ? " " : "",
+			 /* ...this is the type name itself */
+			 name,
+			 /* ...suffixed by the appropriate '*', '[]' suffixes */
+			 strlen(name) > 0 && strlen(ptr_suffix) > 0 ? " " : "",
+			 ptr_suffix,
+			 array_suffix, parens);
+
+	return d->data.state.name;
+}
+
+static const char *btf_dump_data_indent(struct btf_dump *d)
+{
+	if (d->data.compact)
+		return "";
+	return spfx(d->data.indent_lvl + d->data.state.depth);
+}
+
+static const char *btf_dump_data_newline(struct btf_dump *d)
+{
+	return d->data.compact ? "" : "\n";
+}
+
+static const char *btf_dump_data_delim(struct btf_dump *d)
+{
+	if (d->data.state.depth == 0)
+		return "";
+
+	if (d->data.compact &&
+	    d->data.state.type &&
+	    BTF_INFO_KIND(d->data.state.type->info) == BTF_KIND_UNION)
+		return "|";
+
+	return ",";
+}
+
+static void btf_dump_data_printf(struct btf_dump *d,
+				 const char *fmt, ...)
+{
+	va_list args;
+
+	/*
+	 * Just checking if there is non-zero data to display at this depth,
+	 * so nothing is displayed.
+	 */
+	if (d->data.state.depth_check)
+		return;
+	va_start(args, fmt);
+	d->printf_fn(d->opts.ctx, fmt, args);
+	va_end(args);
+}
+
+/* Macros are used here as btf_type_value[s]() prepends and appends
+ * format specifiers to the format specifier passed in; these do the work of
+ * adding indentation, delimiters etc while the caller simply has to specify
+ * the type value(s) in the format specifier + value(s).
+ */
+#define btf_dump_emit_type_value(d, fmt, value)				     \
+	do {								     \
+		if ((value) != 0 || d->data.zero ||			     \
+		    d->data.state.depth == 0) {				     \
+			btf_dump_data_printf(d, "%s%s" fmt "%s%s",	     \
+					     btf_dump_data_indent(d),	     \
+					     btf_dump_data_name(d),          \
+					     value,			     \
+					     btf_dump_data_delim(d),	     \
+					     btf_dump_data_newline(d));      \
+			if (d->data.state.depth >			     \
+			    d->data.state.depth_to_show)		     \
+				d->data.state.depth_to_show =		     \
+					d->data.state.depth;		     \
+		}							     \
+	} while (0)
+
+#define btf_dump_emit_type_values(d, fmt, ...)				\
+	do {								\
+		btf_dump_data_printf(d, "%s%s" fmt "%s%s",		\
+				     btf_dump_data_indent(d),		\
+				     btf_dump_data_name(d),		\
+				     __VA_ARGS__,			\
+				     btf_dump_data_delim(d),		\
+				     btf_dump_data_newline(d));		\
+		if (d->data.state.depth >				\
+		    d->data.state.depth_to_show)			\
+			d->data.state.depth_to_show =			\
+				d->data.state.depth;			\
+	} while (0)
+
+/* Set the type we are starting to show. */
+static void btf_dump_start_type(struct btf_dump *d,
+				const struct btf_type *t,
+				__u32 type_id)
+{
+	d->data.state.type = t;
+	d->data.state.type_id = type_id;
+	d->data.state.name[0] = '\0';
+}
+
+static void btf_dump_end_type(struct btf_dump *d)
+{
+	d->data.state.type = NULL;
+	d->data.state.type_id = 0;
+	d->data.state.name[0] = '\0';
+}
+
+static void btf_dump_start_aggr_type(struct btf_dump *d,
+				     const struct btf_type *t,
+				     __u32 type_id)
+{
+	btf_dump_start_type(d, t, type_id);
+
+	btf_dump_data_printf(d, "%s%s%s",
+			     btf_dump_data_indent(d),
+			     btf_dump_data_name(d),
+			     btf_dump_data_newline(d));
+	d->data.state.depth++;
+}
+
+static void btf_dump_end_aggr_type(struct btf_dump *d,
+				   const char *suffix)
+{
+	d->data.state.depth--;
+	btf_dump_data_printf(d, "%s%s%s%s",
+			     btf_dump_data_indent(d),
+			     suffix,
+			     btf_dump_data_delim(d),
+			     btf_dump_data_newline(d));
+	btf_dump_end_type(d);
+}
+
+static void btf_dump_start_member(struct btf_dump *d,
+				  const struct btf_member *m)
+{
+	d->data.state.member = m;
+}
+
+static void btf_dump_start_array_member(struct btf_dump *d)
+{
+	d->data.state.array_member = 1;
+	btf_dump_start_member(d, NULL);
+}
+
+static void btf_dump_end_member(struct btf_dump *d)
+{
+	d->data.state.member = NULL;
+}
+
+static void btf_dump_end_array_member(struct btf_dump *d)
+{
+	d->data.state.array_member = 0;
+	btf_dump_end_member(d);
+}
+
+static void btf_dump_start_array_type(struct btf_dump *d,
+				      const struct btf_type *t,
+				      __u32 type_id,
+				      __u16 array_encoding)
+{
+	d->data.state.array_encoding = array_encoding;
+	d->data.state.array_terminated = 0;
+	btf_dump_start_aggr_type(d, t, type_id);
+}
+
+static void btf_dump_end_array_type(struct btf_dump *d)
+{
+	d->data.state.array_encoding = 0;
+	d->data.state.array_terminated = 0;
+	btf_dump_end_aggr_type(d, "]");
+}
+
+static void btf_dump_start_struct_type(struct btf_dump *d,
+				       const struct btf_type *t,
+				       __u32 type_id)
+{
+	btf_dump_start_aggr_type(d, t, type_id);
+}
+
+static void btf_dump_end_struct_type(struct btf_dump *d)
+{
+	btf_dump_end_aggr_type(d, "}");
+}
+
+static void btf_dump_emit_df_data(struct btf_dump *d,
+				  const struct btf_type *t,
+				  __u32 id,
+				  void *data,
+				  __u8 bits_offset)
+{
+	btf_dump_data_printf(d, "<unsupported kind:%u>",
+			     BTF_INFO_KIND(t->info));
+}
+
+static void btf_dump_emit_int128(struct btf_dump *d, void *data)
+{
+	/* data points to a __int128 number.
+	 * Suppose
+	 *	int128_num = *(__int128 *)data;
+	 * The below formulas shows what upper_num and lower_num represents:
+	 *     upper_num = int128_num >> 64;
+	 *     lower_num = int128_num & 0xffffffffFFFFFFFFULL;
+	 */
+	__u64 upper_num, lower_num;
+
+#ifdef __BIG_ENDIAN_BITFIELD
+	upper_num = *(__u64 *)data;
+	lower_num = *(__u64 *)(data + 8);
+#else
+	upper_num = *(__u64 *)(data + 8);
+	lower_num = *(__u64 *)data;
+#endif
+	if (upper_num == 0)
+		btf_dump_emit_type_value(d, "0x%llx", lower_num);
+	else
+		btf_dump_emit_type_values(d, "0x%llx%016llx", upper_num,
+					  lower_num);
+}
+
+static void btf_int128_shift(__u64 *print_num, __u16 left_shift_bits,
+			     __u16 right_shift_bits)
+{
+	__u64 upper_num, lower_num;
+
+#ifdef __BIG_ENDIAN_BITFIELD
+	upper_num = print_num[0];
+	lower_num = print_num[1];
+#else
+	upper_num = print_num[1];
+	lower_num = print_num[0];
+#endif
+
+	/* shake out un-needed bits by shift/or operations */
+	if (left_shift_bits >= 64) {
+		upper_num = lower_num << (left_shift_bits - 64);
+		lower_num = 0;
+	} else {
+		upper_num = (upper_num << left_shift_bits) |
+			    (lower_num >> (64 - left_shift_bits));
+		lower_num = lower_num << left_shift_bits;
+	}
+
+	if (right_shift_bits >= 64) {
+		lower_num = upper_num >> (right_shift_bits - 64);
+		upper_num = 0;
+	} else {
+		lower_num = (lower_num >> right_shift_bits) |
+			    (upper_num << (64 - right_shift_bits));
+		upper_num = upper_num >> right_shift_bits;
+	}
+
+#ifdef __BIG_ENDIAN_BITFIELD
+	print_num[0] = upper_num;
+	print_num[1] = lower_num;
+#else
+	print_num[0] = lower_num;
+	print_num[1] = upper_num;
+#endif
+}
+
+static void btf_dump_emit_bitfield_data(struct btf_dump *d,
+					void *data,
+					__u8 bits_offset,
+					__u8 nr_bits)
+{
+	__u16 left_shift_bits, right_shift_bits;
+	__u8 nr_copy_bytes;
+	__u8 nr_copy_bits;
+	__u64 print_num[2] = {};
+
+	nr_copy_bits = nr_bits + bits_offset;
+	nr_copy_bytes = BITS_ROUNDUP_BYTES(nr_copy_bits);
+
+	memcpy(print_num, data, nr_copy_bytes);
+
+#ifdef __BIG_ENDIAN_BITFIELD
+	left_shift_bits = bits_offset;
+#else
+	left_shift_bits = BITS_PER_U128 - nr_copy_bits;
+#endif
+	right_shift_bits = BITS_PER_U128 - nr_bits;
+
+	btf_int128_shift(print_num, left_shift_bits, right_shift_bits);
+	btf_dump_emit_int128(d, print_num);
+}
+
+static void btf_dump_emit_int_bits(struct btf_dump *d,
+				   const struct btf_type *t,
+				   void *data,
+				   __u8 bits_offset)
+{
+	__u32 int_data = btf_int(t);
+	__u8 nr_bits = BTF_INT_BITS(int_data);
+	__u8 total_bits_offset;
+
+	/*
+	 * bits_offset is at most 7.
+	 * BTF_INT_OFFSET() cannot exceed 128 bits.
+	 */
+	total_bits_offset = bits_offset + BTF_INT_OFFSET(int_data);
+	data += BITS_ROUNDDOWN_BYTES(total_bits_offset);
+	bits_offset = BITS_PER_BYTE_MASKED(total_bits_offset);
+	btf_dump_emit_bitfield_data(d, data, bits_offset, nr_bits);
+}
+
+static void btf_dump_emit_int_data(struct btf_dump *d,
+				   const struct btf_type *t,
+				   __u32 type_id,
+				   void *data,
+				   __u8 bits_offset)
+{
+	__u32 int_data = btf_int(t);
+	__u8 encoding = BTF_INT_ENCODING(int_data);
+	bool sign = encoding & BTF_INT_SIGNED;
+	__u8 nr_bits = BTF_INT_BITS(int_data);
+
+	btf_dump_start_type(d, t, type_id);
+
+	if (bits_offset || BTF_INT_OFFSET(int_data) ||
+	    BITS_PER_BYTE_MASKED(nr_bits)) {
+		btf_dump_emit_int_bits(d, t, data, bits_offset);
+		goto out;
+	}
+
+	switch (nr_bits) {
+	case 128:
+		btf_dump_emit_int128(d, data);
+		break;
+	case 64:
+		if (sign)
+			btf_dump_emit_type_value(d, "%lld", *(__s64 *)data);
+		else
+			btf_dump_emit_type_value(d, "%llu", *(__u64 *)data);
+		break;
+	case 32:
+		if (sign)
+			btf_dump_emit_type_value(d, "%d", *(__s32 *)data);
+		else
+			btf_dump_emit_type_value(d, "%u", *(__u32 *)data);
+		break;
+	case 16:
+		if (sign)
+			btf_dump_emit_type_value(d, "%d", *(__s16 *)data);
+		else
+			btf_dump_emit_type_value(d, "%u", *(__u16 *)data);
+		break;
+	case 8:
+		if (d->data.state.array_encoding == BTF_INT_CHAR) {
+			/* check for null terminator */
+			if (d->data.state.array_terminated)
+				break;
+			if (*(char *)data == '\0') {
+				d->data.state.array_terminated = 1;
+				break;
+			}
+			if (isprint(*(char *)data)) {
+				btf_dump_emit_type_value(d, "'%c'",
+							 *(char *)data);
+				break;
+			}
+		}
+		if (sign)
+			btf_dump_emit_type_value(d, "%d", *(__s8 *)data);
+		else
+			btf_dump_emit_type_value(d, "%u", *(__u8 *)data);
+		break;
+	default:
+		btf_dump_emit_int_bits(d, t, data, bits_offset);
+		break;
+	}
+out:
+	btf_dump_end_type(d);
+}
+
+static void btf_dump_emit_modifier_data(struct btf_dump *d,
+					const struct btf_type *t,
+					__u32 id,
+					void *data,
+					__u8 bits_offset)
+{
+	t = skip_mods_and_typedefs(d->btf, id, NULL);
+	__btf_dump_emit_type_data(d, t, id, data, bits_offset);
+}
+
+static void btf_dump_emit_var_data(struct btf_dump *d,
+				   const struct btf_type *t,
+				   __u32 id,
+				   void *data,
+				   __u8 bits_offset)
+{
+	__u32 linkage = btf_var(t)->linkage;
+
+	btf_dump_data_printf(d, "%s%s =",
+			     linkage ? "" : "static ",
+			     btf_name_of(d, t->name_off));
+	t = btf__type_by_id(d->btf, t->type);
+	__btf_dump_emit_type_data(d, t, t->type, data, bits_offset);
+}
+
+static void __btf_dump_emit_array_data(struct btf_dump *d,
+				       const struct btf_type *t,
+				       __u32 id,
+				       void *data,
+				       __u8 bits_offset)
+{
+	const struct btf_array *array = btf_array(t);
+	const struct btf_type *elem_type;
+	__u32 i, elem_size = 0, elem_type_id;
+	__u16 encoding = 0;
+
+	elem_type_id = array->type;
+	elem_type = skip_mods_and_typedefs(d->btf, elem_type_id, NULL);
+	if (elem_type && btf_has_size(elem_type))
+		elem_size = elem_type->size;
+
+	if (elem_type && btf_is_int(elem_type)) {
+		__u32 int_type = btf_int(elem_type);
+
+		encoding = BTF_INT_ENCODING(int_type);
+
+		/*
+		 * BTF_INT_CHAR encoding never seems to be set for
+		 * char arrays, so if size is 1 and element is
+		 * printable as a char, we'll do that.
+		 */
+		if (elem_size == 1)
+			encoding = BTF_INT_CHAR;
+	}
+
+	btf_dump_start_array_type(d, t, id, encoding);
+
+	if (!elem_type)
+		goto out;
+
+	for (i = 0; i < array->nelems; i++) {
+
+		btf_dump_start_array_member(d);
+
+		__btf_dump_emit_type_data(d, elem_type, elem_type_id,
+					  data, bits_offset);
+		data += elem_size;
+
+		btf_dump_end_array_member(d);
+
+		if (d->data.state.array_terminated)
+			break;
+	}
+out:
+	btf_dump_end_array_type(d);
+}
+
+static void btf_dump_emit_array_data(struct btf_dump *d,
+				     const struct btf_type *t,
+				     __u32 id,
+				     void *data,
+				     __u8 bits_offset)
+{
+	const struct btf_member *m = d->data.state.member;
+
+	/*
+	 * First check if any members would be shown (are non-zero).
+	 * See comments above "struct btf_dump_data" definition for more
+	 * details on how this works at a high-level.
+	 */
+	if (d->data.state.depth > 0 && !d->data.zero) {
+		if (!d->data.state.depth_check) {
+			d->data.state.depth_check = d->data.state.depth + 1;
+			d->data.state.depth_to_show = 0;
+		}
+		__btf_dump_emit_array_data(d, t, id, data, bits_offset);
+		d->data.state.member = m;
+
+		if (d->data.state.depth_check != d->data.state.depth + 1)
+			return;
+		d->data.state.depth_check = 0;
+
+		if (d->data.state.depth_to_show <= d->data.state.depth)
+			return;
+		/*
+		 * Reaching here indicates we have recursed and found
+		 * non-zero array member(s).
+		 */
+	}
+	__btf_dump_emit_array_data(d, t, id, data, bits_offset);
+}
+
+#define for_each_member(i, struct_type, member)			\
+	for (i = 0, member = btf_members(struct_type);		\
+	     i < btf_vlen(struct_type);				\
+	     i++, member++)
+
+static void __btf_dump_emit_struct_data(struct btf_dump *d,
+					const struct btf_type *t,
+					__u32 id,
+					void *data,
+					__u8 bits_offset)
+{
+	const struct btf_member *member;
+	__u32 i;
+
+	btf_dump_start_struct_type(d, t, id);
+
+	for_each_member(i, t, member) {
+		const struct btf_type *member_type;
+		__u32 member_offset, bitfield_size;
+		__u32 bytes_offset;
+		__u8 bits8_offset;
+
+		member_type = btf__type_by_id(d->btf, member->type);
+		btf_dump_start_member(d, member);
+
+		member_offset = btf_member_bit_offset(t, i);
+		bitfield_size = btf_member_bitfield_size(t, i);
+		bytes_offset = BITS_ROUNDDOWN_BYTES(member_offset);
+		bits8_offset = BITS_PER_BYTE_MASKED(member_offset);
+		if (bitfield_size) {
+			btf_dump_start_type(d, member_type, member->type);
+			btf_dump_emit_bitfield_data(d,
+						    data + bytes_offset,
+						    bits8_offset,
+						    bitfield_size);
+			btf_dump_end_type(d);
+		} else {
+			__btf_dump_emit_type_data(d, member_type, member->type,
+					     data + bytes_offset, bits8_offset);
+		}
+		btf_dump_end_member(d);
+	}
+	btf_dump_end_struct_type(d);
+}
+
+static void btf_dump_emit_struct_data(struct btf_dump *d,
+				      const struct btf_type *t,
+				      __u32 id,
+				      void *data,
+				      __u8 bits_offset)
+{
+	const struct btf_member *m = d->data.state.member;
+
+	/*
+	 * First check if any members would be shown (are non-zero).
+	 * See comments above "struct btf_dump_data" definition for more
+	 * details on how this works at a high-level.
+	 */
+	if (d->data.state.depth > 0 && !d->data.zero) {
+		if (!d->data.state.depth_check) {
+			d->data.state.depth_check = d->data.state.depth + 1;
+			d->data.state.depth_to_show = 0;
+		}
+		__btf_dump_emit_struct_data(d, t, id, data, bits_offset);
+		/* Restore saved member data here */
+		d->data.state.member = m;
+		if (d->data.state.depth_check != d->data.state.depth + 1)
+			return;
+		d->data.state.depth_check = 0;
+
+		if (d->data.state.depth_to_show <= d->data.state.depth)
+			return;
+		/*
+		 * Reaching here indicates we have recursed and found
+		 * non-zero child values.
+		 */
+	}
+
+	__btf_dump_emit_struct_data(d, t, id, data, bits_offset);
+}
+
+static void btf_dump_emit_ptr_data(struct btf_dump *d,
+				   const struct btf_type *t,
+				   __u32 id,
+				   void *data,
+				   __u8 bits_offset)
+{
+	btf_dump_start_type(d, t, id);
+
+	btf_dump_emit_type_value(d, "%p", *(void **)data);
+	btf_dump_end_type(d);
+}
+
+static void btf_dump_emit_enum_data(struct btf_dump *d,
+				    const struct btf_type *t,
+				    __u32 id,
+				    void *data,
+				    __u8 bits_offset)
+{
+	const struct btf_enum *enums = btf_enum(t);
+	__s64 value;
+	__u16 i;
+
+	btf_dump_start_type(d, t, id);
+
+	switch (t->size) {
+	case 8:
+		value = *(__s64 *)data;
+		break;
+	case 4:
+		value = *(__s32 *)data;
+		break;
+	case 2:
+		value = *(__s16 *)data;
+		break;
+	case 1:
+		value = *(__s8 *)data;
+		break;
+	default:
+		pr_warn("unexpected size %d for enum, id:[%u]\n", t->size,
+			id);
+		d->data.state.err = -EINVAL;
+		return;
+	}
+
+	for (i = 0; i < btf_vlen(t); i++) {
+		if (value == enums[i].val) {
+			btf_dump_emit_type_value(d, "%s",
+						 btf_name_of(d,
+							     enums[i].name_off));
+			btf_dump_end_type(d);
+			return;
+		}
+	}
+
+	btf_dump_emit_type_value(d, "%d", value);
+	btf_dump_end_type(d);
+}
+
+#define for_each_vsi(i, struct_type, member)			\
+	for (i = 0, member = btf_var_secinfos(struct_type);	\
+	     i < btf_vlen(struct_type);				\
+	     i++, member++)
+
+static void btf_dump_emit_datasec_data(struct btf_dump *d,
+				       const struct btf_type *t,
+				       __u32 id,
+				       void *data,
+				       __u8 bits_offset)
+{
+	const struct btf_var_secinfo *vsi;
+	const struct btf_type *var;
+	__u32 i;
+
+	btf_dump_start_type(d, t, id);
+
+	btf_dump_emit_type_value(d, "section (\"%s\") = {",
+				 btf_name_of(d, t->name_off));
+	for_each_vsi(i, t, vsi) {
+		var = btf__type_by_id(d->btf, vsi->type);
+		if (i)
+			btf_dump_data_printf(d, ",");
+		__btf_dump_emit_type_data(d, var, vsi->type,
+					  data + vsi->offset,
+					  bits_offset);
+	}
+	btf_dump_end_type(d);
+}
+
+typedef void (*btf_dump_emit_kind_data)(struct btf_dump *d,
+					const struct btf_type *t,
+					__u32 id,
+					void *data,
+					__u8 bits_offset);
+
+static btf_dump_emit_kind_data dump_emit_kind_data[NR_BTF_KINDS] = {
+	&btf_dump_emit_df_data,
+	&btf_dump_emit_int_data,
+	&btf_dump_emit_ptr_data,
+	&btf_dump_emit_array_data,
+	&btf_dump_emit_struct_data,
+	&btf_dump_emit_struct_data,
+	&btf_dump_emit_enum_data,
+	&btf_dump_emit_df_data,
+	&btf_dump_emit_modifier_data,
+	&btf_dump_emit_modifier_data,
+	&btf_dump_emit_modifier_data,
+	&btf_dump_emit_modifier_data,
+	&btf_dump_emit_df_data,
+	&btf_dump_emit_df_data,
+	&btf_dump_emit_var_data,
+	&btf_dump_emit_datasec_data,
+};
+
+static void __btf_dump_emit_type_data(struct btf_dump *d,
+				      const struct btf_type *t,
+				      __u32 id,
+				      void *data,
+				      __u8 bits_offset)
+{
+	dump_emit_kind_data[BTF_INFO_KIND(t->info)](d, t, id, data,
+						    bits_offset);
+}
+
+static void btf_dump_emit_type_data(struct btf_dump *d, __u32 id, void *data)
+{
+	const struct btf_type *t = btf__type_by_id(d->btf, id);
+
+	memset(&d->data.state, 0, sizeof(d->data.state));
+
+	if (!t) {
+		pr_warn("no type info, id [%u]\n", id);
+		d->data.state.err = -EINVAL;
+		return;
+	}
+
+	__btf_dump_emit_type_data(d, t, id, data, 0);
+}
+
+int btf_dump__emit_type_data(struct btf_dump *d, __u32 id,
+			     const struct btf_dump_emit_type_data_opts *opts,
+			     void *data)
+{
+	int err;
+
+	if (!OPTS_VALID(opts, btf_dump_emit_type_data_opts))
+		return -EINVAL;
+
+	d->data.indent_lvl = OPTS_GET(opts, indent_level, 0);
+	d->data.compact = OPTS_GET(opts, compact, false);
+	d->data.noname = OPTS_GET(opts, noname, false);
+	d->data.zero = OPTS_GET(opts, zero, false);
+	btf_dump_emit_type_data(d, id, data);
+	err = d->data.state.err;
+	memset(&d->data, 0, sizeof(d->data));
+	return err;
+}
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
index 1c0fd2d..b81c069 100644
--- a/tools/lib/bpf/libbpf.map
+++ b/tools/lib/bpf/libbpf.map
@@ -350,3 +350,8 @@ LIBBPF_0.3.0 {
 		xsk_setup_xdp_prog;
 		xsk_socket__update_xskmap;
 } LIBBPF_0.2.0;
+
+LIBBPF_0.4.0 {
+	global:
+		btf_dump__emit_type_data;
+} LIBBPF_0.3.0;
