The BTF dumper code currently displays arrays of characters as just that -
arrays, with each character formatted individually. Sometimes this is what
makes sense, but it's nice to be able to treat that array as a string.
This change adds a special case to the btf_dump functionality to allow
arrays of single-byte integer values to be printed as character strings.
Characters for which isprint() returns false are printed as hex-escaped
values. This is enabled when the new ".print_strings" is set to 1 in the
btf_dump_type_data_opts structure.
As an example, here's what it looks like to dump the string "hello" using
a few different field values for btf_dump_type_data_opts (.compact = 1):
- .print_strings = 0, .skip_names = 0: (char[6])['h','e','l','l','o',]
- .print_strings = 0, .skip_names = 1: ['h','e','l','l','o',]
- .print_strings = 1, .skip_names = 0: (char[6])"hello"
- .print_strings = 1, .skip_names = 1: "hello"
Here's the string "h\xff", dumped with .compact = 1 and .skip_names = 1:
- .print_strings = 0: ['h',-1,]
- .print_strings = 1: "h\xff"
Signed-off-by: Blake Jones <blakejones@google.com>
---
tools/lib/bpf/btf.h | 3 ++-
tools/lib/bpf/btf_dump.c | 51 +++++++++++++++++++++++++++++++++++++++-
2 files changed, 52 insertions(+), 2 deletions(-)
diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h
index 4392451d634b..be8e8e26d245 100644
--- a/tools/lib/bpf/btf.h
+++ b/tools/lib/bpf/btf.h
@@ -326,9 +326,10 @@ struct btf_dump_type_data_opts {
bool compact; /* no newlines/indentation */
bool skip_names; /* skip member/type names */
bool emit_zeroes; /* show 0-valued fields */
+ bool print_strings; /* print char arrays as strings */
size_t :0;
};
-#define btf_dump_type_data_opts__last_field emit_zeroes
+#define btf_dump_type_data_opts__last_field print_strings
LIBBPF_API int
btf_dump__dump_type_data(struct btf_dump *d, __u32 id,
diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c
index 460c3e57fadb..a07dd5accdd8 100644
--- a/tools/lib/bpf/btf_dump.c
+++ b/tools/lib/bpf/btf_dump.c
@@ -75,6 +75,7 @@ struct btf_dump_data {
bool is_array_member;
bool is_array_terminated;
bool is_array_char;
+ bool print_strings;
};
struct btf_dump {
@@ -2028,6 +2029,50 @@ static int btf_dump_var_data(struct btf_dump *d,
return btf_dump_dump_type_data(d, NULL, t, type_id, data, 0, 0);
}
+static int btf_dump_string_data(struct btf_dump *d,
+ const struct btf_type *t,
+ __u32 id,
+ const void *data)
+{
+ const struct btf_array *array = btf_array(t);
+ __u32 i;
+
+ if (!btf_is_int(skip_mods_and_typedefs(d->btf, array->type, NULL)) ||
+ btf__resolve_size(d->btf, array->type) != 1 ||
+ !d->typed_dump->print_strings) {
+ pr_warn("unexpected %s() call for array type %u\n",
+ __func__, array->type);
+ return -EINVAL;
+ }
+
+ btf_dump_data_pfx(d);
+ btf_dump_printf(d, "\"");
+
+ for (i = 0; i < array->nelems; i++, data++) {
+ char c;
+
+ if (data >= d->typed_dump->data_end)
+ return -E2BIG;
+
+ c = *(char *)data;
+ if (c == '\0') {
+ /* When printing character arrays as strings, NUL bytes
+ * are always treated as string terminators; they are
+ * never printed.
+ */
+ break;
+ }
+ if (isprint(c))
+ btf_dump_printf(d, "%c", c);
+ else
+ btf_dump_printf(d, "\\x%02x", *(__u8 *)data);
+ }
+
+ btf_dump_printf(d, "\"");
+
+ return 0;
+}
+
static int btf_dump_array_data(struct btf_dump *d,
const struct btf_type *t,
__u32 id,
@@ -2055,8 +2100,11 @@ static int btf_dump_array_data(struct btf_dump *d,
* char arrays, so if size is 1 and element is
* printable as a char, we'll do that.
*/
- if (elem_size == 1)
+ if (elem_size == 1) {
+ if (d->typed_dump->print_strings)
+ return btf_dump_string_data(d, t, id, data);
d->typed_dump->is_array_char = true;
+ }
}
/* note that we increment depth before calling btf_dump_print() below;
@@ -2544,6 +2592,7 @@ int btf_dump__dump_type_data(struct btf_dump *d, __u32 id,
d->typed_dump->compact = OPTS_GET(opts, compact, false);
d->typed_dump->skip_names = OPTS_GET(opts, skip_names, false);
d->typed_dump->emit_zeroes = OPTS_GET(opts, emit_zeroes, false);
+ d->typed_dump->print_strings = OPTS_GET(opts, print_strings, false);
ret = btf_dump_dump_type_data(d, NULL, t, id, data, 0, 0);
--
2.49.0.1143.g0be31eac6b-goog
On Wed, May 21, 2025 at 03:27:23PM -0700, Blake Jones wrote:
> The BTF dumper code currently displays arrays of characters as just that -
> arrays, with each character formatted individually. Sometimes this is what
> makes sense, but it's nice to be able to treat that array as a string.
>
> This change adds a special case to the btf_dump functionality to allow
> arrays of single-byte integer values to be printed as character strings.
> Characters for which isprint() returns false are printed as hex-escaped
> values. This is enabled when the new ".print_strings" is set to 1 in the
> btf_dump_type_data_opts structure.
>
> As an example, here's what it looks like to dump the string "hello" using
> a few different field values for btf_dump_type_data_opts (.compact = 1):
>
> - .print_strings = 0, .skip_names = 0: (char[6])['h','e','l','l','o',]
> - .print_strings = 0, .skip_names = 1: ['h','e','l','l','o',]
> - .print_strings = 1, .skip_names = 0: (char[6])"hello"
> - .print_strings = 1, .skip_names = 1: "hello"
>
> Here's the string "h\xff", dumped with .compact = 1 and .skip_names = 1:
>
> - .print_strings = 0: ['h',-1,]
> - .print_strings = 1: "h\xff"
I'll test this but unsure if this part should go thru the perf tool
tree, perhaps should go, together with some test case, via the libbpf
tree?
- Arnaldo
> Signed-off-by: Blake Jones <blakejones@google.com>
> ---
> tools/lib/bpf/btf.h | 3 ++-
> tools/lib/bpf/btf_dump.c | 51 +++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 52 insertions(+), 2 deletions(-)
>
> diff --git a/tools/lib/bpf/btf.h b/tools/lib/bpf/btf.h
> index 4392451d634b..be8e8e26d245 100644
> --- a/tools/lib/bpf/btf.h
> +++ b/tools/lib/bpf/btf.h
> @@ -326,9 +326,10 @@ struct btf_dump_type_data_opts {
> bool compact; /* no newlines/indentation */
> bool skip_names; /* skip member/type names */
> bool emit_zeroes; /* show 0-valued fields */
> + bool print_strings; /* print char arrays as strings */
> size_t :0;
> };
> -#define btf_dump_type_data_opts__last_field emit_zeroes
> +#define btf_dump_type_data_opts__last_field print_strings
>
> LIBBPF_API int
> btf_dump__dump_type_data(struct btf_dump *d, __u32 id,
> diff --git a/tools/lib/bpf/btf_dump.c b/tools/lib/bpf/btf_dump.c
> index 460c3e57fadb..a07dd5accdd8 100644
> --- a/tools/lib/bpf/btf_dump.c
> +++ b/tools/lib/bpf/btf_dump.c
> @@ -75,6 +75,7 @@ struct btf_dump_data {
> bool is_array_member;
> bool is_array_terminated;
> bool is_array_char;
> + bool print_strings;
> };
>
> struct btf_dump {
> @@ -2028,6 +2029,50 @@ static int btf_dump_var_data(struct btf_dump *d,
> return btf_dump_dump_type_data(d, NULL, t, type_id, data, 0, 0);
> }
>
> +static int btf_dump_string_data(struct btf_dump *d,
> + const struct btf_type *t,
> + __u32 id,
> + const void *data)
> +{
> + const struct btf_array *array = btf_array(t);
> + __u32 i;
> +
> + if (!btf_is_int(skip_mods_and_typedefs(d->btf, array->type, NULL)) ||
> + btf__resolve_size(d->btf, array->type) != 1 ||
> + !d->typed_dump->print_strings) {
> + pr_warn("unexpected %s() call for array type %u\n",
> + __func__, array->type);
> + return -EINVAL;
> + }
> +
> + btf_dump_data_pfx(d);
> + btf_dump_printf(d, "\"");
> +
> + for (i = 0; i < array->nelems; i++, data++) {
> + char c;
> +
> + if (data >= d->typed_dump->data_end)
> + return -E2BIG;
> +
> + c = *(char *)data;
> + if (c == '\0') {
> + /* When printing character arrays as strings, NUL bytes
> + * are always treated as string terminators; they are
> + * never printed.
> + */
> + break;
> + }
> + if (isprint(c))
> + btf_dump_printf(d, "%c", c);
> + else
> + btf_dump_printf(d, "\\x%02x", *(__u8 *)data);
> + }
> +
> + btf_dump_printf(d, "\"");
> +
> + return 0;
> +}
> +
> static int btf_dump_array_data(struct btf_dump *d,
> const struct btf_type *t,
> __u32 id,
> @@ -2055,8 +2100,11 @@ static int btf_dump_array_data(struct btf_dump *d,
> * char arrays, so if size is 1 and element is
> * printable as a char, we'll do that.
> */
> - if (elem_size == 1)
> + if (elem_size == 1) {
> + if (d->typed_dump->print_strings)
> + return btf_dump_string_data(d, t, id, data);
> d->typed_dump->is_array_char = true;
> + }
> }
>
> /* note that we increment depth before calling btf_dump_print() below;
> @@ -2544,6 +2592,7 @@ int btf_dump__dump_type_data(struct btf_dump *d, __u32 id,
> d->typed_dump->compact = OPTS_GET(opts, compact, false);
> d->typed_dump->skip_names = OPTS_GET(opts, skip_names, false);
> d->typed_dump->emit_zeroes = OPTS_GET(opts, emit_zeroes, false);
> + d->typed_dump->print_strings = OPTS_GET(opts, print_strings, false);
>
> ret = btf_dump_dump_type_data(d, NULL, t, id, data, 0, 0);
>
> --
> 2.49.0.1143.g0be31eac6b-goog
Hi Arnaldo, On Thu, May 22, 2025 at 1:42 PM Arnaldo Carvalho de Melo <acme@kernel.org> wrote: > I'll test this but unsure if this part should go thru the perf tool > tree, perhaps should go, together with some test case, via the libbpf > tree? Thanks for taking a look at this. I'd appreciate your guidance here - I sent it here because the other two patches in my patch set depend on this one. Blake
Hi Arnaldo, On Thu, May 22, 2025 at 11:19 AM Blake Jones <blakejones@google.com> wrote: > On Thu, May 22, 2025 at 1:42 PM Arnaldo Carvalho de Melo > <acme@kernel.org> wrote: > > I'll test this but unsure if this part should go thru the perf tool > > tree, perhaps should go, together with some test case, via the libbpf > > tree? > > Thanks for taking a look at this. I'd appreciate your guidance here - I > sent it here because the other two patches in my patch set depend on this > one. Do you think this can be merged through the perf tree, or should I separate this patch and send it though the BPF tree first?
Hi Blake, On Wed, May 28, 2025 at 05:58:32PM -0700, Blake Jones wrote: > Hi Arnaldo, > > On Thu, May 22, 2025 at 11:19 AM Blake Jones <blakejones@google.com> wrote: > > On Thu, May 22, 2025 at 1:42 PM Arnaldo Carvalho de Melo > > <acme@kernel.org> wrote: > > > I'll test this but unsure if this part should go thru the perf tool > > > tree, perhaps should go, together with some test case, via the libbpf > > > tree? > > > > Thanks for taking a look at this. I'd appreciate your guidance here - I > > sent it here because the other two patches in my patch set depend on this > > one. > > Do you think this can be merged through the perf tree, or should I separate > this patch and send it though the BPF tree first? I think it's better to go to the bpf tree although it'd take longer to get your perf patches. Thanks, Namhyung
Hi Namhyung, On Fri, May 30, 2025 at 10:40 AM Namhyung Kim <namhyung@kernel.org> wrote: > I think it's better to go to the bpf tree although it'd take longer to > get your perf patches. Thanks for the suggestion. I've sent this patch to the bpf tree, and I'll resend the rest of this series once that change makes its way to this tree. Blake
The libbpf patch set is under discussion right now. Once it converges, is there a way to include those patches in the perf tree without waiting for them to go up to the main tree and then back down? Could I resend them here, or include them as the first part of my next patch series? Thanks in advance for the guidance. Blake On Sat, May 31, 2025 at 12:26 AM Blake Jones <blakejones@google.com> wrote: > > Hi Namhyung, > > On Fri, May 30, 2025 at 10:40 AM Namhyung Kim <namhyung@kernel.org> wrote: > > I think it's better to go to the bpf tree although it'd take longer to > > get your perf patches. > > Thanks for the suggestion. I've sent this patch to the bpf tree, and I'll > resend the rest of this series once that change makes its way to this tree. > > Blake
On Tue, Jun 3, 2025 at 11:24 AM Blake Jones <blakejones@google.com> wrote: > > The libbpf patch set is under discussion right now. Once it converges, > is there a way to include those patches in the perf tree without > waiting for them to go up to the main tree and then back down? Could I > resend them here, or include them as the first part of my next patch > series? Not really. libbpf is developed in kernel tree, sync to github and released from github.
On Tue, Jun 03, 2025 at 11:43:29AM -0700, Alexei Starovoitov wrote: > On Tue, Jun 3, 2025 at 11:24 AM Blake Jones <blakejones@google.com> wrote: > > > > The libbpf patch set is under discussion right now. Once it converges, > > is there a way to include those patches in the perf tree without > > waiting for them to go up to the main tree and then back down? Could I > > resend them here, or include them as the first part of my next patch > > series? > > Not really. libbpf is developed in kernel tree, sync to github > and released from github. But you can continue to work on the perf side after adding a feature test. I think we can accept the change once the libbpf change is finalized. It won't actually work until the both changes are merged to the mainline in the next cycle, but you can test it locally. Thanks, Namhyung
© 2016 - 2025 Red Hat, Inc.