Skip to content

Commit c3f8644

Browse files
sesseacmel
authored andcommitted
perf report: Support LLVM for addr2line()
In addition to the existing support for libbfd and calling out to an external addr2line command, add support for using libllvm directly. This is both faster than libbfd, and can be enabled in distro builds (the LLVM license has an explicit provision for GPLv2 compatibility). Thus, it is set as the primary choice if available. As an example, running 'perf report' on a medium-size profile with DWARF-based backtraces took 58 seconds with LLVM, 78 seconds with libbfd, 153 seconds with external llvm-addr2line, and I got tired and aborted the test after waiting for 55 minutes with external bfd addr2line (which is the default for perf as compiled by distributions today). Evidently, for this case, the bfd addr2line process needs 18 seconds (on a 5.2 GHz Zen 3) to load the .debug ELF in question, hits the 1-second timeout and gets killed during initialization, getting restarted anew every time. Having an in-process addr2line makes this much more robust. As future extensions, libllvm can be used in many other places where we currently use libbfd or other libraries: - Symbol enumeration (in particular, for PE binaries). - Demangling (including non-Itanium demangling, e.g. Microsoft or Rust). - Disassembling (perf annotate). However, these are much less pressing; most people don't profile PE binaries, and perf has non-bfd paths for ELF. The same with demangling; the default _cxa_demangle path works fine for most users, and while bfd objdump can be slow on large binaries, it is possible to use --objdump=llvm-objdump to get the speed benefits. (It appears LLVM-based demangling is very simple, should we want that.) Tested with LLVM 14, 15, 16, 18 and 19. For some reason, LLVM 12 was not correctly detected using feature_check, and thus was not tested. Committer notes: Added the name and a __maybe_unused to address: 1 13.50 almalinux:8 : FAIL gcc version 8.5.0 20210514 (Red Hat 8.5.0-22) (GCC) util/srcline.c: In function 'dso__free_a2l': util/srcline.c:184:20: error: parameter name omitted void dso__free_a2l(struct dso *) ^~~~~~~~~~~~ make[3]: *** [/git/perf-6.11.0-rc3/tools/build/Makefile.build:158: util] Error 2 Signed-off-by: Steinar H. Gunderson <sesse@google.com> Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com> Cc: Ian Rogers <irogers@google.com> Link: https://lore.kernel.org/r/20240803152008.2818485-1-sesse@google.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
1 parent 0e7eb23 commit c3f8644

File tree

8 files changed

+263
-1
lines changed

8 files changed

+263
-1
lines changed

tools/build/Makefile.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ FEATURE_DISPLAY ?= \
136136
libunwind \
137137
libdw-dwarf-unwind \
138138
libcapstone \
139+
llvm \
139140
zlib \
140141
lzma \
141142
get_cpuid \

tools/perf/Makefile.config

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,23 @@ ifdef BUILD_NONDISTRO
980980
endif
981981
endif
982982

983+
ifndef NO_LIBLLVM
984+
$(call feature_check,llvm)
985+
ifeq ($(feature-llvm), 1)
986+
CFLAGS += -DHAVE_LIBLLVM_SUPPORT
987+
CFLAGS += $(shell $(LLVM_CONFIG) --cflags)
988+
CXXFLAGS += -DHAVE_LIBLLVM_SUPPORT
989+
CXXFLAGS += $(shell $(LLVM_CONFIG) --cxxflags)
990+
LIBLLVM = $(shell $(LLVM_CONFIG) --libs all) $(shell $(LLVM_CONFIG) --system-libs)
991+
EXTLIBS += -L$(shell $(LLVM_CONFIG) --libdir) $(LIBLLVM)
992+
EXTLIBS += -lstdc++
993+
$(call detected,CONFIG_LIBLLVM)
994+
else
995+
$(warning No libllvm found, slower source file resolution, please install llvm-devel/llvm-dev)
996+
NO_LIBLLVM := 1
997+
endif
998+
endif
999+
9831000
ifndef NO_DEMANGLE
9841001
$(call feature_check,cxa-demangle)
9851002
ifeq ($(feature-cxa-demangle), 1)

