* [PATCH bpf-next v12 0/3] bpftool: Add recursive map dumping
@ 2026-11-11 7:19 Tianyi Chen
2026-11-11 7:19 ` [PATCH bpf-next v12 1/3] bpftool: Reset map info before each query in show and dump loops Tianyi Chen
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Tianyi Chen @ 2026-11-11 7:19 UTC (permalink / raw)
To: bpf
Cc: Tianyi Chen, ast, daniel, andrii, eddyz87, memxor, shuah,
linux-kselftest, qmo, ihor.solodrai
Dump each inner map in the entry that references it. This series is
based on the batch dump v10 series [1]; apply that series first.
Changes in v12:
- Separate plain inner-map dumps with blank lines and indent raw
entries and counts, including wrapped and per-CPU values.
- Read outer entries individually during recursion; keep batch lookups
for inner maps.
- Use LIBBPF_OPTS in test_unchanged() and check formatting and lookup
counts. Size the per-CPU layout buffer from the possible CPU count.
All 33 subtests in the combined batch/dump/access run, including the
11 recursive subtests, pass in x86-64 KVM with both 2 and 128 possible
CPUs (2 CPUs online). The old combined bpftool fails the formatting and
outer-lookup checks. Non-recursive output is unchanged.
[1] https://lore.kernel.org/r/cover.1794380384.git.hi@tychen.cc
v11: https://lore.kernel.org/r/cover.1793818386.git.hi@tychen.cc
Tianyi Chen (3):
bpftool: Reset map info before each query in show and dump loops
bpftool: Add recursive map dumping
selftests/bpf: Cover recursive bpftool map dumps
.../bpf/bpftool/Documentation/bpftool-map.rst | 10 +-
tools/bpf/bpftool/bash-completion/bpftool | 3 +
tools/bpf/bpftool/map.c | 131 ++++-
.../prog_tests/bpftool_map_dump_recursive.c | 480 ++++++++++++++++++
.../bpf/progs/bpftool_map_dump_recursive.c | 38 ++
5 files changed, 639 insertions(+), 23 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_map_dump_recursive.c
create mode 100644 tools/testing/selftests/bpf/progs/bpftool_map_dump_recursive.c
base-commit: 0fb18ce8a2e9de830433923cf65838933d615359
prerequisite-patch-id: f2cb564c4555c7d7f3c87a7e9827a6e91816affd
prerequisite-patch-id: b8e7968eb997646d032c3489c5462230ee5837e6
--
2.56.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH bpf-next v12 1/3] bpftool: Reset map info before each query in show and dump loops
2026-11-11 7:19 [PATCH bpf-next v12 0/3] bpftool: Add recursive map dumping Tianyi Chen
@ 2026-11-11 7:19 ` Tianyi Chen
2026-11-11 7:19 ` [PATCH bpf-next v12 2/3] bpftool: Add recursive map dumping Tianyi Chen
2026-11-11 7:19 ` [PATCH bpf-next v12 3/3] selftests/bpf: Cover recursive bpftool map dumps Tianyi Chen
2 siblings, 0 replies; 6+ messages in thread
From: Tianyi Chen @ 2026-11-11 7:19 UTC (permalink / raw)
To: bpf
Cc: Tianyi Chen, ast, daniel, andrii, eddyz87, memxor, shuah,
linux-kselftest, qmo, ihor.solodrai
On kernels that copy bpf_map_info from userspace without clearing output
fields, reusing the structure for a map without BTF retains the preceding
map's BTF IDs.
Clear the structure and restore its length before each query in map
listing, named-map listing, BTF detection and dumping. This prevents
stale BTF IDs and formatting a map with another map's key and value types,
including when bpftool runs on kernels without the kernel-side fix.
Fixes: 71bb428fe2c1 ("tools: bpf: add bpftool")
Fixes: 99f9863a0c45 ("bpftool: Match maps by name")
Reported-by: Quentin Monnet <qmo@kernel.org>
Link: https://lore.kernel.org/r/3acbe829-06f0-4df4-b57c-a8c7b802ec96@kernel.org
Acked-by: Quentin Monnet <qmo@kernel.org>
Assisted-by: LLM
Signed-off-by: Tianyi Chen <hi@tychen.cc>
---
tools/bpf/bpftool/map.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c
index c5c56323f062..9e4dc3881060 100644
--- a/tools/bpf/bpftool/map.c
+++ b/tools/bpf/bpftool/map.c
@@ -650,6 +650,8 @@ static int do_show_subset(int argc, char **argv)
if (json_output && nb_fds > 1)
jsonw_start_array(json_wtr); /* root array */
for (i = 0; i < nb_fds; i++) {
+ memset(&info, 0, sizeof(info));
+ len = sizeof(info);
err = bpf_map_get_info_by_fd(fds[i], &info, &len);
if (err) {
p_err("can't get map info: %s",
@@ -721,6 +723,8 @@ static int do_show(int argc, char **argv)
break;
}
+ memset(&info, 0, sizeof(info));
+ len = sizeof(info);
err = bpf_map_get_info_by_fd(fd, &info, &len);
if (err) {
p_err("can't get map info: %s", strerror(errno));
@@ -837,6 +841,8 @@ static int maps_have_btf(int *fds, int nb_fds)
int err, i;
for (i = 0; i < nb_fds; i++) {
+ memset(&info, 0, sizeof(info));
+ len = sizeof(info);
err = bpf_map_get_info_by_fd(fds[i], &info, &len);
if (err) {
p_err("can't get map info: %s", strerror(errno));
@@ -1038,6 +1044,8 @@ static int do_dump(int argc, char **argv)
if (wtr && nb_fds > 1)
jsonw_start_array(wtr); /* root array */
for (i = 0; i < nb_fds; i++) {
+ memset(&info, 0, sizeof(info));
+ len = sizeof(info);
if (bpf_map_get_info_by_fd(fds[i], &info, &len)) {
p_err("can't get map info: %s", strerror(errno));
err = -1;
--
2.56.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH bpf-next v12 2/3] bpftool: Add recursive map dumping
2026-11-11 7:19 [PATCH bpf-next v12 0/3] bpftool: Add recursive map dumping Tianyi Chen
2026-11-11 7:19 ` [PATCH bpf-next v12 1/3] bpftool: Reset map info before each query in show and dump loops Tianyi Chen
@ 2026-11-11 7:19 ` Tianyi Chen
2026-11-11 16:24 ` Quentin Monnet
2026-11-11 7:19 ` [PATCH bpf-next v12 3/3] selftests/bpf: Cover recursive bpftool map dumps Tianyi Chen
2 siblings, 1 reply; 6+ messages in thread
From: Tianyi Chen @ 2026-11-11 7:19 UTC (permalink / raw)
To: bpf
Cc: Tianyi Chen, ast, daniel, andrii, eddyz87, memxor, shuah,
linux-kselftest, qmo, ihor.solodrai
Dumping an array_of_maps or a hash_of_maps shows the IDs of the inner
maps but not their contents. Add a recursive keyword to map dump that
also dumps each inner map in the entry that references it. JSON gains
an "inner_map" object next to "inner_map_id". Plain output separates
inner maps with blank lines and indents their raw entries and element
counts. Output without the keyword is unchanged.
The kernel permits only one map-in-map level. Look up outer entries
individually, then open, dump and close each referenced inner map
immediately. This uses one extra map FD; inner dumps may still use batch
lookups. Shared inner maps are dumped at every referring entry. If an
inner map has disappeared, report the error in that entry and continue.
Link: https://github.com/libbpf/bpftool/issues/58
Suggested-by: Alexei Starovoitov <ast@kernel.org>
Assisted-by: LLM
Signed-off-by: Tianyi Chen <hi@tychen.cc>
---
.../bpf/bpftool/Documentation/bpftool-map.rst | 10 +-
tools/bpf/bpftool/bash-completion/bpftool | 3 +
tools/bpf/bpftool/map.c | 123 +++++++++++++++---
3 files changed, 113 insertions(+), 23 deletions(-)
diff --git a/tools/bpf/bpftool/Documentation/bpftool-map.rst b/tools/bpf/bpftool/Documentation/bpftool-map.rst
index e4ed7e701c9e..92706668df76 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-map.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-map.rst
@@ -29,7 +29,7 @@ MAP COMMANDS
| **bpftool** **map create** *FILE* **type** *TYPE* **key** *KEY_SIZE* **value** *VALUE_SIZE* \
| **entries** *MAX_ENTRIES* **name** *NAME* [**flags** *FLAGS*] [**inner_map** *MAP*] \
| [**offload_dev** *NAME*]
-| **bpftool** **map dump** *MAP*
+| **bpftool** **map dump** *MAP* [**recursive**]
| **bpftool** **map update** *MAP* [**key** *DATA*] [**value** *VALUE*] [*UPDATE_FLAGS*]
| **bpftool** **map lookup** *MAP* [**key** *DATA*]
| **bpftool** **map getnext** *MAP* [**key** *DATA*]
@@ -87,10 +87,16 @@ bpftool map create *FILE* type *TYPE* key *KEY_SIZE* value *VALUE_SIZE* entries
Keyword **offload_dev** expects a network interface name, and is used to
request hardware offload for the map.
-bpftool map dump *MAP*
+bpftool map dump *MAP* [recursive]
Dump all entries in a given *MAP*. In case of **name**, *MAP* may match
several maps which will all be dumped.
+ With **recursive**, each entry of an **array_of_maps** or **hash_of_maps**
+ also has the dump of the inner map that it refers to. Plain output
+ separates inner maps with blank lines and indents their entries. An inner
+ map that cannot be opened, for example because it was replaced, is
+ reported as an error in that entry.
+
bpftool map update *MAP* [key *DATA*] [value *VALUE*] [*UPDATE_FLAGS*]
Update map entry for a given *KEY*.
diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool
index a28af31937ad..78fdb9396e96 100644
--- a/tools/bpf/bpftool/bash-completion/bpftool
+++ b/tools/bpf/bpftool/bash-completion/bpftool
@@ -718,6 +718,9 @@ _bpftool()
return 0
;;
*)
+ if [[ $command == dump && $cword -eq 5 ]]; then
+ COMPREPLY=( $( compgen -W 'recursive' -- "$cur" ) )
+ fi
return 0
;;
esac
diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c
index 9e4dc3881060..79d33af257b6 100644
--- a/tools/bpf/bpftool/map.c
+++ b/tools/bpf/bpftool/map.c
@@ -22,6 +22,10 @@
#include "main.h"
static struct hashmap *map_table;
+static bool recursive_dump;
+static const char *map_dump_prefix = "";
+
+static void dump_inner_map(__u32 id);
static bool map_is_per_cpu(__u32 type)
{
@@ -143,9 +147,14 @@ static void print_entry_json(struct bpf_map_info *info, unsigned char *key,
print_hex_data_json(key, info->key_size);
jsonw_name(json_wtr, "value");
print_hex_data_json(value, info->value_size);
- if (map_is_map_of_maps(info->type))
+ if (map_is_map_of_maps(info->type)) {
jsonw_uint_field(json_wtr, "inner_map_id",
*(unsigned int *)value);
+ if (recursive_dump) {
+ jsonw_name(json_wtr, "inner_map");
+ dump_inner_map(*(unsigned int *)value);
+ }
+ }
if (btf) {
struct btf_dumper d = {
.btf = btf,
@@ -194,6 +203,17 @@ static void print_entry_json(struct bpf_map_info *info, unsigned char *key,
jsonw_end_object(json_wtr);
}
+static void print_hex_data_plain(void *data, unsigned int size)
+{
+ unsigned int i;
+
+ for (i = 0; i < size; i += 16) {
+ if (i)
+ printf("\n%s", map_dump_prefix);
+ fprint_hex(stdout, data + i, min(size - i, 16U), " ");
+ }
+}
+
static void
print_entry_error_msg(struct bpf_map_info *info, unsigned char *key,
const char *error_msg)
@@ -204,12 +224,15 @@ print_entry_error_msg(struct bpf_map_info *info, unsigned char *key,
break_names = info->key_size > 16 || msg_size > 16;
single_line = info->key_size + msg_size <= 24 && !break_names;
- printf("key:%c", break_names ? '\n' : ' ');
- fprint_hex(stdout, key, info->key_size, " ");
+ printf("%skey:%c%s", map_dump_prefix, break_names ? '\n' : ' ',
+ break_names ? map_dump_prefix : "");
+ print_hex_data_plain(key, info->key_size);
printf(single_line ? " " : "\n");
- printf("value:%c%s", break_names ? '\n' : ' ', error_msg);
+ printf("%svalue:%c%s%s", single_line ? "" : map_dump_prefix,
+ break_names ? '\n' : ' ', break_names ? map_dump_prefix : "",
+ error_msg);
printf("\n");
}
@@ -259,23 +282,30 @@ static void print_entry_plain(struct bpf_map_info *info, unsigned char *key,
!break_names;
if (info->key_size) {
- printf("key:%c", break_names ? '\n' : ' ');
- fprint_hex(stdout, key, info->key_size, " ");
+ printf("%skey:%c%s", map_dump_prefix,
+ break_names ? '\n' : ' ', break_names ? map_dump_prefix : "");
+ print_hex_data_plain(key, info->key_size);
printf(single_line ? " " : "\n");
}
if (info->value_size) {
+ if (!info->key_size || !single_line)
+ printf("%s", map_dump_prefix);
if (map_is_map_of_maps(info->type)) {
- printf("inner_map_id:%c", break_names ? '\n' : ' ');
+ printf("inner_map_id:%c%s", break_names ? '\n' : ' ',
+ break_names ? map_dump_prefix : "");
printf("%u ", *(unsigned int *)value);
} else {
- printf("value:%c", break_names ? '\n' : ' ');
- fprint_hex(stdout, value, info->value_size, " ");
+ printf("value:%c%s", break_names ? '\n' : ' ',
+ break_names ? map_dump_prefix : "");
+ print_hex_data_plain(value, info->value_size);
}
}
printf("\n");
+ if (recursive_dump && map_is_map_of_maps(info->type))
+ dump_inner_map(*(unsigned int *)value);
} else {
unsigned int step;
int i;
@@ -283,16 +313,16 @@ static void print_entry_plain(struct bpf_map_info *info, unsigned char *key,
step = round_up(info->value_size, 8);
if (info->key_size) {
- printf("key:\n");
- fprint_hex(stdout, key, info->key_size, " ");
+ printf("%skey:\n%s", map_dump_prefix, map_dump_prefix);
+ print_hex_data_plain(key, info->key_size);
printf("\n");
}
if (info->value_size) {
for (i = 0; i < cpu_cnt; i++) {
- printf("value (CPU %02d):%c",
- cpu_ids[i], info->value_size > 16 ? '\n' : ' ');
- fprint_hex(stdout, value + i * step,
- info->value_size, " ");
+ printf("%svalue (CPU %02d):%c%s", map_dump_prefix,
+ cpu_ids[i], info->value_size > 16 ? '\n' : ' ',
+ info->value_size > 16 ? map_dump_prefix : "");
+ print_hex_data_plain(value + i * step, info->value_size);
printf("\n");
}
}
@@ -954,6 +984,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
}
jsonw_start_array(wtr); /* elements */
} else if (show_header) {
+ printf("%s", map_dump_prefix);
show_map_header_plain(info);
}
@@ -965,8 +996,10 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
p_info("Warning: cannot read values from %s map with value_size != 8",
map_type_str);
}
- done = dump_map_batch(fd, key, value, info, btf, wtr, cpu_ids, cpu_cnt,
- &num_elems);
+ /* Open each inner map immediately after looking up its ID. */
+ if (!recursive_dump || !map_is_map_of_maps(info->type))
+ done = dump_map_batch(fd, key, value, info, btf, wtr, cpu_ids, cpu_cnt,
+ &num_elems);
if (num_elems)
prev_key = key;
while (!done) {
@@ -990,7 +1023,7 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
if (show_header)
jsonw_end_object(wtr); /* map object */
} else if (!err) {
- printf("Found %u element%s\n", num_elems,
+ printf("%sFound %u element%s\n", map_dump_prefix, num_elems,
num_elems != 1 ? "s" : "");
}
@@ -1003,6 +1036,43 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
return err;
}
+/* The kernel has one level of map-in-map: inner maps have no inner maps. */
+static void dump_inner_map(__u32 id)
+{
+ LIBBPF_OPTS(bpf_get_fd_by_id_opts, opts, .open_flags = BPF_F_RDONLY);
+ struct bpf_map_info info = {};
+ json_writer_t *btf_wtr = NULL;
+ __u32 len = sizeof(info);
+ int fd;
+
+ fd = bpf_map_get_fd_by_id_opts(id, &opts);
+ if (fd < 0) {
+ p_err("can't get map by id (%u): %s", id, strerror(errno));
+ return;
+ }
+ if (bpf_map_get_info_by_fd(fd, &info, &len)) {
+ p_err("can't get map info: %s", strerror(errno));
+ goto out;
+ }
+
+ if (!json_output && info.btf_value_type_id)
+ btf_wtr = get_btf_writer();
+
+ if (!json_output) {
+ printf("\n");
+ map_dump_prefix = " ";
+ }
+ map_dump(fd, &info, json_output ? json_wtr : btf_wtr, true);
+ map_dump_prefix = "";
+
+ if (btf_wtr)
+ jsonw_destroy(&btf_wtr);
+ if (!json_output)
+ printf("\n");
+out:
+ close(fd);
+}
+
static int do_dump(int argc, char **argv)
{
json_writer_t *wtr = NULL, *btf_wtr = NULL;
@@ -1012,8 +1082,8 @@ static int do_dump(int argc, char **argv)
int *fds = NULL;
int err = -1;
- if (argc != 2)
- usage();
+ if (!REQ_ARGS(2))
+ return -1;
fds = malloc(sizeof(int));
if (!fds) {
@@ -1024,6 +1094,16 @@ static int do_dump(int argc, char **argv)
if (nb_fds < 1)
goto exit_free;
+ while (argc) {
+ if (**argv && is_prefix(*argv, "recursive")) {
+ NEXT_ARG();
+ recursive_dump = true;
+ } else {
+ p_err("expected 'recursive', got: '%s'", *argv);
+ goto exit_close;
+ }
+ }
+
if (json_output) {
wtr = json_wtr;
} else {
@@ -1070,6 +1150,7 @@ static int do_dump(int argc, char **argv)
exit_free:
free(fds);
free_btf_vmlinux();
+ recursive_dump = false;
return err;
}
@@ -1578,7 +1659,7 @@ static int do_help(int argc, char **argv)
" %1$s %2$s create FILE type TYPE key KEY_SIZE value VALUE_SIZE \\\n"
" entries MAX_ENTRIES name NAME [flags FLAGS] \\\n"
" [inner_map MAP] [offload_dev NAME]\n"
- " %1$s %2$s dump MAP\n"
+ " %1$s %2$s dump MAP [recursive]\n"
" %1$s %2$s update MAP [key DATA] [value VALUE] [UPDATE_FLAGS]\n"
" %1$s %2$s lookup MAP [key DATA]\n"
" %1$s %2$s getnext MAP [key DATA]\n"
--
2.56.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH bpf-next v12 3/3] selftests/bpf: Cover recursive bpftool map dumps
2026-11-11 7:19 [PATCH bpf-next v12 0/3] bpftool: Add recursive map dumping Tianyi Chen
2026-11-11 7:19 ` [PATCH bpf-next v12 1/3] bpftool: Reset map info before each query in show and dump loops Tianyi Chen
2026-11-11 7:19 ` [PATCH bpf-next v12 2/3] bpftool: Add recursive map dumping Tianyi Chen
@ 2026-11-11 7:19 ` Tianyi Chen
2026-11-11 16:24 ` Quentin Monnet
2 siblings, 1 reply; 6+ messages in thread
From: Tianyi Chen @ 2026-11-11 7:19 UTC (permalink / raw)
To: bpf
Cc: Tianyi Chen, ast, daniel, andrii, eddyz87, memxor, shuah,
linux-kselftest, qmo, ihor.solodrai
Compare recursive dumps of array_of_maps and hash_of_maps with individual
inner-map dumps in JSON and plain output. Cover shared and empty inner
maps, an empty array slot, BTF values, and multiple roots selected by
name. Check indentation of wrapped keys, values and per-CPU entries.
Use fmod_ret to make an inner map unavailable and check that only its
entry reports an error. Count lookup calls to check that outer entries
are read individually while inner maps still use batches. Also check
unchanged output without recursion and reject invalid arguments.
Assisted-by: LLM
Signed-off-by: Tianyi Chen <hi@tychen.cc>
---
.../prog_tests/bpftool_map_dump_recursive.c | 480 ++++++++++++++++++
.../bpf/progs/bpftool_map_dump_recursive.c | 38 ++
2 files changed, 518 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_map_dump_recursive.c
create mode 100644 tools/testing/selftests/bpf/progs/bpftool_map_dump_recursive.c
diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump_recursive.c b/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump_recursive.c
new file mode 100644
index 000000000000..bc82d1cd5645
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_map_dump_recursive.c
@@ -0,0 +1,480 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <stdarg.h>
+#include <test_progs.h>
+#include <bpftool_helpers.h>
+#include <bpf/btf.h>
+#include "bpftool_map_dump_recursive.skel.h"
+
+#define OUTPUT_SIZE 16384
+
+static bool run(char *output, const char *fmt, ...)
+{
+ char command[MAX_BPFTOOL_CMD_LEN];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(command, sizeof(command), fmt, ap);
+ va_end(ap);
+ output[0] = '\0';
+ return ASSERT_OK(get_bpftool_command_output(command, output, OUTPUT_SIZE),
+ command) &&
+ ASSERT_LT(strlen(output), OUTPUT_SIZE - 1, "output_fits");
+}
+
+static bool append(char *buf, size_t *len, const char *fmt, ...)
+{
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = vsnprintf(buf + *len, OUTPUT_SIZE - *len, fmt, ap);
+ va_end(ap);
+ if (!ASSERT_GE(n, 0, "format") || !ASSERT_LT(n, OUTPUT_SIZE - *len, "expected_fits"))
+ return false;
+ *len += n;
+ return true;
+}
+
+static int count_token(const char *output, const char *token)
+{
+ int count = 0;
+
+ while ((output = strstr(output, token))) {
+ count++;
+ output += strlen(token);
+ }
+ return count;
+}
+
+static __u32 map_id(int fd)
+{
+ struct bpf_map_info info = {};
+ __u32 len = sizeof(info);
+
+ if (!ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &len), "map_info"))
+ return 0;
+ return info.id;
+}
+
+static bool map_info(__u32 id, struct bpf_map_info *info)
+{
+ __u32 len = sizeof(*info);
+ int fd, err;
+
+ memset(info, 0, sizeof(*info));
+ fd = bpf_map_get_fd_by_id(id);
+ if (!ASSERT_OK_FD(fd, "map_fd"))
+ return false;
+ err = bpf_map_get_info_by_fd(fd, info, &len);
+ close(fd);
+ return ASSERT_OK(err, "map_info");
+}
+
+/* Expect an "inner_map" member after each "inner_map_id" of a default dump. */
+static bool expect_json(const char *dump, __u32 hidden, char *expected)
+{
+ const char *tag = "\"inner_map_id\":", *p;
+ char inner[OUTPUT_SIZE];
+ struct bpf_map_info info;
+ size_t len = 0;
+ char *end;
+ __u32 id;
+
+ while ((p = strstr(dump, tag))) {
+ id = strtoul(p + strlen(tag), &end, 10);
+ if (!append(expected, &len, "%.*s,\"inner_map\":", (int)(end - dump), dump))
+ return false;
+ dump = end;
+ if (id == hidden) {
+ if (!append(expected, &len, "{\"error\":\"can't get map by id (%u): %s\"}",
+ id, strerror(ENOENT)))
+ return false;
+ continue;
+ }
+ if (!map_info(id, &info) || !run(inner, "-j map dump id %u", id))
+ return false;
+ inner[strcspn(inner, "\n")] = '\0';
+ if (!append(expected, &len,
+ "{\"id\":%u,\"type\":\"%s\",\"name\":\"%s\",\"flags\":%u,\"elements\":%s}",
+ id, libbpf_bpf_map_type_str(info.type), info.name,
+ info.map_flags, inner))
+ return false;
+ }
+ return append(expected, &len, "%s", dump);
+}
+
+/* Expect the inner map after each "inner_map_id" line of a default dump. */
+static bool expect_plain(const char *dump, __u32 hidden, char *expected)
+{
+ const char *tag = "inner_map_id: ", *p, *eol, *line;
+ char inner[OUTPUT_SIZE];
+ struct bpf_map_info info;
+ size_t len = 0;
+ __u32 id;
+
+ while ((p = strstr(dump, tag))) {
+ id = strtoul(p + strlen(tag), NULL, 10);
+ eol = strchr(p, '\n');
+ if (!ASSERT_OK_PTR(eol, "entry_line") ||
+ !append(expected, &len, "%.*s", (int)(eol + 1 - dump), dump))
+ return false;
+ dump = eol + 1;
+ if (id == hidden)
+ continue;
+ if (!map_info(id, &info) || !run(inner, "map dump id %u", id) ||
+ !append(expected, &len, "\n %u: %s name %s flags 0x%x\n", id,
+ libbpf_bpf_map_type_str(info.type), info.name,
+ info.map_flags))
+ return false;
+ for (line = inner; *line; line = eol + 1) {
+ eol = strchr(line, '\n');
+ if (!ASSERT_OK_PTR(eol, "inner_line") ||
+ !append(expected, &len, " %.*s\n", (int)(eol - line), line))
+ return false;
+ }
+ if (!append(expected, &len, "\n"))
+ return false;
+ }
+ return append(expected, &len, "%s", dump);
+}
+
+static void check_dumps(const char *handle, __u32 hidden, bool plain)
+{
+ char dump[OUTPUT_SIZE], expected[OUTPUT_SIZE], output[OUTPUT_SIZE];
+
+ if (run(dump, "-j map dump %s", handle) &&
+ expect_json(dump, hidden, expected) &&
+ run(output, "-j map dump %s recursive", handle))
+ ASSERT_STREQ(output, expected, "json");
+ if (plain && run(dump, "map dump %s", handle) &&
+ expect_plain(dump, hidden, expected) &&
+ run(output, "map dump %s recursive 2>/dev/null", handle))
+ ASSERT_STREQ(output, expected, "plain");
+}
+
+static int create_inner(const char *name, struct btf **btf, bool populate)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts);
+ __u32 key = 0, value = 0x01010101;
+ int fd;
+
+ if (btf) {
+ *btf = btf__new_empty();
+ if (!ASSERT_OK_PTR(*btf, "btf_new") ||
+ !ASSERT_EQ(btf__add_int(*btf, "unsigned int", 4, 0), 1, "btf_int") ||
+ !ASSERT_OK(btf__load_into_kernel(*btf), "btf_load"))
+ return -1;
+ opts.btf_fd = btf__fd(*btf);
+ opts.btf_key_type_id = 1;
+ opts.btf_value_type_id = 1;
+ }
+ fd = bpf_map_create(BPF_MAP_TYPE_HASH, name, sizeof(key), sizeof(value), 1, &opts);
+ if (!ASSERT_OK_FD(fd, "create_inner"))
+ return -1;
+ if (populate && !ASSERT_OK(bpf_map_update_elem(fd, &key, &value, BPF_ANY),
+ "update_inner")) {
+ close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+/* Entries 0 and 2 share @a, entry 1 refers to @b; arrays leave slot 3 empty. */
+static int create_outer(enum bpf_map_type type, const char *name, int a, int b)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts, .inner_map_fd = a);
+ int inner[] = { a, b, a };
+ __u32 key;
+ int fd;
+
+ fd = bpf_map_create(type, name, sizeof(key), sizeof(__u32), 4, &opts);
+ if (!ASSERT_OK_FD(fd, "create_outer"))
+ return -1;
+ for (key = 0; key < ARRAY_SIZE(inner); key++) {
+ if (!ASSERT_OK(bpf_map_update_elem(fd, &key, &inner[key], BPF_ANY),
+ "update_outer")) {
+ close(fd);
+ return -1;
+ }
+ }
+ return fd;
+}
+
+static void test_outer(enum bpf_map_type type, bool typed_inner)
+{
+ char handle[32], token[96], output[OUTPUT_SIZE];
+ int a = -1, b = -1, outer = -1;
+ struct btf *btf = NULL;
+ __u32 a_id, b_id;
+
+ a = create_inner("rec_a", typed_inner ? &btf : NULL, true);
+ b = create_inner("rec_b", NULL, false);
+ if (a < 0 || b < 0)
+ goto out;
+ outer = create_outer(type, "rec_outer", a, b);
+ if (outer < 0)
+ goto out;
+ snprintf(handle, sizeof(handle), "id %u", map_id(outer));
+ check_dumps(handle, 0, !typed_inner);
+ if (!typed_inner)
+ goto out;
+
+ /* Plain output uses BTF for the inner maps that have it. */
+ a_id = map_id(a);
+ b_id = map_id(b);
+ if (!run(output, "map dump %s recursive", handle))
+ goto out;
+ snprintf(token, sizeof(token), "\"id\": %u,", a_id);
+ ASSERT_EQ(count_token(output, token), 2, "btf_inner_dumps");
+ ASSERT_EQ(count_token(output, "\"value\": 16843009"), 2, "btf_inner_values");
+ snprintf(token, sizeof(token),
+ " %u: hash name rec_b flags 0x0\n Found 0 elements\n",
+ b_id);
+ ASSERT_EQ(count_token(output, token), 1, "plain_inner_dump");
+out:
+ if (outer >= 0)
+ close(outer);
+ if (a >= 0)
+ close(a);
+ if (b >= 0)
+ close(b);
+ btf__free(btf);
+}
+
+static void test_plain_layout(bool percpu)
+{
+ int inner = -1, outer = -1, cpus;
+ char command[64], *output = NULL;
+ __u32 key[5] = {};
+ size_t output_size;
+ void *values;
+
+ cpus = percpu ? libbpf_num_possible_cpus() : 1;
+ if (!ASSERT_GT(cpus, 0, "possible_cpus"))
+ return;
+ values = calloc(cpus, 24);
+ /* Three inner dumps, including per-CPU labels and indentation. */
+ output_size = 1024 + (size_t)cpus * 512;
+ output = malloc(output_size);
+ if (!ASSERT_OK_PTR(values, "values") || !ASSERT_OK_PTR(output, "output"))
+ goto out;
+
+ /* Long keys and values wrap; per-CPU values also have separate labels. */
+ inner = bpf_map_create(percpu ? BPF_MAP_TYPE_PERCPU_ARRAY : BPF_MAP_TYPE_HASH,
+ "rec_layout", percpu ? 4 : sizeof(key), 20, 1, NULL);
+ if (!ASSERT_OK_FD(inner, "create_inner") ||
+ !ASSERT_OK(bpf_map_update_elem(inner, key, values, BPF_ANY), "update_inner"))
+ goto out;
+ outer = create_outer(BPF_MAP_TYPE_HASH_OF_MAPS, "rec_outer", inner, inner);
+ if (outer < 0)
+ goto out;
+ snprintf(command, sizeof(command), "map dump id %u recursive", map_id(outer));
+ if (!ASSERT_OK(get_bpftool_command_output(command, output, output_size), "dump") ||
+ !ASSERT_LT(strlen(output), output_size - 1, "output_fits"))
+ goto out;
+ ASSERT_EQ(count_token(output, "\n key:\n"), 3, "key_labels");
+ ASSERT_EQ(count_token(output, percpu ? "\n value (CPU " : "\n value:\n"),
+ 3 * (percpu ? cpus : 1), "value_labels");
+ ASSERT_EQ(count_token(output,
+ "\n 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n"),
+ 3 * (percpu ? cpus : 2), "first_hex_lines");
+ /* A per-CPU map also prints its four-byte key on a separate line. */
+ ASSERT_EQ(count_token(output, "\n 00 00 00 00\n"),
+ 3 * (percpu ? cpus + 1 : 2), "last_hex_lines");
+ ASSERT_EQ(count_token(output, "\n Found 1 element\n\n"), 3, "inner_counts");
+out:
+ if (outer >= 0)
+ close(outer);
+ if (inner >= 0)
+ close(inner);
+ free(output);
+ free(values);
+}
+
+static void test_batch_lookups(enum bpf_map_type type)
+{
+ const char * const options[] = { "", "-j " };
+ struct bpftool_map_dump_recursive *skel;
+ char output[OUTPUT_SIZE];
+ int inner = -1, outer = -1;
+ __u32 id;
+ size_t i;
+
+ skel = bpftool_map_dump_recursive__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+ return;
+ skel->bss->test_tgid = getpid();
+ if (!ASSERT_OK(bpftool_map_dump_recursive__attach(skel), "skel_attach"))
+ goto out;
+ inner = create_inner("rec_a", NULL, true);
+ if (inner < 0)
+ goto out;
+ outer = create_outer(type, "rec_outer", inner, inner);
+ if (outer < 0)
+ goto out;
+ id = map_id(outer);
+ for (i = 0; i < ARRAY_SIZE(options); i++) {
+ skel->bss->batch_calls = 0;
+ skel->bss->elem_calls = 0;
+ if (!run(output, "%smap dump id %u recursive", options[i], id))
+ break;
+ /* Only the three inner dumps use batches; array slot 3 is empty. */
+ ASSERT_EQ(skel->bss->batch_calls, 3, "inner_batches");
+ ASSERT_EQ(skel->bss->elem_calls,
+ type == BPF_MAP_TYPE_ARRAY_OF_MAPS ? 4 : 3, "outer_lookups");
+ }
+out:
+ if (outer >= 0)
+ close(outer);
+ if (inner >= 0)
+ close(inner);
+ bpftool_map_dump_recursive__destroy(skel);
+}
+
+static void test_missing_inner(void)
+{
+ char handle[32], message[96], output[OUTPUT_SIZE];
+ struct bpftool_map_dump_recursive *skel;
+ int a = -1, b = -1, outer = -1;
+ __u32 hidden;
+
+ skel = bpftool_map_dump_recursive__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+ return;
+ if (!ASSERT_OK(bpftool_map_dump_recursive__attach(skel), "skel_attach"))
+ goto out;
+ a = create_inner("rec_a", NULL, true);
+ b = create_inner("rec_b", NULL, true);
+ if (a < 0 || b < 0)
+ goto out;
+ outer = create_outer(BPF_MAP_TYPE_HASH_OF_MAPS, "rec_outer", a, b);
+ hidden = map_id(b);
+ if (outer < 0 || !hidden)
+ goto out;
+ snprintf(handle, sizeof(handle), "id %u", map_id(outer));
+ skel->bss->hidden_id = hidden;
+
+ /* Only the entry of the missing map has an error; the dump succeeds. */
+ check_dumps(handle, hidden, true);
+ snprintf(message, sizeof(message), "Error: can't get map by id (%u): %s\n",
+ hidden, strerror(ENOENT));
+ if (run(output, "map dump %s recursive 2>&1 >/dev/null", handle))
+ ASSERT_STREQ(output, message, "plain_error");
+out:
+ if (outer >= 0)
+ close(outer);
+ if (a >= 0)
+ close(a);
+ if (b >= 0)
+ close(b);
+ bpftool_map_dump_recursive__destroy(skel);
+}
+
+static void test_multiple_roots(void)
+{
+ int a = -1, b = -1, outer[] = { -1, -1 };
+ char name[BPF_OBJ_NAME_LEN], handle[32];
+ size_t i;
+
+ snprintf(name, sizeof(name), "rec_root_%u", getpid() % 100000);
+ a = create_inner("rec_a", NULL, true);
+ b = create_inner("rec_b", NULL, false);
+ if (a < 0 || b < 0)
+ goto out;
+ for (i = 0; i < ARRAY_SIZE(outer); i++) {
+ outer[i] = create_outer(BPF_MAP_TYPE_ARRAY_OF_MAPS, name, a, b);
+ if (outer[i] < 0)
+ goto out;
+ }
+ snprintf(handle, sizeof(handle), "name %s", name);
+ check_dumps(handle, 0, true);
+out:
+ for (i = 0; i < ARRAY_SIZE(outer); i++)
+ if (outer[i] >= 0)
+ close(outer[i]);
+ if (a >= 0)
+ close(a);
+ if (b >= 0)
+ close(b);
+}
+
+/* The keyword has no effect on other maps or on maps of maps without entries. */
+static void test_unchanged(void)
+{
+ LIBBPF_OPTS(bpf_map_create_opts, opts);
+ char dump[OUTPUT_SIZE], output[OUTPUT_SIZE];
+ const char * const options[] = { "", "-j " };
+ int fds[] = { -1, -1 };
+ size_t i, j;
+
+ fds[0] = create_inner("rec_a", NULL, true);
+ if (fds[0] < 0)
+ return;
+ opts.inner_map_fd = fds[0];
+ fds[1] = bpf_map_create(BPF_MAP_TYPE_HASH_OF_MAPS, "rec_empty", 4, 4, 1, &opts);
+ if (!ASSERT_OK_FD(fds[1], "create_outer"))
+ goto out;
+ for (i = 0; i < ARRAY_SIZE(fds); i++) {
+ for (j = 0; j < ARRAY_SIZE(options); j++) {
+ if (run(dump, "%smap dump id %u", options[j], map_id(fds[i])) &&
+ run(output, "%smap dump id %u recursive", options[j],
+ map_id(fds[i])))
+ ASSERT_STREQ(output, dump, "unchanged");
+ }
+ }
+out:
+ for (i = 0; i < ARRAY_SIZE(fds); i++)
+ if (fds[i] >= 0)
+ close(fds[i]);
+}
+
+static void test_arguments(void)
+{
+ const char * const invalid[] = { "''", "unknown", "recursive unknown" };
+ char command[MAX_BPFTOOL_CMD_LEN], output[OUTPUT_SIZE] = {};
+ __u32 id;
+ size_t i;
+ int fd;
+
+ fd = create_inner("rec_args", NULL, false);
+ if (fd < 0)
+ return;
+ id = map_id(fd);
+ for (i = 0; i < ARRAY_SIZE(invalid); i++) {
+ snprintf(command, sizeof(command), "map dump id %u %s", id, invalid[i]);
+ ASSERT_NEQ(run_bpftool_command(command), 0, invalid[i]);
+ }
+ snprintf(command, sizeof(command), "map dump id %u rec", id);
+ ASSERT_OK(run_bpftool_command(command), "prefix");
+ snprintf(command, sizeof(command), "-j map dump recursive id %u", id);
+ ASSERT_NEQ(get_bpftool_command_output(command, output, sizeof(output)), 0,
+ "misplaced_recursive");
+ ASSERT_HAS_SUBSTR(output, "got: 'recursive'", "misplaced_error");
+ close(fd);
+}
+
+void test_bpftool_map_dump_recursive(void)
+{
+ if (test__start_subtest("array_of_maps"))
+ test_outer(BPF_MAP_TYPE_ARRAY_OF_MAPS, false);
+ if (test__start_subtest("hash_of_maps"))
+ test_outer(BPF_MAP_TYPE_HASH_OF_MAPS, false);
+ if (test__start_subtest("btf_inner"))
+ test_outer(BPF_MAP_TYPE_HASH_OF_MAPS, true);
+ if (test__start_subtest("wide_inner"))
+ test_plain_layout(false);
+ if (test__start_subtest("percpu_inner"))
+ test_plain_layout(true);
+ if (test__start_subtest("batch_array_of_maps"))
+ test_batch_lookups(BPF_MAP_TYPE_ARRAY_OF_MAPS);
+ if (test__start_subtest("batch_hash_of_maps"))
+ test_batch_lookups(BPF_MAP_TYPE_HASH_OF_MAPS);
+ if (test__start_subtest("missing_inner"))
+ test_missing_inner();
+ if (test__start_subtest("multiple_roots"))
+ test_multiple_roots();
+ if (test__start_subtest("unchanged"))
+ test_unchanged();
+ if (test__start_subtest("arguments"))
+ test_arguments();
+}
diff --git a/tools/testing/selftests/bpf/progs/bpftool_map_dump_recursive.c b/tools/testing/selftests/bpf/progs/bpftool_map_dump_recursive.c
new file mode 100644
index 000000000000..b130cd493d3a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpftool_map_dump_recursive.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_core_read.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+#define ENOENT 2
+
+char _license[] SEC("license") = "GPL";
+
+/* Opening this map fails as if it had been freed. */
+__u32 hidden_id;
+int test_tgid;
+int batch_calls;
+int elem_calls;
+
+SEC("fmod_ret/security_bpf_map")
+int BPF_PROG(hide_map, struct bpf_map *map, int fmode)
+{
+ return map->id == hidden_id ? -ENOENT : 0;
+}
+
+SEC("fmod_ret/security_bpf")
+int BPF_PROG(count_lookups, int cmd, union bpf_attr *attr,
+ unsigned int size, bool kernel)
+{
+ struct task_struct *task = bpf_get_current_task_btf();
+
+ if (kernel ||
+ (BPF_CORE_READ(task, real_parent, tgid) != test_tgid &&
+ BPF_CORE_READ(task, real_parent, real_parent, tgid) != test_tgid))
+ return 0;
+ if (cmd == BPF_MAP_LOOKUP_BATCH)
+ batch_calls++;
+ else if (cmd == BPF_MAP_LOOKUP_ELEM)
+ elem_calls++;
+ return 0;
+}
--
2.56.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH bpf-next v12 2/3] bpftool: Add recursive map dumping
2026-11-11 7:19 ` [PATCH bpf-next v12 2/3] bpftool: Add recursive map dumping Tianyi Chen
@ 2026-11-11 16:24 ` Quentin Monnet
0 siblings, 0 replies; 6+ messages in thread
From: Quentin Monnet @ 2026-11-11 16:24 UTC (permalink / raw)
To: Tianyi Chen, bpf
Cc: ast, daniel, andrii, eddyz87, memxor, shuah, linux-kselftest,
ihor.solodrai
2026-11-11 16:19 UTC+0900 ~ Tianyi Chen <hi@tychen.cc>
> Dumping an array_of_maps or a hash_of_maps shows the IDs of the inner
> maps but not their contents. Add a recursive keyword to map dump that
> also dumps each inner map in the entry that references it. JSON gains
> an "inner_map" object next to "inner_map_id". Plain output separates
> inner maps with blank lines and indents their raw entries and element
> counts. Output without the keyword is unchanged.
>
> The kernel permits only one map-in-map level. Look up outer entries
> individually, then open, dump and close each referenced inner map
> immediately. This uses one extra map FD; inner dumps may still use batch
> lookups. Shared inner maps are dumped at every referring entry. If an
> inner map has disappeared, report the error in that entry and continue.
>
> Link: https://github.com/libbpf/bpftool/issues/58
> Suggested-by: Alexei Starovoitov <ast@kernel.org>
> Assisted-by: LLM
> Signed-off-by: Tianyi Chen <hi@tychen.cc>
> ---
> .../bpf/bpftool/Documentation/bpftool-map.rst | 10 +-
> tools/bpf/bpftool/bash-completion/bpftool | 3 +
> tools/bpf/bpftool/map.c | 123 +++++++++++++++---
> 3 files changed, 113 insertions(+), 23 deletions(-)
>
[...]
> diff --git a/tools/bpf/bpftool/map.c b/tools/bpf/bpftool/map.c
> index 9e4dc3881060..79d33af257b6 100644
> --- a/tools/bpf/bpftool/map.c
> +++ b/tools/bpf/bpftool/map.c
[...]
> @@ -1003,6 +1036,43 @@ map_dump(int fd, struct bpf_map_info *info, json_writer_t *wtr,
> return err;
> }
>
> +/* The kernel has one level of map-in-map: inner maps have no inner maps. */
> +static void dump_inner_map(__u32 id)
> +{
> + LIBBPF_OPTS(bpf_get_fd_by_id_opts, opts, .open_flags = BPF_F_RDONLY);
> + struct bpf_map_info info = {};
> + json_writer_t *btf_wtr = NULL;
> + __u32 len = sizeof(info);
> + int fd;
> +
> + fd = bpf_map_get_fd_by_id_opts(id, &opts);
> + if (fd < 0) {
> + p_err("can't get map by id (%u): %s", id, strerror(errno));
> + return;
> + }
> + if (bpf_map_get_info_by_fd(fd, &info, &len)) {
> + p_err("can't get map info: %s", strerror(errno));
> + goto out;
> + }
> +
> + if (!json_output && info.btf_value_type_id)
> + btf_wtr = get_btf_writer();
This will not indent inner map entries for inner maps with BTF info,
will it? But I guess indenting them would require changing the JSON
writer, probably not worth it. It's probably OK if we have blank lines
to delimit the different maps in that case.
Acked-by: Quentin Monnet <qmo@kernel.org>
Thank you!
The series will need to be rebased once the batch lookups are merged, so
that it can go through CI.
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH bpf-next v12 3/3] selftests/bpf: Cover recursive bpftool map dumps
2026-11-11 7:19 ` [PATCH bpf-next v12 3/3] selftests/bpf: Cover recursive bpftool map dumps Tianyi Chen
@ 2026-11-11 16:24 ` Quentin Monnet
0 siblings, 0 replies; 6+ messages in thread
From: Quentin Monnet @ 2026-11-11 16:24 UTC (permalink / raw)
To: Tianyi Chen, bpf
Cc: ast, daniel, andrii, eddyz87, memxor, shuah, linux-kselftest,
ihor.solodrai
2026-11-11 16:19 UTC+0900 ~ Tianyi Chen <hi@tychen.cc>
> Compare recursive dumps of array_of_maps and hash_of_maps with individual
> inner-map dumps in JSON and plain output. Cover shared and empty inner
> maps, an empty array slot, BTF values, and multiple roots selected by
> name. Check indentation of wrapped keys, values and per-CPU entries.
>
> Use fmod_ret to make an inner map unavailable and check that only its
> entry reports an error. Count lookup calls to check that outer entries
> are read individually while inner maps still use batches. Also check
> unchanged output without recursion and reject invalid arguments.
>
> Assisted-by: LLM
> Signed-off-by: Tianyi Chen <hi@tychen.cc>
I'm good with the coverage provided by the tests, but see the comment
from Alexei on another series [0]: selftests tend to grow very fast, and
you're creating objects (or invoking bpftool) more than what caused
Alexei to push back. So when reposting, for example once the batch
lookups series is merged, please look into reducing the cost.
Some leads:
- Dump each inner map once per mode when building the expected output,
with some cache in expect_json()/expect_plain()
- I think testing the plain output in test_multiple_roots() brings no value?
- In test_outer(), you could have one full comparison per map type
instead of two, because the recursion code only checks
map_is_map_of_maps() and doesn't depend on the type, so dropping
hash_of_maps should be OK
- Maybe drop test_unchanged()?
Thanks,
Quentin
[0] https://lore.kernel.org/bpf/DMOO53QTDUQ4.2Z9UXMZEPX568@gmail.com/
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-11-11 16:24 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11 7:19 [PATCH bpf-next v12 0/3] bpftool: Add recursive map dumping Tianyi Chen
2026-11-11 7:19 ` [PATCH bpf-next v12 1/3] bpftool: Reset map info before each query in show and dump loops Tianyi Chen
2026-11-11 7:19 ` [PATCH bpf-next v12 2/3] bpftool: Add recursive map dumping Tianyi Chen
2026-11-11 16:24 ` Quentin Monnet
2026-11-11 7:19 ` [PATCH bpf-next v12 3/3] selftests/bpf: Cover recursive bpftool map dumps Tianyi Chen
2026-11-11 16:24 ` Quentin Monnet
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox