[PATCH v1 23/23] perf machine: Add inline information to frame pointer and LBR callchains

Ian Rogers
2026-01-17T05:30:03Z

Use append_inlines in frame pointer and LBR cases. Update the
addr2line test to also test frame pointers. LBR is also updated but
inaccuracy in the branched to IP means the inline information is
missing in the leaf. Leave LBR callchains untested for now.

Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/tests/shell/addr2line_inlines.sh | 31 +++++-
tools/perf/util/machine.c | 104 +++++++++++---------
2 files changed, 86 insertions(+), 49 deletions(-)

diff --git a/tools/perf/tests/shell/addr2line_inlines.sh b/tools/perf/tests/shell/addr2line_inlines.sh
index 4a5b6f5be23d..ce30d9c7e0bf 100755
--- a/tools/perf/tests/shell/addr2line_inlines.sh
+++ b/tools/perf/tests/shell/addr2line_inlines.sh
@@ -21,8 +21,28 @@ trap_cleanup() {
}
trap trap_cleanup EXIT TERM INT

-test_inlinedloop() {
- echo "Inline unwinding verification test"
+test_fp() {
+ echo "Inline unwinding fp verification test"
+ # Record data. Currently only dwarf callchains support inlined functions.
+ perf record --call-graph fp -e task-clock:u -o "${perf_data}" -- perf test -w inlineloop 1
+
+ # Check output with inline (default) and srcline
+ perf script -i "${perf_data}" --fields +srcline > "${perf_script_txt}"
+
+ # Expect the leaf and middle functions to occur on lines in the 20s, with
+ # the non-inlined parent function on a line in the 30s.
+ if grep -q "inlineloop.c:2. (inlined)" "${perf_script_txt}" &&
+ grep -q "inlineloop.c:3.$" "${perf_script_txt}"
+ then
+ echo "Inline unwinding fp verification test [Success]"
+ else
+ echo "Inline unwinding fp verification test [Failed missing inlined functions]"
+ err=1
+ fi
+}
+
+test_dwarf() {
+ echo "Inline unwinding dwarf verification test"
# Record data. Currently only dwarf callchains support inlined functions.
perf record --call-graph dwarf -e task-clock:u -o "${perf_data}" -- perf test -w inlineloop 1

@@ -34,14 +54,15 @@ test_inlinedloop() {
if grep -q "inlineloop.c:2. (inlined)" "${perf_script_txt}" &&
grep -q "inlineloop.c:3.$" "${perf_script_txt}"
then
- echo "Inline unwinding verification test [Success]"
+ echo "Inline unwinding dwarf verification test [Success]"
else
- echo "Inline unwinding verification test [Failed missing inlined functions]"
+ echo "Inline unwinding dwarf verification test [Failed missing inlined functions]"
err=1
fi
}

-test_inlinedloop
+test_fp
+test_dwarf

cleanup
exit $err
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
index 841b711d970e..30d606fbf040 100644
--- a/tools/perf/util/machine.c
+++ b/tools/perf/util/machine.c
@@ -2090,6 +2090,59 @@ struct iterations {
u64 cycles;
};

+static int append_inlines(struct callchain_cursor *cursor, struct map_symbol *ms, u64 ip,
+ bool branch, struct branch_flags *flags, int nr_loop_iter,
+ u64 iter_cycles, u64 branch_from)
+{
+ struct symbol *sym = ms->sym;
+ struct map *map = ms->map;
+ struct inline_node *inline_node;
+ struct inline_list *ilist;
+ struct dso *dso;
+ u64 addr;
+ int ret = 1;
+ struct map_symbol ilist_ms;
+ bool first = true;
+
+ if (!symbol_conf.inline_name || !map || !sym)
+ return ret;
+
+ addr = map__dso_map_ip(map, ip);
+ addr = map__rip_2objdump(map, addr);
+ dso = map__dso(map);
+
+ inline_node = inlines__tree_find(dso__inlined_nodes(dso), addr);
+ if (!inline_node) {
+ inline_node = dso__parse_addr_inlines(dso, addr, sym);
+ if (!inline_node)
+ return ret;
+ inlines__tree_insert(dso__inlined_nodes(dso), inline_node);
+ }
+
+ ilist_ms = (struct map_symbol) {
+ .maps = maps__get(ms->maps),
+ .map = map__get(map),
+ };
+ list_for_each_entry(ilist, &inline_node->val, list) {
+ ilist_ms.sym = ilist->symbol;
+ if (first) {
+ ret = callchain_cursor_append(cursor, ip, &ilist_ms,
+ branch, flags, nr_loop_iter,
+ iter_cycles, branch_from, ilist->srcline);
+ } else {
+ ret = callchain_cursor_append(cursor, ip, &ilist_ms, false,
+ NULL, 0, 0, 0, ilist->srcline);
+ }
+ first = false;
+
+ if (ret != 0)
+ return ret;
+ }
+ map_symbol__exit(&ilist_ms);
+
+ return ret;
+}
+
static int add_callchain_ip(struct thread *thread,
struct callchain_cursor *cursor,
struct symbol **parent,
@@ -2170,6 +2223,11 @@ static int add_callchain_ip(struct thread *thread,
ms.maps = maps__get(al.maps);
ms.map = map__get(al.map);
ms.sym = al.sym;
+
+ if (append_inlines(cursor, &ms, ip, branch, flags, nr_loop_iter,
+ iter_cycles, branch_from) == 0)
+ goto out;
+
srcline = callchain_srcline(&ms, al.addr);
err = callchain_cursor_append(cursor, ip, &ms,
branch, flags, nr_loop_iter,
@@ -2888,49 +2946,6 @@ static int thread__resolve_callchain_sample(struct thread *thread,
return 0;
}

-static int append_inlines(struct callchain_cursor *cursor, struct map_symbol *ms, u64 ip)
-{
- struct symbol *sym = ms->sym;
- struct map *map = ms->map;
- struct inline_node *inline_node;
- struct inline_list *ilist;
- struct dso *dso;
- u64 addr;
- int ret = 1;
- struct map_symbol ilist_ms;
-
- if (!symbol_conf.inline_name || !map || !sym)
- return ret;
-
- addr = map__dso_map_ip(map, ip);
- addr = map__rip_2objdump(map, addr);
- dso = map__dso(map);
-
- inline_node = inlines__tree_find(dso__inlined_nodes(dso), addr);
- if (!inline_node) {
- inline_node = dso__parse_addr_inlines(dso, addr, sym);
- if (!inline_node)
- return ret;
- inlines__tree_insert(dso__inlined_nodes(dso), inline_node);
- }
-
- ilist_ms = (struct map_symbol) {
- .maps = maps__get(ms->maps),
- .map = map__get(map),
- };
- list_for_each_entry(ilist, &inline_node->val, list) {
- ilist_ms.sym = ilist->symbol;
- ret = callchain_cursor_append(cursor, ip, &ilist_ms, false,
- NULL, 0, 0, 0, ilist->srcline);
-
- if (ret != 0)
- return ret;
- }
- map_symbol__exit(&ilist_ms);
-
- return ret;
-}
-
static int unwind_entry(struct unwind_entry *entry, void *arg)
{
struct callchain_cursor *cursor = arg;
@@ -2940,7 +2955,8 @@ static int unwind_entry(struct unwind_entry *entry, void *arg)
if (symbol_conf.hide_unresolved && entry->ms.sym == NULL)
return 0;

- if (append_inlines(cursor, &entry->ms, entry->ip) == 0)
+ if (append_inlines(cursor, &entry->ms, entry->ip, /*branch=*/false, /*branch_flags=*/NULL,
+ /*nr_loop_iter=*/0, /*iter_cycles=*/0, /*branch_from=*/0) == 0)
return 0;

/*
--
2.52.0.457.g6b5491de43-goog