[PATCH 2/2] perf tools: make the GTK4 report browser actually loadable at runtime

From: Matt Turner

Date: Sun Sep 06 2026 - 10:08:18 EST


perf report --gtk dlopen()s libperf-gtk.so and expects it to resolve
symbols back against the running perf binary (callchain_param,
symbol_conf, evsel__name, and friends all live in perf itself, not in
the plugin). Two things stood in the way of that after the GTK 2 to
GTK 4 port:

perf never passed -rdynamic, so none of its own symbols were in its
dynamic symbol table for a dlopen()ed plugin to find at all. Add
-rdynamic to LDFLAGS when GTK4 support is enabled.

annotated_source__hist_entry() was a static inline in annotate.h, so
ui/gtk/annotate.c calling it pulled hashmap__find()'s expansion,
hashmap_find(), directly into libperf-gtk.so as an undefined symbol.
The only hashmap_find perf links against in the common case is
libbpf's internal one (tools/lib/bpf/hashmap.c), which libbpf
compiles with -fvisibility=hidden; a hidden symbol can never be
exported to a dlopen()ed plugin no matter what LDFLAGS perf itself
gets. Move annotated_source__hist_entry() out of the header and into
annotate.c, as an ordinary exported function, so the plugin depends
on it the same way it already depends on evsel__group_desc() and
friends, instead of reaching for hashmap_find directly.

With both fixes, a default 'make GTK4=1' build (libbpf statically
linked, as usual) can dlopen() libperf-gtk.so and open the report
browser without needing NO_LIBBPF=1 or manual LDFLAGS. Verified with
perf report --gtk against a real perf.data on an actual GTK4 desktop
session.

Signed-off-by: Matt Turner <mattst88@xxxxxxxxx>
Claude-Session: https://claude.ai/code/session_019moPUkPJjtTUWR8CBm1QdW
---
tools/perf/Makefile.config | 5 +++++
tools/perf/util/annotate.c | 11 +++++++++++
tools/perf/util/annotate.h | 12 ++----------
3 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config
index 3ac1826a0cbe..ef7419a5b013 100644
--- a/tools/perf/Makefile.config
+++ b/tools/perf/Makefile.config
@@ -789,6 +789,11 @@ ifdef GTK4
GTK_CFLAGS += $(shell $(PKG_CONFIG) --cflags gtk4 2>/dev/null)
GTK_LIBS := $(shell $(PKG_CONFIG) --libs gtk4 2>/dev/null)
EXTLIBS += -ldl
+ # libperf-gtk.so is dlopen()ed at runtime and calls back into
+ # symbols defined in the perf binary itself (callchain_param,
+ # symbol_conf, evsel__name, ...): perf needs to export those
+ # dynamically for the plugin to resolve them.
+ LDFLAGS += -rdynamic
endif
endif

diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
index df70e95a8470..123b6d5fcea7 100644
--- a/tools/perf/util/annotate.c
+++ b/tools/perf/util/annotate.c
@@ -145,6 +145,17 @@ static int annotated_source__alloc_histograms(struct annotated_source *src,
return src->histograms ? 0 : -1;
}

+struct sym_hist_entry *
+annotated_source__hist_entry(struct annotated_source *src, const struct evsel *evsel, u64 offset)
+{
+ struct sym_hist_entry *entry;
+ long key = offset << 16 | evsel->core.idx;
+
+ if (!hashmap__find(src->samples, key, &entry))
+ return NULL;
+ return entry;
+}
+
void symbol__annotate_zero_histograms(struct symbol *sym)
{
struct annotation *notes = symbol__annotation(sym);
diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h
index fa08d09b80f7..40038a3779d4 100644
--- a/tools/perf/util/annotate.h
+++ b/tools/perf/util/annotate.h
@@ -406,16 +406,8 @@ static inline struct sym_hist *annotation__histogram(struct annotation *notes,
return annotated_source__histogram(notes->src, evsel);
}

-static inline struct sym_hist_entry *
-annotated_source__hist_entry(struct annotated_source *src, const struct evsel *evsel, u64 offset)
-{
- struct sym_hist_entry *entry;
- long key = offset << 16 | evsel->core.idx;
-
- if (!hashmap__find(src->samples, key, &entry))
- return NULL;
- return entry;
-}
+struct sym_hist_entry *
+annotated_source__hist_entry(struct annotated_source *src, const struct evsel *evsel, u64 offset);

static inline struct annotation *symbol__annotation(struct symbol *sym)
{

--
2.54.0