mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Eduard Zateev <hackerowskiy@gmail.com>
To: bpf@vger.kernel.org
Cc: qmo@kernel.org, ast@kernel.org, daniel@iogearbox.net,
	andrii@kernel.org, eddyz87@gmail.com, memxor@gmail.com,
	martin.lau@linux.dev, song@kernel.org, yonghong.song@linux.dev,
	jolsa@kernel.org, emil@etsalapatis.com, ihor.solodrai@linux.dev,
	shuah@kernel.org, linux-kernel@vger.kernel.org,
	linux-kselftest@vger.kernel.org,
	Eduard Zateev <hackerowskiy@gmail.com>
Subject: [PATCH] bpftool: fix line info in multi-function CFG dumps
Date: Sat, 24 Oct 2026 08:23:29 +0200	[thread overview]
Message-ID: <20261024062329.264086-1-hackerowskiy@gmail.com> (raw)

The CFG dumper computes BTF line-info offsets relative to each function, but bpf_prog_linfo__lfind() expects offsets from the start of the whole program. As a result, line annotations in later functions can resolve to source lines from the first function.

Pass the program instruction base to the graph dumper and use the global instruction offset for line-info lookup and display. Keep function-info matching relative to the function as before.

Add a selftest with a BPF-to-BPF call that checks the second function source line in visual output.

Link: https://github.com/libbpf/bpftool/issues/178
Signed-off-by: Eduard Zateev <hackerowskiy@gmail.com>
---
 tools/bpf/bpftool/cfg.c                       | 19 +++++----
 tools/bpf/bpftool/xlated_dumper.c             | 10 +++--
 tools/bpf/bpftool/xlated_dumper.h             |  3 +-
 .../selftests/bpf/prog_tests/bpftool_cfg.c    | 41 +++++++++++++++++++
 .../testing/selftests/bpf/progs/bpftool_cfg.c | 16 ++++++++
 5 files changed, 75 insertions(+), 14 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/bpftool_cfg.c
 create mode 100644 tools/testing/selftests/bpf/progs/bpftool_cfg.c

diff --git a/tools/bpf/bpftool/cfg.c b/tools/bpf/bpftool/cfg.c
index e3785f9a6..65ec45469 100644
--- a/tools/bpf/bpftool/cfg.c
+++ b/tools/bpf/bpftool/cfg.c
@@ -383,7 +383,7 @@ static void cfg_destroy(struct cfg *cfg)
 
 static void
 draw_bb_node(struct func_node *func, struct bb_node *bb, struct dump_data *dd,
-	     bool opcodes, bool linum)
+	     struct bpf_insn *prog_start, bool opcodes, bool linum)
 {
 	const char *shape;
 
@@ -400,11 +400,11 @@ draw_bb_node(struct func_node *func, struct bb_node *bb, struct dump_data *dd,
 	} else if (bb->idx == EXIT_BLOCK_INDEX) {
 		printf("EXIT");
 	} else {
-		unsigned int start_idx;
+		unsigned int start_idx = bb->head - func->start;
+
 		printf("{\\\n");
-		start_idx = bb->head - func->start;
 		dump_xlated_for_graph(dd, bb->head, bb->tail, start_idx,
-				      opcodes, linum);
+				      prog_start, opcodes, linum);
 		printf("}");
 	}
 
@@ -432,12 +432,12 @@ static void draw_bb_succ_edges(struct func_node *func, struct bb_node *bb)
 
 static void
 func_output_bb_def(struct func_node *func, struct dump_data *dd,
-		   bool opcodes, bool linum)
+		   struct bpf_insn *prog_start, bool opcodes, bool linum)
 {
 	struct bb_node *bb;
 
 	list_for_each_entry(bb, &func->bbs, l) {
-		draw_bb_node(func, bb, dd, opcodes, linum);
+		draw_bb_node(func, bb, dd, prog_start, opcodes, linum);
 	}
 }
 
@@ -458,7 +458,8 @@ static void func_output_edges(struct func_node *func)
 }
 
 static void
-cfg_dump(struct cfg *cfg, struct dump_data *dd, bool opcodes, bool linum)
+cfg_dump(struct cfg *cfg, struct dump_data *dd, struct bpf_insn *prog_start,
+	 bool opcodes, bool linum)
 {
 	struct func_node *func;
 
@@ -466,7 +467,7 @@ cfg_dump(struct cfg *cfg, struct dump_data *dd, bool opcodes, bool linum)
 	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, dd, opcodes, linum);
+		func_output_bb_def(func, dd, prog_start, opcodes, linum);
 		func_output_edges(func);
 		printf("}\n");
 	}
@@ -483,7 +484,7 @@ void dump_xlated_cfg(struct dump_data *dd, void *buf, unsigned int len,
 	if (cfg_build(&cfg, insn, len))
 		return;
 
-	cfg_dump(&cfg, dd, opcodes, linum);
+	cfg_dump(&cfg, dd, insn, opcodes, linum);
 
 	cfg_destroy(&cfg);
 }
