summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexei Starovoitov <ast@kernel.org>2023-04-05 21:27:27 -0700
committerAlexei Starovoitov <ast@kernel.org>2023-04-05 21:27:28 -0700
commitc6ebae4ccc43aaf279e36bb9bbe627137d7e88e1 (patch)
tree2fec7cffe9e8f41a97431a9a055f8f3543464d8d
parent5af607a861d43ffff830fc1890033e579ec44799 (diff)
parent7319296855f1bac0e7fb003388f44ecbd4515102 (diff)
Merge branch 'bpftool: Add inline annotations when dumping program CFGs'
Quentin Monnet says: ==================== This set contains some improvements for bpftool's "visual" program dump option, which produces the control flow graph in a DOT format. The main objective is to add support for inline annotations on such graphs, so that we can have the C source code for the program showing up alongside the instructions, when available. The last commits also make it possible to display the line numbers or the bare opcodes in the graph, as supported by regular program dumps. v3: - Fixed formatting of DOT graph: escape spaces, and remove indent that would cause some unwanted spaces to show up in the resulting graph. - Don't print line information if the record is empty. - Add '<' and ' ' to the list of escaped characters for generting the DOT graph. - Truncate long file paths, use shorter field names ("line", "col") for code location information in the graph, add missing separator space. - Add a commit to return an error if JSON output and CFG are both required. - Add a drive-by, clean up commit for bash completion (avoid unnecessary calls to _bpftool_once_attr()). v2: Replace fputc(..., stdout) with putchar(...) in dotlabel_puts(). ==================== Signed-off-by: Alexei Starovoitov <ast@kernel.org>
-rw-r--r--tools/bpf/bpftool/Documentation/bpftool-prog.rst18
-rw-r--r--tools/bpf/bpftool/bash-completion/bpftool42
-rw-r--r--tools/bpf/bpftool/btf_dumper.c83
-rw-r--r--tools/bpf/bpftool/cfg.c29
-rw-r--r--tools/bpf/bpftool/cfg.h5
-rw-r--r--tools/bpf/bpftool/main.h2
-rw-r--r--tools/bpf/bpftool/prog.c78
-rw-r--r--tools/bpf/bpftool/xlated_dumper.c54
-rw-r--r--tools/bpf/bpftool/xlated_dumper.h3
9 files changed, 226 insertions, 88 deletions
diff --git a/tools/bpf/bpftool/Documentation/bpftool-prog.rst b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
index 14de72544995..9443c524bb76 100644
--- a/tools/bpf/bpftool/Documentation/bpftool-prog.rst
+++ b/tools/bpf/bpftool/Documentation/bpftool-prog.rst
@@ -28,8 +28,8 @@ PROG COMMANDS
=============
| **bpftool** **prog** { **show** | **list** } [*PROG*]
-| **bpftool** **prog dump xlated** *PROG* [{**file** *FILE* | **opcodes** | **visual** | **linum**}]
-| **bpftool** **prog dump jited** *PROG* [{**file** *FILE* | **opcodes** | **linum**}]
+| **bpftool** **prog dump xlated** *PROG* [{ **file** *FILE* | [**opcodes**] [**linum**] [**visual**] }]
+| **bpftool** **prog dump jited** *PROG* [{ **file** *FILE* | [**opcodes**] [**linum**] }]
| **bpftool** **prog pin** *PROG* *FILE*
| **bpftool** **prog** { **load** | **loadall** } *OBJ* *PATH* [**type** *TYPE*] [**map** {**idx** *IDX* | **name** *NAME*} *MAP*] [**dev** *NAME*] [**pinmaps** *MAP_DIR*] [**autoattach**]
| **bpftool** **prog attach** *PROG* *ATTACH_TYPE* [*MAP*]
@@ -88,7 +88,7 @@ DESCRIPTION
programs. On such kernels bpftool will automatically emit this
information as well.
- **bpftool prog dump xlated** *PROG* [{ **file** *FILE* | **opcodes** | **visual** | **linum** }]
+ **bpftool prog dump xlated** *PROG* [{ **file** *FILE* | [**opcodes**] [**linum**] [**visual**] }]
Dump eBPF instructions of the programs from the kernel. By
default, eBPF will be disassembled and printed to standard
output in human-readable format. In this case, **opcodes**
@@ -106,11 +106,10 @@ DESCRIPTION
CFG in DOT format, on standard output.
If the programs have line_info available, the source line will
- be displayed by default. If **linum** is specified,
- the filename, line number and line column will also be
- displayed on top of the source line.
+ be displayed. If **linum** is specified, the filename, line
+ number and line column will also be displayed.
- **bpftool prog dump jited** *PROG* [{ **file** *FILE* | **opcodes** | **linum** }]
+ **bpftool prog dump jited** *PROG* [{ **file** *FILE* | [**opcodes**] [**linum**] }]
Dump jited image (host machine code) of the program.
If *FILE* is specified image will be written to a file,
@@ -120,9 +119,8 @@ DESCRIPTION
**opcodes** controls if raw opcodes will be printed.
If the prog has line_info available, the source line will
- be displayed by default. If **linum** is specified,
- the filename, line number and line column will also be
- displayed on top of the source line.
+ be displayed. If **linum** is specified, the filename, line
+ number and line column will also be displayed.
**bpftool prog pin** *PROG* *FILE*
Pin program *PROG* as *FILE*.
diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/bash-completion/bpftool
index 35f26f7c1124..e7234d1a5306 100644
--- a/tools/bpf/bpftool/bash-completion/bpftool
+++ b/tools/bpf/bpftool/bash-completion/bpftool
@@ -255,20 +255,23 @@ _bpftool_map_update_get_name()
_bpftool()
{
- local cur prev words objword
+ local cur prev words objword json=0
_init_completion || return
# Deal with options
if [[ ${words[cword]} == -* ]]; then
local c='--version --json --pretty --bpffs --mapcompat --debug \
- --use-loader --base-btf'
+ --use-loader --base-btf'
COMPREPLY=( $( compgen -W "$c" -- "$cur" ) )
return 0
fi
+ if _bpftool_search_list -j --json -p --pretty; then
+ json=1
+ fi
# Deal with simplest keywords
case $prev in
- help|hex|opcodes|visual|linum)
+ help|hex)
return 0
;;
tag)
@@ -366,13 +369,16 @@ _bpftool()
return 0
;;
*)
- _bpftool_once_attr 'file'
- if _bpftool_search_list 'xlated'; then
- COMPREPLY+=( $( compgen -W 'opcodes visual linum' -- \
- "$cur" ) )
- else
- COMPREPLY+=( $( compgen -W 'opcodes linum' -- \
- "$cur" ) )
+ # "file" is not compatible with other keywords here
+ if _bpftool_search_list 'file'; then
+ return 0
+ fi
+ if ! _bpftool_search_list 'linum opcodes visual'; then
+ _bpftool_once_attr 'file'
+ fi
+ _bpftool_once_attr 'linum opcodes'
+ if _bpftool_search_list 'xlated' && [[ "$json" == 0 ]]; then
+ _bpftool_once_attr 'visual'
fi
return 0
;;
@@ -502,10 +508,7 @@ _bpftool()
;;
*)
COMPREPLY=( $( compgen -W "map" -- "$cur" ) )
- _bpftool_once_attr 'type'
- _bpftool_once_attr 'dev'
- _bpftool_once_attr 'pinmaps'
- _bpftool_once_attr 'autoattach'
+ _bpftool_once_attr 'type dev pinmaps autoattach'
return 0
;;
esac
@@ -730,16 +733,10 @@ _bpftool()
esac
;;
*)
- _bpftool_once_attr 'type'
- _bpftool_once_attr 'key'
- _bpftool_once_attr 'value'
- _bpftool_once_attr 'entries'
- _bpftool_once_attr 'name'
- _bpftool_once_attr 'flags'
+ _bpftool_once_attr 'type key value entries name flags dev'
if _bpftool_search_list 'array_of_maps' 'hash_of_maps'; then
_bpftool_once_attr 'inner_map'
fi
- _bpftool_once_attr 'dev'
return 0
;;
esac
@@ -880,8 +877,7 @@ _bpftool()
return 0
;;
*)
- _bpftool_once_attr 'cpu'
- _bpftool_once_attr 'index'
+ _bpftool_once_attr 'cpu index'
return 0
;;
esac
diff --git a/tools/bpf/bpftool/btf_dumper.c b/tools/bpf/bpftool/btf_dumper.c
index e7f6ec3a8f35..6c5e0e82da22 100644
--- a/tools/bpf/bpftool/btf_dumper.c
+++ b/tools/bpf/bpftool/btf_dumper.c
@@ -821,3 +821,86 @@ void btf_dump_linfo_json(const struct btf *btf,
BPF_LINE_INFO_LINE_COL(linfo->line_col));
}
}
+
+static void dotlabel_puts(const char *s)
+{
+ for (; *s; ++s) {
+ switch (*s) {
+ case '\\':
+ case '"':
+ case '{':
+ case '}':
+ case '<':
+ case '>':
+ case '|':
+ case ' ':
+ putchar('\\');
+ __fallthrough;
+ default:
+ putchar(*s);
+ }
+ }
+}
+
+static const char *shorten_path(const char *path)
+{
+ const unsigned int MAX_PATH_LEN = 32;
+ size_t len = strlen(path);
+ const char *shortpath;
+
+ if (len <= MAX_PATH_LEN)
+ return path;
+
+ /* Search for last '/' under the MAX_PATH_LEN limit */
+ shortpath = strchr(path + len - MAX_PATH_LEN, '/');
+ if (shortpath) {
+ if (shortpath < path + strlen("..."))
+ /* We removed a very short prefix, e.g. "/w", and we'll
+ * make the path longer by prefixing with the ellipsis.
+ * Not worth it, keep initial path.
+ */
+ return path;
+ return shortpath;
+ }
+
+ /* File base name length is > MAX_PATH_LEN, search for last '/' */
+ shortpath = strrchr(path, '/');
+ if (shortpath)
+ return shortpath;
+
+ return path;
+}
+
+void btf_dump_linfo_dotlabel(const struct btf *btf,
+ const struct bpf_line_info *linfo, bool linum)
+{
+ const char *line = btf__name_by_offset(btf, linfo->line_off);
+
+ if (!line || !strlen(line))
+ return;
+ line = ltrim(line);
+
+ if (linum) {
+ const char *file = btf__name_by_offset(btf, linfo->file_name_off);
+ const char *shortfile;
+
+ /* More forgiving on file because linum option is
+ * expected to provide more info than the already
+ * available src line.
+ */
+ if (!file)
+ shortfile = "";
+ else
+ shortfile = shorten_path(file);
+
+ printf("; [%s", shortfile > file ? "..." : "");
+ dotlabel_puts(shortfile);
+ printf(" line:%u col:%u]\\l\\\n",
+ BPF_LINE_INFO_LINE_NUM(linfo->line_col),
+ BPF_LINE_INFO_LINE_COL(linfo->line_col));
+ }
+
+ printf("; ");
+ dotlabel_puts(line);
+ printf("\\l\\\n");
+}
diff --git a/tools/bpf/bpftool/cfg.c b/tools/bpf/bpftool/cfg.c
index 1951219a9af7..eec437cca2ea 100644
--- a/tools/bpf/bpftool/cfg.c
+++ b/tools/bpf/bpftool/cfg.c
@@ -380,7 +380,9 @@ static void cfg_destroy(struct cfg *cfg)
}
}
-static void draw_bb_node(struct func_node *func, struct bb_node *bb)
+static void
+draw_bb_node(struct func_node *func, struct bb_node *bb, struct dump_data *dd,
+ bool opcodes, bool linum)
{
const char *shape;
@@ -398,13 +400,10 @@ static void draw_bb_node(struct func_node *func, struct bb_node *bb)
printf("EXIT");
} else {
unsigned int start_idx;
- struct dump_data dd = {};
-
- printf("{");
- kernel_syms_load(&dd);
+ printf("{\\\n");
start_idx = bb->head - func->start;
- dump_xlated_for_graph(&dd, bb->head, bb->tail, start_idx);
- kernel_syms_destroy(&dd);
+ dump_xlated_for_graph(dd, bb->head, bb->tail, start_idx,
+ opcodes, linum);
printf("}");
}
@@ -430,12 +429,14 @@ static void draw_bb_succ_edges(struct func_node *func, struct bb_node *bb)
}
}
-static void func_output_bb_def(struct func_node *func)
+static void
+func_output_bb_def(struct func_node *func, struct dump_data *dd,
+ bool opcodes, bool linum)
{
struct bb_node *bb;
list_for_each_entry(bb, &func->bbs, l) {
- draw_bb_node(func, bb);
+ draw_bb_node(func, bb, dd, opcodes, linum);
}
}
@@ -455,7 +456,8 @@ static void func_output_edges(struct func_node *func)
func_idx, ENTRY_BLOCK_INDEX, func_idx, EXIT_BLOCK_INDEX);
}
-static void cfg_dump(struct cfg *cfg)
+static void
+cfg_dump(struct cfg *cfg, struct dump_data *dd, bool opcodes, bool linum)
{
struct func_node *func;
@@ -463,14 +465,15 @@ static void cfg_dump(struct cfg *cfg)
list_for_each_entry(func, &cfg->funcs, l) {
printf("subgraph \"cluster_%d\" {\n\tstyle=\"dashed\";\n\tcolor=\"black\";\n\tlabel=\"func_%d ()\";\n",
func->idx, func->idx);
- func_output_bb_def(func);
+ func_output_bb_def(func, dd, opcodes, linum);
func_output_edges(func);
printf("}\n");
}
printf("}\n");
}
-void dump_xlated_cfg(void *buf, unsigned int len)
+void dump_xlated_cfg(struct dump_data *dd, void *buf, unsigned int len,
+ bool opcodes, bool linum)
{
struct bpf_insn *insn = buf;
struct cfg cfg;
@@ -479,7 +482,7 @@ void dump_xlated_cfg(void *buf, unsigned int len)
if (cfg_build(&cfg, insn, len))
return;
- cfg_dump(&cfg);
+ cfg_dump(&cfg, dd, opcodes, linum);
cfg_destroy(&cfg);
}
diff --git a/tools/bpf/bpftool/cfg.h b/tools/bpf/bpftool/cfg.h
index e144257ea6d2..b3793f4e1783 100644
--- a/tools/bpf/bpftool/cfg.h
+++ b/tools/bpf/bpftool/cfg.h
@@ -4,6 +4,9 @@
#ifndef __BPF_TOOL_CFG_H
#define __BPF_TOOL_CFG_H
-void dump_xlated_cfg(void *buf, unsigned int len);
+#include "xlated_dumper.h"
+
+void dump_xlated_cfg(struct dump_data *dd, void *buf, unsigned int len,
+ bool opcodes, bool linum);
#endif /* __BPF_TOOL_CFG_H */
diff --git a/tools/bpf/bpftool/main.h b/tools/bpf/bpftool/main.h
index 0ef373cef4c7..00d11ca6d3f2 100644
--- a/tools/bpf/bpftool/main.h
+++ b/tools/bpf/bpftool/main.h
@@ -229,6 +229,8 @@ void btf_dump_linfo_plain(const struct btf *btf,
const char *prefix, bool linum);
void btf_dump_linfo_json(const struct btf *btf,
const struct bpf_line_info *linfo, bool linum);
+void btf_dump_linfo_dotlabel(const struct btf *btf,
+ const struct bpf_line_info *linfo, bool linum);
struct nlattr;
struct ifinfomsg;
diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
index afbe3ec342c8..430f72306409 100644
--- a/tools/bpf/bpftool/prog.c
+++ b/tools/bpf/bpftool/prog.c
@@ -840,11 +840,6 @@ prog_dump(struct bpf_prog_info *info, enum dump_mode mode,
false))
goto exit_free;
}
- } else if (visual) {
- if (json_output)
- jsonw_null(json_wtr);
- else
- dump_xlated_cfg(buf, member_len);
} else {
kernel_syms_load(&dd);
dd.nr_jited_ksyms = info->nr_jited_ksyms;
@@ -855,11 +850,11 @@ prog_dump(struct bpf_prog_info *info, enum dump_mode mode,
dd.prog_linfo = prog_linfo;
if (json_output)
- dump_xlated_json(&dd, buf, member_len, opcodes,
- linum);
+ dump_xlated_json(&dd, buf, member_len, opcodes, linum);
+ else if (visual)
+ dump_xlated_cfg(&dd, buf, member_len, opcodes, linum);
else
- dump_xlated_plain(&dd, buf, member_len, opcodes,
- linum);
+ dump_xlated_plain(&dd, buf, member_len, opcodes, linum);
kernel_syms_destroy(&dd);
}
@@ -910,37 +905,46 @@ static int do_dump(int argc, char **argv)
if (nb_fds < 1)
goto exit_free;
- if (is_prefix(*argv, "file")) {
- NEXT_ARG();
- if (!argc) {
- p_err("expected file path");
- goto exit_close;
- }
- if (nb_fds > 1) {
- p_err("several programs matched");
- goto exit_close;
- }
+ while (argc) {
+ if (is_prefix(*argv, "file")) {
+ NEXT_ARG();
+ if (!argc) {
+ p_err("expected file path");
+ goto exit_close;
+ }
+ if (nb_fds > 1) {
+ p_err("several programs matched");
+ goto exit_close;
+ }
- filepath = *argv;
- NEXT_ARG();
- } else if (is_prefix(*argv, "opcodes")) {
- opcodes = true;
- NEXT_ARG();
- } else if (is_prefix(*argv, "visual")) {
- if (nb_fds > 1) {
- p_err("several programs matched");
+ filepath = *argv;
+ NEXT_ARG();
+ } else if (is_prefix(*argv, "opcodes")) {
+ opcodes = true;
+ NEXT_ARG();
+ } else if (is_prefix(*argv, "visual")) {
+ if (nb_fds > 1) {
+ p_err("several programs matched");
+ goto exit_close;
+ }
+
+ visual = true;
+ NEXT_ARG();
+ } else if (is_prefix(*argv, "linum")) {
+ linum = true;
+ NEXT_ARG();
+ } else {
+ usage();
goto exit_close;
}
-
- visual = true;
- NEXT_ARG();
- } else if (is_prefix(*argv, "linum")) {
- linum = true;
- NEXT_ARG();
}
- if (argc) {
- usage();
+ if (filepath && (opcodes || visual || linum)) {
+ p_err("'file' is not compatible with 'opcodes', 'visual', or 'linum'");
+ goto exit_close;
+ }
+ if (json_output && visual) {
+ p_err("'visual' is not compatible with JSON output");
goto exit_close;
}
@@ -2420,8 +2424,8 @@ static int do_help(int argc, char **argv)
fprintf(stderr,
"Usage: %1$s %2$s { show | list } [PROG]\n"
- " %1$s %2$s dump xlated PROG [{ file FILE | opcodes | visual | linum }]\n"
- " %1$s %2$s dump jited PROG [{ file FILE | opcodes | linum }]\n"
+ " %1$s %2$s dump xlated PROG [{ file FILE | [opcodes] [linum] [visual] }]\n"
+ " %1$s %2$s dump jited PROG [{ file FILE | [opcodes] [linum] }]\n"
" %1$s %2$s pin PROG FILE\n"
" %1$s %2$s { load | loadall } OBJ PATH \\\n"
" [type TYPE] [dev NAME] \\\n"
diff --git a/tools/bpf/bpftool/xlated_dumper.c b/tools/bpf/bpftool/xlated_dumper.c
index 6fe3134ae45d..da608e10c843 100644
--- a/tools/bpf/bpftool/xlated_dumper.c
+++ b/tools/bpf/bpftool/xlated_dumper.c
@@ -361,7 +361,8 @@ void dump_xlated_plain(struct dump_data *dd, void *buf, unsigned int len,
}
void dump_xlated_for_graph(struct dump_data *dd, void *buf_start, void *buf_end,
- unsigned int start_idx)
+ unsigned int start_idx,
+ bool opcodes, bool linum)
{
const struct bpf_insn_cbs cbs = {
.cb_print = print_insn_for_graph,
@@ -369,14 +370,61 @@ void dump_xlated_for_graph(struct dump_data *dd, void *buf_start, void *buf_end,
.cb_imm = print_imm,
.private_data = dd,
};
+ const struct bpf_prog_linfo *prog_linfo = dd->prog_linfo;
+ const struct bpf_line_info *last_linfo = NULL;
+ struct bpf_func_info *record = dd->func_info;
struct bpf_insn *insn_start = buf_start;
struct bpf_insn *insn_end = buf_end;
struct bpf_insn *cur = insn_start;
+ struct btf *btf = dd->btf;
+ bool double_insn = false;
+ char func_sig[1024];
for (; cur <= insn_end; cur++) {
- printf("% 4d: ", (int)(cur - insn_start + start_idx));
+ unsigned int insn_off;
+
+ if (double_insn) {
+ double_insn = false;
+ continue;
+ }
+ double_insn = cur->code == (BPF_LD | BPF_IMM | BPF_DW);
+
+ insn_off = (unsigned int)(cur - insn_start + start_idx);
+ if (btf && record) {
+ if (record->insn_off == insn_off) {
+ btf_dumper_type_only(btf, record->type_id,
+ func_sig,
+ sizeof(func_sig));
+ if (func_sig[0] != '\0')
+ printf("; %s:\\l\\\n", func_sig);
+ record = (void *)record + dd->finfo_rec_size;
+ }
+ }
+
+ if (prog_linfo) {
+ const struct bpf_line_info *linfo;
+
+ linfo = bpf_prog_linfo__lfind(prog_linfo, insn_off, 0);
+ if (linfo && linfo != last_linfo) {
+ btf_dump_linfo_dotlabel(btf, linfo, linum);
+ last_linfo = linfo;
+ }
+ }
+
+ printf("%d: ", insn_off);
print_bpf_insn(&cbs, cur, true);
+
+ if (opcodes) {
+ printf("\\ \\ \\ \\ ");
+ fprint_hex(stdout, cur, 8, " ");
+ if (double_insn && cur <= insn_end - 1) {
+ printf(" ");
+ fprint_hex(stdout, cur + 1, 8, " ");
+ }
+ printf("\\l\\\n");
+ }
+
if (cur != insn_end)
- printf(" | ");
+ printf("| ");
}
}
diff --git a/tools/bpf/bpftool/xlated_dumper.h b/tools/bpf/bpftool/xlated_dumper.h
index 54847e174273..9a946377b0e6 100644
--- a/tools/bpf/bpftool/xlated_dumper.h
+++ b/tools/bpf/bpftool/xlated_dumper.h
@@ -34,6 +34,7 @@ void dump_xlated_json(struct dump_data *dd, void *buf, unsigned int len,
void dump_xlated_plain(struct dump_data *dd, void *buf, unsigned int len,
bool opcodes, bool linum);
void dump_xlated_for_graph(struct dump_data *dd, void *buf, void *buf_end,
- unsigned int start_index);
+ unsigned int start_index,
+ bool opcodes, bool linum);
#endif