[PATCH v3 07/18] perf llvm: Support for dlopen-ing libLLVM.so

Ian Rogers posted 18 patches 11 months ago
There is a newer version of this series
[PATCH v3 07/18] perf llvm: Support for dlopen-ing libLLVM.so
Posted by Ian Rogers 11 months ago
If perf wasn't built against libLLVM, no HAVE_LIBLLVM_SUPPORT, support
dlopen-ing libLLVM.so and then calling the necessary functions by
looking them up using dlsym. As the C++ code in llvm-c-helpers used
for addr2line is problematic to call using dlsym, build that C++ code
against libLLVM.so as a separate shared object, and support dynamic
loading of it. This build option is enabled with LIBLLVM_DYNAMIC=1

Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/Makefile.config         |  13 ++
 tools/perf/Makefile.perf           |  23 ++-
 tools/perf/tests/make              |   2 +
 tools/perf/util/Build              |   2 +-
 tools/perf/util/llvm-c-helpers.cpp | 113 +++++++++++-
 tools/perf/util/llvm.c             | 271 +++++++++++++++++++++++++----
 6 files changed, 386 insertions(+), 38 deletions(-)

diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config
index cd773fbbc176..5c2814acc5d5 100644
--- a/tools/perf/Makefile.config
+++ b/tools/perf/Makefile.config
@@ -963,6 +963,19 @@ ifndef NO_LIBLLVM
     NO_LIBLLVM := 1
   endif
 endif
+ifdef LIBLLVM_DYNAMIC
+  ifndef NO_LIBLLVM
+    $(error LIBLLVM_DYNAMIC should be used with NO_LIBLLVM)
+  endif
+  $(call feature_check,llvm-perf)
+  ifneq ($(feature-llvm-perf), 1)
+    $(warning LIBLLVM_DYNAMIC requires libLLVM.so which wasn't feature detected)
+  endif
+  CFLAGS += -DHAVE_LIBLLVM_DYNAMIC
+  CFLAGS += $(shell $(LLVM_CONFIG) --cflags)
+  CXXFLAGS += -DHAVE_LIBLLVM_DYNAMIC
+  CXXFLAGS += $(shell $(LLVM_CONFIG) --cxxflags)
+endif
 
 ifndef NO_DEMANGLE
   $(call feature_check,cxa-demangle)
diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf
index 55d6ce9ea52f..eae77f6af59d 100644
--- a/tools/perf/Makefile.perf
+++ b/tools/perf/Makefile.perf
@@ -456,6 +456,12 @@ ifndef NO_JVMTI
 PROGRAMS += $(OUTPUT)$(LIBJVMTI)
 endif
 
+LIBPERF_LLVM = libperf-llvm.so
+
+ifdef LIBLLVM_DYNAMIC
+PROGRAMS += $(OUTPUT)$(LIBPERF_LLVM)
+endif
+
 DLFILTERS := dlfilter-test-api-v0.so dlfilter-test-api-v2.so dlfilter-show-cycles.so
 DLFILTERS := $(patsubst %,$(OUTPUT)dlfilters/%,$(DLFILTERS))
 
@@ -1019,6 +1025,16 @@ $(LIBSYMBOL)-clean:
 	$(call QUIET_CLEAN, libsymbol)
 	$(Q)$(RM) -r -- $(LIBSYMBOL_OUTPUT)
 
+ifdef LIBLLVM_DYNAMIC
+LIBPERF_LLVM_CXXFLAGS := $(call filter-out,-DHAVE_LIBLLVM_DYNAMIC,$(CXXFLAGS)) -DHAVE_LIBLLVM_SUPPORT
+LIBPERF_LLVM_LIBS = -L$(shell $(LLVM_CONFIG) --libdir) $(LIBLLVM) -lstdc++
+
+$(OUTPUT)$(LIBPERF_LLVM): util/llvm-c-helpers.cpp
+	$(QUIET_LINK)$(CXX) $(LIBPERF_LLVM_CXXFLAGS) $(LIBPERF_LLVM_LIBS) -shared -o $@ $<
+
+$(OUTPUT)perf: $(OUTPUT)$(LIBPERF_LLVM)
+endif
+
 help:
 	@echo 'Perf make targets:'
 	@echo '  doc		- make *all* documentation (see below)'
@@ -1120,6 +1136,11 @@ ifndef NO_JVMTI
 	$(call QUIET_INSTALL, $(LIBJVMTI)) \
 		$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(libdir_SQ)'; \
 		$(INSTALL) $(OUTPUT)$(LIBJVMTI) '$(DESTDIR_SQ)$(libdir_SQ)';
+endif
+ifdef LIBLLVM_DYNAMIC
+	$(call QUIET_INSTALL, $(LIBPERF_LLVM)) \
+		$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(libdir_SQ)'; \
+		$(INSTALL) $(OUTPUT)$(LIBPERF_LLVM) '$(DESTDIR_SQ)$(libdir_SQ)';
 endif
 	$(call QUIET_INSTALL, libexec) \
 		$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)'