diff --git a/tools/bpf/bpftool/xlated_dumper.c b/tools/bpf/bpftool/xlated_dumper.c
index 5579173a6..847241bbe 100644
--- a/tools/bpf/bpftool/xlated_dumper.c
+++ b/tools/bpf/bpftool/xlated_dumper.c
@@ -353,7 +353,7 @@ 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, struct bpf_insn *prog_start,
 			   bool opcodes, bool linum)
 {
 	const struct bpf_insn_cbs cbs = {
@@ -374,6 +374,7 @@ void dump_xlated_for_graph(struct dump_data *dd, void *buf_start, void *buf_end,
 
 	for (; cur <= insn_end; cur++) {
 		unsigned int insn_off;
+		unsigned int linfo_offset;
 
 		if (double_insn) {
 			double_insn = false;
@@ -381,7 +382,8 @@ void dump_xlated_for_graph(struct dump_data *dd, void *buf_start, void *buf_end,
 		}
 		double_insn = cur->code == (BPF_LD | BPF_IMM | BPF_DW);
 
-		insn_off = (unsigned int)(cur - insn_start + start_idx);
+		insn_off = (unsigned int)(cur - insn_start) + start_idx;
+		linfo_offset = (unsigned int)(cur - prog_start);
 		if (btf && record) {
 			if (record->insn_off == insn_off) {
 				btf_dumper_type_only(btf, record->type_id,
@@ -396,14 +398,14 @@ void dump_xlated_for_graph(struct dump_data *dd, void *buf_start, void *buf_end,
 		if (prog_linfo) {
 			const struct bpf_line_info *linfo;
 
-			linfo = bpf_prog_linfo__lfind(prog_linfo, insn_off, 0);
+			linfo = bpf_prog_linfo__lfind(prog_linfo, linfo_offset, 0);
 			if (linfo && linfo != last_linfo) {
 				btf_dump_linfo_dotlabel(btf, linfo, linum);
 				last_linfo = linfo;
 			}
 		}
 
-		printf("%u: ", insn_off);
+		printf("%u: ", linfo_offset);
 		print_bpf_insn(&cbs, cur, true);
 		printf("\\l\\\n");
 
diff --git a/tools/bpf/bpftool/xlated_dumper.h b/tools/bpf/bpftool/xlated_dumper.h
index db3ba0671..d2af7bd10 100644
--- a/tools/bpf/bpftool/xlated_dumper.h
+++ b/tools/bpf/bpftool/xlated_dumper.h
@@ -8,6 +8,7 @@
 #define MODULE_MAX_NAME	64
 
 struct bpf_prog_linfo;
+struct bpf_insn;
 
 struct kernel_sym {
 	unsigned long address;
@@ -36,7 +37,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_idx, struct bpf_insn *prog_start,
 			   bool opcodes, bool linum);
 
 #endif
diff --git a/tools/testing/selftests/bpf/prog_tests/bpftool_cfg.c b/tools/testing/selftests/bpf/prog_tests/bpftool_cfg.c
new file mode 100644
index 000000000..7d5b80927
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/bpftool_cfg.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <bpftool_helpers.h>
+#include <test_progs.h>
+#include <linux/bpf.h>
+#include "bpftool_cfg.skel.h"
+
+#define MAX_BPFTOOL_OUTPUT_LEN (64 * 1024)
+
+void test_bpftool_cfg(void)
+{
+	struct bpftool_cfg *skel = NULL;
+	struct bpf_prog_info info = {};
+	__u32 info_len = sizeof(info);
+	char output[MAX_BPFTOOL_OUTPUT_LEN];
+	char cmd[MAX_BPFTOOL_CMD_LEN];
+	const char *expected = "return\\ bpf_ktime_get_ns()\\ ?\\ skb->len\\ :\\ 0;";
+	int err;
+
+	skel = bpftool_cfg__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "bpftool_cfg__open_and_load"))
+		return;
+
+	err = bpf_obj_get_info_by_fd(bpf_program__fd(skel->progs.bpftool_cfg),
+				     &info, &info_len);
+	if (!ASSERT_OK(err, "bpf_obj_get_info_by_fd"))
+		goto cleanup;
+
+	err = snprintf(cmd, sizeof(cmd), "prog dump xlated id %u visual linum",
+		       info.id);
+	if (!ASSERT_GT(err, 0, "format bpftool command"))
+		goto cleanup;
+
+	err = get_bpftool_command_output(cmd, output, sizeof(output));
+	if (ASSERT_OK(err, "dump program CFG")) {
+		ASSERT_HAS_SUBSTR(output, "cluster_1", "second function in CFG");
+		ASSERT_HAS_SUBSTR(output, expected, "second function line info");
+	}
+
+cleanup:
+	bpftool_cfg__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/bpftool_cfg.c b/tools/testing/selftests/bpf/progs/bpftool_cfg.c
new file mode 100644
index 000000000..db01f20c8
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpftool_cfg.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+static __noinline int helper(struct __sk_buff *skb)
+{
+	return bpf_ktime_get_ns() ? skb->len : 0;
+}
+
+SEC("socket")
+int bpftool_cfg(struct __sk_buff *skb)
+{
+	return helper(skb);
+}
+
+char _license[] SEC("license") = "GPL";
-- 
2.55.0


             reply	other threads:[~2026-10-24  6:23 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-24  6:23 Eduard Zateev [this message]
2026-10-24  7:05 ` bot+bpf-ci
2026-10-26 10:58 ` Quentin Monnet

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=20261024062329.264086-1-hackerowskiy@gmail.com \
    --to=hackerowskiy@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=emil@etsalapatis.com \
    --cc=ihor.solodrai@linux.dev \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=qmo@kernel.org \
    --cc=shuah@kernel.org \
    --cc=song@kernel.org \
    --cc=yonghong.song@linux.dev \
    /path/to/YOUR_REPLY

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

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

all inboxes | Powered by JetHome®