tools/perf/builtin-version.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ static void library_status(void)
6565
STATUS(HAVE_LIBBFD_SUPPORT, libbfd);
6666
STATUS(HAVE_DEBUGINFOD_SUPPORT, debuginfod);
6767
STATUS(HAVE_LIBELF_SUPPORT, libelf);
68+
STATUS(HAVE_LIBLLVM_SUPPORT, libllvm);
6869
STATUS(HAVE_LIBNUMA_SUPPORT, libnuma);
6970
STATUS(HAVE_LIBNUMA_SUPPORT, numa_num_possible_cpus);
7071
STATUS(HAVE_LIBPERL_SUPPORT, libperl);

tools/perf/tests/make

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ make_no_libbpf := NO_LIBBPF=1
9393
make_libbpf_dynamic := LIBBPF_DYNAMIC=1
9494
make_no_libbpf_DEBUG := NO_LIBBPF=1 DEBUG=1
9595
make_no_libcrypto := NO_LIBCRYPTO=1
96+
make_no_libllvm := NO_LIBLLVM=1
9697
make_with_babeltrace:= LIBBABELTRACE=1
9798
make_with_coresight := CORESIGHT=1
9899
make_no_sdt := NO_SDT=1
@@ -163,6 +164,7 @@ run += make_no_auxtrace
163164
run += make_no_libbpf
164165
run += make_no_libbpf_DEBUG
165166
run += make_no_libcrypto
167+
run += make_no_libllvm
166168
run += make_no_sdt
167169
run += make_no_syscall_tbl
168170
run += make_with_babeltrace

tools/perf/util/Build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ perf-util-$(CONFIG_CXX_DEMANGLE) += demangle-cxx.o
229229
perf-util-y += demangle-ocaml.o
230230
perf-util-y += demangle-java.o
231231
perf-util-y += demangle-rust.o
232+
perf-util-$(CONFIG_LIBLLVM) += llvm-c-helpers.o
232233

233234
ifdef CONFIG_JITDUMP
234235
perf-util-$(CONFIG_LIBELF) += jitdump.o