@@ -1301,7 +1322,7 @@ clean:: $(LIBAPI)-clean $(LIBBPF)-clean $(LIBSUBCMD)-clean $(LIBSYMBOL)-clean $(
 		-name '\.*.cmd' -delete -o -name '\.*.d' -delete -o -name '*.shellcheck_log' -delete
 	$(Q)$(RM) $(OUTPUT).config-detected
 	$(call QUIET_CLEAN, core-progs) $(RM) $(ALL_PROGRAMS) perf perf-read-vdso32 \
-		perf-read-vdsox32 $(OUTPUT)$(LIBJVMTI).so
+		perf-read-vdsox32 $(OUTPUT)$(LIBJVMTI) $(OUTPUT)$(LIBPERF_LLVM)
 	$(call QUIET_CLEAN, core-gen)   $(RM)  *.spec *.pyc *.pyo */*.pyc */*.pyo \
 		$(OUTPUT)common-cmds.h TAGS tags cscope* $(OUTPUT)PERF-VERSION-FILE \
 		$(OUTPUT)FEATURE-DUMP $(OUTPUT)util/*-bison* $(OUTPUT)util/*-flex* \
diff --git a/tools/perf/tests/make b/tools/perf/tests/make
index 0ee94caf9ec1..44d76eacce49 100644
--- a/tools/perf/tests/make
+++ b/tools/perf/tests/make
@@ -93,6 +93,7 @@ make_libbpf_dynamic := LIBBPF_DYNAMIC=1
 make_no_libbpf_DEBUG := NO_LIBBPF=1 DEBUG=1
 make_no_libcrypto   := NO_LIBCRYPTO=1
 make_no_libllvm     := NO_LIBLLVM=1
+make_libllvm_dynamic := NO_LIBLLVM=1 LIBLLVM_DYNAMIC=1
 make_with_babeltrace:= LIBBABELTRACE=1
 make_with_coresight := CORESIGHT=1
 make_no_sdt	    := NO_SDT=1
@@ -162,6 +163,7 @@ run += make_no_libbpf
 run += make_no_libbpf_DEBUG
 run += make_no_libcrypto
 run += make_no_libllvm
+run += make_libllvm_dynamic
 run += make_no_sdt
 run += make_no_syscall_tbl
 run += make_with_babeltrace
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 6fe0b5882c97..eb00c599e179 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -27,6 +27,7 @@ perf-util-y += find_bit.o
 perf-util-y += get_current_dir_name.o
 perf-util-y += levenshtein.o
 perf-util-y += llvm.o
+perf-util-y += llvm-c-helpers.o
 perf-util-y += mmap.o
 perf-util-y += memswap.o
 perf-util-y += parse-events.o
@@ -239,7 +240,6 @@ perf-util-$(CONFIG_CXX_DEMANGLE) += demangle-cxx.o
 perf-util-y += demangle-ocaml.o
 perf-util-y += demangle-java.o
 perf-util-y += demangle-rust.o
-perf-util-$(CONFIG_LIBLLVM) += llvm-c-helpers.o
 
 ifdef CONFIG_JITDUMP
 perf-util-$(CONFIG_LIBELF) += jitdump.o
diff --git a/tools/perf/util/llvm-c-helpers.cpp b/tools/perf/util/llvm-c-helpers.cpp
index 004081bd12c9..5a6f76e6b705 100644
--- a/tools/perf/util/llvm-c-helpers.cpp
+++ b/tools/perf/util/llvm-c-helpers.cpp
@@ -5,17 +5,23 @@
  * macros (e.g. noinline) that conflict with compiler builtins used
  * by LLVM.
  */
+#ifdef HAVE_LIBLLVM_SUPPORT
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wunused-parameter"  /* Needed for LLVM <= 15 */
 #include <llvm/DebugInfo/Symbolize/Symbolize.h>
 #include <llvm/Support/TargetSelect.h>
 #pragma GCC diagnostic pop
+#endif
 
+#if !defined(HAVE_LIBLLVM_SUPPORT) || defined(HAVE_LIBLLVM_DYNAMIC)
+#include <dlfcn.h>
+#endif
 #include <inttypes.h>
 #include <stdio.h>
 #include <sys/types.h>
 #include <linux/compiler.h>
 extern "C" {
+#include "debug.h"
 #include <linux/zalloc.h>
 }
 #include "llvm-c-helpers.h"
@@ -23,14 +29,33 @@ extern "C" {
 extern "C"
 char *dso__demangle_sym(struct dso *dso, int kmodule, const char *elf_name);
 
+#ifdef HAVE_LIBLLVM_SUPPORT
 using namespace llvm;
 using llvm::symbolize::LLVMSymbolizer;
+#endif
+
+#if !defined(HAVE_LIBLLVM_SUPPORT) && defined(HAVE_LIBLLVM_DYNAMIC)
+static void *perf_llvm_c_helpers_dll_handle(void)
+{
+	static bool dll_handle_init;
+	static void *dll_handle;
+
+	if (!dll_handle_init) {
+		dll_handle_init = true;
+		dll_handle = dlopen("libperf-llvm.so", RTLD_LAZY);
+		if (!dll_handle)
+			pr_debug("dlopen failed for libperf-llvm.so\n");
+	}
+	return dll_handle;
+}
+#endif
 
 /*
  * Allocate a static LLVMSymbolizer, which will live to the end of the program.
  * Unlike the bfd paths, LLVMSymbolizer has its own cache, so we do not need
  * to store anything in the dso struct.
  */
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
 static LLVMSymbolizer *get_symbolizer()
 {
 	static LLVMSymbolizer *instance = nullptr;
@@ -49,8 +74,10 @@ static LLVMSymbolizer *get_symbolizer()
 	}
 	return instance;
 }
+#endif
 
 /* Returns 0 on error, 1 on success. */
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
 static int extract_file_and_line(const DILineInfo &line_info, char **file,
 				 unsigned int *line)
 {
@@ -69,13 +96,15 @@ static int extract_file_and_line(const DILineInfo &line_info, char **file,
 		*line = line_info.Line;
 	return 1;
 }
+#endif
 
 extern "C"
-int llvm_addr2line(const char *dso_name, u64 addr,
-		   char **file, unsigned int *line,
-		   bool unwind_inlines,
-		   llvm_a2l_frame **inline_frames)
+int llvm_addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused,
+		   char **file __maybe_unused, unsigned int *line __maybe_unused,
+		   bool unwind_inlines __maybe_unused,
+		   llvm_a2l_frame **inline_frames __maybe_unused)
 {
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
 	LLVMSymbolizer *symbolizer = get_symbolizer();
 	object::SectionedAddress sectioned_addr = {
 		addr,
@@ -135,8 +164,33 @@ int llvm_addr2line(const char *dso_name, u64 addr,
 			return 0;
 		return extract_file_and_line(*res_or_err, file, line);
 	}
+#elif defined(HAVE_LIBLLVM_DYNAMIC)
+	static bool fn_init;
+	static int (*fn)(const char *dso_name, u64 addr,
+			 char **file, unsigned int *line,
+			 bool unwind_inlines,
+			 llvm_a2l_frame **inline_frames);
+
+	if (!fn_init) {
+		void * handle = perf_llvm_c_helpers_dll_handle();
+
+		if (!handle)
+			return 0;
+
+		fn = reinterpret_cast<decltype(fn)>(dlsym(handle, "llvm_addr2line"));
+		if (!fn)
+			pr_debug("dlsym failed for llvm_addr2line\n");
+		fn_init = true;
+	}
+	if (!fn)
+		return 0;
+	return fn(dso_name, addr, file, line, unwind_inlines, inline_frames);
+#else
+	return 0;
+#endif
 }
 
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
 static char *
 make_symbol_relative_string(struct dso *dso, const char *sym_name,
 			    u64 addr, u64 base_addr)
@@ -158,10 +212,13 @@ make_symbol_relative_string(struct dso *dso, const char *sym_name,
 			return strdup(sym_name);
 	}
 }
+#endif
 
 extern "C"
-char *llvm_name_for_code(struct dso *dso, const char *dso_name, u64 addr)
+char *llvm_name_for_code(struct dso *dso __maybe_unused, const char *dso_name __maybe_unused,
+			 u64 addr __maybe_unused)
 {
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
 	LLVMSymbolizer *symbolizer = get_symbolizer();
 	object::SectionedAddress sectioned_addr = {
 		addr,
@@ -175,11 +232,34 @@ char *llvm_name_for_code(struct dso *dso, const char *dso_name, u64 addr)
 	return make_symbol_relative_string(
 		dso, res_or_err->FunctionName.c_str(),
 		addr, res_or_err->StartAddress ? *res_or_err->StartAddress : 0);
+#elif defined(HAVE_LIBLLVM_DYNAMIC)
+	static bool fn_init;
+	static char *(*fn)(struct dso *dso, const char *dso_name, u64 addr);
+
+	if (!fn_init) {
+		void * handle = perf_llvm_c_helpers_dll_handle();
+
+		if (!handle)
+			return NULL;
+
+		fn = reinterpret_cast<decltype(fn)>(dlsym(handle, "llvm_name_for_code"));
+		if (!fn)
+			pr_debug("dlsym failed for llvm_name_for_code\n");
+		fn_init = true;
+	}
+	if (!fn)
+		return NULL;
+	return fn(dso, dso_name, addr);
+#else
+	return 0;
+#endif
 }
 
 extern "C"
-char *llvm_name_for_data(struct dso *dso, const char *dso_name, u64 addr)
+char *llvm_name_for_data(struct dso *dso __maybe_unused, const char *dso_name __maybe_unused,
+			 u64 addr __maybe_unused)
 {
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
 	LLVMSymbolizer *symbolizer = get_symbolizer();
 	object::SectionedAddress sectioned_addr = {
 		addr,
@@ -193,4 +273,25 @@ char *llvm_name_for_data(struct dso *dso, const char *dso_name, u64 addr)
 	return make_symbol_relative_string(
 		dso, res_or_err->Name.c_str(),
 		addr, res_or_err->Start);
+#elif defined(HAVE_LIBLLVM_DYNAMIC)
+	static bool fn_init;
+	static char *(*fn)(struct dso *dso, const char *dso_name, u64 addr);
+
+	if (!fn_init) {
+		void * handle = perf_llvm_c_helpers_dll_handle();
+
+		if (!handle)
+			return NULL;
+
+		fn = reinterpret_cast<decltype(fn)>(dlsym(handle, "llvm_name_for_data"));
+		if (!fn)
+			pr_debug("dlsym failed for llvm_name_for_data\n");
+		fn_init = true;
+	}
+	if (!fn)
+		return NULL;
+	return fn(dso, dso_name, addr);
+#else
+	return 0;
+#endif
 }
diff --git a/tools/perf/util/llvm.c b/tools/perf/util/llvm.c
index ddc737194692..f6a8943b7c9d 100644
--- a/tools/perf/util/llvm.c
+++ b/tools/perf/util/llvm.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 #include "llvm.h"
+#include "llvm-c-helpers.h"
 #include "annotate.h"
 #include "debug.h"
 #include "dso.h"
@@ -7,17 +8,243 @@
 #include "namespaces.h"
 #include "srcline.h"
 #include "symbol.h"
+#include <dlfcn.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <unistd.h>
 #include <linux/zalloc.h>
 
-#ifdef HAVE_LIBLLVM_SUPPORT
-#include "llvm-c-helpers.h"
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
 #include <llvm-c/Disassembler.h>
 #include <llvm-c/Target.h>
+#else
+typedef void *LLVMDisasmContextRef;
+typedef int (*LLVMOpInfoCallback)(void *dis_info, uint64_t pc, uint64_t offset,
+				  uint64_t op_size, uint64_t inst_size,
+				  int tag_type, void *tag_buf);
+typedef const char *(*LLVMSymbolLookupCallback)(void *dis_info,
+						uint64_t reference_value,
+						uint64_t *reference_type,
+						uint64_t reference_pc,
+						const char **reference_name);
+#define LLVMDisassembler_ReferenceType_InOut_None 0
+#define LLVMDisassembler_ReferenceType_In_Branch 1
+#define LLVMDisassembler_ReferenceType_In_PCrel_Load 2
+#define LLVMDisassembler_Option_PrintImmHex 2
+#define LLVMDisassembler_Option_AsmPrinterVariant 4
+const char *llvm_targets[] = {
+	"AMDGPU",
+	"ARM",
+	"AVR",
+	"BPF",
+	"Hexagon",
+	"Lanai",
+	"LoongArch",
+	"Mips",
+	"MSP430",
+	"NVPTX",
+	"PowerPC",
+	"RISCV",
+	"Sparc",
+	"SystemZ",
+	"VE",
+	"WebAssembly",
+	"X86",
+	"XCore",
+	"M68k",
+	"Xtensa",
+};
+#endif
+
+#if !defined(HAVE_LIBLLVM_SUPPORT) || defined(HAVE_LIBLLVM_DYNAMIC)
+static void *perf_llvm_dll_handle(void)
+{
+	static bool dll_handle_init;
+	static void *dll_handle;
+
+	if (!dll_handle_init) {
+		dll_handle_init = true;
+		dll_handle = dlopen("libLLVM.so", RTLD_LAZY);
+		if (!dll_handle)
+			pr_debug("dlopen failed for libLLVM.so\n");
+	}
+	return dll_handle;
+}
+#endif
+
+#if !defined(HAVE_LIBLLVM_SUPPORT) || defined(HAVE_LIBLLVM_DYNAMIC)
+static void *perf_llvm_dll_fun(const char *fmt, const char *target)
+{
+	char buf[128];
+	void *fn;
+
+	snprintf(buf, sizeof(buf), fmt, target);
+	fn = dlsym(perf_llvm_dll_handle(), buf);
+	if (!fn)
+		pr_debug("dlsym failed for %s\n", buf);
+
+	return fn;
+}
+#endif
+
+static void perf_LLVMInitializeAllTargetInfos(void)
+{
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
+	LLVMInitializeAllTargetInfos();
+#else
+	/* LLVMInitializeAllTargetInfos is a header file function not available as a symbol. */
+	static bool done_init;
+
+	if (done_init)
+		return;
+
+	for (size_t i = 0; i < ARRAY_SIZE(llvm_targets); i++) {
+		void (*fn)(void) = perf_llvm_dll_fun("LLVMInitialize%sTargetInfo",
+						     llvm_targets[i]);
+
+		if (!fn)
+			continue;
+		fn();
+	}
+	done_init = true;
+#endif
+}
+
+static void perf_LLVMInitializeAllTargetMCs(void)
+{
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
+	LLVMInitializeAllTargetMCs();
+#else
+	/* LLVMInitializeAllTargetMCs is a header file function not available as a symbol. */
+	static bool done_init;
+
+	if (done_init)
+		return;
+
+	for (size_t i = 0; i < ARRAY_SIZE(llvm_targets); i++) {
+		void (*fn)(void) = perf_llvm_dll_fun("LLVMInitialize%sTargetMC",
+						     llvm_targets[i]);
+
+		if (!fn)
+			continue;
+		fn();
+	}
+	done_init = true;
+#endif
+}
+
+static void perf_LLVMInitializeAllDisassemblers(void)
+{
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
+	LLVMInitializeAllDisassemblers();
+#else
+	/* LLVMInitializeAllDisassemblers is a header file function not available as a symbol. */
+	static bool done_init;
+
+	if (done_init)
+		return;
+
+	for (size_t i = 0; i < ARRAY_SIZE(llvm_targets); i++) {
+		void (*fn)(void) = perf_llvm_dll_fun("LLVMInitialize%sDisassembler",
+						     llvm_targets[i]);
+
+		if (!fn)
+			continue;
+		fn();
+	}
+	done_init = true;
+#endif
+}
+
+static LLVMDisasmContextRef perf_LLVMCreateDisasm(const char *triple_name, void *dis_info,
+						int tag_type, LLVMOpInfoCallback get_op_info,
+						LLVMSymbolLookupCallback symbol_lookup)
+{
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
+	return LLVMCreateDisasm(triple_name, dis_info, tag_type, get_op_info, symbol_lookup);
+#else
+	static bool fn_init;
+	static LLVMDisasmContextRef (*fn)(const char *triple_name, void *dis_info,
+					int tag_type, LLVMOpInfoCallback get_op_info,
+					LLVMSymbolLookupCallback symbol_lookup);
+
+	if (!fn_init) {
+		fn = dlsym(perf_llvm_dll_handle(), "LLVMCreateDisasm");
+		if (!fn)
+			pr_debug("dlsym failed for LLVMCreateDisasm\n");
+		fn_init = true;
+	}
+	if (!fn)
+		return NULL;
+	return fn(triple_name, dis_info, tag_type, get_op_info, symbol_lookup);
+#endif
+}
+
+static int perf_LLVMSetDisasmOptions(LLVMDisasmContextRef context, uint64_t options)
+{
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
+	return LLVMSetDisasmOptions(context, options);
+#else
+	static bool fn_init;
+	static int (*fn)(LLVMDisasmContextRef context, uint64_t options);
+
+	if (!fn_init) {
+		fn = dlsym(perf_llvm_dll_handle(), "LLVMSetDisasmOptions");
+		if (!fn)
+			pr_debug("dlsym failed for LLVMSetDisasmOptions\n");
+		fn_init = true;
+	}
+	if (!fn)
+		return 0;
+	return fn(context, options);
+#endif
+}
+
+static size_t perf_LLVMDisasmInstruction(LLVMDisasmContextRef context, uint8_t *bytes,
+					uint64_t bytes_size, uint64_t pc,
+					char *out_string, size_t out_string_size)
+{
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
+	return LLVMDisasmInstruction(context, bytes, bytes_size, pc, out_string, out_string_size);
+#else
+	static bool fn_init;
+	static int (*fn)(LLVMDisasmContextRef context, uint8_t *bytes,
+			uint64_t bytes_size, uint64_t pc,
+			char *out_string, size_t out_string_size);
+
+	if (!fn_init) {
+		fn = dlsym(perf_llvm_dll_handle(), "LLVMDisasmInstruction");
+		if (!fn)
+			pr_debug("dlsym failed for LLVMDisasmInstruction\n");
+		fn_init = true;
+	}
+	if (!fn)
+		return 0;
+	return fn(context, bytes, bytes_size, pc, out_string, out_string_size);
+#endif
+}
+
+static void perf_LLVMDisasmDispose(LLVMDisasmContextRef context)
+{
+#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
+	LLVMDisasmDispose(context);
+#else
+	static bool fn_init;
+	static int (*fn)(LLVMDisasmContextRef context);
+
+	if (!fn_init) {
+		fn = dlsym(perf_llvm_dll_handle(), "LLVMDisasmDispose");
+		if (!fn)
+			pr_debug("dlsym failed for LLVMDisasmDispose\n");
+		fn_init = true;
+	}
+	if (!fn)
+		return;
+	fn(context);
 #endif
+}
+
 
-#ifdef HAVE_LIBLLVM_SUPPORT
 static void free_llvm_inline_frames(struct llvm_a2l_frame *inline_frames,
 				    int num_frames)
 {
@@ -29,14 +256,12 @@ static void free_llvm_inline_frames(struct llvm_a2l_frame *inline_frames,
 		zfree(&inline_frames);
 	}
 }
-#endif
 
 int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused,
 		     char **file __maybe_unused, unsigned int *line __maybe_unused,
 		     struct dso *dso __maybe_unused, bool unwind_inlines __maybe_unused,
 		     struct inline_node *node __maybe_unused, struct symbol *sym __maybe_unused)
 {
-#ifdef HAVE_LIBLLVM_SUPPORT
 	struct llvm_a2l_frame *inline_frames = NULL;
 	int num_frames = llvm_addr2line(dso_name, addr, file, line,
 					node && unwind_inlines, &inline_frames);
@@ -64,9 +289,6 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused
 	free_llvm_inline_frames(inline_frames, num_frames);
 
 	return num_frames;
-#else
-	return -1;
-#endif
 }
 
 void dso__free_a2l_llvm(struct dso *dso __maybe_unused)
@@ -75,7 +297,6 @@ void dso__free_a2l_llvm(struct dso *dso __maybe_unused)
 }
 
 
-#if defined(HAVE_LIBLLVM_SUPPORT)
 struct find_file_offset_data {
 	u64 ip;
 	u64 offset;
@@ -139,7 +360,6 @@ read_symbol(const char *filename, struct map *map, struct symbol *sym,
 	free(buf);
 	return NULL;
 }
-#endif
 
 /*
  * Whenever LLVM wants to resolve an address into a symbol, it calls this
@@ -149,7 +369,6 @@ read_symbol(const char *filename, struct map *map, struct symbol *sym,
  * should add some textual annotation for after the instruction. The caller
  * will use this information to add the actual annotation.
  */
-#ifdef HAVE_LIBLLVM_SUPPORT
 struct symbol_lookup_storage {
 	u64 branch_addr;
 	u64 pcrel_load_addr;
@@ -170,12 +389,10 @@ symbol_lookup_callback(void *disinfo, uint64_t value,
 	*ref_type = LLVMDisassembler_ReferenceType_InOut_None;
 	return NULL;
 }
-#endif
 
 int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
 			     struct annotate_args *args __maybe_unused)
 {
-#ifdef HAVE_LIBLLVM_SUPPORT
 	struct annotation *notes = symbol__annotation(sym);
 	struct map *map = args->ms.map;
 	struct dso *dso = map__dso(map);
@@ -197,9 +414,9 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
 	if (args->options->objdump_path)
 		return -1;
 
-	LLVMInitializeAllTargetInfos();
-	LLVMInitializeAllTargetMCs();
-	LLVMInitializeAllDisassemblers();
+	perf_LLVMInitializeAllTargetInfos();
+	perf_LLVMInitializeAllTargetMCs();
+	perf_LLVMInitializeAllDisassemblers();
 
 	buf = read_symbol(filename, map, sym, &len, &is_64bit);
 	if (buf == NULL)
@@ -215,15 +432,14 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
 			  args->arch->name);
 	}
 
-	disasm = LLVMCreateDisasm(triplet, &storage, 0, NULL,
-				  symbol_lookup_callback);
+	disasm = perf_LLVMCreateDisasm(triplet, &storage, 0, NULL,
+				       symbol_lookup_callback);
 	if (disasm == NULL)
 		goto err;
 
 	if (args->options->disassembler_style &&
 	    !strcmp(args->options->disassembler_style, "intel"))
-		LLVMSetDisasmOptions(disasm,
-				     LLVMDisassembler_Option_AsmPrinterVariant);
+		perf_LLVMSetDisasmOptions(disasm, LLVMDisassembler_Option_AsmPrinterVariant);
 
 	/*
 	 * This needs to be set after AsmPrinterVariant, due to a bug in LLVM;
@@ -231,7 +447,7 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
 	 * forget about the PrintImmHex flag (which is applied before if both
 	 * are given to the same call).
 	 */
-	LLVMSetDisasmOptions(disasm, LLVMDisassembler_Option_PrintImmHex);
+	perf_LLVMSetDisasmOptions(disasm, LLVMDisassembler_Option_PrintImmHex);
 
 	/* add the function address and name */
 	scnprintf(disasm_buf, sizeof(disasm_buf), "%#"PRIx64" <%s>:",
@@ -256,9 +472,9 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
 		storage.branch_addr = 0;
 		storage.pcrel_load_addr = 0;
 
-		ins_len = LLVMDisasmInstruction(disasm, buf + offset,
-						len - offset, pc,
-						disasm_buf, sizeof(disasm_buf));
+		ins_len = perf_LLVMDisasmInstruction(disasm, buf + offset,
+						     len - offset, pc,
+						     disasm_buf, sizeof(disasm_buf));
 		if (ins_len == 0)
 			goto err;
 		disasm_len = strlen(disasm_buf);
@@ -314,13 +530,8 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
 	ret = 0;
 
 err:
-	LLVMDisasmDispose(disasm);
+	perf_LLVMDisasmDispose(disasm);
 	free(buf);
 	free(line_storage);
 	return ret;
-#else // HAVE_LIBLLVM_SUPPORT
-	pr_debug("The LLVM disassembler isn't linked in for %s in %s\n",
-		 sym->name, filename);
-	return -1;
-#endif
 }
-- 
2.48.1.262.g85cc9f2d1e-goog
Re: [PATCH v3 07/18] perf llvm: Support for dlopen-ing libLLVM.so
Posted by Namhyung Kim 10 months, 4 weeks ago
On Wed, Jan 22, 2025 at 09:42:57AM -0800, Ian Rogers wrote:
> If perf wasn't built against libLLVM, no HAVE_LIBLLVM_SUPPORT, support
> dlopen-ing libLLVM.so and then calling the necessary functions by
> looking them up using dlsym. As the C++ code in llvm-c-helpers used
> for addr2line is problematic to call using dlsym, build that C++ code
> against libLLVM.so as a separate shared object, and support dynamic
> loading of it. This build option is enabled with LIBLLVM_DYNAMIC=1

You mean dlopen libllvm is supported only if this build option is used,
right?  I'm afraid that would also make others hard to use this feature.
Anyway I think _DYNAMIC is more about static link vs. dynamic link.
Maybe is LIBLLVM_DLOPEN=1 a little better?  Also please add a
description to Makefile.perf when you add a build option.

> 
> Signed-off-by: Ian Rogers <irogers@google.com>
> ---
>  tools/perf/Makefile.config         |  13 ++
>  tools/perf/Makefile.perf           |  23 ++-
>  tools/perf/tests/make              |   2 +
>  tools/perf/util/Build              |   2 +-
>  tools/perf/util/llvm-c-helpers.cpp | 113 +++++++++++-
>  tools/perf/util/llvm.c             | 271 +++++++++++++++++++++++++----
>  6 files changed, 386 insertions(+), 38 deletions(-)
> 
> diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config
> index cd773fbbc176..5c2814acc5d5 100644
> --- a/tools/perf/Makefile.config
> +++ b/tools/perf/Makefile.config
> @@ -963,6 +963,19 @@ ifndef NO_LIBLLVM
>      NO_LIBLLVM := 1
>    endif
>  endif
> +ifdef LIBLLVM_DYNAMIC
> +  ifndef NO_LIBLLVM
> +    $(error LIBLLVM_DYNAMIC should be used with NO_LIBLLVM)

Hmm.. it doesn't seem reasonable to use these two options together.
Maybe you could force NO_LIBLLVM=1 when LIBLLVM_DYNAMIC is used.


> +  endif
> +  $(call feature_check,llvm-perf)
> +  ifneq ($(feature-llvm-perf), 1)
> +    $(warning LIBLLVM_DYNAMIC requires libLLVM.so which wasn't feature detected)

Huh?  It's not clear whether you need libLLVM.so or not here.  Can you
proceed without it?  Why isn't it an error?

Also it looks like it's against the original purpose.  I think you
wanted dlopen because the library is not available at build time.  But
now you need it in the build script?


> +  endif
> +  CFLAGS += -DHAVE_LIBLLVM_DYNAMIC
> +  CFLAGS += $(shell $(LLVM_CONFIG) --cflags)
> +  CXXFLAGS += -DHAVE_LIBLLVM_DYNAMIC
> +  CXXFLAGS += $(shell $(LLVM_CONFIG) --cxxflags)
> +endif
>  
>  ifndef NO_DEMANGLE
>    $(call feature_check,cxa-demangle)
> diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf
> index 55d6ce9ea52f..eae77f6af59d 100644
> --- a/tools/perf/Makefile.perf
> +++ b/tools/perf/Makefile.perf
> @@ -456,6 +456,12 @@ ifndef NO_JVMTI
>  PROGRAMS += $(OUTPUT)$(LIBJVMTI)
>  endif
>  
> +LIBPERF_LLVM = libperf-llvm.so
> +
> +ifdef LIBLLVM_DYNAMIC
> +PROGRAMS += $(OUTPUT)$(LIBPERF_LLVM)
> +endif
> +
>  DLFILTERS := dlfilter-test-api-v0.so dlfilter-test-api-v2.so dlfilter-show-cycles.so
>  DLFILTERS := $(patsubst %,$(OUTPUT)dlfilters/%,$(DLFILTERS))
>  
> @@ -1019,6 +1025,16 @@ $(LIBSYMBOL)-clean:
>  	$(call QUIET_CLEAN, libsymbol)
>  	$(Q)$(RM) -r -- $(LIBSYMBOL_OUTPUT)
>  
> +ifdef LIBLLVM_DYNAMIC
> +LIBPERF_LLVM_CXXFLAGS := $(call filter-out,-DHAVE_LIBLLVM_DYNAMIC,$(CXXFLAGS)) -DHAVE_LIBLLVM_SUPPORT
> +LIBPERF_LLVM_LIBS = -L$(shell $(LLVM_CONFIG) --libdir) $(LIBLLVM) -lstdc++
> +
> +$(OUTPUT)$(LIBPERF_LLVM): util/llvm-c-helpers.cpp
> +	$(QUIET_LINK)$(CXX) $(LIBPERF_LLVM_CXXFLAGS) $(LIBPERF_LLVM_LIBS) -shared -o $@ $<
> +
> +$(OUTPUT)perf: $(OUTPUT)$(LIBPERF_LLVM)
> +endif
> +
>  help:
>  	@echo 'Perf make targets:'
>  	@echo '  doc		- make *all* documentation (see below)'
> @@ -1120,6 +1136,11 @@ ifndef NO_JVMTI
>  	$(call QUIET_INSTALL, $(LIBJVMTI)) \
>  		$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(libdir_SQ)'; \
>  		$(INSTALL) $(OUTPUT)$(LIBJVMTI) '$(DESTDIR_SQ)$(libdir_SQ)';
> +endif
> +ifdef LIBLLVM_DYNAMIC
> +	$(call QUIET_INSTALL, $(LIBPERF_LLVM)) \
> +		$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(libdir_SQ)'; \
> +		$(INSTALL) $(OUTPUT)$(LIBPERF_LLVM) '$(DESTDIR_SQ)$(libdir_SQ)';
>  endif
>  	$(call QUIET_INSTALL, libexec) \
>  		$(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)'
> @@ -1301,7 +1322,7 @@ clean:: $(LIBAPI)-clean $(LIBBPF)-clean $(LIBSUBCMD)-clean $(LIBSYMBOL)-clean $(
>  		-name '\.*.cmd' -delete -o -name '\.*.d' -delete -o -name '*.shellcheck_log' -delete
>  	$(Q)$(RM) $(OUTPUT).config-detected
>  	$(call QUIET_CLEAN, core-progs) $(RM) $(ALL_PROGRAMS) perf perf-read-vdso32 \
> -		perf-read-vdsox32 $(OUTPUT)$(LIBJVMTI).so
> +		perf-read-vdsox32 $(OUTPUT)$(LIBJVMTI) $(OUTPUT)$(LIBPERF_LLVM)
>  	$(call QUIET_CLEAN, core-gen)   $(RM)  *.spec *.pyc *.pyo */*.pyc */*.pyo \
>  		$(OUTPUT)common-cmds.h TAGS tags cscope* $(OUTPUT)PERF-VERSION-FILE \
>  		$(OUTPUT)FEATURE-DUMP $(OUTPUT)util/*-bison* $(OUTPUT)util/*-flex* \
> diff --git a/tools/perf/tests/make b/tools/perf/tests/make
> index 0ee94caf9ec1..44d76eacce49 100644
> --- a/tools/perf/tests/make
> +++ b/tools/perf/tests/make
> @@ -93,6 +93,7 @@ make_libbpf_dynamic := LIBBPF_DYNAMIC=1
>  make_no_libbpf_DEBUG := NO_LIBBPF=1 DEBUG=1
>  make_no_libcrypto   := NO_LIBCRYPTO=1
>  make_no_libllvm     := NO_LIBLLVM=1
> +make_libllvm_dynamic := NO_LIBLLVM=1 LIBLLVM_DYNAMIC=1
>  make_with_babeltrace:= LIBBABELTRACE=1
>  make_with_coresight := CORESIGHT=1
>  make_no_sdt	    := NO_SDT=1
> @@ -162,6 +163,7 @@ run += make_no_libbpf
>  run += make_no_libbpf_DEBUG
>  run += make_no_libcrypto
>  run += make_no_libllvm
> +run += make_libllvm_dynamic
>  run += make_no_sdt
>  run += make_no_syscall_tbl
>  run += make_with_babeltrace
> diff --git a/tools/perf/util/Build b/tools/perf/util/Build
> index 6fe0b5882c97..eb00c599e179 100644
> --- a/tools/perf/util/Build
> +++ b/tools/perf/util/Build
> @@ -27,6 +27,7 @@ perf-util-y += find_bit.o
>  perf-util-y += get_current_dir_name.o
>  perf-util-y += levenshtein.o
>  perf-util-y += llvm.o
> +perf-util-y += llvm-c-helpers.o
>  perf-util-y += mmap.o
>  perf-util-y += memswap.o
>  perf-util-y += parse-events.o
> @@ -239,7 +240,6 @@ perf-util-$(CONFIG_CXX_DEMANGLE) += demangle-cxx.o
>  perf-util-y += demangle-ocaml.o
>  perf-util-y += demangle-java.o
>  perf-util-y += demangle-rust.o
> -perf-util-$(CONFIG_LIBLLVM) += llvm-c-helpers.o
>  
>  ifdef CONFIG_JITDUMP
>  perf-util-$(CONFIG_LIBELF) += jitdump.o
> diff --git a/tools/perf/util/llvm-c-helpers.cpp b/tools/perf/util/llvm-c-helpers.cpp
> index 004081bd12c9..5a6f76e6b705 100644
> --- a/tools/perf/util/llvm-c-helpers.cpp
> +++ b/tools/perf/util/llvm-c-helpers.cpp
> @@ -5,17 +5,23 @@
>   * macros (e.g. noinline) that conflict with compiler builtins used
>   * by LLVM.
>   */
> +#ifdef HAVE_LIBLLVM_SUPPORT
>  #pragma GCC diagnostic push
>  #pragma GCC diagnostic ignored "-Wunused-parameter"  /* Needed for LLVM <= 15 */
>  #include <llvm/DebugInfo/Symbolize/Symbolize.h>
>  #include <llvm/Support/TargetSelect.h>
>  #pragma GCC diagnostic pop
> +#endif
>  
> +#if !defined(HAVE_LIBLLVM_SUPPORT) || defined(HAVE_LIBLLVM_DYNAMIC)
> +#include <dlfcn.h>
> +#endif
>  #include <inttypes.h>
>  #include <stdio.h>
>  #include <sys/types.h>
>  #include <linux/compiler.h>
>  extern "C" {
> +#include "debug.h"
>  #include <linux/zalloc.h>
>  }
>  #include "llvm-c-helpers.h"
> @@ -23,14 +29,33 @@ extern "C" {
>  extern "C"
>  char *dso__demangle_sym(struct dso *dso, int kmodule, const char *elf_name);
>  
> +#ifdef HAVE_LIBLLVM_SUPPORT
>  using namespace llvm;
>  using llvm::symbolize::LLVMSymbolizer;
> +#endif
> +
> +#if !defined(HAVE_LIBLLVM_SUPPORT) && defined(HAVE_LIBLLVM_DYNAMIC)

Like I said, it'd be simpler if you could make HAVE_LIBLLVM_SUPPORT and
HAVE_LIBLLVM_DYNAMIC (or _DLOPEN) mutually exclusive.

And the same argument for the code organization.

Thanks,
Namhyung


> +static void *perf_llvm_c_helpers_dll_handle(void)
> +{
> +	static bool dll_handle_init;
> +	static void *dll_handle;
> +
> +	if (!dll_handle_init) {
> +		dll_handle_init = true;
> +		dll_handle = dlopen("libperf-llvm.so", RTLD_LAZY);
> +		if (!dll_handle)
> +			pr_debug("dlopen failed for libperf-llvm.so\n");
> +	}
> +	return dll_handle;
> +}
> +#endif
>  
>  /*
>   * Allocate a static LLVMSymbolizer, which will live to the end of the program.
>   * Unlike the bfd paths, LLVMSymbolizer has its own cache, so we do not need
>   * to store anything in the dso struct.
>   */
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
>  static LLVMSymbolizer *get_symbolizer()
>  {
>  	static LLVMSymbolizer *instance = nullptr;
> @@ -49,8 +74,10 @@ static LLVMSymbolizer *get_symbolizer()
>  	}
>  	return instance;
>  }
> +#endif
>  
>  /* Returns 0 on error, 1 on success. */
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
>  static int extract_file_and_line(const DILineInfo &line_info, char **file,
>  				 unsigned int *line)
>  {
> @@ -69,13 +96,15 @@ static int extract_file_and_line(const DILineInfo &line_info, char **file,
>  		*line = line_info.Line;
>  	return 1;
>  }
> +#endif
>  
>  extern "C"
> -int llvm_addr2line(const char *dso_name, u64 addr,
> -		   char **file, unsigned int *line,
> -		   bool unwind_inlines,
> -		   llvm_a2l_frame **inline_frames)
> +int llvm_addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused,
> +		   char **file __maybe_unused, unsigned int *line __maybe_unused,
> +		   bool unwind_inlines __maybe_unused,
> +		   llvm_a2l_frame **inline_frames __maybe_unused)
>  {
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
>  	LLVMSymbolizer *symbolizer = get_symbolizer();
>  	object::SectionedAddress sectioned_addr = {
>  		addr,
> @@ -135,8 +164,33 @@ int llvm_addr2line(const char *dso_name, u64 addr,
>  			return 0;
>  		return extract_file_and_line(*res_or_err, file, line);
>  	}
> +#elif defined(HAVE_LIBLLVM_DYNAMIC)
> +	static bool fn_init;
> +	static int (*fn)(const char *dso_name, u64 addr,
> +			 char **file, unsigned int *line,
> +			 bool unwind_inlines,
> +			 llvm_a2l_frame **inline_frames);
> +
> +	if (!fn_init) {
> +		void * handle = perf_llvm_c_helpers_dll_handle();
> +
> +		if (!handle)
> +			return 0;
> +
> +		fn = reinterpret_cast<decltype(fn)>(dlsym(handle, "llvm_addr2line"));
> +		if (!fn)
> +			pr_debug("dlsym failed for llvm_addr2line\n");
> +		fn_init = true;
> +	}
> +	if (!fn)
> +		return 0;
> +	return fn(dso_name, addr, file, line, unwind_inlines, inline_frames);
> +#else
> +	return 0;
> +#endif
>  }
>  
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
>  static char *
>  make_symbol_relative_string(struct dso *dso, const char *sym_name,
>  			    u64 addr, u64 base_addr)
> @@ -158,10 +212,13 @@ make_symbol_relative_string(struct dso *dso, const char *sym_name,
>  			return strdup(sym_name);
>  	}
>  }
> +#endif
>  
>  extern "C"
> -char *llvm_name_for_code(struct dso *dso, const char *dso_name, u64 addr)
> +char *llvm_name_for_code(struct dso *dso __maybe_unused, const char *dso_name __maybe_unused,
> +			 u64 addr __maybe_unused)
>  {
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
>  	LLVMSymbolizer *symbolizer = get_symbolizer();
>  	object::SectionedAddress sectioned_addr = {
>  		addr,
> @@ -175,11 +232,34 @@ char *llvm_name_for_code(struct dso *dso, const char *dso_name, u64 addr)
>  	return make_symbol_relative_string(
>  		dso, res_or_err->FunctionName.c_str(),
>  		addr, res_or_err->StartAddress ? *res_or_err->StartAddress : 0);
> +#elif defined(HAVE_LIBLLVM_DYNAMIC)
> +	static bool fn_init;
> +	static char *(*fn)(struct dso *dso, const char *dso_name, u64 addr);
> +
> +	if (!fn_init) {
> +		void * handle = perf_llvm_c_helpers_dll_handle();
> +
> +		if (!handle)
> +			return NULL;
> +
> +		fn = reinterpret_cast<decltype(fn)>(dlsym(handle, "llvm_name_for_code"));
> +		if (!fn)
> +			pr_debug("dlsym failed for llvm_name_for_code\n");
> +		fn_init = true;
> +	}
> +	if (!fn)
> +		return NULL;
> +	return fn(dso, dso_name, addr);
> +#else
> +	return 0;
> +#endif
>  }
>  
>  extern "C"
> -char *llvm_name_for_data(struct dso *dso, const char *dso_name, u64 addr)
> +char *llvm_name_for_data(struct dso *dso __maybe_unused, const char *dso_name __maybe_unused,
> +			 u64 addr __maybe_unused)
>  {
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
>  	LLVMSymbolizer *symbolizer = get_symbolizer();
>  	object::SectionedAddress sectioned_addr = {
>  		addr,
> @@ -193,4 +273,25 @@ char *llvm_name_for_data(struct dso *dso, const char *dso_name, u64 addr)
>  	return make_symbol_relative_string(
>  		dso, res_or_err->Name.c_str(),
>  		addr, res_or_err->Start);
> +#elif defined(HAVE_LIBLLVM_DYNAMIC)
> +	static bool fn_init;
> +	static char *(*fn)(struct dso *dso, const char *dso_name, u64 addr);
> +
> +	if (!fn_init) {
> +		void * handle = perf_llvm_c_helpers_dll_handle();
> +
> +		if (!handle)
> +			return NULL;
> +
> +		fn = reinterpret_cast<decltype(fn)>(dlsym(handle, "llvm_name_for_data"));
> +		if (!fn)
> +			pr_debug("dlsym failed for llvm_name_for_data\n");
> +		fn_init = true;
> +	}
> +	if (!fn)
> +		return NULL;
> +	return fn(dso, dso_name, addr);
> +#else
> +	return 0;
> +#endif
>  }
> diff --git a/tools/perf/util/llvm.c b/tools/perf/util/llvm.c
> index ddc737194692..f6a8943b7c9d 100644
> --- a/tools/perf/util/llvm.c
> +++ b/tools/perf/util/llvm.c
> @@ -1,5 +1,6 @@
>  // SPDX-License-Identifier: GPL-2.0
>  #include "llvm.h"
> +#include "llvm-c-helpers.h"
>  #include "annotate.h"
>  #include "debug.h"
>  #include "dso.h"
> @@ -7,17 +8,243 @@
>  #include "namespaces.h"
>  #include "srcline.h"
>  #include "symbol.h"
> +#include <dlfcn.h>
>  #include <fcntl.h>
> +#include <inttypes.h>
>  #include <unistd.h>
>  #include <linux/zalloc.h>
>  
> -#ifdef HAVE_LIBLLVM_SUPPORT
> -#include "llvm-c-helpers.h"
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
>  #include <llvm-c/Disassembler.h>
>  #include <llvm-c/Target.h>
> +#else
> +typedef void *LLVMDisasmContextRef;
> +typedef int (*LLVMOpInfoCallback)(void *dis_info, uint64_t pc, uint64_t offset,
> +				  uint64_t op_size, uint64_t inst_size,
> +				  int tag_type, void *tag_buf);
> +typedef const char *(*LLVMSymbolLookupCallback)(void *dis_info,
> +						uint64_t reference_value,
> +						uint64_t *reference_type,
> +						uint64_t reference_pc,
> +						const char **reference_name);
> +#define LLVMDisassembler_ReferenceType_InOut_None 0
> +#define LLVMDisassembler_ReferenceType_In_Branch 1
> +#define LLVMDisassembler_ReferenceType_In_PCrel_Load 2
> +#define LLVMDisassembler_Option_PrintImmHex 2
> +#define LLVMDisassembler_Option_AsmPrinterVariant 4
> +const char *llvm_targets[] = {
> +	"AMDGPU",
> +	"ARM",
> +	"AVR",
> +	"BPF",
> +	"Hexagon",
> +	"Lanai",
> +	"LoongArch",
> +	"Mips",
> +	"MSP430",
> +	"NVPTX",
> +	"PowerPC",
> +	"RISCV",
> +	"Sparc",
> +	"SystemZ",
> +	"VE",
> +	"WebAssembly",
> +	"X86",
> +	"XCore",
> +	"M68k",
> +	"Xtensa",
> +};
> +#endif
> +
> +#if !defined(HAVE_LIBLLVM_SUPPORT) || defined(HAVE_LIBLLVM_DYNAMIC)
> +static void *perf_llvm_dll_handle(void)
> +{
> +	static bool dll_handle_init;
> +	static void *dll_handle;
> +
> +	if (!dll_handle_init) {
> +		dll_handle_init = true;
> +		dll_handle = dlopen("libLLVM.so", RTLD_LAZY);
> +		if (!dll_handle)
> +			pr_debug("dlopen failed for libLLVM.so\n");
> +	}
> +	return dll_handle;
> +}
> +#endif
> +
> +#if !defined(HAVE_LIBLLVM_SUPPORT) || defined(HAVE_LIBLLVM_DYNAMIC)
> +static void *perf_llvm_dll_fun(const char *fmt, const char *target)
> +{
> +	char buf[128];
> +	void *fn;
> +
> +	snprintf(buf, sizeof(buf), fmt, target);
> +	fn = dlsym(perf_llvm_dll_handle(), buf);
> +	if (!fn)
> +		pr_debug("dlsym failed for %s\n", buf);
> +
> +	return fn;
> +}
> +#endif
> +
> +static void perf_LLVMInitializeAllTargetInfos(void)
> +{
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> +	LLVMInitializeAllTargetInfos();
> +#else
> +	/* LLVMInitializeAllTargetInfos is a header file function not available as a symbol. */
> +	static bool done_init;
> +
> +	if (done_init)
> +		return;
> +
> +	for (size_t i = 0; i < ARRAY_SIZE(llvm_targets); i++) {
> +		void (*fn)(void) = perf_llvm_dll_fun("LLVMInitialize%sTargetInfo",
> +						     llvm_targets[i]);
> +
> +		if (!fn)
> +			continue;
> +		fn();
> +	}
> +	done_init = true;
> +#endif
> +}
> +
> +static void perf_LLVMInitializeAllTargetMCs(void)
> +{
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> +	LLVMInitializeAllTargetMCs();
> +#else
> +	/* LLVMInitializeAllTargetMCs is a header file function not available as a symbol. */
> +	static bool done_init;
> +
> +	if (done_init)
> +		return;
> +
> +	for (size_t i = 0; i < ARRAY_SIZE(llvm_targets); i++) {
> +		void (*fn)(void) = perf_llvm_dll_fun("LLVMInitialize%sTargetMC",
> +						     llvm_targets[i]);
> +
> +		if (!fn)
> +			continue;
> +		fn();
> +	}
> +	done_init = true;
> +#endif
> +}
> +
> +static void perf_LLVMInitializeAllDisassemblers(void)
> +{
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> +	LLVMInitializeAllDisassemblers();
> +#else
> +	/* LLVMInitializeAllDisassemblers is a header file function not available as a symbol. */
> +	static bool done_init;
> +
> +	if (done_init)
> +		return;
> +
> +	for (size_t i = 0; i < ARRAY_SIZE(llvm_targets); i++) {
> +		void (*fn)(void) = perf_llvm_dll_fun("LLVMInitialize%sDisassembler",
> +						     llvm_targets[i]);
> +
> +		if (!fn)
> +			continue;
> +		fn();
> +	}
> +	done_init = true;
> +#endif
> +}
> +
> +static LLVMDisasmContextRef perf_LLVMCreateDisasm(const char *triple_name, void *dis_info,
> +						int tag_type, LLVMOpInfoCallback get_op_info,
> +						LLVMSymbolLookupCallback symbol_lookup)
> +{
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> +	return LLVMCreateDisasm(triple_name, dis_info, tag_type, get_op_info, symbol_lookup);
> +#else
> +	static bool fn_init;
> +	static LLVMDisasmContextRef (*fn)(const char *triple_name, void *dis_info,
> +					int tag_type, LLVMOpInfoCallback get_op_info,
> +					LLVMSymbolLookupCallback symbol_lookup);
> +
> +	if (!fn_init) {
> +		fn = dlsym(perf_llvm_dll_handle(), "LLVMCreateDisasm");
> +		if (!fn)
> +			pr_debug("dlsym failed for LLVMCreateDisasm\n");
> +		fn_init = true;
> +	}
> +	if (!fn)
> +		return NULL;
> +	return fn(triple_name, dis_info, tag_type, get_op_info, symbol_lookup);
> +#endif
> +}
> +
> +static int perf_LLVMSetDisasmOptions(LLVMDisasmContextRef context, uint64_t options)
> +{
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> +	return LLVMSetDisasmOptions(context, options);
> +#else
> +	static bool fn_init;
> +	static int (*fn)(LLVMDisasmContextRef context, uint64_t options);
> +
> +	if (!fn_init) {
> +		fn = dlsym(perf_llvm_dll_handle(), "LLVMSetDisasmOptions");
> +		if (!fn)
> +			pr_debug("dlsym failed for LLVMSetDisasmOptions\n");
> +		fn_init = true;
> +	}
> +	if (!fn)
> +		return 0;
> +	return fn(context, options);
> +#endif
> +}
> +
> +static size_t perf_LLVMDisasmInstruction(LLVMDisasmContextRef context, uint8_t *bytes,
> +					uint64_t bytes_size, uint64_t pc,
> +					char *out_string, size_t out_string_size)
> +{
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> +	return LLVMDisasmInstruction(context, bytes, bytes_size, pc, out_string, out_string_size);
> +#else
> +	static bool fn_init;
> +	static int (*fn)(LLVMDisasmContextRef context, uint8_t *bytes,
> +			uint64_t bytes_size, uint64_t pc,
> +			char *out_string, size_t out_string_size);
> +
> +	if (!fn_init) {
> +		fn = dlsym(perf_llvm_dll_handle(), "LLVMDisasmInstruction");
> +		if (!fn)
> +			pr_debug("dlsym failed for LLVMDisasmInstruction\n");
> +		fn_init = true;
> +	}
> +	if (!fn)
> +		return 0;
> +	return fn(context, bytes, bytes_size, pc, out_string, out_string_size);
> +#endif
> +}
> +
> +static void perf_LLVMDisasmDispose(LLVMDisasmContextRef context)
> +{
> +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> +	LLVMDisasmDispose(context);
> +#else
> +	static bool fn_init;
> +	static int (*fn)(LLVMDisasmContextRef context);
> +
> +	if (!fn_init) {
> +		fn = dlsym(perf_llvm_dll_handle(), "LLVMDisasmDispose");
> +		if (!fn)
> +			pr_debug("dlsym failed for LLVMDisasmDispose\n");
> +		fn_init = true;
> +	}
> +	if (!fn)
> +		return;
> +	fn(context);
>  #endif
> +}
> +
>  
> -#ifdef HAVE_LIBLLVM_SUPPORT
>  static void free_llvm_inline_frames(struct llvm_a2l_frame *inline_frames,
>  				    int num_frames)
>  {
> @@ -29,14 +256,12 @@ static void free_llvm_inline_frames(struct llvm_a2l_frame *inline_frames,
>  		zfree(&inline_frames);
>  	}
>  }
> -#endif
>  
>  int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused,
>  		     char **file __maybe_unused, unsigned int *line __maybe_unused,
>  		     struct dso *dso __maybe_unused, bool unwind_inlines __maybe_unused,
>  		     struct inline_node *node __maybe_unused, struct symbol *sym __maybe_unused)
>  {
> -#ifdef HAVE_LIBLLVM_SUPPORT
>  	struct llvm_a2l_frame *inline_frames = NULL;
>  	int num_frames = llvm_addr2line(dso_name, addr, file, line,
>  					node && unwind_inlines, &inline_frames);
> @@ -64,9 +289,6 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused
>  	free_llvm_inline_frames(inline_frames, num_frames);
>  
>  	return num_frames;
> -#else
> -	return -1;
> -#endif
>  }
>  
>  void dso__free_a2l_llvm(struct dso *dso __maybe_unused)
> @@ -75,7 +297,6 @@ void dso__free_a2l_llvm(struct dso *dso __maybe_unused)
>  }
>  
>  
> -#if defined(HAVE_LIBLLVM_SUPPORT)
>  struct find_file_offset_data {
>  	u64 ip;
>  	u64 offset;
> @@ -139,7 +360,6 @@ read_symbol(const char *filename, struct map *map, struct symbol *sym,
>  	free(buf);
>  	return NULL;
>  }
> -#endif
>  
>  /*
>   * Whenever LLVM wants to resolve an address into a symbol, it calls this
> @@ -149,7 +369,6 @@ read_symbol(const char *filename, struct map *map, struct symbol *sym,
>   * should add some textual annotation for after the instruction. The caller
>   * will use this information to add the actual annotation.
>   */
> -#ifdef HAVE_LIBLLVM_SUPPORT
>  struct symbol_lookup_storage {
>  	u64 branch_addr;
>  	u64 pcrel_load_addr;
> @@ -170,12 +389,10 @@ symbol_lookup_callback(void *disinfo, uint64_t value,
>  	*ref_type = LLVMDisassembler_ReferenceType_InOut_None;
>  	return NULL;
>  }
> -#endif
>  
>  int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
>  			     struct annotate_args *args __maybe_unused)
>  {
> -#ifdef HAVE_LIBLLVM_SUPPORT
>  	struct annotation *notes = symbol__annotation(sym);
>  	struct map *map = args->ms.map;
>  	struct dso *dso = map__dso(map);
> @@ -197,9 +414,9 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
>  	if (args->options->objdump_path)
>  		return -1;
>  
> -	LLVMInitializeAllTargetInfos();
> -	LLVMInitializeAllTargetMCs();
> -	LLVMInitializeAllDisassemblers();
> +	perf_LLVMInitializeAllTargetInfos();
> +	perf_LLVMInitializeAllTargetMCs();
> +	perf_LLVMInitializeAllDisassemblers();
>  
>  	buf = read_symbol(filename, map, sym, &len, &is_64bit);
>  	if (buf == NULL)
> @@ -215,15 +432,14 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
>  			  args->arch->name);
>  	}
>  
> -	disasm = LLVMCreateDisasm(triplet, &storage, 0, NULL,
> -				  symbol_lookup_callback);
> +	disasm = perf_LLVMCreateDisasm(triplet, &storage, 0, NULL,
> +				       symbol_lookup_callback);
>  	if (disasm == NULL)
>  		goto err;
>  
>  	if (args->options->disassembler_style &&
>  	    !strcmp(args->options->disassembler_style, "intel"))
> -		LLVMSetDisasmOptions(disasm,
> -				     LLVMDisassembler_Option_AsmPrinterVariant);
> +		perf_LLVMSetDisasmOptions(disasm, LLVMDisassembler_Option_AsmPrinterVariant);
>  
>  	/*
>  	 * This needs to be set after AsmPrinterVariant, due to a bug in LLVM;
> @@ -231,7 +447,7 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
>  	 * forget about the PrintImmHex flag (which is applied before if both
>  	 * are given to the same call).
>  	 */
> -	LLVMSetDisasmOptions(disasm, LLVMDisassembler_Option_PrintImmHex);
> +	perf_LLVMSetDisasmOptions(disasm, LLVMDisassembler_Option_PrintImmHex);
>  
>  	/* add the function address and name */
>  	scnprintf(disasm_buf, sizeof(disasm_buf), "%#"PRIx64" <%s>:",
> @@ -256,9 +472,9 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
>  		storage.branch_addr = 0;
>  		storage.pcrel_load_addr = 0;
>  
> -		ins_len = LLVMDisasmInstruction(disasm, buf + offset,
> -						len - offset, pc,
> -						disasm_buf, sizeof(disasm_buf));
> +		ins_len = perf_LLVMDisasmInstruction(disasm, buf + offset,
> +						     len - offset, pc,
> +						     disasm_buf, sizeof(disasm_buf));
>  		if (ins_len == 0)
>  			goto err;
>  		disasm_len = strlen(disasm_buf);
> @@ -314,13 +530,8 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
>  	ret = 0;
>  
>  err:
> -	LLVMDisasmDispose(disasm);
> +	perf_LLVMDisasmDispose(disasm);
>  	free(buf);
>  	free(line_storage);
>  	return ret;
> -#else // HAVE_LIBLLVM_SUPPORT
> -	pr_debug("The LLVM disassembler isn't linked in for %s in %s\n",
> -		 sym->name, filename);
> -	return -1;
> -#endif
>  }
> -- 
> 2.48.1.262.g85cc9f2d1e-goog
>
Re: [PATCH v3 07/18] perf llvm: Support for dlopen-ing libLLVM.so
Posted by Ian Rogers 10 months, 4 weeks ago
On Fri, Jan 24, 2025 at 2:54 PM Namhyung Kim <namhyung@kernel.org> wrote:
>
> On Wed, Jan 22, 2025 at 09:42:57AM -0800, Ian Rogers wrote:
> > If perf wasn't built against libLLVM, no HAVE_LIBLLVM_SUPPORT, support
> > dlopen-ing libLLVM.so and then calling the necessary functions by
> > looking them up using dlsym. As the C++ code in llvm-c-helpers used
> > for addr2line is problematic to call using dlsym, build that C++ code
> > against libLLVM.so as a separate shared object, and support dynamic
> > loading of it. This build option is enabled with LIBLLVM_DYNAMIC=1
>
> You mean dlopen libllvm is supported only if this build option is used,
> right?  I'm afraid that would also make others hard to use this feature.
> Anyway I think _DYNAMIC is more about static link vs. dynamic link.
> Maybe is LIBLLVM_DLOPEN=1 a little better?  Also please add a
> description to Makefile.perf when you add a build option.

Agreed on adding a comment and that LIBLLVM_DYNAMIC name perhaps isn't the best.

The situation with the series is that:
1) if libllvm is detected then HAVE_LIBLLVM_SUPPORT is enabled and we
link with LLVM - this matches behavior in the tree currently;
2) if libllvm isn't detected (or NO_LIBLLVM=1 is passed to the build)
then we use dlopen for the disassembler in libLLVM.so and for
addr2line in libperf-llvm.so.

We need an option to build libperf-llvm.so and that is what setting
LIBLLVM_DYNAMIC does. We can't determine it by HAVE_LIBLLVM_SUPPORT as
that will link against libllvm and not use dlopen. We can't determine
it by not HAVE_LIBLLVM_SUPPORT, as that indicates dlopen is used but
also that libllvm wasn't feature detected.

So NO_LIBLLVM=1 is saying don't link with libllvm, LIBLLVM_DYNAMIC=1
is saying, build libperf-llvm.so. Things were done this way to be
compatible with existing build flag behavior.

> >
> > Signed-off-by: Ian Rogers <irogers@google.com>
> > ---
> >  tools/perf/Makefile.config         |  13 ++
> >  tools/perf/Makefile.perf           |  23 ++-
> >  tools/perf/tests/make              |   2 +
> >  tools/perf/util/Build              |   2 +-
> >  tools/perf/util/llvm-c-helpers.cpp | 113 +++++++++++-
> >  tools/perf/util/llvm.c             | 271 +++++++++++++++++++++++++----
> >  6 files changed, 386 insertions(+), 38 deletions(-)
> >
> > diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config
> > index cd773fbbc176..5c2814acc5d5 100644
> > --- a/tools/perf/Makefile.config
> > +++ b/tools/perf/Makefile.config
> > @@ -963,6 +963,19 @@ ifndef NO_LIBLLVM
> >      NO_LIBLLVM := 1
> >    endif
> >  endif
> > +ifdef LIBLLVM_DYNAMIC
> > +  ifndef NO_LIBLLVM
> > +    $(error LIBLLVM_DYNAMIC should be used with NO_LIBLLVM)
>
> Hmm.. it doesn't seem reasonable to use these two options together.
> Maybe you could force NO_LIBLLVM=1 when LIBLLVM_DYNAMIC is used.
>
>
> > +  endif
> > +  $(call feature_check,llvm-perf)
> > +  ifneq ($(feature-llvm-perf), 1)
> > +    $(warning LIBLLVM_DYNAMIC requires libLLVM.so which wasn't feature detected)
>
> Huh?  It's not clear whether you need libLLVM.so or not here.  Can you
> proceed without it?  Why isn't it an error?

It should be an error but I lowered it to a warning to work around
issues with build-test in common with most user errors in
Makefile.config just being warnings.

> Also it looks like it's against the original purpose.  I think you
> wanted dlopen because the library is not available at build time.  But
> now you need it in the build script?

No, the dynamic is saying we want to build libperf-llvm.so for the
sake of addr2line. We want libllvm for that end, we expect regularly
linked llvm support to be disabled.

> > +  endif
> > +  CFLAGS += -DHAVE_LIBLLVM_DYNAMIC
> > +  CFLAGS += $(shell $(LLVM_CONFIG) --cflags)
> > +  CXXFLAGS += -DHAVE_LIBLLVM_DYNAMIC
> > +  CXXFLAGS += $(shell $(LLVM_CONFIG) --cxxflags)
> > +endif
> >
> >  ifndef NO_DEMANGLE
> >    $(call feature_check,cxa-demangle)
> > diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf
> > index 55d6ce9ea52f..eae77f6af59d 100644
> > --- a/tools/perf/Makefile.perf
> > +++ b/tools/perf/Makefile.perf
> > @@ -456,6 +456,12 @@ ifndef NO_JVMTI
> >  PROGRAMS += $(OUTPUT)$(LIBJVMTI)
> >  endif
> >
> > +LIBPERF_LLVM = libperf-llvm.so
> > +
> > +ifdef LIBLLVM_DYNAMIC
> > +PROGRAMS += $(OUTPUT)$(LIBPERF_LLVM)
> > +endif
> > +
> >  DLFILTERS := dlfilter-test-api-v0.so dlfilter-test-api-v2.so dlfilter-show-cycles.so
> >  DLFILTERS := $(patsubst %,$(OUTPUT)dlfilters/%,$(DLFILTERS))
> >
> > @@ -1019,6 +1025,16 @@ $(LIBSYMBOL)-clean:
> >       $(call QUIET_CLEAN, libsymbol)
> >       $(Q)$(RM) -r -- $(LIBSYMBOL_OUTPUT)
> >
> > +ifdef LIBLLVM_DYNAMIC
> > +LIBPERF_LLVM_CXXFLAGS := $(call filter-out,-DHAVE_LIBLLVM_DYNAMIC,$(CXXFLAGS)) -DHAVE_LIBLLVM_SUPPORT
> > +LIBPERF_LLVM_LIBS = -L$(shell $(LLVM_CONFIG) --libdir) $(LIBLLVM) -lstdc++
> > +
> > +$(OUTPUT)$(LIBPERF_LLVM): util/llvm-c-helpers.cpp
> > +     $(QUIET_LINK)$(CXX) $(LIBPERF_LLVM_CXXFLAGS) $(LIBPERF_LLVM_LIBS) -shared -o $@ $<
> > +
> > +$(OUTPUT)perf: $(OUTPUT)$(LIBPERF_LLVM)
> > +endif
> > +
> >  help:
> >       @echo 'Perf make targets:'
> >       @echo '  doc            - make *all* documentation (see below)'
> > @@ -1120,6 +1136,11 @@ ifndef NO_JVMTI
> >       $(call QUIET_INSTALL, $(LIBJVMTI)) \
> >               $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(libdir_SQ)'; \
> >               $(INSTALL) $(OUTPUT)$(LIBJVMTI) '$(DESTDIR_SQ)$(libdir_SQ)';
> > +endif
> > +ifdef LIBLLVM_DYNAMIC
> > +     $(call QUIET_INSTALL, $(LIBPERF_LLVM)) \
> > +             $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(libdir_SQ)'; \
> > +             $(INSTALL) $(OUTPUT)$(LIBPERF_LLVM) '$(DESTDIR_SQ)$(libdir_SQ)';
> >  endif
> >       $(call QUIET_INSTALL, libexec) \
> >               $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)'
> > @@ -1301,7 +1322,7 @@ clean:: $(LIBAPI)-clean $(LIBBPF)-clean $(LIBSUBCMD)-clean $(LIBSYMBOL)-clean $(
> >               -name '\.*.cmd' -delete -o -name '\.*.d' -delete -o -name '*.shellcheck_log' -delete
> >       $(Q)$(RM) $(OUTPUT).config-detected
> >       $(call QUIET_CLEAN, core-progs) $(RM) $(ALL_PROGRAMS) perf perf-read-vdso32 \
> > -             perf-read-vdsox32 $(OUTPUT)$(LIBJVMTI).so
> > +             perf-read-vdsox32 $(OUTPUT)$(LIBJVMTI) $(OUTPUT)$(LIBPERF_LLVM)
> >       $(call QUIET_CLEAN, core-gen)   $(RM)  *.spec *.pyc *.pyo */*.pyc */*.pyo \
> >               $(OUTPUT)common-cmds.h TAGS tags cscope* $(OUTPUT)PERF-VERSION-FILE \
> >               $(OUTPUT)FEATURE-DUMP $(OUTPUT)util/*-bison* $(OUTPUT)util/*-flex* \
> > diff --git a/tools/perf/tests/make b/tools/perf/tests/make
> > index 0ee94caf9ec1..44d76eacce49 100644
> > --- a/tools/perf/tests/make
> > +++ b/tools/perf/tests/make
> > @@ -93,6 +93,7 @@ make_libbpf_dynamic := LIBBPF_DYNAMIC=1
> >  make_no_libbpf_DEBUG := NO_LIBBPF=1 DEBUG=1
> >  make_no_libcrypto   := NO_LIBCRYPTO=1
> >  make_no_libllvm     := NO_LIBLLVM=1
> > +make_libllvm_dynamic := NO_LIBLLVM=1 LIBLLVM_DYNAMIC=1
> >  make_with_babeltrace:= LIBBABELTRACE=1
> >  make_with_coresight := CORESIGHT=1
> >  make_no_sdt      := NO_SDT=1
> > @@ -162,6 +163,7 @@ run += make_no_libbpf
> >  run += make_no_libbpf_DEBUG
> >  run += make_no_libcrypto
> >  run += make_no_libllvm
> > +run += make_libllvm_dynamic
> >  run += make_no_sdt
> >  run += make_no_syscall_tbl
> >  run += make_with_babeltrace
> > diff --git a/tools/perf/util/Build b/tools/perf/util/Build
> > index 6fe0b5882c97..eb00c599e179 100644
> > --- a/tools/perf/util/Build
> > +++ b/tools/perf/util/Build
> > @@ -27,6 +27,7 @@ perf-util-y += find_bit.o
> >  perf-util-y += get_current_dir_name.o
> >  perf-util-y += levenshtein.o
> >  perf-util-y += llvm.o
> > +perf-util-y += llvm-c-helpers.o
> >  perf-util-y += mmap.o
> >  perf-util-y += memswap.o
> >  perf-util-y += parse-events.o
> > @@ -239,7 +240,6 @@ perf-util-$(CONFIG_CXX_DEMANGLE) += demangle-cxx.o
> >  perf-util-y += demangle-ocaml.o
> >  perf-util-y += demangle-java.o
> >  perf-util-y += demangle-rust.o
> > -perf-util-$(CONFIG_LIBLLVM) += llvm-c-helpers.o
> >
> >  ifdef CONFIG_JITDUMP
> >  perf-util-$(CONFIG_LIBELF) += jitdump.o
> > diff --git a/tools/perf/util/llvm-c-helpers.cpp b/tools/perf/util/llvm-c-helpers.cpp
> > index 004081bd12c9..5a6f76e6b705 100644
> > --- a/tools/perf/util/llvm-c-helpers.cpp
> > +++ b/tools/perf/util/llvm-c-helpers.cpp
> > @@ -5,17 +5,23 @@
> >   * macros (e.g. noinline) that conflict with compiler builtins used
> >   * by LLVM.
> >   */
> > +#ifdef HAVE_LIBLLVM_SUPPORT
> >  #pragma GCC diagnostic push
> >  #pragma GCC diagnostic ignored "-Wunused-parameter"  /* Needed for LLVM <= 15 */
> >  #include <llvm/DebugInfo/Symbolize/Symbolize.h>
> >  #include <llvm/Support/TargetSelect.h>
> >  #pragma GCC diagnostic pop
> > +#endif
> >
> > +#if !defined(HAVE_LIBLLVM_SUPPORT) || defined(HAVE_LIBLLVM_DYNAMIC)
> > +#include <dlfcn.h>
> > +#endif
> >  #include <inttypes.h>
> >  #include <stdio.h>
> >  #include <sys/types.h>
> >  #include <linux/compiler.h>
> >  extern "C" {
> > +#include "debug.h"
> >  #include <linux/zalloc.h>
> >  }
> >  #include "llvm-c-helpers.h"
> > @@ -23,14 +29,33 @@ extern "C" {
> >  extern "C"
> >  char *dso__demangle_sym(struct dso *dso, int kmodule, const char *elf_name);
> >
> > +#ifdef HAVE_LIBLLVM_SUPPORT
> >  using namespace llvm;
> >  using llvm::symbolize::LLVMSymbolizer;
> > +#endif
> > +
> > +#if !defined(HAVE_LIBLLVM_SUPPORT) && defined(HAVE_LIBLLVM_DYNAMIC)
>
> Like I said, it'd be simpler if you could make HAVE_LIBLLVM_SUPPORT and
> HAVE_LIBLLVM_DYNAMIC (or _DLOPEN) mutually exclusive.
>
> And the same argument for the code organization.

I think you're missing the point that we (not strictly) need
libperf-llvm.so. The reason being is that the llvm-c-helpers depend on
the C++ LLVM API. The LLVM C API does a fine job of exposing the
disassembler which is why in llvm.c in the shim code without linking
against libllvm we can just dlsym into it. llvm-c-helpers is really a
simple LLVM addr2line implementation using the LLVM C++ headers.
Perhaps a better name than llvm-c-helpers.cpp would be
llvm-addr2line.cpp, maybe libperf-llvm.so should be
libperf-llvm-addr2line.so. llvm-c-helpers.cpp/llvm-addr2line.cpp is
either going to get called by perf's C code via an extern "C" API or
in the dlopen case the extern "C" API will do the dlopen/dlsym. The
function called by dlsym isn't all the LLVM C++ API shimmed, using
dlsym with C++ code at best isn't great. Instead the same
llvm-c-helpers.cpp/llvm-addr2line.cpp code is compiled as a .so
exposing the extern "C" API function with a slightly mangled name.
This means we have perf's C code calling libperf-llvm.so extern "C"
code, libperf-llvm.so is dynamically linked against libLLVM.so (not
dlopen-ing it as its all C++ code).

Thanks,
Ian

> > +static void *perf_llvm_c_helpers_dll_handle(void)
> > +{
> > +     static bool dll_handle_init;
> > +     static void *dll_handle;
> > +
> > +     if (!dll_handle_init) {
> > +             dll_handle_init = true;
> > +             dll_handle = dlopen("libperf-llvm.so", RTLD_LAZY);
> > +             if (!dll_handle)
> > +                     pr_debug("dlopen failed for libperf-llvm.so\n");
> > +     }
> > +     return dll_handle;
> > +}
> > +#endif
> >
> >  /*
> >   * Allocate a static LLVMSymbolizer, which will live to the end of the program.
> >   * Unlike the bfd paths, LLVMSymbolizer has its own cache, so we do not need
> >   * to store anything in the dso struct.
> >   */
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> >  static LLVMSymbolizer *get_symbolizer()
> >  {
> >       static LLVMSymbolizer *instance = nullptr;
> > @@ -49,8 +74,10 @@ static LLVMSymbolizer *get_symbolizer()
> >       }
> >       return instance;
> >  }
> > +#endif
> >
> >  /* Returns 0 on error, 1 on success. */
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> >  static int extract_file_and_line(const DILineInfo &line_info, char **file,
> >                                unsigned int *line)
> >  {
> > @@ -69,13 +96,15 @@ static int extract_file_and_line(const DILineInfo &line_info, char **file,
> >               *line = line_info.Line;
> >       return 1;
> >  }
> > +#endif
> >
> >  extern "C"
> > -int llvm_addr2line(const char *dso_name, u64 addr,
> > -                char **file, unsigned int *line,
> > -                bool unwind_inlines,
> > -                llvm_a2l_frame **inline_frames)
> > +int llvm_addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused,
> > +                char **file __maybe_unused, unsigned int *line __maybe_unused,
> > +                bool unwind_inlines __maybe_unused,
> > +                llvm_a2l_frame **inline_frames __maybe_unused)
> >  {
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> >       LLVMSymbolizer *symbolizer = get_symbolizer();
> >       object::SectionedAddress sectioned_addr = {
> >               addr,
> > @@ -135,8 +164,33 @@ int llvm_addr2line(const char *dso_name, u64 addr,
> >                       return 0;
> >               return extract_file_and_line(*res_or_err, file, line);
> >       }
> > +#elif defined(HAVE_LIBLLVM_DYNAMIC)
> > +     static bool fn_init;
> > +     static int (*fn)(const char *dso_name, u64 addr,
> > +                      char **file, unsigned int *line,
> > +                      bool unwind_inlines,
> > +                      llvm_a2l_frame **inline_frames);
> > +
> > +     if (!fn_init) {
> > +             void * handle = perf_llvm_c_helpers_dll_handle();
> > +
> > +             if (!handle)
> > +                     return 0;
> > +
> > +             fn = reinterpret_cast<decltype(fn)>(dlsym(handle, "llvm_addr2line"));
> > +             if (!fn)
> > +                     pr_debug("dlsym failed for llvm_addr2line\n");
> > +             fn_init = true;
> > +     }
> > +     if (!fn)
> > +             return 0;
> > +     return fn(dso_name, addr, file, line, unwind_inlines, inline_frames);
> > +#else
> > +     return 0;
> > +#endif
> >  }
> >
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> >  static char *
> >  make_symbol_relative_string(struct dso *dso, const char *sym_name,
> >                           u64 addr, u64 base_addr)
> > @@ -158,10 +212,13 @@ make_symbol_relative_string(struct dso *dso, const char *sym_name,
> >                       return strdup(sym_name);
> >       }
> >  }
> > +#endif
> >
> >  extern "C"
> > -char *llvm_name_for_code(struct dso *dso, const char *dso_name, u64 addr)
> > +char *llvm_name_for_code(struct dso *dso __maybe_unused, const char *dso_name __maybe_unused,
> > +                      u64 addr __maybe_unused)
> >  {
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> >       LLVMSymbolizer *symbolizer = get_symbolizer();
> >       object::SectionedAddress sectioned_addr = {
> >               addr,
> > @@ -175,11 +232,34 @@ char *llvm_name_for_code(struct dso *dso, const char *dso_name, u64 addr)
> >       return make_symbol_relative_string(
> >               dso, res_or_err->FunctionName.c_str(),
> >               addr, res_or_err->StartAddress ? *res_or_err->StartAddress : 0);
> > +#elif defined(HAVE_LIBLLVM_DYNAMIC)
> > +     static bool fn_init;
> > +     static char *(*fn)(struct dso *dso, const char *dso_name, u64 addr);
> > +
> > +     if (!fn_init) {
> > +             void * handle = perf_llvm_c_helpers_dll_handle();
> > +
> > +             if (!handle)
> > +                     return NULL;
> > +
> > +             fn = reinterpret_cast<decltype(fn)>(dlsym(handle, "llvm_name_for_code"));
> > +             if (!fn)
> > +                     pr_debug("dlsym failed for llvm_name_for_code\n");
> > +             fn_init = true;
> > +     }
> > +     if (!fn)
> > +             return NULL;
> > +     return fn(dso, dso_name, addr);
> > +#else
> > +     return 0;
> > +#endif
> >  }
> >
> >  extern "C"
> > -char *llvm_name_for_data(struct dso *dso, const char *dso_name, u64 addr)
> > +char *llvm_name_for_data(struct dso *dso __maybe_unused, const char *dso_name __maybe_unused,
> > +                      u64 addr __maybe_unused)
> >  {
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> >       LLVMSymbolizer *symbolizer = get_symbolizer();
> >       object::SectionedAddress sectioned_addr = {
> >               addr,
> > @@ -193,4 +273,25 @@ char *llvm_name_for_data(struct dso *dso, const char *dso_name, u64 addr)
> >       return make_symbol_relative_string(
> >               dso, res_or_err->Name.c_str(),
> >               addr, res_or_err->Start);
> > +#elif defined(HAVE_LIBLLVM_DYNAMIC)
> > +     static bool fn_init;
> > +     static char *(*fn)(struct dso *dso, const char *dso_name, u64 addr);
> > +
> > +     if (!fn_init) {
> > +             void * handle = perf_llvm_c_helpers_dll_handle();
> > +
> > +             if (!handle)
> > +                     return NULL;
> > +
> > +             fn = reinterpret_cast<decltype(fn)>(dlsym(handle, "llvm_name_for_data"));
> > +             if (!fn)
> > +                     pr_debug("dlsym failed for llvm_name_for_data\n");
> > +             fn_init = true;
> > +     }
> > +     if (!fn)
> > +             return NULL;
> > +     return fn(dso, dso_name, addr);
> > +#else
> > +     return 0;
> > +#endif
> >  }
> > diff --git a/tools/perf/util/llvm.c b/tools/perf/util/llvm.c
> > index ddc737194692..f6a8943b7c9d 100644
> > --- a/tools/perf/util/llvm.c
> > +++ b/tools/perf/util/llvm.c
> > @@ -1,5 +1,6 @@
> >  // SPDX-License-Identifier: GPL-2.0
> >  #include "llvm.h"
> > +#include "llvm-c-helpers.h"
> >  #include "annotate.h"
> >  #include "debug.h"
> >  #include "dso.h"
> > @@ -7,17 +8,243 @@
> >  #include "namespaces.h"
> >  #include "srcline.h"
> >  #include "symbol.h"
> > +#include <dlfcn.h>
> >  #include <fcntl.h>
> > +#include <inttypes.h>
> >  #include <unistd.h>
> >  #include <linux/zalloc.h>
> >
> > -#ifdef HAVE_LIBLLVM_SUPPORT
> > -#include "llvm-c-helpers.h"
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> >  #include <llvm-c/Disassembler.h>
> >  #include <llvm-c/Target.h>
> > +#else
> > +typedef void *LLVMDisasmContextRef;
> > +typedef int (*LLVMOpInfoCallback)(void *dis_info, uint64_t pc, uint64_t offset,
> > +                               uint64_t op_size, uint64_t inst_size,
> > +                               int tag_type, void *tag_buf);
> > +typedef const char *(*LLVMSymbolLookupCallback)(void *dis_info,
> > +                                             uint64_t reference_value,
> > +                                             uint64_t *reference_type,
> > +                                             uint64_t reference_pc,
> > +                                             const char **reference_name);
> > +#define LLVMDisassembler_ReferenceType_InOut_None 0
> > +#define LLVMDisassembler_ReferenceType_In_Branch 1
> > +#define LLVMDisassembler_ReferenceType_In_PCrel_Load 2
> > +#define LLVMDisassembler_Option_PrintImmHex 2
> > +#define LLVMDisassembler_Option_AsmPrinterVariant 4
> > +const char *llvm_targets[] = {
> > +     "AMDGPU",
> > +     "ARM",
> > +     "AVR",
> > +     "BPF",
> > +     "Hexagon",
> > +     "Lanai",
> > +     "LoongArch",
> > +     "Mips",
> > +     "MSP430",
> > +     "NVPTX",
> > +     "PowerPC",
> > +     "RISCV",
> > +     "Sparc",
> > +     "SystemZ",
> > +     "VE",
> > +     "WebAssembly",
> > +     "X86",
> > +     "XCore",
> > +     "M68k",
> > +     "Xtensa",
> > +};
> > +#endif
> > +
> > +#if !defined(HAVE_LIBLLVM_SUPPORT) || defined(HAVE_LIBLLVM_DYNAMIC)
> > +static void *perf_llvm_dll_handle(void)
> > +{
> > +     static bool dll_handle_init;
> > +     static void *dll_handle;
> > +
> > +     if (!dll_handle_init) {
> > +             dll_handle_init = true;
> > +             dll_handle = dlopen("libLLVM.so", RTLD_LAZY);
> > +             if (!dll_handle)
> > +                     pr_debug("dlopen failed for libLLVM.so\n");
> > +     }
> > +     return dll_handle;
> > +}
> > +#endif
> > +
> > +#if !defined(HAVE_LIBLLVM_SUPPORT) || defined(HAVE_LIBLLVM_DYNAMIC)
> > +static void *perf_llvm_dll_fun(const char *fmt, const char *target)
> > +{
> > +     char buf[128];
> > +     void *fn;
> > +
> > +     snprintf(buf, sizeof(buf), fmt, target);
> > +     fn = dlsym(perf_llvm_dll_handle(), buf);
> > +     if (!fn)
> > +             pr_debug("dlsym failed for %s\n", buf);
> > +
> > +     return fn;
> > +}
> > +#endif
> > +
> > +static void perf_LLVMInitializeAllTargetInfos(void)
> > +{
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> > +     LLVMInitializeAllTargetInfos();
> > +#else
> > +     /* LLVMInitializeAllTargetInfos is a header file function not available as a symbol. */
> > +     static bool done_init;
> > +
> > +     if (done_init)
> > +             return;
> > +
> > +     for (size_t i = 0; i < ARRAY_SIZE(llvm_targets); i++) {
> > +             void (*fn)(void) = perf_llvm_dll_fun("LLVMInitialize%sTargetInfo",
> > +                                                  llvm_targets[i]);
> > +
> > +             if (!fn)
> > +                     continue;
> > +             fn();
> > +     }
> > +     done_init = true;
> > +#endif
> > +}
> > +
> > +static void perf_LLVMInitializeAllTargetMCs(void)
> > +{
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> > +     LLVMInitializeAllTargetMCs();
> > +#else
> > +     /* LLVMInitializeAllTargetMCs is a header file function not available as a symbol. */
> > +     static bool done_init;
> > +
> > +     if (done_init)
> > +             return;
> > +
> > +     for (size_t i = 0; i < ARRAY_SIZE(llvm_targets); i++) {
> > +             void (*fn)(void) = perf_llvm_dll_fun("LLVMInitialize%sTargetMC",
> > +                                                  llvm_targets[i]);
> > +
> > +             if (!fn)
> > +                     continue;
> > +             fn();
> > +     }
> > +     done_init = true;
> > +#endif
> > +}
> > +
> > +static void perf_LLVMInitializeAllDisassemblers(void)
> > +{
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> > +     LLVMInitializeAllDisassemblers();
> > +#else
> > +     /* LLVMInitializeAllDisassemblers is a header file function not available as a symbol. */
> > +     static bool done_init;
> > +
> > +     if (done_init)
> > +             return;
> > +
> > +     for (size_t i = 0; i < ARRAY_SIZE(llvm_targets); i++) {
> > +             void (*fn)(void) = perf_llvm_dll_fun("LLVMInitialize%sDisassembler",
> > +                                                  llvm_targets[i]);
> > +
> > +             if (!fn)
> > +                     continue;
> > +             fn();
> > +     }
> > +     done_init = true;
> > +#endif
> > +}
> > +
> > +static LLVMDisasmContextRef perf_LLVMCreateDisasm(const char *triple_name, void *dis_info,
> > +                                             int tag_type, LLVMOpInfoCallback get_op_info,
> > +                                             LLVMSymbolLookupCallback symbol_lookup)
> > +{
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> > +     return LLVMCreateDisasm(triple_name, dis_info, tag_type, get_op_info, symbol_lookup);
> > +#else
> > +     static bool fn_init;
> > +     static LLVMDisasmContextRef (*fn)(const char *triple_name, void *dis_info,
> > +                                     int tag_type, LLVMOpInfoCallback get_op_info,
> > +                                     LLVMSymbolLookupCallback symbol_lookup);
> > +
> > +     if (!fn_init) {
> > +             fn = dlsym(perf_llvm_dll_handle(), "LLVMCreateDisasm");
> > +             if (!fn)
> > +                     pr_debug("dlsym failed for LLVMCreateDisasm\n");
> > +             fn_init = true;
> > +     }
> > +     if (!fn)
> > +             return NULL;
> > +     return fn(triple_name, dis_info, tag_type, get_op_info, symbol_lookup);
> > +#endif
> > +}
> > +
> > +static int perf_LLVMSetDisasmOptions(LLVMDisasmContextRef context, uint64_t options)
> > +{
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> > +     return LLVMSetDisasmOptions(context, options);
> > +#else
> > +     static bool fn_init;
> > +     static int (*fn)(LLVMDisasmContextRef context, uint64_t options);
> > +
> > +     if (!fn_init) {
> > +             fn = dlsym(perf_llvm_dll_handle(), "LLVMSetDisasmOptions");
> > +             if (!fn)
> > +                     pr_debug("dlsym failed for LLVMSetDisasmOptions\n");
> > +             fn_init = true;
> > +     }
> > +     if (!fn)
> > +             return 0;
> > +     return fn(context, options);
> > +#endif
> > +}
> > +
> > +static size_t perf_LLVMDisasmInstruction(LLVMDisasmContextRef context, uint8_t *bytes,
> > +                                     uint64_t bytes_size, uint64_t pc,
> > +                                     char *out_string, size_t out_string_size)
> > +{
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> > +     return LLVMDisasmInstruction(context, bytes, bytes_size, pc, out_string, out_string_size);
> > +#else
> > +     static bool fn_init;
> > +     static int (*fn)(LLVMDisasmContextRef context, uint8_t *bytes,
> > +                     uint64_t bytes_size, uint64_t pc,
> > +                     char *out_string, size_t out_string_size);
> > +
> > +     if (!fn_init) {
> > +             fn = dlsym(perf_llvm_dll_handle(), "LLVMDisasmInstruction");
> > +             if (!fn)
> > +                     pr_debug("dlsym failed for LLVMDisasmInstruction\n");
> > +             fn_init = true;
> > +     }
> > +     if (!fn)
> > +             return 0;
> > +     return fn(context, bytes, bytes_size, pc, out_string, out_string_size);
> > +#endif
> > +}
> > +
> > +static void perf_LLVMDisasmDispose(LLVMDisasmContextRef context)
> > +{
> > +#if defined(HAVE_LIBLLVM_SUPPORT) && !defined(HAVE_LIBLLVM_DYNAMIC)
> > +     LLVMDisasmDispose(context);
> > +#else
> > +     static bool fn_init;
> > +     static int (*fn)(LLVMDisasmContextRef context);
> > +
> > +     if (!fn_init) {
> > +             fn = dlsym(perf_llvm_dll_handle(), "LLVMDisasmDispose");
> > +             if (!fn)
> > +                     pr_debug("dlsym failed for LLVMDisasmDispose\n");
> > +             fn_init = true;
> > +     }
> > +     if (!fn)
> > +             return;
> > +     fn(context);
> >  #endif
> > +}
> > +
> >
> > -#ifdef HAVE_LIBLLVM_SUPPORT
> >  static void free_llvm_inline_frames(struct llvm_a2l_frame *inline_frames,
> >                                   int num_frames)
> >  {
> > @@ -29,14 +256,12 @@ static void free_llvm_inline_frames(struct llvm_a2l_frame *inline_frames,
> >               zfree(&inline_frames);
> >       }
> >  }
> > -#endif
> >
> >  int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused,
> >                    char **file __maybe_unused, unsigned int *line __maybe_unused,
> >                    struct dso *dso __maybe_unused, bool unwind_inlines __maybe_unused,
> >                    struct inline_node *node __maybe_unused, struct symbol *sym __maybe_unused)
> >  {
> > -#ifdef HAVE_LIBLLVM_SUPPORT
> >       struct llvm_a2l_frame *inline_frames = NULL;
> >       int num_frames = llvm_addr2line(dso_name, addr, file, line,
> >                                       node && unwind_inlines, &inline_frames);
> > @@ -64,9 +289,6 @@ int llvm__addr2line(const char *dso_name __maybe_unused, u64 addr __maybe_unused
> >       free_llvm_inline_frames(inline_frames, num_frames);
> >
> >       return num_frames;
> > -#else
> > -     return -1;
> > -#endif
> >  }
> >
> >  void dso__free_a2l_llvm(struct dso *dso __maybe_unused)
> > @@ -75,7 +297,6 @@ void dso__free_a2l_llvm(struct dso *dso __maybe_unused)
> >  }
> >
> >
> > -#if defined(HAVE_LIBLLVM_SUPPORT)
> >  struct find_file_offset_data {
> >       u64 ip;
> >       u64 offset;
> > @@ -139,7 +360,6 @@ read_symbol(const char *filename, struct map *map, struct symbol *sym,
> >       free(buf);
> >       return NULL;
> >  }
> > -#endif
> >
> >  /*
> >   * Whenever LLVM wants to resolve an address into a symbol, it calls this
> > @@ -149,7 +369,6 @@ read_symbol(const char *filename, struct map *map, struct symbol *sym,
> >   * should add some textual annotation for after the instruction. The caller
> >   * will use this information to add the actual annotation.
> >   */
> > -#ifdef HAVE_LIBLLVM_SUPPORT
> >  struct symbol_lookup_storage {
> >       u64 branch_addr;
> >       u64 pcrel_load_addr;
> > @@ -170,12 +389,10 @@ symbol_lookup_callback(void *disinfo, uint64_t value,
> >       *ref_type = LLVMDisassembler_ReferenceType_InOut_None;
> >       return NULL;
> >  }
> > -#endif
> >
> >  int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
> >                            struct annotate_args *args __maybe_unused)
> >  {
> > -#ifdef HAVE_LIBLLVM_SUPPORT
> >       struct annotation *notes = symbol__annotation(sym);
> >       struct map *map = args->ms.map;
> >       struct dso *dso = map__dso(map);
> > @@ -197,9 +414,9 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
> >       if (args->options->objdump_path)
> >               return -1;
> >
> > -     LLVMInitializeAllTargetInfos();
> > -     LLVMInitializeAllTargetMCs();
> > -     LLVMInitializeAllDisassemblers();
> > +     perf_LLVMInitializeAllTargetInfos();
> > +     perf_LLVMInitializeAllTargetMCs();
> > +     perf_LLVMInitializeAllDisassemblers();
> >
> >       buf = read_symbol(filename, map, sym, &len, &is_64bit);
> >       if (buf == NULL)
> > @@ -215,15 +432,14 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
> >                         args->arch->name);
> >       }
> >
> > -     disasm = LLVMCreateDisasm(triplet, &storage, 0, NULL,
> > -                               symbol_lookup_callback);
> > +     disasm = perf_LLVMCreateDisasm(triplet, &storage, 0, NULL,
> > +                                    symbol_lookup_callback);
> >       if (disasm == NULL)
> >               goto err;
> >
> >       if (args->options->disassembler_style &&
> >           !strcmp(args->options->disassembler_style, "intel"))
> > -             LLVMSetDisasmOptions(disasm,
> > -                                  LLVMDisassembler_Option_AsmPrinterVariant);
> > +             perf_LLVMSetDisasmOptions(disasm, LLVMDisassembler_Option_AsmPrinterVariant);
> >
> >       /*
> >        * This needs to be set after AsmPrinterVariant, due to a bug in LLVM;
> > @@ -231,7 +447,7 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
> >        * forget about the PrintImmHex flag (which is applied before if both
> >        * are given to the same call).
> >        */
> > -     LLVMSetDisasmOptions(disasm, LLVMDisassembler_Option_PrintImmHex);
> > +     perf_LLVMSetDisasmOptions(disasm, LLVMDisassembler_Option_PrintImmHex);
> >
> >       /* add the function address and name */
> >       scnprintf(disasm_buf, sizeof(disasm_buf), "%#"PRIx64" <%s>:",
> > @@ -256,9 +472,9 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
> >               storage.branch_addr = 0;
> >               storage.pcrel_load_addr = 0;
> >
> > -             ins_len = LLVMDisasmInstruction(disasm, buf + offset,
> > -                                             len - offset, pc,
> > -                                             disasm_buf, sizeof(disasm_buf));
> > +             ins_len = perf_LLVMDisasmInstruction(disasm, buf + offset,
> > +                                                  len - offset, pc,
> > +                                                  disasm_buf, sizeof(disasm_buf));
> >               if (ins_len == 0)
> >                       goto err;
> >               disasm_len = strlen(disasm_buf);
> > @@ -314,13 +530,8 @@ int symbol__disassemble_llvm(const char *filename, struct symbol *sym,
> >       ret = 0;
> >
> >  err:
> > -     LLVMDisasmDispose(disasm);
> > +     perf_LLVMDisasmDispose(disasm);
> >       free(buf);
> >       free(line_storage);
> >       return ret;
> > -#else // HAVE_LIBLLVM_SUPPORT
> > -     pr_debug("The LLVM disassembler isn't linked in for %s in %s\n",
> > -              sym->name, filename);
> > -     return -1;
> > -#endif
> >  }
> > --
> > 2.48.1.262.g85cc9f2d1e-goog
> >