Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf callchain: Fix stitch LBR memory leaks

The 'struct callchain_cursor_node' has a 'struct map_symbol' whose maps
and map members are reference counted. Ensure these values use a _get
routine to increment the reference counts and use map_symbol__exit() to
release the reference counts.

Do similar for 'struct thread's prev_lbr_cursor, but save the size of
the prev_lbr_cursor array so that it may be iterated.

Ensure that when stitch_nodes are placed on the free list the
map_symbols are exited.

Fix resolve_lbr_callchain_sample() by replacing list_replace_init() to
list_splice_init(), so the whole list is moved and nodes aren't leaked.

A reproduction of the memory leaks is possible with a leak sanitizer
build in the perf report command of:

```
$ perf record -e cycles --call-graph lbr perf test -w thloop
$ perf report --stitch-lbr
```

Reviewed-by: Kan Liang <kan.liang@linux.intel.com>
Fixes: ff165628d72644e3 ("perf callchain: Stitch LBR call stack")
Signed-off-by: Ian Rogers <irogers@google.com>
[ Basic tests after applying the patch, repeating the example above ]
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Anne Macedo <retpolanne@posteo.net>
Cc: Changbin Du <changbin.du@huawei.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://lore.kernel.org/r/20240808054644.1286065-1-irogers@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>

authored by

Ian Rogers and committed by
Arnaldo Carvalho de Melo
599c1939 37e2a19c

+20 -2
+15 -2
tools/perf/util/machine.c
··· 2270 2270 cursor->curr = cursor->first; 2271 2271 else 2272 2272 cursor->curr = cursor->curr->next; 2273 + 2274 + map_symbol__exit(&lbr_stitch->prev_lbr_cursor[idx].ms); 2273 2275 memcpy(&lbr_stitch->prev_lbr_cursor[idx], cursor->curr, 2274 2276 sizeof(struct callchain_cursor_node)); 2277 + lbr_stitch->prev_lbr_cursor[idx].ms.maps = maps__get(cursor->curr->ms.maps); 2278 + lbr_stitch->prev_lbr_cursor[idx].ms.map = map__get(cursor->curr->ms.map); 2275 2279 2276 2280 lbr_stitch->prev_lbr_cursor[idx].valid = true; 2277 2281 cursor->pos++; ··· 2486 2482 memcpy(&stitch_node->cursor, &lbr_stitch->prev_lbr_cursor[i], 2487 2483 sizeof(struct callchain_cursor_node)); 2488 2484 2485 + stitch_node->cursor.ms.maps = maps__get(lbr_stitch->prev_lbr_cursor[i].ms.maps); 2486 + stitch_node->cursor.ms.map = map__get(lbr_stitch->prev_lbr_cursor[i].ms.map); 2487 + 2489 2488 if (callee) 2490 2489 list_add(&stitch_node->node, &lbr_stitch->lists); 2491 2490 else ··· 2511 2504 calloc(max_lbr + 1, sizeof(struct callchain_cursor_node)); 2512 2505 if (!thread__lbr_stitch(thread)->prev_lbr_cursor) 2513 2506 goto free_lbr_stitch; 2507 + 2508 + thread__lbr_stitch(thread)->prev_lbr_cursor_size = max_lbr + 1; 2514 2509 2515 2510 INIT_LIST_HEAD(&thread__lbr_stitch(thread)->lists); 2516 2511 INIT_LIST_HEAD(&thread__lbr_stitch(thread)->free_lists); ··· 2569 2560 max_lbr, callee); 2570 2561 2571 2562 if (!stitched_lbr && !list_empty(&lbr_stitch->lists)) { 2572 - list_replace_init(&lbr_stitch->lists, 2573 - &lbr_stitch->free_lists); 2563 + struct stitch_list *stitch_node; 2564 + 2565 + list_for_each_entry(stitch_node, &lbr_stitch->lists, node) 2566 + map_symbol__exit(&stitch_node->cursor.ms); 2567 + 2568 + list_splice_init(&lbr_stitch->lists, &lbr_stitch->free_lists); 2574 2569 } 2575 2570 memcpy(&lbr_stitch->prev_sample, sample, sizeof(*sample)); 2576 2571 }
+4
tools/perf/util/thread.c
··· 476 476 return; 477 477 478 478 list_for_each_entry_safe(pos, tmp, &lbr_stitch->lists, node) { 479 + map_symbol__exit(&pos->cursor.ms); 479 480 list_del_init(&pos->node); 480 481 free(pos); 481 482 } ··· 485 484 list_del_init(&pos->node); 486 485 free(pos); 487 486 } 487 + 488 + for (unsigned int i = 0 ; i < lbr_stitch->prev_lbr_cursor_size; i++) 489 + map_symbol__exit(&lbr_stitch->prev_lbr_cursor[i].ms); 488 490 489 491 zfree(&lbr_stitch->prev_lbr_cursor); 490 492 free(thread__lbr_stitch(thread));
+1
tools/perf/util/thread.h
··· 26 26 struct list_head free_lists; 27 27 struct perf_sample prev_sample; 28 28 struct callchain_cursor_node *prev_lbr_cursor; 29 + unsigned int prev_lbr_cursor_size; 29 30 }; 30 31 31 32 DECLARE_RC_STRUCT(thread) {