tools/perf/util/llvm-c-helpers.cpp

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
/*
4+
* Must come before the linux/compiler.h include, which defines several
5+
* macros (e.g. noinline) that conflict with compiler builtins used
6+
* by LLVM.
7+
*/
8+
#pragma GCC diagnostic push
9+
#pragma GCC diagnostic ignored "-Wunused-parameter" /* Needed for LLVM <= 15 */
10+
#include <llvm/DebugInfo/Symbolize/Symbolize.h>
11+
#pragma GCC diagnostic pop
12+
13+
#include <stdio.h>
14+
#include <sys/types.h>
15+
#include <linux/compiler.h>
16+
extern "C" {
17+
#include <linux/zalloc.h>
18+
}
19+
#include "symbol_conf.h"
20+
#include "llvm-c-helpers.h"
21+
22+
using namespace llvm;
23+
using llvm::symbolize::LLVMSymbolizer;
24+
25+
/*
26+
* Allocate a static LLVMSymbolizer, which will live to the end of the program.
27+
* Unlike the bfd paths, LLVMSymbolizer has its own cache, so we do not need
28+
* to store anything in the dso struct.
29+
*/
30+
static LLVMSymbolizer *get_symbolizer()
31+
{
32+
static LLVMSymbolizer *instance = nullptr;
33+
if (instance == nullptr) {
34+
LLVMSymbolizer::Options opts;
35+
/*
36+
* LLVM sometimes demangles slightly different from the rest
37+
* of the code, and this mismatch can cause new_inline_sym()
38+
* to get confused and mark non-inline symbol as inlined
39+
* (since the name does not properly match up with base_sym).
40+
* Thus, disable the demangling and let the rest of the code
41+
* handle it.
42+
*/
43+
opts.Demangle = false;
44+
instance = new LLVMSymbolizer(opts);
45+
}
46+
return instance;
47+
}
48+
49+
/* Returns 0 on error, 1 on success. */
50+
static int extract_file_and_line(const DILineInfo &line_info, char **file,
51+
unsigned int *line)
52+
{
53+
if (file) {
54+
if (line_info.FileName == "<invalid>") {
55+
/* Match the convention of libbfd. */
56+
*file = nullptr;
57+
} else {
58+
/* The caller expects to get something it can free(). */
59+
*file = strdup(line_info.FileName.c_str());
60+
if (*file == nullptr)
61+
return 0;
62+
}
63+
}
64+
if (line)
65+
*line = line_info.Line;
66+
return 1;
67+
}
68+
69+
extern "C"
70+
int llvm_addr2line(const char *dso_name, u64 addr,
71+
char **file, unsigned int *line,
72+
bool unwind_inlines,
73+
llvm_a2l_frame **inline_frames)
74+
{
75+
LLVMSymbolizer *symbolizer = get_symbolizer();
76+
object::SectionedAddress sectioned_addr = {
77+
addr,
78+
object::SectionedAddress::UndefSection
79+
};
80+
81+
if (unwind_inlines) {
82+
Expected<DIInliningInfo> res_or_err =
83+
symbolizer->symbolizeInlinedCode(dso_name,
84+
sectioned_addr);
85+
if (!res_or_err)
86+
return 0;
87+
unsigned num_frames = res_or_err->getNumberOfFrames();
88+
if (num_frames == 0)
89+
return 0;
90+
91+
if (extract_file_and_line(res_or_err->getFrame(0),
92+
file, line) == 0)
93+
return 0;
94+
95+
*inline_frames = (llvm_a2l_frame *)calloc(
96+
num_frames, sizeof(**inline_frames));
97+
if (*inline_frames == nullptr)
98+
return 0;
99+
100+
for (unsigned i = 0; i < num_frames; ++i) {
101+
const DILineInfo &src = res_or_err->getFrame(i);
102+
103+
llvm_a2l_frame &dst = (*inline_frames)[i];
104+
if (src.FileName == "<invalid>")
105+
/* Match the convention of libbfd. */
106+
dst.filename = nullptr;
107+
else
108+
dst.filename = strdup(src.FileName.c_str());
109+
dst.funcname = strdup(src.FunctionName.c_str());
110+
dst.line = src.Line;
111+
112+
if (dst.filename == nullptr ||
113+
dst.funcname == nullptr) {
114+
for (unsigned j = 0; j <= i; ++j) {
115+
zfree(&(*inline_frames)[j].filename);
116+
zfree(&(*inline_frames)[j].funcname);
117+
}
118+
zfree(inline_frames);
119+
return 0;
120+
}
121+
}
122+
123+
return num_frames;
124+
} else {
125+
if (inline_frames)
126+
*inline_frames = nullptr;
127+
128+
Expected<DILineInfo> res_or_err =
129+
symbolizer->symbolizeCode(dso_name, sectioned_addr);
130+
if (!res_or_err)
131+
return 0;
132+
return extract_file_and_line(*res_or_err, file, line);
133+
}
134+
}

