* [PATCH bpf-next 0/4] bpftool: Add struct_ops support
@ 2020-03-16 0:55 Martin KaFai Lau
2020-03-16 0:56 ` [PATCH bpf-next 1/4] bpftool: Print the enum's name instead of value Martin KaFai Lau
` (4 more replies)
0 siblings, 5 replies; 13+ messages in thread
From: Martin KaFai Lau @ 2020-03-16 0:55 UTC (permalink / raw)
To: bpf; +Cc: Alexei Starovoitov, Daniel Borkmann, kernel-team, netdev
This set adds "struct_ops" support to bpftool.
The first two patches improve the btf_dumper in bpftool.
Patch 1: print the enum's name (if it is found) instead of the
enum's value.
Patch 2: print a char[] as a string if all characters are printable.
"struct_ops" stores the prog_id in a func ptr.
Instead of printing a prog_id,
patch 3 adds an option to btf_dumper to allow a func ptr's value
to be printed with the full func_proto info and the prog_name.
Patch 4 implements the "struct_ops" bpftool command.
Martin KaFai Lau (4):
bpftool: Print the enum's name instead of value
bpftool: Print as a string for char array
bpftool: Translate prog_id to its bpf prog_name
bpftool: Add struct_ops support
.../Documentation/bpftool-struct_ops.rst | 106 ++++
tools/bpf/bpftool/bash-completion/bpftool | 28 +
tools/bpf/bpftool/btf_dumper.c | 194 +++++-
tools/bpf/bpftool/main.c | 3 +-
tools/bpf/bpftool/main.h | 2 +
tools/bpf/bpftool/struct_ops.c | 595 ++++++++++++++++++
6 files changed, 912 insertions(+), 16 deletions(-)
create mode 100644 tools/bpf/bpftool/Documentation/bpftool-struct_ops.rst
create mode 100644 tools/bpf/bpftool/struct_ops.c
--
2.17.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH bpf-next 1/4] bpftool: Print the enum's name instead of value
2020-03-16 0:55 [PATCH bpf-next 0/4] bpftool: Add struct_ops support Martin KaFai Lau
@ 2020-03-16 0:56 ` Martin KaFai Lau
2020-03-17 20:03 ` Andrii Nakryiko
2020-03-16 0:56 ` [PATCH bpf-next 2/4] bpftool: Print as a string for char array Martin KaFai Lau
` (3 subsequent siblings)
4 siblings, 1 reply; 13+ messages in thread
From: Martin KaFai Lau @ 2020-03-16 0:56 UTC (permalink / raw)
To: bpf; +Cc: Alexei Starovoitov, Daniel Borkmann, kernel-team, netdev
This patch prints the enum's name if there is one found in
the array of btf_enum.
The commit 9eea98497951 ("bpf: fix BTF verification of enums")
has details about an enum could have any power-of-2 size (up to 8 bytes).
This patch also takes this chance to accommodate these non 4 byte
enums.
Signed-off-by: Martin KaFai Lau <kafai@fb.com>
---
tools/bpf/bpftool/btf_dumper.c | 35 +++++++++++++++++++++++++++++++---
1 file changed, 32 insertions(+), 3 deletions(-)
diff --git a/tools/bpf/bpftool/btf_dumper.c b/tools/bpf/bpftool/btf_dumper.c
index 01cc52b834fa..57bd6c0fafc9 100644
--- a/tools/bpf/bpftool/btf_dumper.c
+++ b/tools/bpf/bpftool/btf_dumper.c
@@ -43,9 +43,38 @@ static int btf_dumper_modifier(const struct btf_dumper *d, __u32 type_id,
return btf_dumper_do_type(d, actual_type_id, bit_offset, data);
}
-static void btf_dumper_enum(const void *data, json_writer_t *jw)
+static void btf_dumper_enum(const struct btf_dumper *d,
+ const struct btf_type *t,
+ const void *data)
{
- jsonw_printf(jw, "%d", *(int *)data);
+ const struct btf_enum *enums = btf_enum(t);
+ __s64 value;
+ __u16 i;
+
+ switch (t->size) {
+ case 8:
+ value = *(__s64 *)data;
+ break;
+ case 4:
+ value = *(__s32 *)data;
+ break;
+ case 2:
+ value = *(__s16 *)data;
+ break;
+ default:
+ value = *(__s8 *)data;
+ }
+
+ for (i = 0; i < btf_vlen(t); i++) {
+ if (value == enums[i].val) {
+ jsonw_string(d->jw,
+ btf__name_by_offset(d->btf,
+ enums[i].name_off));
+ return;
+ }
+ }
+
+ jsonw_int(d->jw, value);
}
static int btf_dumper_array(const struct btf_dumper *d, __u32 type_id,
@@ -366,7 +395,7 @@ static int btf_dumper_do_type(const struct btf_dumper *d, __u32 type_id,
case BTF_KIND_ARRAY:
return btf_dumper_array(d, type_id, data);
case BTF_KIND_ENUM:
- btf_dumper_enum(data, d->jw);
+ btf_dumper_enum(d, t, data);
return 0;
case BTF_KIND_PTR:
btf_dumper_ptr(data, d->jw, d->is_plain_text);
--
2.17.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH bpf-next 2/4] bpftool: Print as a string for char array
2020-03-16 0:55 [PATCH bpf-next 0/4] bpftool: Add struct_ops support Martin KaFai Lau
2020-03-16 0:56 ` [PATCH bpf-next 1/4] bpftool: Print the enum's name instead of value Martin KaFai Lau
@ 2020-03-16 0:56 ` Martin KaFai Lau
2020-03-17 20:08 ` Andrii Nakryiko
2020-03-16 0:56 ` [PATCH bpf-next 3/4] bpftool: Translate prog_id to its bpf prog_name Martin KaFai Lau
` (2 subsequent siblings)
4 siblings, 1 reply; 13+ messages in thread
From: Martin KaFai Lau @ 2020-03-16 0:56 UTC (permalink / raw)
To: bpf; +Cc: Alexei Starovoitov, Daniel Borkmann, kernel-team, netdev
A char[] is currently printed as an integer array.
This patch will print it as a string when
1) The array element type is an one byte int
2) The array element type has a BTF_INT_CHAR encoding or
the array element type's name is "char"
3) All characters is between (0x1f, 0x7f) and it is terminated
by a null character.
Signed-off-by: Martin KaFai Lau <kafai@fb.com>
---
tools/bpf/bpftool/btf_dumper.c | 41 ++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/tools/bpf/bpftool/btf_dumper.c b/tools/bpf/bpftool/btf_dumper.c
index 57bd6c0fafc9..1d2d8d2cedea 100644
--- a/tools/bpf/bpftool/btf_dumper.c
+++ b/tools/bpf/bpftool/btf_dumper.c
@@ -77,6 +77,42 @@ static void btf_dumper_enum(const struct btf_dumper *d,
jsonw_int(d->jw, value);
}
+static bool is_str_array(const struct btf *btf, const struct btf_array *arr,
+ const char *s)
+{
+ const struct btf_type *elem_type;
+ const char *end_s;
+
+ if (!arr->nelems)
+ return false;
+
+ elem_type = btf__type_by_id(btf, arr->type);
+ /* Not skipping typedef. typedef to char does not count as
+ * a string now.
+ */
+ while (elem_type && btf_is_mod(elem_type))
+ elem_type = btf__type_by_id(btf, elem_type->type);
+
+ if (!elem_type || !btf_is_int(elem_type) || elem_type->size != 1)
+ return false;
+
+ if (btf_int_encoding(elem_type) != BTF_INT_CHAR &&
+ strcmp("char", btf__name_by_offset(btf, elem_type->name_off)))
+ return false;
+
+ end_s = s + arr->nelems;
+ while (s < end_s) {
+ if (!*s)
+ return true;
+ if (*s <= 0x1f || *s >= 0x7f)
+ return false;
+ s++;
+ }
+
+ /* '\0' is not found */
+ return false;
+}
+
static int btf_dumper_array(const struct btf_dumper *d, __u32 type_id,
const void *data)
{
@@ -86,6 +122,11 @@ static int btf_dumper_array(const struct btf_dumper *d, __u32 type_id,
int ret = 0;
__u32 i;
+ if (is_str_array(d->btf, arr, data)) {
+ jsonw_string(d->jw, data);
+ return 0;
+ }
+
elem_size = btf__resolve_size(d->btf, arr->type);
if (elem_size < 0)
return elem_size;
--
2.17.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH bpf-next 3/4] bpftool: Translate prog_id to its bpf prog_name
2020-03-16 0:55 [PATCH bpf-next 0/4] bpftool: Add struct_ops support Martin KaFai Lau
2020-03-16 0:56 ` [PATCH bpf-next 1/4] bpftool: Print the enum's name instead of value Martin KaFai Lau
2020-03-16 0:56 ` [PATCH bpf-next 2/4] bpftool: Print as a string for char array Martin KaFai Lau
@ 2020-03-16 0:56 ` Martin KaFai Lau
2020-03-16 0:56 ` [PATCH bpf-next 4/4] bpftool: Add struct_ops support Martin KaFai Lau
2020-03-16 11:54 ` [PATCH bpf-next 0/4] " Quentin Monnet
4 siblings, 0 replies; 13+ messages in thread
From: Martin KaFai Lau @ 2020-03-16 0:56 UTC (permalink / raw)
To: bpf; +Cc: Alexei Starovoitov, Daniel Borkmann, kernel-team, netdev
The kernel struct_ops obj has kernel's func ptrs implemented by bpf_progs.
The bpf prog_id is stored as the value of the func ptr for introspection
purpose. In the latter patch, a struct_ops dump subcmd will be added
to introspect these func ptrs. It is desired to print the actual bpf
prog_name instead of only printing the prog_id.
Since struct_ops is the only usecase storing prog_id in the func ptr,
this patch adds a prog_id_as_func_ptr bool (default is false) to
"struct btf_dumper" in order not to mis-interpret the ptr value
for the other existing use-cases.
While printing a func_ptr as a bpf prog_name,
this patch also prefix the bpf prog_name with the ptr's func_proto.
[ Note that it is the ptr's func_proto instead of the bpf prog's
func_proto ]
It reuses the current btf_dump_func() to obtain the ptr's func_proto
string.
Here is an example from the bpf_cubic.c:
"void (struct sock *, u32, u32) bictcp_cong_avoid/prog_id:140"
Signed-off-by: Martin KaFai Lau <kafai@fb.com>
---
tools/bpf/bpftool/btf_dumper.c | 118 +++++++++++++++++++++++++++++----
tools/bpf/bpftool/main.h | 1 +
2 files changed, 107 insertions(+), 12 deletions(-)
diff --git a/tools/bpf/bpftool/btf_dumper.c b/tools/bpf/bpftool/btf_dumper.c
index 1d2d8d2cedea..2897a6c28a91 100644
--- a/tools/bpf/bpftool/btf_dumper.c
+++ b/tools/bpf/bpftool/btf_dumper.c
@@ -4,11 +4,13 @@
#include <ctype.h>
#include <stdio.h> /* for (FILE *) used by json_writer */
#include <string.h>
+#include <unistd.h>
#include <asm/byteorder.h>
#include <linux/bitops.h>
#include <linux/btf.h>
#include <linux/err.h>
#include <bpf/btf.h>
+#include <bpf/bpf.h>
#include "json_writer.h"
#include "main.h"
@@ -22,13 +24,102 @@
static int btf_dumper_do_type(const struct btf_dumper *d, __u32 type_id,
__u8 bit_offset, const void *data);
-static void btf_dumper_ptr(const void *data, json_writer_t *jw,
- bool is_plain_text)
+static int btf_dump_func(const struct btf *btf, char *func_sig,
+ const struct btf_type *func_proto,
+ const struct btf_type *func, int pos, int size);
+
+static int dump_prog_id_as_func_ptr(const struct btf_dumper *d,
+ const struct btf_type *func_proto,
+ __u32 prog_id)
{
- if (is_plain_text)
- jsonw_printf(jw, "%p", *(void **)data);
+ struct bpf_prog_info_linear *prog_info = NULL;
+ const struct btf_type *func_type;
+ const char *prog_name = NULL;
+ struct bpf_func_info *finfo;
+ struct btf *prog_btf = NULL;
+ struct bpf_prog_info *info;
+ int prog_fd, func_sig_len;
+ char prog_str[1024];
+
+ /* Get the ptr's func_proto */
+ func_sig_len = btf_dump_func(d->btf, prog_str, func_proto, NULL, 0,
+ sizeof(prog_str));
+ if (func_sig_len == -1)
+ return -1;
+
+ if (!prog_id)
+ goto print;
+
+ /* Get the bpf_prog's name. Obtain from func_info. */
+ prog_fd = bpf_prog_get_fd_by_id(prog_id);
+ if (prog_fd == -1)
+ goto print;
+
+ prog_info = bpf_program__get_prog_info_linear(prog_fd,
+ 1UL << BPF_PROG_INFO_FUNC_INFO);
+ close(prog_fd);
+ if (IS_ERR(prog_info)) {
+ prog_info = NULL;
+ goto print;
+ }
+ info = &prog_info->info;
+
+ if (!info->btf_id || !info->nr_func_info ||
+ btf__get_from_id(info->btf_id, &prog_btf))
+ goto print;
+ finfo = (struct bpf_func_info *)info->func_info;
+ func_type = btf__type_by_id(prog_btf, finfo->type_id);
+ if (!func_type || !btf_is_func(func_type))
+ goto print;
+
+ prog_name = btf__name_by_offset(prog_btf, func_type->name_off);
+
+print:
+ if (!prog_id)
+ snprintf(&prog_str[func_sig_len],
+ sizeof(prog_str) - func_sig_len, " 0");
+ else if (prog_name)
+ snprintf(&prog_str[func_sig_len],
+ sizeof(prog_str) - func_sig_len,
+ " %s/prog_id:%u", prog_name, prog_id);
else
- jsonw_printf(jw, "%lu", *(unsigned long *)data);
+ snprintf(&prog_str[func_sig_len],
+ sizeof(prog_str) - func_sig_len,
+ " <unknown_prog_name>/prog_id:%u", prog_id);
+
+ prog_str[sizeof(prog_str) - 1] = '\0';
+ jsonw_string(d->jw, prog_str);
+ btf__free(prog_btf);
+ free(prog_info);
+ return 0;
+}
+
+static void btf_dumper_ptr(const struct btf_dumper *d,
+ const struct btf_type *t,
+ const void *data)
+{
+ unsigned long value = *(unsigned long *)data;
+ const struct btf_type *ptr_type;
+ __s32 ptr_type_id;
+
+ if (!d->prog_id_as_func_ptr || value > UINT32_MAX)
+ goto print_ptr_value;
+
+ ptr_type_id = btf__resolve_type(d->btf, t->type);
+ if (ptr_type_id < 0)
+ goto print_ptr_value;
+ ptr_type = btf__type_by_id(d->btf, ptr_type_id);
+ if (!ptr_type || !btf_is_func_proto(ptr_type))
+ goto print_ptr_value;
+
+ if (!dump_prog_id_as_func_ptr(d, ptr_type, value))
+ return;
+
+print_ptr_value:
+ if (d->is_plain_text)
+ jsonw_printf(d->jw, "%p", (void *)value);
+ else
+ jsonw_printf(d->jw, "%lu", value);
}
static int btf_dumper_modifier(const struct btf_dumper *d, __u32 type_id,
@@ -439,7 +530,7 @@ static int btf_dumper_do_type(const struct btf_dumper *d, __u32 type_id,
btf_dumper_enum(d, t, data);
return 0;
case BTF_KIND_PTR:
- btf_dumper_ptr(data, d->jw, d->is_plain_text);
+ btf_dumper_ptr(d, t, data);
return 0;
case BTF_KIND_UNKN:
jsonw_printf(d->jw, "(unknown)");
@@ -484,10 +575,6 @@ int btf_dumper_type(const struct btf_dumper *d, __u32 type_id,
return -1; \
} while (0)
-static int btf_dump_func(const struct btf *btf, char *func_sig,
- const struct btf_type *func_proto,
- const struct btf_type *func, int pos, int size);
-
static int __btf_dumper_type_only(const struct btf *btf, __u32 type_id,
char *func_sig, int pos, int size)
{
@@ -596,8 +683,15 @@ static int btf_dump_func(const struct btf *btf, char *func_sig,
BTF_PRINT_ARG(", ");
if (arg->type) {
BTF_PRINT_TYPE(arg->type);
- BTF_PRINT_ARG("%s",
- btf__name_by_offset(btf, arg->name_off));
+ if (arg->name_off)
+ BTF_PRINT_ARG("%s",
+ btf__name_by_offset(btf, arg->name_off));
+ else if (pos && func_sig[pos - 1] == ' ')
+ /* Remove unnecessary space for
+ * FUNC_PROTO that does not have
+ * arg->name_off
+ */
+ func_sig[--pos] = '\0';
} else {
BTF_PRINT_ARG("...");
}
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
index 5f6dccd43622..6db2398ae7e9 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -205,6 +205,7 @@ struct btf_dumper {
const struct btf *btf;
json_writer_t *jw;
bool is_plain_text;
+ bool prog_id_as_func_ptr;
};
/* btf_dumper_type - print data along with type information
--
2.17.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH bpf-next 4/4] bpftool: Add struct_ops support
2020-03-16 0:55 [PATCH bpf-next 0/4] bpftool: Add struct_ops support Martin KaFai Lau
` (2 preceding siblings ...)
2020-03-16 0:56 ` [PATCH bpf-next 3/4] bpftool: Translate prog_id to its bpf prog_name Martin KaFai Lau
@ 2020-03-16 0:56 ` Martin KaFai Lau
2020-03-16 11:54 ` Quentin Monnet
2020-03-16 11:54 ` [PATCH bpf-next 0/4] " Quentin Monnet
4 siblings, 1 reply; 13+ messages in thread
From: Martin KaFai Lau @ 2020-03-16 0:56 UTC (permalink / raw)
To: bpf; +Cc: Alexei Starovoitov, Daniel Borkmann, kernel-team, netdev
This patch adds struct_ops support to the bpftool.
To recap a bit on the recent bpf_struct_ops feature on the kernel side:
It currently supports "struct tcp_congestion_ops" to be implemented
in bpf. At a high level, bpf_struct_ops is struct_ops map populated
with a number of bpf progs. bpf_struct_ops currently supports the
"struct tcp_congestion_ops". However, the bpf_struct_ops design is
generic enough that other kernel struct ops can be supported in
the future.
Although struct_ops is map+progs at a high lever, there are differences
in details. For example,
1) After registering a struct_ops, the struct_ops is held by the kernel
subsystem (e.g. tcp-cc). Thus, there is no need to pin a
struct_ops map or its progs in order to keep them around.
2) To iterate all struct_ops in a system, it iterates all maps
in type BPF_MAP_TYPE_STRUCT_OPS. BPF_MAP_TYPE_STRUCT_OPS is
the current usual filter. In the future, it may need to
filter by other struct_ops specific properties. e.g. filter by
tcp_congestion_ops or other kernel subsystem ops in the future.
3) struct_ops requires the running kernel having BTF info. That allows
more flexibility in handling other kernel structs. e.g. it can
always dump the latest bpf_map_info.
4) Also, "struct_ops" command is not intended to repeat all features
already provided by "map" or "prog". For example, if there really
is a need to pin the struct_ops map, the user can use the "map" cmd
to do that.
While the first attempt was to reuse parts from map/prog.c, it ended up
not a lot to share. The only obvious item is the map_parse_fds() but
that still requires modifications to accommodate struct_ops map specific
filtering (for the immediate and the future needs). Together with the
earlier mentioned differences, it is better to part away from map/prog.c.
The initial set of subcmds are, register, unregister, show, and dump.
For register, it registers all struct_ops maps that can be found in an
obj file. Option can be added in the future to specify a particular
struct_ops map. Also, the common bpf_tcp_cc is stateless (e.g.
bpf_cubic.c and bpf_dctcp.c). The "reuse map" feature is not
implemented in this patch and it can be considered later also.
For other subcmds, please see the man doc for details.
A sample output of dump:
[root@arch-fb-vm1 bpf]# bpftool struct_ops dump name cubic
[{
"bpf_map_info": {
"type": 26,
"id": 64,
"key_size": 4,
"value_size": 256,
"max_entries": 1,
"map_flags": 0,
"name": "cubic",
"ifindex": 0,
"btf_vmlinux_value_type_id": 18452,
"netns_dev": 0,
"netns_ino": 0,
"btf_id": 52,
"btf_key_type_id": 0,
"btf_value_type_id": 0
}
},{
"bpf_struct_ops_tcp_congestion_ops": {
"refcnt": {
"refs": {
"counter": 1
}
},
"state": "BPF_STRUCT_OPS_STATE_INUSE",
"data": {
"list": {
"next": 0,
"prev": 0
},
"key": 0,
"flags": 0,
"init": "void (struct sock *) bictcp_init/prog_id:138",
"release": "void (struct sock *) 0",
"ssthresh": "u32 (struct sock *) bictcp_recalc_ssthresh/prog_id:141",
"cong_avoid": "void (struct sock *, u32, u32) bictcp_cong_avoid/prog_id:140",
"set_state": "void (struct sock *, u8) bictcp_state/prog_id:142",
"cwnd_event": "void (struct sock *, enum tcp_ca_event) bictcp_cwnd_event/prog_id:139",
"in_ack_event": "void (struct sock *, u32) 0",
"undo_cwnd": "u32 (struct sock *) tcp_reno_undo_cwnd/prog_id:144",
"pkts_acked": "void (struct sock *, const struct ack_sample *) bictcp_acked/prog_id:143",
"min_tso_segs": "u32 (struct sock *) 0",
"sndbuf_expand": "u32 (struct sock *) 0",
"cong_control": "void (struct sock *, const struct rate_sample *) 0",
"get_info": "size_t (struct sock *, u32, int *, union tcp_cc_info *) 0",
"name": "bpf_cubic",
"owner": 0
}
}
}
]
Signed-off-by: Martin KaFai Lau <kafai@fb.com>
---
.../Documentation/bpftool-struct_ops.rst | 106 ++++
tools/bpf/bpftool/bash-completion/bpftool | 28 +
tools/bpf/bpftool/main.c | 3 +-
tools/bpf/bpftool/main.h | 1 +
tools/bpf/bpftool/struct_ops.c | 595 ++++++++++++++++++
5 files changed, 732 insertions(+), 1 deletion(-)
create mode 100644 tools/bpf/bpftool/Documentation/bpftool-struct_ops.rst
create mode 100644 tools/bpf/bpftool/struct_ops.c
diff --git a/tools/bpf/bpftool/Documentation/bpftool-struct_ops.rst b/tools/bpf/bpftool/Documentation/bpftool-struct_ops.rst
new file mode 100644
index 000000000000..27aae5bc632e
--- /dev/null
+++ b/tools/bpf/bpftool/Documentation/bpftool-struct_ops.rst
@@ -0,0 +1,106 @@
+==================
+bpftool-struct_ops
+==================
+-------------------------------------------------------------------------------
+tool to register/unregister/introspect BPF struct_ops
+-------------------------------------------------------------------------------
+
+:Manual section: 8
+
+SYNOPSIS
+========
+
+ **bpftool** [*OPTIONS*] **struct_ops** *COMMAND*
+
+ *OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] }
+
+ *COMMANDS* :=
+ { **show** | **list** | **dump** | **register** | **unregister** | **help** }
+
+STRUCT_OPS COMMANDS
+===================
+
+| **bpftool** **struct_ops { show | list }** [*STRUCT_OPS_MAP*]
+| **bpftool** **struct_ops dump** [*STRUCT_OPS_MAP*]
+| **bpftool** **struct_ops register** *OBJ*
+| **bpftool** **struct_ops unregister** *STRUCT_OPS_MAP*
+| **bpftool** **struct_ops help**
+|
+| *STRUCT_OPS_MAP* := { **id** *STRUCT_OPS_MAP_ID* | **name** *STRUCT_OPS_MAP_NAME* }
+| *OBJ* := /a/file/of/bpf_struct_ops.o
+
+
+DESCRIPTION
+===========
+ **bpftool struct_ops { show | list }** [*STRUCT_OPS_MAP*]
+ Show brief information about the struct_ops in the system.
+ If *STRUCT_OPS_MAP* is specified, it shows information only
+ for the given struct_ops. Otherwise, it lists all struct_ops
+ currently exists in the system.
+
+ Output will start with struct_ops map ID, followed by its map
+ name and its struct_ops's kernel type.
+
+ **bpftool struct_ops dump** [*STRUCT_OPS_MAP*]
+ Dump details information about the struct_ops in the system.
+ If *STRUCT_OPS_MAP* is specified, it dumps information only
+ for the given struct_ops. Otherwise, it dumps all struct_ops
+ currently exists in the system.
+
+ **bpftool struct_ops register** *OBJ*
+ Register bpf struct_ops from *OBJ*. All struct_ops under
+ the ELF section ".struct_ops" will be registered to
+ its kernel subsystem.
+
+ **bpftool struct_ops unregister** *STRUCT_OPS_MAP*
+ Unregister the *STRUCT_OPS_MAP* from the kernel subsystem.
+
+ **bpftool struct_ops help**
+ Print short help message.
+
+OPTIONS
+=======
+ -h, --help
+ Print short generic help message (similar to **bpftool help**).
+
+ -V, --version
+ Print version number (similar to **bpftool version**).
+
+ -j, --json
+ Generate JSON output. For commands that cannot produce JSON, this
+ option has no effect.
+
+ -p, --pretty
+ Generate human-readable JSON output. Implies **-j**.
+
+ -d, --debug
+ Print all logs available, even debug-level information. This
+ includes logs from libbpf as well as from the verifier, when
+ attempting to load programs.
+
+EXAMPLES
+========
+**# bpftool struct_ops show**
+
+::
+
+ 100: dctcp tcp_congestion_ops
+ 105: cubic tcp_congestion_ops
+
+**# bpftool struct_ops unregister id 105**
+
+::
+
+ Unregistered tcp_congestion_ops cubic id 105
+
+**# bpftool struct_ops register bpf_cubic.o**
+
+::
+
+ Registered tcp_congestion_ops cubic id 110
+
+
+SEE ALSO
+========
+ **bpftool-map**\ (8)
+ **bpftool-prog**\ (8)
diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool
index 9b0534f558f1..45ee99b159e2 100644
--- a/tools/bpf/bpftool/bash-completion/bpftool
+++ b/tools/bpf/bpftool/bash-completion/bpftool
@@ -576,6 +576,34 @@ _bpftool()
;;
esac
;;
+ struct_ops)
+ local STRUCT_OPS_TYPE='id name'
+ case $command in
+ show|list|dump|unregister)
+ case $prev in
+ $command)
+ COMPREPLY=( $( compgen -W "$STRUCT_OPS_TYPE" -- "$cur" ) )
+ ;;
+ id)
+ _bpftool_get_map_ids_for_type struct_ops
+ ;;
+ name)
+ _bpftool_get_map_names_for_type struct_ops
+ ;;
+ esac
+ return 0
+ ;;
+ register)
+ _filedir
+ return 0
+ ;;
+ *)
+ [[ $prev == $object ]] && \
+ COMPREPLY=( $( compgen -W 'register unregister show list dump help' \
+ -- "$cur" ) )
+ ;;
+ esac
+ ;;
map)
local MAP_TYPE='id pinned name'
case $command in
diff --git a/tools/bpf/bpftool/main.c b/tools/bpf/bpftool/main.c
index 06449e846e4b..466c269eabdd 100644
--- a/tools/bpf/bpftool/main.c
+++ b/tools/bpf/bpftool/main.c
@@ -58,7 +58,7 @@ static int do_help(int argc, char **argv)
" %s batch file FILE\n"
" %s version\n"
"\n"
- " OBJECT := { prog | map | cgroup | perf | net | feature | btf | gen }\n"
+ " OBJECT := { prog | map | cgroup | perf | net | feature | btf | gen | struct_ops }\n"
" " HELP_SPEC_OPTIONS "\n"
"",
bin_name, bin_name, bin_name);
@@ -221,6 +221,7 @@ static const struct cmd cmds[] = {
{ "feature", do_feature },
{ "btf", do_btf },
{ "gen", do_gen },
+ { "struct_ops", do_struct_ops },
{ "version", do_version },
{ 0 }
};
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
index 6db2398ae7e9..86f14ce26fd7 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -161,6 +161,7 @@ int do_tracelog(int argc, char **arg);
int do_feature(int argc, char **argv);
int do_btf(int argc, char **argv);
int do_gen(int argc, char **argv);
+int do_struct_ops(int argc, char **argv);
int parse_u32_arg(int *argc, char ***argv, __u32 *val, const char *what);
int prog_parse_fd(int *argc, char ***argv);
diff --git a/tools/bpf/bpftool/struct_ops.c b/tools/bpf/bpftool/struct_ops.c
new file mode 100644
index 000000000000..ba145e3d0d5d
--- /dev/null
+++ b/tools/bpf/bpftool/struct_ops.c
@@ -0,0 +1,595 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/* Copyright (C) 2020 Facebook */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <linux/err.h>
+#include <bpf/libbpf.h>
+#include <bpf/bpf.h>
+#include <bpf/btf.h>
+
+#include "json_writer.h"
+#include "main.h"
+
+#define STRUCT_OPS_VALUE_PREFIX "bpf_struct_ops_"
+
+static const struct btf_type *map_info_type;
+static __u32 map_info_alloc_len;
+static struct btf *btf_vmlinux;
+static __s32 map_info_type_id;
+
+struct res {
+ unsigned int nr_maps;
+ unsigned int nr_errs;
+};
+
+static const struct btf *get_btf_vmlinux(void)
+{
+ if (btf_vmlinux)
+ return btf_vmlinux;
+
+ btf_vmlinux = libbpf_find_kernel_btf();
+ if (IS_ERR(btf_vmlinux))
+ p_err("struct_ops requires kernel CONFIG_DEBUG_INFO_BTF=y");
+
+ return btf_vmlinux;
+}
+
+static const char *get_kern_struct_ops_name(const struct bpf_map_info *info)
+{
+ const struct btf *kern_btf;
+ const struct btf_type *t;
+ const char *st_ops_name;
+
+ kern_btf = get_btf_vmlinux();
+ if (IS_ERR(kern_btf))
+ return "<btf_vmlinux_not_found>";
+
+ t = btf__type_by_id(kern_btf, info->btf_vmlinux_value_type_id);
+ st_ops_name = btf__name_by_offset(kern_btf, t->name_off);
+ st_ops_name += strlen(STRUCT_OPS_VALUE_PREFIX);
+
+ return st_ops_name;
+}
+
+static __s32 get_map_info_type_id(void)
+{
+ const struct btf *kern_btf;
+
+ if (map_info_type_id)
+ return map_info_type_id;
+
+ kern_btf = get_btf_vmlinux();
+ if (IS_ERR(kern_btf)) {
+ map_info_type_id = PTR_ERR(kern_btf);
+ return map_info_type_id;
+ }
+
+ map_info_type_id = btf__find_by_name_kind(kern_btf, "bpf_map_info",
+ BTF_KIND_STRUCT);
+ if (map_info_type_id < 0) {
+ p_err("can't find bpf_map_info from btf_vmlinux");
+ return map_info_type_id;
+ }
+ map_info_type = btf__type_by_id(kern_btf, map_info_type_id);
+
+ /* Ensure map_info_alloc() has at least what the bpftool needs */
+ map_info_alloc_len = map_info_type->size;
+ if (map_info_alloc_len < sizeof(struct bpf_map_info))
+ map_info_alloc_len = sizeof(struct bpf_map_info);
+
+ return map_info_type_id;
+}
+
+/* If the subcmd needs to print out the bpf_map_info,
+ * it should always call map_info_alloc to allocate
+ * a bpf_map_info object instead of allocating it
+ * on the stack.
+ *
+ * map_info_alloc() will take the running kernel's btf
+ * into account. i.e. it will consider the
+ * sizeof(struct bpf_map_info) of the running kernel.
+ *
+ * It will enable the "struct_ops" cmd to print the latest
+ * "struct bpf_map_info".
+ *
+ * [ Recall that "struct_ops" requires the kernel's btf to
+ * be available ]
+ */
+static struct bpf_map_info *map_info_alloc(__u32 *alloc_len)
+{
+ struct bpf_map_info *info;
+
+ if (get_map_info_type_id() < 0)
+ return NULL;
+
+ info = calloc(1, map_info_alloc_len);
+ if (!info)
+ p_err("mem alloc failed");
+ else
+ *alloc_len = map_info_alloc_len;
+
+ return info;
+}
+
+/* It iterates all struct_ops maps of the system.
+ * It returns the fd in "*res_fd" and map_info in "*info".
+ * In the very first iteration, info->id should be 0.
+ * An optional map "*name" filter can be specified.
+ * The filter can be made more flexibile in the future.
+ * e.g. filter by kernel-struct-ops-name, regex-name, glob-name, ...etc.
+ *
+ * Return value:
+ * 1: A struct_ops map found. It is returned in "*res_fd" and "*info".
+ * The caller can continue to call get_next in the future.
+ * 0: No struct_ops map is returned.
+ * All struct_ops map has been found.
+ * -1: Error and the caller should abort the iteration.
+ */
+static int get_next_struct_ops_map(const char *name, int *res_fd,
+ struct bpf_map_info *info, __u32 info_len)
+{
+ __u32 id = info->id;
+ int err, fd;
+
+ while (true) {
+ err = bpf_map_get_next_id(id, &id);
+ if (err) {
+ if (errno == ENOENT)
+ return 0;
+ p_err("can't get next map %s", strerror(errno));
+ return -1;
+ }
+
+ fd = bpf_map_get_fd_by_id(id);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ continue;
+ p_err("can't get map by id (%u): %s",
+ id, strerror(errno));
+ return -1;
+ }
+
+ err = bpf_obj_get_info_by_fd(fd, info, &info_len);
+ if (err) {
+ p_err("can't get map info: %s", strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ if (info->type == BPF_MAP_TYPE_STRUCT_OPS &&
+ (!name || !strcmp(name, info->name))) {
+ *res_fd = fd;
+ return 1;
+ }
+ close(fd);
+ }
+}
+
+static int cmd_retval(const struct res *res, bool must_have_one_map)
+{
+ if (res->nr_errs || (!res->nr_maps && must_have_one_map))
+ return -1;
+
+ return 0;
+}
+
+/* "data" is the work_func private storage */
+typedef int (*work_func)(int fd, const struct bpf_map_info *info, void *data,
+ struct json_writer *wtr);
+
+/* Find all struct_ops map in the system.
+ * Filter out by "name" (if specified).
+ * Then call "func(fd, info, data, wtr)" on each struct_ops map found.
+ */
+static struct res do_search(const char *name, work_func func, void *data,
+ struct json_writer *wtr)
+{
+ struct bpf_map_info *info;
+ struct res res = {};
+ __u32 info_len;
+ int fd, err;
+
+ info = map_info_alloc(&info_len);
+ if (!info) {
+ res.nr_errs++;
+ return res;
+ }
+
+ if (wtr)
+ jsonw_start_array(wtr);
+ while ((err = get_next_struct_ops_map(name, &fd, info, info_len)) == 1) {
+ res.nr_maps++;
+ err = func(fd, info, data, wtr);
+ if (err)
+ res.nr_errs++;
+ close(fd);
+ }
+ if (wtr)
+ jsonw_end_array(wtr);
+
+ if (err)
+ res.nr_errs++;
+
+ if (!wtr && name && !res.nr_errs && !res.nr_maps)
+ /* It is not printing empty [].
+ * Thus, needs to specifically say nothing found
+ * for "name" here.
+ */
+ p_err("no struct_ops found for %s", name);
+ else if (!wtr && json_output && !res.nr_errs)
+ /* The "func()" above is not writing any json (i.e. !wtr
+ * test here).
+ *
+ * However, "-j" is enabled and there is no errs here,
+ * so call json_null() as the current convention of
+ * other cmds.
+ */
+ jsonw_null(json_wtr);
+
+ free(info);
+ return res;
+}
+
+static struct res do_one_id(const char *id_str, work_func func, void *data,
+ struct json_writer *wtr)
+{
+ struct bpf_map_info *info;
+ struct res res = {};
+ unsigned long id;
+ __u32 info_len;
+ char *endptr;
+ int fd;
+
+ id = strtoul(id_str, &endptr, 0);
+ if (*endptr || !id || id > UINT32_MAX) {
+ p_err("invalid id %s", id_str);
+ res.nr_errs++;
+ return res;
+ }
+
+ fd = bpf_map_get_fd_by_id(id);
+ if (fd == -1) {
+ p_err("can't get map by id (%lu): %s", id, strerror(errno));
+ res.nr_errs++;
+ return res;
+ }
+
+ info = map_info_alloc(&info_len);
+ if (!info) {
+ res.nr_errs++;
+ goto done;
+ }
+
+ if (bpf_obj_get_info_by_fd(fd, info, &info_len)) {
+ p_err("can't get map info: %s", strerror(errno));
+ res.nr_errs++;
+ goto done;
+ }
+
+ if (info->type != BPF_MAP_TYPE_STRUCT_OPS) {
+ p_err("%s id %u is not a struct_ops map", info->name, info->id);
+ res.nr_errs++;
+ goto done;
+ }
+
+ res.nr_maps++;
+
+ if (func(fd, info, data, wtr))
+ res.nr_errs++;
+ else if (!wtr && json_output)
+ /* The "func()" above is not writing any json (i.e. !wtr
+ * test here).
+ *
+ * However, "-j" is enabled and there is no errs here,
+ * so call json_null() as the current convention of
+ * other cmds.
+ */
+ jsonw_null(json_wtr);
+
+done:
+ free(info);
+ close(fd);
+
+ return res;
+}
+
+static struct res do_work_on_struct_ops(const char *search_type,
+ const char *search_term,
+ work_func func, void *data,
+ struct json_writer *wtr)
+{
+ if (search_type) {
+ if (is_prefix(search_type, "id"))
+ return do_one_id(search_term, func, data, wtr);
+ else if (!is_prefix(search_type, "name"))
+ usage();
+ }
+
+ return do_search(search_term, func, data, wtr);
+}
+
+static int __do_show(int fd, const struct bpf_map_info *info, void *data,
+ struct json_writer *wtr)
+{
+ if (wtr) {
+ jsonw_start_object(wtr);
+ jsonw_uint_field(wtr, "id", info->id);
+ jsonw_string_field(wtr, "name", info->name);
+ jsonw_string_field(wtr, "kernel_struct_ops",
+ get_kern_struct_ops_name(info));
+ jsonw_end_object(wtr);
+ } else {
+ printf("%u: %-15s %-32s\n", info->id, info->name,
+ get_kern_struct_ops_name(info));
+ }
+
+ return 0;
+}
+
+static int do_show(int argc, char **argv)
+{
+ const char *search_type = NULL, *search_term = NULL;
+ struct res res;
+
+ if (argc && argc != 2)
+ usage();
+
+ if (argc == 2) {
+ search_type = argv[0];
+ search_term = argv[1];
+ }
+
+ res = do_work_on_struct_ops(search_type, search_term, __do_show,
+ NULL, json_wtr);
+
+ return cmd_retval(&res, !!search_term);
+}
+
+static int __do_dump(int fd, const struct bpf_map_info *info, void *data,
+ struct json_writer *wtr)
+{
+ struct btf_dumper *d = (struct btf_dumper *)data;
+ const struct btf_type *struct_ops_type;
+ const struct btf *kern_btf = d->btf;
+ const char *struct_ops_name;
+ int zero = 0;
+ void *value;
+
+ /* note: d->jw == wtr */
+
+ kern_btf = d->btf;
+
+ /* The kernel supporting BPF_MAP_TYPE_STRUCT_OPS must have
+ * btf_vmlinux_value_type_id.
+ */
+ struct_ops_type = btf__type_by_id(kern_btf,
+ info->btf_vmlinux_value_type_id);
+ struct_ops_name = btf__name_by_offset(kern_btf,
+ struct_ops_type->name_off);
+ value = calloc(1, info->value_size);
+ if (!value) {
+ p_err("mem alloc failed");
+ return -1;
+ }
+
+ if (bpf_map_lookup_elem(fd, &zero, value)) {
+ p_err("can't lookup struct_ops map %s id %u",
+ info->name, info->id);
+ free(value);
+ return -1;
+ }
+
+ jsonw_start_object(wtr);
+ jsonw_name(wtr, "bpf_map_info");
+ btf_dumper_type(d, map_info_type_id, (void *)info);
+ jsonw_end_object(wtr);
+
+ jsonw_start_object(wtr);
+ jsonw_name(wtr, struct_ops_name);
+ btf_dumper_type(d, info->btf_vmlinux_value_type_id, value);
+ jsonw_end_object(wtr);
+
+ free(value);
+
+ return 0;
+}
+
+static int do_dump(int argc, char **argv)
+{
+ const char *search_type = NULL, *search_term = NULL;
+ json_writer_t *wtr = json_wtr;
+ const struct btf *kern_btf;
+ struct btf_dumper d = {};
+ struct res res;
+
+ if (argc && argc != 2)
+ usage();
+
+ if (argc == 2) {
+ search_type = argv[0];
+ search_term = argv[1];
+ }
+
+ kern_btf = get_btf_vmlinux();
+ if (IS_ERR(kern_btf))
+ return -1;
+
+ if (!json_output) {
+ wtr = jsonw_new(stdout);
+ if (!wtr) {
+ p_err("can't create json writer");
+ return -1;
+ }
+ jsonw_pretty(wtr, true);
+ }
+
+ d.btf = kern_btf;
+ d.jw = wtr;
+ d.is_plain_text = !json_output;
+ d.prog_id_as_func_ptr = true;
+
+ res = do_work_on_struct_ops(search_type, search_term, __do_dump, &d,
+ wtr);
+
+ if (!json_output)
+ jsonw_destroy(&wtr);
+
+ return cmd_retval(&res, !!search_term);
+}
+
+static int __do_unregister(int fd, const struct bpf_map_info *info, void *data,
+ struct json_writer *wtr)
+{
+ int zero = 0;
+
+ if (bpf_map_delete_elem(fd, &zero)) {
+ p_err("can't unload %s %s id %u: %s",
+ get_kern_struct_ops_name(info), info->name,
+ info->id, strerror(errno));
+ return -1;
+ }
+
+ p_info("Unregistered %s %s id %u",
+ get_kern_struct_ops_name(info), info->name,
+ info->id);
+
+ return 0;
+}
+
+static int do_unregister(int argc, char **argv)
+{
+ const char *search_type, *search_term;
+ struct res res;
+
+ if (argc != 2)
+ usage();
+
+ search_type = argv[0];
+ search_term = argv[1];
+
+ res = do_work_on_struct_ops(search_type, search_term,
+ __do_unregister, NULL, NULL);
+
+ return cmd_retval(&res, true);
+}
+
+static int do_register(int argc, char **argv)
+{
+ const struct bpf_map_def *def;
+ struct bpf_map_info info = {};
+ __u32 info_len = sizeof(info);
+ int nr_errs = 0, nr_maps = 0;
+ struct bpf_object *obj;
+ struct bpf_link *link;
+ struct bpf_map *map;
+ const char *file;
+
+ if (argc != 1)
+ usage();
+
+ file = argv[0];
+
+ obj = bpf_object__open(file);
+ if (IS_ERR_OR_NULL(obj))
+ return -1;
+
+ set_max_rlimit();
+
+ if (bpf_object__load(obj)) {
+ bpf_object__close(obj);
+ return -1;
+ }
+
+ bpf_object__for_each_map(map, obj) {
+ def = bpf_map__def(map);
+ if (def->type != BPF_MAP_TYPE_STRUCT_OPS)
+ continue;
+
+ link = bpf_map__attach_struct_ops(map);
+ if (IS_ERR(link)) {
+ p_err("can't register struct_ops %s: %s",
+ bpf_map__name(map),
+ strerror(-PTR_ERR(link)));
+ nr_errs++;
+ continue;
+ }
+ nr_maps++;
+
+ bpf_link__disconnect(link);
+ bpf_link__destroy(link);
+
+ if (!bpf_obj_get_info_by_fd(bpf_map__fd(map), &info,
+ &info_len))
+ p_info("Registered %s %s id %u",
+ get_kern_struct_ops_name(&info),
+ bpf_map__name(map),
+ info.id);
+ else
+ /* Not p_err. The struct_ops was attached
+ * successfully.
+ */
+ p_info("Registered %s but can't find id: %s",
+ bpf_map__name(map), strerror(errno));
+ }
+
+ bpf_object__close(obj);
+
+ if (nr_errs)
+ return -1;
+
+ if (!nr_maps) {
+ p_err("no struct_ops found in %s", file);
+ return -1;
+ }
+
+ if (json_output)
+ jsonw_null(json_wtr);
+
+ return 0;
+}
+
+static int do_help(int argc, char **argv)
+{
+ if (json_output) {
+ jsonw_null(json_wtr);
+ return 0;
+ }
+
+ fprintf(stderr,
+ "Usage: %s %s { show | list } [STRUCT_OPS_MAP]\n"
+ " %s %s dump [STRUCT_OPS_MAP]\n"
+ " %s %s register OBJ\n"
+ " %s %s unregister STRUCT_OPS_MAP\n"
+ " %s %s help\n"
+ "\n"
+ " OPTIONS := { {-j|--json} [{-p|--pretty}] }\n"
+ " STRUCT_OPS_MAP := [ id STRUCT_OPS_MAP_ID | name STRUCT_OPS_MAP_NAME ]\n",
+ bin_name, argv[-2], bin_name, argv[-2],
+ bin_name, argv[-2], bin_name, argv[-2],
+ bin_name, argv[-2]);
+
+ return 0;
+}
+
+static const struct cmd cmds[] = {
+ { "show", do_show },
+ { "list", do_show },
+ { "register", do_register },
+ { "unregister", do_unregister },
+ { "dump", do_dump },
+ { "help", do_help },
+ { 0 }
+};
+
+int do_struct_ops(int argc, char **argv)
+{
+ int err;
+
+ err = cmd_select(cmds, argc, argv, do_help);
+
+ btf__free(btf_vmlinux);
+ return err;
+}
--
2.17.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next 4/4] bpftool: Add struct_ops support
2020-03-16 0:56 ` [PATCH bpf-next 4/4] bpftool: Add struct_ops support Martin KaFai Lau
@ 2020-03-16 11:54 ` Quentin Monnet
2020-03-17 0:24 ` Martin KaFai Lau
0 siblings, 1 reply; 13+ messages in thread
From: Quentin Monnet @ 2020-03-16 11:54 UTC (permalink / raw)
To: Martin KaFai Lau, bpf
Cc: Alexei Starovoitov, Daniel Borkmann, kernel-team, netdev
2020-03-15 17:56 UTC-0700 ~ Martin KaFai Lau <kafai@fb.com>
> This patch adds struct_ops support to the bpftool.
[...]
> Signed-off-by: Martin KaFai Lau <kafai@fb.com>
> ---
> .../Documentation/bpftool-struct_ops.rst | 106 ++++
> tools/bpf/bpftool/bash-completion/bpftool | 28 +
> tools/bpf/bpftool/main.c | 3 +-
> tools/bpf/bpftool/main.h | 1 +
> tools/bpf/bpftool/struct_ops.c | 595 ++++++++++++++++++
> 5 files changed, 732 insertions(+), 1 deletion(-)
> create mode 100644 tools/bpf/bpftool/Documentation/bpftool-struct_ops.rst
> create mode 100644 tools/bpf/bpftool/struct_ops.c
>
> diff --git a/tools/bpf/bpftool/Documentation/bpftool-struct_ops.rst b/tools/bpf/bpftool/Documentation/bpftool-struct_ops.rst
> new file mode 100644
> index 000000000000..27aae5bc632e
> --- /dev/null
> +++ b/tools/bpf/bpftool/Documentation/bpftool-struct_ops.rst
> @@ -0,0 +1,106 @@
> +==================
> +bpftool-struct_ops
> +==================
> +-------------------------------------------------------------------------------
> +tool to register/unregister/introspect BPF struct_ops
> +-------------------------------------------------------------------------------
> +
> +:Manual section: 8
> +
> +SYNOPSIS
> +========
> +
> + **bpftool** [*OPTIONS*] **struct_ops** *COMMAND*
> +
> + *OPTIONS* := { { **-j** | **--json** } [{ **-p** | **--pretty** }] }
> +
> + *COMMANDS* :=
> + { **show** | **list** | **dump** | **register** | **unregister** | **help** }
> +
> +STRUCT_OPS COMMANDS
> +===================
> +
> +| **bpftool** **struct_ops { show | list }** [*STRUCT_OPS_MAP*]
> +| **bpftool** **struct_ops dump** [*STRUCT_OPS_MAP*]
> +| **bpftool** **struct_ops register** *OBJ*
> +| **bpftool** **struct_ops unregister** *STRUCT_OPS_MAP*
> +| **bpftool** **struct_ops help**
> +|
> +| *STRUCT_OPS_MAP* := { **id** *STRUCT_OPS_MAP_ID* | **name** *STRUCT_OPS_MAP_NAME* }
> +| *OBJ* := /a/file/of/bpf_struct_ops.o
> +
> +
> +DESCRIPTION
> +===========
> + **bpftool struct_ops { show | list }** [*STRUCT_OPS_MAP*]
> + Show brief information about the struct_ops in the system.
> + If *STRUCT_OPS_MAP* is specified, it shows information only
> + for the given struct_ops. Otherwise, it lists all struct_ops
> + currently exists in the system.
Typo: s/exists/existing/
> +
> + Output will start with struct_ops map ID, followed by its map
> + name and its struct_ops's kernel type.
> +
> + **bpftool struct_ops dump** [*STRUCT_OPS_MAP*]
> + Dump details information about the struct_ops in the system.
> + If *STRUCT_OPS_MAP* is specified, it dumps information only
> + for the given struct_ops. Otherwise, it dumps all struct_ops
> + currently exists in the system.
Same here.
> +
> + **bpftool struct_ops register** *OBJ*
> + Register bpf struct_ops from *OBJ*. All struct_ops under
> + the ELF section ".struct_ops" will be registered to
> + its kernel subsystem.
> +
> + **bpftool struct_ops unregister** *STRUCT_OPS_MAP*
> + Unregister the *STRUCT_OPS_MAP* from the kernel subsystem.
> +
> + **bpftool struct_ops help**
> + Print short help message.
> +
> +OPTIONS
> +=======
> + -h, --help
> + Print short generic help message (similar to **bpftool help**).
> +
> + -V, --version
> + Print version number (similar to **bpftool version**).
> +
> + -j, --json
> + Generate JSON output. For commands that cannot produce JSON, this
> + option has no effect.
> +
> + -p, --pretty
> + Generate human-readable JSON output. Implies **-j**.
> +
> + -d, --debug
> + Print all logs available, even debug-level information. This
> + includes logs from libbpf as well as from the verifier, when
> + attempting to load programs.
> +
> +EXAMPLES
> +========
> +**# bpftool struct_ops show**
> +
> +::
> +
> + 100: dctcp tcp_congestion_ops
> + 105: cubic tcp_congestion_ops
> +
> +**# bpftool struct_ops unregister id 105**
> +
> +::
> +
> + Unregistered tcp_congestion_ops cubic id 105
> +
> +**# bpftool struct_ops register bpf_cubic.o**
> +
> +::
> +
> + Registered tcp_congestion_ops cubic id 110
> +
> +
> +SEE ALSO
> +========
> + **bpftool-map**\ (8)
> + **bpftool-prog**\ (8)
Other man pages link to all available bpftool-* pages. If you do not
want to do that, could you at least link to bpf(2) and bpftool(8) please?
[...]
> diff --git a/tools/bpf/bpftool/struct_ops.c b/tools/bpf/bpftool/struct_ops.c
> new file mode 100644
> index 000000000000..ba145e3d0d5d
> --- /dev/null
> +++ b/tools/bpf/bpftool/struct_ops.c
> @@ -0,0 +1,595 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +/* Copyright (C) 2020 Facebook */
> +
> +#include <unistd.h>
> +#include <stdio.h>
> +#include <errno.h>
> +
> +#include <linux/err.h>
Nit: line break here, please
> +#include <bpf/libbpf.h>
> +#include <bpf/bpf.h>
> +#include <bpf/btf.h>
Nit again: Could you please have the includes in each block ordered
alphabetically?
> +
> +#include "json_writer.h"
> +#include "main.h"
> +
> +#define STRUCT_OPS_VALUE_PREFIX "bpf_struct_ops_"
> +
[...]
> +static struct bpf_map_info *map_info_alloc(__u32 *alloc_len)
> +{
> + struct bpf_map_info *info;
> +
> + if (get_map_info_type_id() < 0)
> + return NULL;
> +
> + info = calloc(1, map_info_alloc_len);
> + if (!info)
> + p_err("mem alloc failed");
> + else
> + *alloc_len = map_info_alloc_len;
> +
> + return info;
> +}
> +
> +/* It iterates all struct_ops maps of the system.
> + * It returns the fd in "*res_fd" and map_info in "*info".
> + * In the very first iteration, info->id should be 0.
> + * An optional map "*name" filter can be specified.
> + * The filter can be made more flexibile in the future.
Typo: flexible
> + * e.g. filter by kernel-struct-ops-name, regex-name, glob-name, ...etc.
> + *
> + * Return value:
> + * 1: A struct_ops map found. It is returned in "*res_fd" and "*info".
> + * The caller can continue to call get_next in the future.
> + * 0: No struct_ops map is returned.
> + * All struct_ops map has been found.
> + * -1: Error and the caller should abort the iteration.
> + */
> +static int get_next_struct_ops_map(const char *name, int *res_fd,
> + struct bpf_map_info *info, __u32 info_len)
> +{
> + __u32 id = info->id;
> + int err, fd;
> +
> + while (true) {
> + err = bpf_map_get_next_id(id, &id);
> + if (err) {
> + if (errno == ENOENT)
> + return 0;
> + p_err("can't get next map %s", strerror(errno));
Nit: Add a colon before "%s"?
> + return -1;
> + }
> +
> + fd = bpf_map_get_fd_by_id(id);
> + if (fd < 0) {
> + if (errno == ENOENT)
> + continue;
> + p_err("can't get map by id (%u): %s",
> + id, strerror(errno));
> + return -1;
> + }
> +
> + err = bpf_obj_get_info_by_fd(fd, info, &info_len);
> + if (err) {
> + p_err("can't get map info: %s", strerror(errno));
> + close(fd);
> + return -1;
> + }
> +
> + if (info->type == BPF_MAP_TYPE_STRUCT_OPS &&
> + (!name || !strcmp(name, info->name))) {
> + *res_fd = fd;
> + return 1;
> + }
> + close(fd);
> + }
> +}
[...]
> +static int do_unregister(int argc, char **argv)
> +{
> + const char *search_type, *search_term;
> + struct res res;
> +
> + if (argc != 2)
> + usage();
Or you could reuse the macros in main.h, for more consistency with other
subcommands:
if (!REQ_ARGS(2))
return -1;
> +
> + search_type = argv[0];
> + search_term = argv[1];
search_type = GET_ARG();
search_term = GET_ARG();
> +
> + res = do_work_on_struct_ops(search_type, search_term,
> + __do_unregister, NULL, NULL);
> +
> + return cmd_retval(&res, true);
> +}
> +
> +static int do_register(int argc, char **argv)
> +{
> + const struct bpf_map_def *def;
> + struct bpf_map_info info = {};
> + __u32 info_len = sizeof(info);
> + int nr_errs = 0, nr_maps = 0;
> + struct bpf_object *obj;
> + struct bpf_link *link;
> + struct bpf_map *map;
> + const char *file;
> +
> + if (argc != 1)
> + usage();
(Same remark here.)
> +
> + file = argv[0];
> +
> + obj = bpf_object__open(file);
> + if (IS_ERR_OR_NULL(obj))
> + return -1;
[...]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next 0/4] bpftool: Add struct_ops support
2020-03-16 0:55 [PATCH bpf-next 0/4] bpftool: Add struct_ops support Martin KaFai Lau
` (3 preceding siblings ...)
2020-03-16 0:56 ` [PATCH bpf-next 4/4] bpftool: Add struct_ops support Martin KaFai Lau
@ 2020-03-16 11:54 ` Quentin Monnet
4 siblings, 0 replies; 13+ messages in thread
From: Quentin Monnet @ 2020-03-16 11:54 UTC (permalink / raw)
To: Martin KaFai Lau, bpf
Cc: Alexei Starovoitov, Daniel Borkmann, kernel-team, netdev
2020-03-15 17:55 UTC-0700 ~ Martin KaFai Lau <kafai@fb.com>
> This set adds "struct_ops" support to bpftool.
>
> The first two patches improve the btf_dumper in bpftool.
> Patch 1: print the enum's name (if it is found) instead of the
> enum's value.
> Patch 2: print a char[] as a string if all characters are printable.
>
> "struct_ops" stores the prog_id in a func ptr.
> Instead of printing a prog_id,
> patch 3 adds an option to btf_dumper to allow a func ptr's value
> to be printed with the full func_proto info and the prog_name.
>
> Patch 4 implements the "struct_ops" bpftool command.
Hi Martin, I have a few very small nits on patch 4 -- please see related
message -- but other than this your series looks good to me.
Acked-by: Quentin Monnet <quentin@isovalent.com>
(I did not test it, though.)
Thanks,
Quentin
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next 4/4] bpftool: Add struct_ops support
2020-03-16 11:54 ` Quentin Monnet
@ 2020-03-17 0:24 ` Martin KaFai Lau
2020-03-17 0:57 ` Martin KaFai Lau
0 siblings, 1 reply; 13+ messages in thread
From: Martin KaFai Lau @ 2020-03-17 0:24 UTC (permalink / raw)
To: Quentin Monnet
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, kernel-team, netdev
On Mon, Mar 16, 2020 at 11:54:28AM +0000, Quentin Monnet wrote:
> [...]
>
> > +static int do_unregister(int argc, char **argv)
> > +{
> > + const char *search_type, *search_term;
> > + struct res res;
> > +
> > + if (argc != 2)
> > + usage();
>
> Or you could reuse the macros in main.h, for more consistency with other
> subcommands:
>
> if (!REQ_ARGS(2))
> return -1;
Thanks for the review!
I prefer to print out "usage();" whenever possible but then "-j" gave
me a 'null' after a json error mesage ...
# bpftool -j struct_ops unregister
{"error":"'unregister' needs at least 2 arguments, 0 found"},null
Then I went without REQ_ARGS(2) which is similar to a few existing
cases like do_dump(), do_updaate()...etc in map.c.
That was my consideration. However, I can go back to use REQ_ARGS(2)
and return -1 without printing usage. no strong preference here.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next 4/4] bpftool: Add struct_ops support
2020-03-17 0:24 ` Martin KaFai Lau
@ 2020-03-17 0:57 ` Martin KaFai Lau
2020-03-17 9:25 ` Quentin Monnet
0 siblings, 1 reply; 13+ messages in thread
From: Martin KaFai Lau @ 2020-03-17 0:57 UTC (permalink / raw)
To: Quentin Monnet
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, kernel-team, netdev
On Mon, Mar 16, 2020 at 05:24:52PM -0700, Martin KaFai Lau wrote:
> On Mon, Mar 16, 2020 at 11:54:28AM +0000, Quentin Monnet wrote:
>
> > [...]
> >
> > > +static int do_unregister(int argc, char **argv)
> > > +{
> > > + const char *search_type, *search_term;
> > > + struct res res;
> > > +
> > > + if (argc != 2)
> > > + usage();
> >
> > Or you could reuse the macros in main.h, for more consistency with other
> > subcommands:
> >
> > if (!REQ_ARGS(2))
> > return -1;
> Thanks for the review!
>
> I prefer to print out "usage();" whenever possible but then "-j" gave
> me a 'null' after a json error mesage ...
>
> # bpftool -j struct_ops unregister
> {"error":"'unregister' needs at least 2 arguments, 0 found"},null
>
> Then I went without REQ_ARGS(2) which is similar to a few existing
> cases like do_dump(), do_updaate()...etc in map.c.
>
> That was my consideration. However, I can go back to use REQ_ARGS(2)
> and return -1 without printing usage. no strong preference here.
After another look, I will keep it as is since REQ_ARGS() is a "<"
check. "argc != 2" is the correct check here. Otherwise,
allowing 'bpftool struct_ops unregister name cubic dctcp' looks weird.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next 4/4] bpftool: Add struct_ops support
2020-03-17 0:57 ` Martin KaFai Lau
@ 2020-03-17 9:25 ` Quentin Monnet
0 siblings, 0 replies; 13+ messages in thread
From: Quentin Monnet @ 2020-03-17 9:25 UTC (permalink / raw)
To: Martin KaFai Lau
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, kernel-team, netdev
2020-03-16 17:57 UTC-0700 ~ Martin KaFai Lau <kafai@fb.com>
> On Mon, Mar 16, 2020 at 05:24:52PM -0700, Martin KaFai Lau wrote:
>> On Mon, Mar 16, 2020 at 11:54:28AM +0000, Quentin Monnet wrote:
>>
>>> [...]
>>>
>>>> +static int do_unregister(int argc, char **argv)
>>>> +{
>>>> + const char *search_type, *search_term;
>>>> + struct res res;
>>>> +
>>>> + if (argc != 2)
>>>> + usage();
>>>
>>> Or you could reuse the macros in main.h, for more consistency with other
>>> subcommands:
>>>
>>> if (!REQ_ARGS(2))
>>> return -1;
>> Thanks for the review!
>>
>> I prefer to print out "usage();" whenever possible but then "-j" gave
>> me a 'null' after a json error mesage ...
>>
>> # bpftool -j struct_ops unregister
>> {"error":"'unregister' needs at least 2 arguments, 0 found"},null
>>
>> Then I went without REQ_ARGS(2) which is similar to a few existing
>> cases like do_dump(), do_updaate()...etc in map.c.
>>
>> That was my consideration. However, I can go back to use REQ_ARGS(2)
>> and return -1 without printing usage. no strong preference here.
> After another look, I will keep it as is since REQ_ARGS() is a "<"
> check. "argc != 2" is the correct check here. Otherwise,
> allowing 'bpftool struct_ops unregister name cubic dctcp' looks weird.
>
Ah right, fair enough. I'm good with v2, thanks for the changes!
Quentin
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next 1/4] bpftool: Print the enum's name instead of value
2020-03-16 0:56 ` [PATCH bpf-next 1/4] bpftool: Print the enum's name instead of value Martin KaFai Lau
@ 2020-03-17 20:03 ` Andrii Nakryiko
0 siblings, 0 replies; 13+ messages in thread
From: Andrii Nakryiko @ 2020-03-17 20:03 UTC (permalink / raw)
To: Martin KaFai Lau
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Kernel Team, Networking
On Sun, Mar 15, 2020 at 5:56 PM Martin KaFai Lau <kafai@fb.com> wrote:
>
> This patch prints the enum's name if there is one found in
> the array of btf_enum.
>
> The commit 9eea98497951 ("bpf: fix BTF verification of enums")
> has details about an enum could have any power-of-2 size (up to 8 bytes).
> This patch also takes this chance to accommodate these non 4 byte
> enums.
>
> Signed-off-by: Martin KaFai Lau <kafai@fb.com>
> ---
> tools/bpf/bpftool/btf_dumper.c | 35 +++++++++++++++++++++++++++++++---
> 1 file changed, 32 insertions(+), 3 deletions(-)
>
> diff --git a/tools/bpf/bpftool/btf_dumper.c b/tools/bpf/bpftool/btf_dumper.c
> index 01cc52b834fa..57bd6c0fafc9 100644
> --- a/tools/bpf/bpftool/btf_dumper.c
> +++ b/tools/bpf/bpftool/btf_dumper.c
> @@ -43,9 +43,38 @@ static int btf_dumper_modifier(const struct btf_dumper *d, __u32 type_id,
> return btf_dumper_do_type(d, actual_type_id, bit_offset, data);
> }
>
> -static void btf_dumper_enum(const void *data, json_writer_t *jw)
> +static void btf_dumper_enum(const struct btf_dumper *d,
> + const struct btf_type *t,
> + const void *data)
> {
> - jsonw_printf(jw, "%d", *(int *)data);
> + const struct btf_enum *enums = btf_enum(t);
> + __s64 value;
> + __u16 i;
> +
> + switch (t->size) {
> + case 8:
> + value = *(__s64 *)data;
> + break;
> + case 4:
> + value = *(__s32 *)data;
> + break;
> + case 2:
> + value = *(__s16 *)data;
> + break;
> + default:
> + value = *(__s8 *)data;
> + }
I'm scared of catch-all defaults like this. Let's do `case 1: `
explicitly and error out on anything else here?
> +
> + for (i = 0; i < btf_vlen(t); i++) {
> + if (value == enums[i].val) {
> + jsonw_string(d->jw,
> + btf__name_by_offset(d->btf,
> + enums[i].name_off));
> + return;
> + }
> + }
> +
> + jsonw_int(d->jw, value);
> }
>
> static int btf_dumper_array(const struct btf_dumper *d, __u32 type_id,
> @@ -366,7 +395,7 @@ static int btf_dumper_do_type(const struct btf_dumper *d, __u32 type_id,
> case BTF_KIND_ARRAY:
> return btf_dumper_array(d, type_id, data);
> case BTF_KIND_ENUM:
> - btf_dumper_enum(data, d->jw);
> + btf_dumper_enum(d, t, data);
> return 0;
> case BTF_KIND_PTR:
> btf_dumper_ptr(data, d->jw, d->is_plain_text);
> --
> 2.17.1
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next 2/4] bpftool: Print as a string for char array
2020-03-16 0:56 ` [PATCH bpf-next 2/4] bpftool: Print as a string for char array Martin KaFai Lau
@ 2020-03-17 20:08 ` Andrii Nakryiko
2020-03-17 21:03 ` Martin KaFai Lau
0 siblings, 1 reply; 13+ messages in thread
From: Andrii Nakryiko @ 2020-03-17 20:08 UTC (permalink / raw)
To: Martin KaFai Lau
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Kernel Team, Networking
On Sun, Mar 15, 2020 at 5:57 PM Martin KaFai Lau <kafai@fb.com> wrote:
>
> A char[] is currently printed as an integer array.
> This patch will print it as a string when
> 1) The array element type is an one byte int
> 2) The array element type has a BTF_INT_CHAR encoding or
> the array element type's name is "char"
> 3) All characters is between (0x1f, 0x7f) and it is terminated
> by a null character.
>
> Signed-off-by: Martin KaFai Lau <kafai@fb.com>
> ---
> tools/bpf/bpftool/btf_dumper.c | 41 ++++++++++++++++++++++++++++++++++
> 1 file changed, 41 insertions(+)
>
> diff --git a/tools/bpf/bpftool/btf_dumper.c b/tools/bpf/bpftool/btf_dumper.c
> index 57bd6c0fafc9..1d2d8d2cedea 100644
> --- a/tools/bpf/bpftool/btf_dumper.c
> +++ b/tools/bpf/bpftool/btf_dumper.c
> @@ -77,6 +77,42 @@ static void btf_dumper_enum(const struct btf_dumper *d,
> jsonw_int(d->jw, value);
> }
>
> +static bool is_str_array(const struct btf *btf, const struct btf_array *arr,
> + const char *s)
> +{
> + const struct btf_type *elem_type;
> + const char *end_s;
> +
> + if (!arr->nelems)
> + return false;
> +
> + elem_type = btf__type_by_id(btf, arr->type);
> + /* Not skipping typedef. typedef to char does not count as
> + * a string now.
> + */
> + while (elem_type && btf_is_mod(elem_type))
> + elem_type = btf__type_by_id(btf, elem_type->type);
> +
> + if (!elem_type || !btf_is_int(elem_type) || elem_type->size != 1)
> + return false;
> +
> + if (btf_int_encoding(elem_type) != BTF_INT_CHAR &&
> + strcmp("char", btf__name_by_offset(btf, elem_type->name_off)))
> + return false;
> +
> + end_s = s + arr->nelems;
> + while (s < end_s) {
> + if (!*s)
> + return true;
> + if (*s <= 0x1f || *s >= 0x7f)
> + return false;
> + s++;
> + }
> +
> + /* '\0' is not found */
> + return false;
> +}
> +
> static int btf_dumper_array(const struct btf_dumper *d, __u32 type_id,
> const void *data)
> {
> @@ -86,6 +122,11 @@ static int btf_dumper_array(const struct btf_dumper *d, __u32 type_id,
> int ret = 0;
> __u32 i;
>
> + if (is_str_array(d->btf, arr, data)) {
> + jsonw_string(d->jw, data);
> + return 0;
> + }
> +
Looks good, but curious how the string that contains ' or " will be
output in json? Will it be escaped properly or will result in
malformed JSON?
Acked-by: Andrii Nakryiko <andriin@fb.com>
> elem_size = btf__resolve_size(d->btf, arr->type);
> if (elem_size < 0)
> return elem_size;
> --
> 2.17.1
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH bpf-next 2/4] bpftool: Print as a string for char array
2020-03-17 20:08 ` Andrii Nakryiko
@ 2020-03-17 21:03 ` Martin KaFai Lau
0 siblings, 0 replies; 13+ messages in thread
From: Martin KaFai Lau @ 2020-03-17 21:03 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: bpf, Alexei Starovoitov, Daniel Borkmann, Kernel Team, Networking
On Tue, Mar 17, 2020 at 01:08:01PM -0700, Andrii Nakryiko wrote:
> On Sun, Mar 15, 2020 at 5:57 PM Martin KaFai Lau <kafai@fb.com> wrote:
> >
> > A char[] is currently printed as an integer array.
> > This patch will print it as a string when
> > 1) The array element type is an one byte int
> > 2) The array element type has a BTF_INT_CHAR encoding or
> > the array element type's name is "char"
> > 3) All characters is between (0x1f, 0x7f) and it is terminated
> > by a null character.
> >
> > Signed-off-by: Martin KaFai Lau <kafai@fb.com>
> > ---
> > tools/bpf/bpftool/btf_dumper.c | 41 ++++++++++++++++++++++++++++++++++
> > 1 file changed, 41 insertions(+)
> >
> > diff --git a/tools/bpf/bpftool/btf_dumper.c b/tools/bpf/bpftool/btf_dumper.c
> > index 57bd6c0fafc9..1d2d8d2cedea 100644
> > --- a/tools/bpf/bpftool/btf_dumper.c
> > +++ b/tools/bpf/bpftool/btf_dumper.c
> > @@ -77,6 +77,42 @@ static void btf_dumper_enum(const struct btf_dumper *d,
> > jsonw_int(d->jw, value);
> > }
> >
> > +static bool is_str_array(const struct btf *btf, const struct btf_array *arr,
> > + const char *s)
> > +{
> > + const struct btf_type *elem_type;
> > + const char *end_s;
> > +
> > + if (!arr->nelems)
> > + return false;
> > +
> > + elem_type = btf__type_by_id(btf, arr->type);
> > + /* Not skipping typedef. typedef to char does not count as
> > + * a string now.
> > + */
> > + while (elem_type && btf_is_mod(elem_type))
> > + elem_type = btf__type_by_id(btf, elem_type->type);
> > +
> > + if (!elem_type || !btf_is_int(elem_type) || elem_type->size != 1)
> > + return false;
> > +
> > + if (btf_int_encoding(elem_type) != BTF_INT_CHAR &&
> > + strcmp("char", btf__name_by_offset(btf, elem_type->name_off)))
> > + return false;
> > +
> > + end_s = s + arr->nelems;
> > + while (s < end_s) {
> > + if (!*s)
> > + return true;
> > + if (*s <= 0x1f || *s >= 0x7f)
> > + return false;
> > + s++;
> > + }
> > +
> > + /* '\0' is not found */
> > + return false;
> > +}
> > +
> > static int btf_dumper_array(const struct btf_dumper *d, __u32 type_id,
> > const void *data)
> > {
> > @@ -86,6 +122,11 @@ static int btf_dumper_array(const struct btf_dumper *d, __u32 type_id,
> > int ret = 0;
> > __u32 i;
> >
> > + if (is_str_array(d->btf, arr, data)) {
> > + jsonw_string(d->jw, data);
> > + return 0;
> > + }
> > +
>
> Looks good, but curious how the string that contains ' or " will be
> output in json? Will it be escaped properly or will result in
> malformed JSON?
They will be escaped.
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2020-03-17 21:03 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-03-16 0:55 [PATCH bpf-next 0/4] bpftool: Add struct_ops support Martin KaFai Lau
2020-03-16 0:56 ` [PATCH bpf-next 1/4] bpftool: Print the enum's name instead of value Martin KaFai Lau
2020-03-17 20:03 ` Andrii Nakryiko
2020-03-16 0:56 ` [PATCH bpf-next 2/4] bpftool: Print as a string for char array Martin KaFai Lau
2020-03-17 20:08 ` Andrii Nakryiko
2020-03-17 21:03 ` Martin KaFai Lau
2020-03-16 0:56 ` [PATCH bpf-next 3/4] bpftool: Translate prog_id to its bpf prog_name Martin KaFai Lau
2020-03-16 0:56 ` [PATCH bpf-next 4/4] bpftool: Add struct_ops support Martin KaFai Lau
2020-03-16 11:54 ` Quentin Monnet
2020-03-17 0:24 ` Martin KaFai Lau
2020-03-17 0:57 ` Martin KaFai Lau
2020-03-17 9:25 ` Quentin Monnet
2020-03-16 11:54 ` [PATCH bpf-next 0/4] " Quentin Monnet
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).