mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 12:11:40 +00:00
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>
This commit is contained in:
commit
c6ebae4ccc
@ -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*.
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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("| ");
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user