Commit c6ebae4c authored by Alexei Starovoitov's avatar Alexei Starovoitov

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: default avatarAlexei Starovoitov <ast@kernel.org>
parents 5af607a8 73192968
...@@ -28,8 +28,8 @@ PROG COMMANDS ...@@ -28,8 +28,8 @@ PROG COMMANDS
============= =============
| **bpftool** **prog** { **show** | **list** } [*PROG*] | **bpftool** **prog** { **show** | **list** } [*PROG*]
| **bpftool** **prog dump xlated** *PROG* [{**file** *FILE* | **opcodes** | **visual** | **linum**}] | **bpftool** **prog dump xlated** *PROG* [{ **file** *FILE* | [**opcodes**] [**linum**] [**visual**] }]
| **bpftool** **prog dump jited** *PROG* [{**file** *FILE* | **opcodes** | **linum**}] | **bpftool** **prog dump jited** *PROG* [{ **file** *FILE* | [**opcodes**] [**linum**] }]
| **bpftool** **prog pin** *PROG* *FILE* | **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** { **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*] | **bpftool** **prog attach** *PROG* *ATTACH_TYPE* [*MAP*]
...@@ -88,7 +88,7 @@ DESCRIPTION ...@@ -88,7 +88,7 @@ DESCRIPTION
programs. On such kernels bpftool will automatically emit this programs. On such kernels bpftool will automatically emit this
information as well. 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 Dump eBPF instructions of the programs from the kernel. By
default, eBPF will be disassembled and printed to standard default, eBPF will be disassembled and printed to standard
output in human-readable format. In this case, **opcodes** output in human-readable format. In this case, **opcodes**
...@@ -106,11 +106,10 @@ DESCRIPTION ...@@ -106,11 +106,10 @@ DESCRIPTION
CFG in DOT format, on standard output. CFG in DOT format, on standard output.
If the programs have line_info available, the source line will If the programs have line_info available, the source line will
be displayed by default. If **linum** is specified, be displayed. If **linum** is specified, the filename, line
the filename, line number and line column will also be number and line column will also be displayed.
displayed on top of the source line.
**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. Dump jited image (host machine code) of the program.
If *FILE* is specified image will be written to a file, If *FILE* is specified image will be written to a file,
...@@ -120,9 +119,8 @@ DESCRIPTION ...@@ -120,9 +119,8 @@ DESCRIPTION
**opcodes** controls if raw opcodes will be printed. **opcodes** controls if raw opcodes will be printed.
If the prog has line_info available, the source line will If the prog has line_info available, the source line will
be displayed by default. If **linum** is specified, be displayed. If **linum** is specified, the filename, line
the filename, line number and line column will also be number and line column will also be displayed.
displayed on top of the source line.
**bpftool prog pin** *PROG* *FILE* **bpftool prog pin** *PROG* *FILE*
Pin program *PROG* as *FILE*. Pin program *PROG* as *FILE*.
......
...@@ -255,20 +255,23 @@ _bpftool_map_update_get_name() ...@@ -255,20 +255,23 @@ _bpftool_map_update_get_name()
_bpftool() _bpftool()
{ {
local cur prev words objword local cur prev words objword json=0
_init_completion || return _init_completion || return
# Deal with options # Deal with options
if [[ ${words[cword]} == -* ]]; then if [[ ${words[cword]} == -* ]]; then
local c='--version --json --pretty --bpffs --mapcompat --debug \ local c='--version --json --pretty --bpffs --mapcompat --debug \
--use-loader --base-btf' --use-loader --base-btf'
COMPREPLY=( $( compgen -W "$c" -- "$cur" ) ) COMPREPLY=( $( compgen -W "$c" -- "$cur" ) )
return 0 return 0
fi fi
if _bpftool_search_list -j --json -p --pretty; then
json=1
fi
# Deal with simplest keywords # Deal with simplest keywords
case $prev in case $prev in
help|hex|opcodes|visual|linum) help|hex)
return 0 return 0
;; ;;
tag) tag)
...@@ -366,13 +369,16 @@ _bpftool() ...@@ -366,13 +369,16 @@ _bpftool()
return 0 return 0
;; ;;
*) *)
_bpftool_once_attr 'file' # "file" is not compatible with other keywords here
if _bpftool_search_list 'xlated'; then if _bpftool_search_list 'file'; then
COMPREPLY+=( $( compgen -W 'opcodes visual linum' -- \ return 0
"$cur" ) ) fi
else if ! _bpftool_search_list 'linum opcodes visual'; then
COMPREPLY+=( $( compgen -W 'opcodes linum' -- \ _bpftool_once_attr 'file'
"$cur" ) ) fi
_bpftool_once_attr 'linum opcodes'
if _bpftool_search_list 'xlated' && [[ "$json" == 0 ]]; then
_bpftool_once_attr 'visual'
fi fi
return 0 return 0
;; ;;
...@@ -502,10 +508,7 @@ _bpftool() ...@@ -502,10 +508,7 @@ _bpftool()
;; ;;
*) *)
COMPREPLY=( $( compgen -W "map" -- "$cur" ) ) COMPREPLY=( $( compgen -W "map" -- "$cur" ) )
_bpftool_once_attr 'type' _bpftool_once_attr 'type dev pinmaps autoattach'
_bpftool_once_attr 'dev'
_bpftool_once_attr 'pinmaps'
_bpftool_once_attr 'autoattach'
return 0 return 0
;; ;;
esac esac
...@@ -730,16 +733,10 @@ _bpftool() ...@@ -730,16 +733,10 @@ _bpftool()
esac esac
;; ;;
*) *)
_bpftool_once_attr 'type' _bpftool_once_attr 'type key value entries name flags dev'
_bpftool_once_attr 'key'
_bpftool_once_attr 'value'
_bpftool_once_attr 'entries'
_bpftool_once_attr 'name'
_bpftool_once_attr 'flags'
if _bpftool_search_list 'array_of_maps' 'hash_of_maps'; then if _bpftool_search_list 'array_of_maps' 'hash_of_maps'; then
_bpftool_once_attr 'inner_map' _bpftool_once_attr 'inner_map'
fi fi
_bpftool_once_attr 'dev'
return 0 return 0
;; ;;
esac esac
...@@ -880,8 +877,7 @@ _bpftool() ...@@ -880,8 +877,7 @@ _bpftool()
return 0 return 0
;; ;;
*) *)
_bpftool_once_attr 'cpu' _bpftool_once_attr 'cpu index'
_bpftool_once_attr 'index'
return 0 return 0
;; ;;
esac esac
......
...@@ -821,3 +821,86 @@ void btf_dump_linfo_json(const struct btf *btf, ...@@ -821,3 +821,86 @@ void btf_dump_linfo_json(const struct btf *btf,
BPF_LINE_INFO_LINE_COL(linfo->line_col)); 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");
}
...@@ -380,7 +380,9 @@ static void cfg_destroy(struct cfg *cfg) ...@@ -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; const char *shape;
...@@ -398,13 +400,10 @@ static void draw_bb_node(struct func_node *func, struct bb_node *bb) ...@@ -398,13 +400,10 @@ static void draw_bb_node(struct func_node *func, struct bb_node *bb)
printf("EXIT"); printf("EXIT");
} else { } else {
unsigned int start_idx; unsigned int start_idx;
struct dump_data dd = {}; printf("{\\\n");
printf("{");
kernel_syms_load(&dd);
start_idx = bb->head - func->start; start_idx = bb->head - func->start;
dump_xlated_for_graph(&dd, bb->head, bb->tail, start_idx); dump_xlated_for_graph(dd, bb->head, bb->tail, start_idx,
kernel_syms_destroy(&dd); opcodes, linum);
printf("}"); printf("}");
} }
...@@ -430,12 +429,14 @@ static void draw_bb_succ_edges(struct func_node *func, struct bb_node *bb) ...@@ -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; struct bb_node *bb;
list_for_each_entry(bb, &func->bbs, l) { 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) ...@@ -455,7 +456,8 @@ static void func_output_edges(struct func_node *func)
func_idx, ENTRY_BLOCK_INDEX, func_idx, EXIT_BLOCK_INDEX); 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; struct func_node *func;
...@@ -463,14 +465,15 @@ static void cfg_dump(struct cfg *cfg) ...@@ -463,14 +465,15 @@ static void cfg_dump(struct cfg *cfg)
list_for_each_entry(func, &cfg->funcs, l) { list_for_each_entry(func, &cfg->funcs, l) {
printf("subgraph \"cluster_%d\" {\n\tstyle=\"dashed\";\n\tcolor=\"black\";\n\tlabel=\"func_%d ()\";\n", printf("subgraph \"cluster_%d\" {\n\tstyle=\"dashed\";\n\tcolor=\"black\";\n\tlabel=\"func_%d ()\";\n",
func->idx, func->idx); func->idx, func->idx);
func_output_bb_def(func); func_output_bb_def(func, dd, opcodes, linum);
func_output_edges(func); func_output_edges(func);
printf("}\n"); printf("}\n");
} }
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 bpf_insn *insn = buf;
struct cfg cfg; struct cfg cfg;
...@@ -479,7 +482,7 @@ void dump_xlated_cfg(void *buf, unsigned int len) ...@@ -479,7 +482,7 @@ void dump_xlated_cfg(void *buf, unsigned int len)
if (cfg_build(&cfg, insn, len)) if (cfg_build(&cfg, insn, len))
return; return;
cfg_dump(&cfg); cfg_dump(&cfg, dd, opcodes, linum);
cfg_destroy(&cfg); cfg_destroy(&cfg);
} }
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
#ifndef __BPF_TOOL_CFG_H #ifndef __BPF_TOOL_CFG_H
#define __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 */ #endif /* __BPF_TOOL_CFG_H */
...@@ -229,6 +229,8 @@ void btf_dump_linfo_plain(const struct btf *btf, ...@@ -229,6 +229,8 @@ void btf_dump_linfo_plain(const struct btf *btf,
const char *prefix, bool linum); const char *prefix, bool linum);
void btf_dump_linfo_json(const struct btf *btf, void btf_dump_linfo_json(const struct btf *btf,
const struct bpf_line_info *linfo, bool linum); 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 nlattr;
struct ifinfomsg; struct ifinfomsg;
......
...@@ -840,11 +840,6 @@ prog_dump(struct bpf_prog_info *info, enum dump_mode mode, ...@@ -840,11 +840,6 @@ prog_dump(struct bpf_prog_info *info, enum dump_mode mode,
false)) false))
goto exit_free; goto exit_free;
} }
} else if (visual) {
if (json_output)
jsonw_null(json_wtr);
else
dump_xlated_cfg(buf, member_len);
} else { } else {
kernel_syms_load(&dd); kernel_syms_load(&dd);
dd.nr_jited_ksyms = info->nr_jited_ksyms; dd.nr_jited_ksyms = info->nr_jited_ksyms;
...@@ -855,11 +850,11 @@ prog_dump(struct bpf_prog_info *info, enum dump_mode mode, ...@@ -855,11 +850,11 @@ prog_dump(struct bpf_prog_info *info, enum dump_mode mode,
dd.prog_linfo = prog_linfo; dd.prog_linfo = prog_linfo;
if (json_output) if (json_output)
dump_xlated_json(&dd, buf, member_len, opcodes, dump_xlated_json(&dd, buf, member_len, opcodes, linum);
linum); else if (visual)
dump_xlated_cfg(&dd, buf, member_len, opcodes, linum);
else else
dump_xlated_plain(&dd, buf, member_len, opcodes, dump_xlated_plain(&dd, buf, member_len, opcodes, linum);
linum);
kernel_syms_destroy(&dd); kernel_syms_destroy(&dd);
} }
...@@ -910,37 +905,46 @@ static int do_dump(int argc, char **argv) ...@@ -910,37 +905,46 @@ static int do_dump(int argc, char **argv)
if (nb_fds < 1) if (nb_fds < 1)
goto exit_free; goto exit_free;
if (is_prefix(*argv, "file")) { while (argc) {
NEXT_ARG(); if (is_prefix(*argv, "file")) {
if (!argc) { NEXT_ARG();
p_err("expected file path"); if (!argc) {
goto exit_close; p_err("expected file path");
} goto exit_close;
if (nb_fds > 1) { }
p_err("several programs matched"); if (nb_fds > 1) {
goto exit_close; p_err("several programs matched");
} goto exit_close;
}
filepath = *argv; filepath = *argv;
NEXT_ARG(); NEXT_ARG();
} else if (is_prefix(*argv, "opcodes")) { } else if (is_prefix(*argv, "opcodes")) {
opcodes = true; opcodes = true;
NEXT_ARG(); NEXT_ARG();
} else if (is_prefix(*argv, "visual")) { } else if (is_prefix(*argv, "visual")) {
if (nb_fds > 1) { if (nb_fds > 1) {
p_err("several programs matched"); 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; goto exit_close;
} }
visual = true;
NEXT_ARG();
} else if (is_prefix(*argv, "linum")) {
linum = true;
NEXT_ARG();
} }
if (argc) { if (filepath && (opcodes || visual || linum)) {
usage(); 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; goto exit_close;
} }
...@@ -2420,8 +2424,8 @@ static int do_help(int argc, char **argv) ...@@ -2420,8 +2424,8 @@ static int do_help(int argc, char **argv)
fprintf(stderr, fprintf(stderr,
"Usage: %1$s %2$s { show | list } [PROG]\n" "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 xlated PROG [{ file FILE | [opcodes] [linum] [visual] }]\n"
" %1$s %2$s dump jited PROG [{ file FILE | opcodes | linum }]\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 pin PROG FILE\n"
" %1$s %2$s { load | loadall } OBJ PATH \\\n" " %1$s %2$s { load | loadall } OBJ PATH \\\n"
" [type TYPE] [dev NAME] \\\n" " [type TYPE] [dev NAME] \\\n"
......
...@@ -361,7 +361,8 @@ void dump_xlated_plain(struct dump_data *dd, void *buf, unsigned int len, ...@@ -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, 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 = { const struct bpf_insn_cbs cbs = {
.cb_print = print_insn_for_graph, .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, ...@@ -369,14 +370,61 @@ void dump_xlated_for_graph(struct dump_data *dd, void *buf_start, void *buf_end,
.cb_imm = print_imm, .cb_imm = print_imm,
.private_data = dd, .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_start = buf_start;
struct bpf_insn *insn_end = buf_end; struct bpf_insn *insn_end = buf_end;
struct bpf_insn *cur = insn_start; struct bpf_insn *cur = insn_start;
struct btf *btf = dd->btf;
bool double_insn = false;
char func_sig[1024];
for (; cur <= insn_end; cur++) { 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); 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) if (cur != insn_end)
printf(" | "); printf("| ");
} }
} }
...@@ -34,6 +34,7 @@ void dump_xlated_json(struct dump_data *dd, void *buf, unsigned int len, ...@@ -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, void dump_xlated_plain(struct dump_data *dd, void *buf, unsigned int len,
bool opcodes, bool linum); bool opcodes, bool linum);
void dump_xlated_for_graph(struct dump_data *dd, void *buf, void *buf_end, 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 #endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment