Commit 152e60e3 authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

Merge branch 'bpftool: Add LLVM as default library for disassembling JIT-ed programs'



Quentin Monnet says:

====================

To disassemble instructions for JIT-ed programs, bpftool has relied on the
libbfd library. This has been problematic in the past: libbfd's interface
is not meant to be stable and has changed several times, hence the
detection of the two related features from the Makefile
(disassembler-four-args and disassembler-init-styled). When it comes to
shipping bpftool, this has also caused issues with several distribution
maintainers unwilling to support the feature (for example, Debian's page
for binutils-dev, libbfd's package, says: "Note that building Debian
packages which depend on the shared libbfd is Not Allowed.").

This patchset adds support for LLVM as the primary library for
disassembling instructions for JIT-ed programs.

We keep libbfd as a fallback. One reason for this is that currently it
works well, we have all we need in terms of features detection in the
Makefile, so it provides a fallback for disassembling JIT-ed programs if
libbfd is installed but LLVM is not. The other reason is that libbfd
supports nfp instruction for Netronome's SmartNICs and can be used to
disassemble offloaded programs, something that LLVM cannot do (Niklas
confirmed that the feature is still in use). However, if libbfd's interface
breaks again in the future, we might reconsider keeping support for it.

v4:
  - Rebase to address a conflict with commit 2c76238e ("bpftool: Add
    "bootstrap" feature to version output").

v3:
  - Extend commit description (patch 6) with notes on llvm-dev and LLVM's
    disassembler stability.

v2:
  - Pass callback when creating the LLVM disassembler, so that the
    branch targets are printed as addresses (instead of byte offsets).
  - Add last commit to "support" other arch with LLVM, although we don't
    know any supported triple yet.
  - Use $(LLVM_CONFIG) instead of llvm-config in Makefile.
  - Pass components to llvm-config --libs to limit the number of
    libraries to pass on the command line, in Makefile.
  - Rebase split of FEATURE_TESTS and FEATURE_DISPLAY in Makefile.
====================

Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents 8f4bc15b 08b8191b
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -7,10 +7,10 @@
	  Print bpftool's version number (similar to **bpftool version**), the
	  number of the libbpf version in use, and optional features that were
	  included when bpftool was compiled. Optional features include linking
	  against libbfd to provide the disassembler for JIT-ted programs
	  (**bpftool prog dump jited**) and usage of BPF skeletons (some
	  features like **bpftool prog profile** or showing pids associated to
	  BPF objects may rely on it).
	  against LLVM or libbfd to provide the disassembler for JIT-ted
	  programs (**bpftool prog dump jited**) and usage of BPF skeletons
	  (some features like **bpftool prog profile** or showing pids
	  associated to BPF objects may rely on it).

-j, --json
	  Generate JSON output. For commands that cannot produce JSON, this
+49 −25
Original line number Diff line number Diff line
@@ -93,11 +93,22 @@ INSTALL ?= install
RM ?= rm -f

FEATURE_USER = .bpftool
FEATURE_TESTS = libbfd libbfd-liberty libbfd-liberty-z \
	disassembler-four-args disassembler-init-styled libcap \
	clang-bpf-co-re
FEATURE_DISPLAY = libbfd libbfd-liberty libbfd-liberty-z \
	libcap clang-bpf-co-re

FEATURE_TESTS := clang-bpf-co-re
FEATURE_TESTS += llvm
FEATURE_TESTS += libcap
FEATURE_TESTS += libbfd
FEATURE_TESTS += libbfd-liberty
FEATURE_TESTS += libbfd-liberty-z
FEATURE_TESTS += disassembler-four-args
FEATURE_TESTS += disassembler-init-styled

FEATURE_DISPLAY := clang-bpf-co-re
FEATURE_DISPLAY += llvm
FEATURE_DISPLAY += libcap
FEATURE_DISPLAY += libbfd
FEATURE_DISPLAY += libbfd-liberty
FEATURE_DISPLAY += libbfd-liberty-z

check_feat := 1
NON_CHECK_FEAT_TARGETS := clean uninstall doc doc-clean doc-install doc-uninstall
@@ -115,13 +126,6 @@ include $(FEATURES_DUMP)
endif
endif

ifeq ($(feature-disassembler-four-args), 1)
CFLAGS += -DDISASM_FOUR_ARGS_SIGNATURE
endif
ifeq ($(feature-disassembler-init-styled), 1)
    CFLAGS += -DDISASM_INIT_STYLED
endif

LIBS = $(LIBBPF) -lelf -lz
LIBS_BOOTSTRAP = $(LIBBPF_BOOTSTRAP) -lelf -lz
ifeq ($(feature-libcap), 1)
@@ -133,10 +137,17 @@ include $(wildcard $(OUTPUT)*.d)

all: $(OUTPUT)bpftool

BFD_SRCS = jit_disasm.c

SRCS = $(filter-out $(BFD_SRCS),$(wildcard *.c))
SRCS := $(wildcard *.c)

ifeq ($(feature-llvm),1)
  # If LLVM is available, use it for JIT disassembly
  CFLAGS  += -DHAVE_LLVM_SUPPORT
  LLVM_CONFIG_LIB_COMPONENTS := mcdisassembler all-targets
  CFLAGS  += $(shell $(LLVM_CONFIG) --cflags --libs $(LLVM_CONFIG_LIB_COMPONENTS))
  LIBS    += $(shell $(LLVM_CONFIG) --libs $(LLVM_CONFIG_LIB_COMPONENTS))
  LDFLAGS += $(shell $(LLVM_CONFIG) --ldflags)
else
  # Fall back on libbfd
  ifeq ($(feature-libbfd),1)
    LIBS += -lbfd -ldl -lopcodes
  else ifeq ($(feature-libbfd-liberty),1)
@@ -145,9 +156,22 @@ else ifeq ($(feature-libbfd-liberty-z),1)
    LIBS += -lbfd -ldl -lopcodes -liberty -lz
  endif

  # If one of the above feature combinations is set, we support libbfd
  ifneq ($(filter -lbfd,$(LIBS)),)
    CFLAGS += -DHAVE_LIBBFD_SUPPORT
SRCS += $(BFD_SRCS)

    # Libbfd interface changed over time, figure out what we need
    ifeq ($(feature-disassembler-four-args), 1)
      CFLAGS += -DDISASM_FOUR_ARGS_SIGNATURE
    endif
    ifeq ($(feature-disassembler-init-styled), 1)
      CFLAGS += -DDISASM_INIT_STYLED
    endif
  endif
endif
ifeq ($(filter -DHAVE_LLVM_SUPPORT -DHAVE_LIBBFD_SUPPORT,$(CFLAGS)),)
  # No support for JIT disassembly
  SRCS := $(filter-out jit_disasm.c,$(SRCS))
endif

HOST_CFLAGS = $(subst -I$(LIBBPF_INCLUDE),-I$(LIBBPF_BOOTSTRAP_INCLUDE),\
+8 −4
Original line number Diff line number Diff line
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
/* Copyright (C) 2017-2018 Netronome Systems, Inc. */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
@@ -625,12 +627,11 @@ static int read_sysfs_netdev_hex_int(char *devname, const char *entry_name)
}

const char *
ifindex_to_bfd_params(__u32 ifindex, __u64 ns_dev, __u64 ns_ino,
		      const char **opt)
ifindex_to_arch(__u32 ifindex, __u64 ns_dev, __u64 ns_ino, const char **opt)
{
	__maybe_unused int device_id;
	char devname[IF_NAMESIZE];
	int vendor_id;
	int device_id;

	if (!ifindex_to_name_ns(ifindex, ns_dev, ns_ino, devname)) {
		p_err("Can't get net device name for ifindex %d: %s", ifindex,
@@ -645,6 +646,7 @@ ifindex_to_bfd_params(__u32 ifindex, __u64 ns_dev, __u64 ns_ino,
	}

	switch (vendor_id) {
#ifdef HAVE_LIBBFD_SUPPORT
	case 0x19ee:
		device_id = read_sysfs_netdev_hex_int(devname, "device");
		if (device_id != 0x4000 &&
@@ -653,8 +655,10 @@ ifindex_to_bfd_params(__u32 ifindex, __u64 ns_dev, __u64 ns_ino,
			p_info("Unknown NFP device ID, assuming it is NFP-6xxx arch");
		*opt = "ctx4";
		return "NFP-6xxx";
#endif /* HAVE_LIBBFD_SUPPORT */
	/* No NFP support in LLVM, we have no valid triple to return. */
	default:
		p_err("Can't get bfd arch name for device vendor id 0x%04x",
		p_err("Can't get arch name for device vendor id 0x%04x",
		      vendor_id);
		return NULL;
	}
+2 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
// Copyright (C) 2020 Facebook

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <unistd.h>
#include <linux/err.h>
#include <bpf/libbpf.h>
+215 −46
Original line number Diff line number Diff line
@@ -11,35 +11,151 @@
 * Licensed under the GNU General Public License, version 2.0 (GPLv2)
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <bfd.h>
#include <dis-asm.h>
#include <sys/stat.h>
#include <limits.h>
#include <bpf/libbpf.h>

#ifdef HAVE_LLVM_SUPPORT
#include <llvm-c/Core.h>
#include <llvm-c/Disassembler.h>
#include <llvm-c/Target.h>
#include <llvm-c/TargetMachine.h>
#endif

#ifdef HAVE_LIBBFD_SUPPORT
#include <bfd.h>
#include <dis-asm.h>
#include <tools/dis-asm-compat.h>
#endif

#include "json_writer.h"
#include "main.h"

static void get_exec_path(char *tpath, size_t size)
static int oper_count;

#ifdef HAVE_LLVM_SUPPORT
#define DISASM_SPACER

typedef LLVMDisasmContextRef disasm_ctx_t;

static int printf_json(char *s)
{
	s = strtok(s, " \t");
	jsonw_string_field(json_wtr, "operation", s);

	jsonw_name(json_wtr, "operands");
	jsonw_start_array(json_wtr);
	oper_count = 1;

	while ((s = strtok(NULL, " \t,()")) != 0) {
		jsonw_string(json_wtr, s);
		oper_count++;
	}
	return 0;
}

/* This callback to set the ref_type is necessary to have the LLVM disassembler
 * print PC-relative addresses instead of byte offsets for branch instruction
 * targets.
 */
static const char *
symbol_lookup_callback(__maybe_unused void *disasm_info,
		       __maybe_unused uint64_t ref_value,
		       uint64_t *ref_type, __maybe_unused uint64_t ref_PC,
		       __maybe_unused const char **ref_name)
{
	*ref_type = LLVMDisassembler_ReferenceType_InOut_None;
	return NULL;
}

static int
init_context(disasm_ctx_t *ctx, const char *arch,
	     __maybe_unused const char *disassembler_options,
	     __maybe_unused unsigned char *image, __maybe_unused ssize_t len)
{
	char *triple;

	if (arch)
		triple = LLVMNormalizeTargetTriple(arch);
	else
		triple = LLVMGetDefaultTargetTriple();
	if (!triple) {
		p_err("Failed to retrieve triple");
		return -1;
	}
	*ctx = LLVMCreateDisasm(triple, NULL, 0, NULL, symbol_lookup_callback);
	LLVMDisposeMessage(triple);

	if (!*ctx) {
		p_err("Failed to create disassembler");
		return -1;
	}

	return 0;
}

static void destroy_context(disasm_ctx_t *ctx)
{
	LLVMDisposeMessage(*ctx);
}

static int
disassemble_insn(disasm_ctx_t *ctx, unsigned char *image, ssize_t len, int pc)
{
	char buf[256];
	int count;

	count = LLVMDisasmInstruction(*ctx, image + pc, len - pc, pc,
				      buf, sizeof(buf));
	if (json_output)
		printf_json(buf);
	else
		printf("%s", buf);

	return count;
}

int disasm_init(void)
{
	LLVMInitializeAllTargetInfos();
	LLVMInitializeAllTargetMCs();
	LLVMInitializeAllDisassemblers();
	return 0;
}
#endif /* HAVE_LLVM_SUPPORT */

#ifdef HAVE_LIBBFD_SUPPORT
#define DISASM_SPACER "\t"

typedef struct {
	struct disassemble_info *info;
	disassembler_ftype disassemble;
	bfd *bfdf;
} disasm_ctx_t;

static int get_exec_path(char *tpath, size_t size)
{
	const char *path = "/proc/self/exe";
	ssize_t len;

	len = readlink(path, tpath, size - 1);
	assert(len > 0);
	if (len <= 0)
		return -1;

	tpath[len] = 0;

	return 0;
}

static int oper_count;
static int printf_json(void *out, const char *fmt, va_list ap)
{
	char *s;
@@ -97,37 +213,44 @@ static int fprintf_json_styled(void *out,
	return r;
}

void disasm_print_insn(unsigned char *image, ssize_t len, int opcodes,
		       const char *arch, const char *disassembler_options,
		       const struct btf *btf,
		       const struct bpf_prog_linfo *prog_linfo,
		       __u64 func_ksym, unsigned int func_idx,
		       bool linum)
static int init_context(disasm_ctx_t *ctx, const char *arch,
			const char *disassembler_options,
			unsigned char *image, ssize_t len)
{
	const struct bpf_line_info *linfo = NULL;
	disassembler_ftype disassemble;
	struct disassemble_info info;
	unsigned int nr_skip = 0;
	int count, i, pc = 0;
	struct disassemble_info *info;
	char tpath[PATH_MAX];
	bfd *bfdf;

	if (!len)
		return;

	memset(tpath, 0, sizeof(tpath));
	get_exec_path(tpath, sizeof(tpath));
	if (get_exec_path(tpath, sizeof(tpath))) {
		p_err("failed to create disasembler (get_exec_path)");
		return -1;
	}

	ctx->bfdf = bfd_openr(tpath, NULL);
	if (!ctx->bfdf) {
		p_err("failed to create disassembler (bfd_openr)");
		return -1;
	}
	if (!bfd_check_format(ctx->bfdf, bfd_object)) {
		p_err("failed to create disassembler (bfd_check_format)");
		goto err_close;
	}
	bfdf = ctx->bfdf;

	bfdf = bfd_openr(tpath, NULL);
	assert(bfdf);
	assert(bfd_check_format(bfdf, bfd_object));
	ctx->info = malloc(sizeof(struct disassemble_info));
	if (!ctx->info) {
		p_err("mem alloc failed");
		goto err_close;
	}
	info = ctx->info;

	if (json_output)
		init_disassemble_info_compat(&info, stdout,
		init_disassemble_info_compat(info, stdout,
					     (fprintf_ftype) fprintf_json,
					     fprintf_json_styled);
	else
		init_disassemble_info_compat(&info, stdout,
		init_disassemble_info_compat(info, stdout,
					     (fprintf_ftype) fprintf,
					     fprintf_styled);

@@ -139,28 +262,77 @@ void disasm_print_insn(unsigned char *image, ssize_t len, int opcodes,
			bfdf->arch_info = inf;
		} else {
			p_err("No libbfd support for %s", arch);
			return;
			goto err_free;
		}
	}

	info.arch = bfd_get_arch(bfdf);
	info.mach = bfd_get_mach(bfdf);
	info->arch = bfd_get_arch(bfdf);
	info->mach = bfd_get_mach(bfdf);
	if (disassembler_options)
		info.disassembler_options = disassembler_options;
	info.buffer = image;
	info.buffer_length = len;
		info->disassembler_options = disassembler_options;
	info->buffer = image;
	info->buffer_length = len;

	disassemble_init_for_target(&info);
	disassemble_init_for_target(info);

#ifdef DISASM_FOUR_ARGS_SIGNATURE
	disassemble = disassembler(info.arch,
	ctx->disassemble = disassembler(info->arch,
					bfd_big_endian(bfdf),
				   info.mach,
					info->mach,
					bfdf);
#else
	disassemble = disassembler(bfdf);
	ctx->disassemble = disassembler(bfdf);
#endif
	assert(disassemble);
	if (!ctx->disassemble) {
		p_err("failed to create disassembler");
		goto err_free;
	}
	return 0;

err_free:
	free(info);
err_close:
	bfd_close(ctx->bfdf);
	return -1;
}

static void destroy_context(disasm_ctx_t *ctx)
{
	free(ctx->info);
	bfd_close(ctx->bfdf);
}

static int
disassemble_insn(disasm_ctx_t *ctx, __maybe_unused unsigned char *image,
		 __maybe_unused ssize_t len, int pc)
{
	return ctx->disassemble(pc, ctx->info);
}

int disasm_init(void)
{
	bfd_init();
	return 0;
}
#endif /* HAVE_LIBBPFD_SUPPORT */

int disasm_print_insn(unsigned char *image, ssize_t len, int opcodes,
		      const char *arch, const char *disassembler_options,
		      const struct btf *btf,
		      const struct bpf_prog_linfo *prog_linfo,
		      __u64 func_ksym, unsigned int func_idx,
		      bool linum)
{
	const struct bpf_line_info *linfo = NULL;
	unsigned int nr_skip = 0;
	int count, i, pc = 0;
	disasm_ctx_t ctx;

	if (!len)
		return -1;

	if (init_context(&ctx, arch, disassembler_options, image, len))
		return -1;

	if (json_output)
		jsonw_start_array(json_wtr);
@@ -185,10 +357,11 @@ void disasm_print_insn(unsigned char *image, ssize_t len, int opcodes,
			if (linfo)
				btf_dump_linfo_plain(btf, linfo, "; ",
						     linum);
			printf("%4x:\t", pc);
			printf("%4x:" DISASM_SPACER, pc);
		}

		count = disassemble(pc, &info);
		count = disassemble_insn(&ctx, image, len, pc);

		if (json_output) {
			/* Operand array, was started in fprintf_json. Before
			 * that, make sure we have a _null_ value if no operand
@@ -224,11 +397,7 @@ void disasm_print_insn(unsigned char *image, ssize_t len, int opcodes,
	if (json_output)
		jsonw_end_array(json_wtr);

	bfd_close(bfdf);
}
	destroy_context(&ctx);

int disasm_init(void)
{
	bfd_init();
	return 0;
}
Loading