All of lore.kernel.org
 help / color / mirror / Atom feed
From: Quentin Monnet <qmo@kernel.org>
To: Tianyi Chen <hi@tychen.cc>, bpf@vger.kernel.org
Cc: andrii@kernel.org, eddyz87@gmail.com, ihor.solodrai@linux.dev,
	linux-kselftest@vger.kernel.org, shuah@kernel.org,
	daniel@iogearbox.net, ast@kernel.org,
	alexei.starovoitov@gmail.com, skokovmaksimevg@gmail.com
Subject: Re: [PATCH bpf-next v11 2/3] bpftool: Add recursive map dumping
Date: Tue, 10 Nov 2026 15:27:44 +0000	[thread overview]
Message-ID: <4290bbfc-c72d-49d4-9d0c-66f316db9dd3@kernel.org> (raw)
In-Reply-To: <9d939026aea3c4a2d4d26702de1080c6733ad566.1793818386.git.hi@tychen.cc>

On 04/11/2026 18:54, Tianyi Chen wrote:
> 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 the inner map referenced by each entry, in that entry. JSON
> entries gain an "inner_map" object next to "inner_map_id", and plain
> output prints the inner map after the entry line. The output without
> the keyword is unchanged.
> 
> The kernel supports a single level of map-in-map, so inner maps have
> no inner maps of their own. Each inner map is opened by ID when its
> entry is printed, dumped and closed, which needs one more map FD. An
> inner map shared by several entries is dumped in each of them. If an
> inner map cannot be opened, for example because it was replaced after
> the outer lookup, the error is reported in that entry and the dump
> continues.
> 
> 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 |  9 ++-
>  tools/bpf/bpftool/bash-completion/bpftool     |  3 +
>  tools/bpf/bpftool/map.c                       | 59 +++++++++++++++++--
>  3 files changed, 65 insertions(+), 6 deletions(-)
> 
> diff --git a/tools/bpf/bpftool/Documentation/bpftool-map.rst b/tools/bpf/bpftool/Documentation/bpftool-map.rst
> index e4ed7e701c9e4..729baab2b7922 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,15 @@ 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. 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 a28af31937ad4..78fdb9396e96f 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 b150a06a25085..fc820c07e9f4f 100644
> --- a/tools/bpf/bpftool/map.c
> +++ b/tools/bpf/bpftool/map.c
> @@ -22,6 +22,9 @@
>  #include "main.h"
>  
>  static struct hashmap *map_table;
> +static bool recursive_dump;
> +
> +static void dump_inner_map(__u32 id);
>  
>  static bool map_is_per_cpu(__u32 type)
>  {
> @@ -140,9 +143,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,
> @@ -273,6 +281,8 @@ static void print_entry_plain(struct bpf_map_info *info, unsigned char *key,
>  		}
>  
>  		printf("\n");
> +		if (recursive_dump && map_is_map_of_maps(info->type))
> +			dump_inner_map(*(unsigned int *)value);
>  	} else {
>  		unsigned int step;
>  		int i;
> @@ -935,6 +945,36 @@ 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();
> +
> +	map_dump(fd, &info, json_output ? json_wtr : btf_wtr, true);


Hi Tianyi, thanks for this version!

With the design in the newer versions (dump as soon as you find the
entry), the plain output will look something like this:

   key: 01 00 00 00  inner_map_id: 80
   80: hash  name c1202206_in2  flags 0x0
   Found 0 elements
   key: 00 00 00 00  inner_map_id: 79
   79: hash  name c1202206_in1  flags 0x0
   key: 07 00 00 00  value: 09 00 00 00
   Found 1 element
   Found 2 elements

It's quite difficult to understand where each inner map starts and
stops. Can we have some sort of delimiter between maps (at least one
empty line, I think that's what we use when dumping several maps with
the same name?), and some sort of indentation or prefix character to
show inner map entries and items count, please?

pw-bot: cr

On a different note, we risk an infinite recursion here if the kernel
ever allows inner maps to recurse. I'm not aware that there is any plan
in that direction though, so it's probably fine to keep as-is for now.


> +
> +	if (btf_wtr)
> +		jsonw_destroy(&btf_wtr);
> +out:
> +	close(fd);
> +}
> +
>  static int do_dump(int argc, char **argv)
>  {
>  	json_writer_t *wtr = NULL, *btf_wtr = NULL;
> @@ -944,8 +984,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) {
> @@ -956,6 +996,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 {
> @@ -1002,6 +1052,7 @@ static int do_dump(int argc, char **argv)
>  exit_free:
>  	free(fds);
>  	free_btf_vmlinux();
> +	recursive_dump = false;
>  	return err;
>  }
>  
> @@ -1510,7 +1561,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"


Note that there may be some interaction between this series and the one
on batch lookups. With the approach suggested by Alexei - dump inner
maps as soon as you find them - it would probably make sense to exclude
the outer map from batch lookups when we do a recursive dump, to limit
the risk of inner maps being replaced in the meantime.

Thanks,
Quentin

  reply	other threads:[~2026-11-10 15:27 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04 18:54 [PATCH bpf-next v11 0/3] bpftool: Add recursive map dumping Tianyi Chen
2026-11-04 18:54 ` [PATCH bpf-next v11 1/3] bpftool: Reset map info before each query in show and dump loops Tianyi Chen
2026-11-04 18:54 ` [PATCH bpf-next v11 2/3] bpftool: Add recursive map dumping Tianyi Chen
2026-11-10 15:27   ` Quentin Monnet [this message]
2026-11-04 18:54 ` [PATCH bpf-next v11 3/3] selftests/bpf: Cover recursive bpftool map dumps Tianyi Chen
2026-11-04 19:24   ` bot+bpf-ci

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4290bbfc-c72d-49d4-9d0c-66f316db9dd3@kernel.org \
    --to=qmo@kernel.org \
    --cc=alexei.starovoitov@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=hi@tychen.cc \
    --cc=ihor.solodrai@linux.dev \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=shuah@kernel.org \
    --cc=skokovmaksimevg@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.