tools/perf/util/llvm-c-helpers.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
#ifndef __PERF_LLVM_C_HELPERS
3+
#define __PERF_LLVM_C_HELPERS 1
4+
5+
/*
6+
* Helpers to call into LLVM C++ code from C, for the parts that do not have
7+
* C APIs.
8+
*/
9+
10+
#include <linux/compiler.h>
11+
12+
#ifdef __cplusplus
13+
extern "C" {
14+
#endif
15+
16+
struct llvm_a2l_frame {
17+
char* filename;
18+
char* funcname;
19+
unsigned int line;
20+
};
21+
22+
/*
23+
* Implement addr2line() using libLLVM. LLVM is a C++ API, and
24+
* many of the linux/ headers cannot be included in a C++ compile unit,
25+
* so we need to make a little bridge code here. llvm_addr2line() will
26+
* convert the inline frame information from LLVM's internal structures
27+
* and put them into a flat array given in inline_frames. The caller
28+
* is then responsible for taking that array and convert it into perf's
29+
* regular inline frame structures (which depend on e.g. struct list_head).
30+
*
31+
* If the address could not be resolved, or an error occurred (e.g. OOM),
32+
* returns 0. Otherwise, returns the number of inline frames (which means 1
33+
* if the address was not part of an inlined function). If unwind_inlines
34+
* is set and the return code is nonzero, inline_frames will be set to
35+
* a newly allocated array with that length. The caller is then responsible
36+
* for freeing both the strings and the array itself.
37+
*/
38+
int llvm_addr2line(const char* dso_name,
39+
u64 addr,
40+
char** file,
41+
unsigned int* line,
42+
bool unwind_inlines,
43+
struct llvm_a2l_frame** inline_frames);
44+
45+
#ifdef __cplusplus
46+
}
47+
#endif
48+
49+
#endif /* __PERF_LLVM_C_HELPERS */

tools/perf/util/srcline.c

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <string.h>
77
#include <sys/types.h>
88

9+
#include <linux/compiler.h>
910
#include <linux/kernel.h>
1011
#include <linux/string.h>
1112
#include <linux/zalloc.h>
@@ -16,6 +17,9 @@
1617
#include "util/debug.h"
1718
#include "util/callchain.h"
1819
#include "util/symbol_conf.h"
20+
#ifdef HAVE_LIBLLVM_SUPPORT
21+
#include "util/llvm-c-helpers.h"
22+
#endif
1923
#include "srcline.h"
2024
#include "string2.h"
2125
#include "symbol.h"
@@ -130,7 +134,60 @@ static struct symbol *new_inline_sym(struct dso *dso,
130134

131135
#define MAX_INLINE_NEST 1024
132136

133-
#ifdef HAVE_LIBBFD_SUPPORT
137+
#ifdef HAVE_LIBLLVM_SUPPORT
138+
139+
static void free_llvm_inline_frames(struct llvm_a2l_frame *inline_frames,
140+
int num_frames)
141+
{
142+
if (inline_frames != NULL) {
143+
for (int i = 0; i < num_frames; ++i) {
144+
zfree(&inline_frames[i].filename);
145+
zfree(&inline_frames[i].funcname);
146+
}
147+
zfree(&inline_frames);
148+
}
149+
}
150+
151+
static int addr2line(const char *dso_name, u64 addr,
152+
char **file, unsigned int *line, struct dso *dso,
153+
bool unwind_inlines, struct inline_node *node,
154+
struct symbol *sym)
155+
{
156+
struct llvm_a2l_frame *inline_frames = NULL;
157+
int num_frames = llvm_addr2line(dso_name, addr, file, line,
158+
node && unwind_inlines, &inline_frames);
159+
160+
if (num_frames == 0 || !inline_frames) {
161+
/* Error, or we didn't want inlines. */
162+
return num_frames;
163+
}
164+
165+
for (int i = 0; i < num_frames; ++i) {
166+
struct symbol *inline_sym =
167+
new_inline_sym(dso, sym, inline_frames[i].funcname);
168+
char *srcline = NULL;
169+
170+
if (inline_frames[i].filename) {
171+
srcline =
172+
srcline_from_fileline(inline_frames[i].filename,
173+
inline_frames[i].line);
174+
}
175+
if (inline_list__append(inline_sym, srcline, node) != 0) {
176+
free_llvm_inline_frames(inline_frames, num_frames);
177+
return 0;
178+
}
179+
}
180+
free_llvm_inline_frames(inline_frames, num_frames);
181+
182+
return num_frames;
183+
}
184+
185+
void dso__free_a2l(struct dso *dso __maybe_unused)
186+
{
187+
/* Nothing to free. */
188+
}
189+
190+
#elif defined(HAVE_LIBBFD_SUPPORT)
134191

135192
/*
136193
* Implement addr2line using libbfd.

0 commit comments

Comments
 (0)