From: Yonghong Song <yonghong.song@linux.dev>
To: Eduard Zingerman <eddyz87@gmail.com>,
bpf@vger.kernel.org, ast@kernel.org
Cc: andrii@kernel.org, daniel@iogearbox.net, martin.lau@linux.dev,
kernel-team@fb.com, hffilwlqm@gmail.com
Subject: Re: [PATCH bpf-next 2/4] selftests/bpf: utility function to get program disassembly after jit
Date: Thu, 15 Aug 2024 12:27:06 -0700 [thread overview]
Message-ID: <de45cb69-8116-4589-a0ba-9e77ba1c3e60@linux.dev> (raw)
In-Reply-To: <20240809010518.1137758-3-eddyz87@gmail.com>
On 8/8/24 6:05 PM, Eduard Zingerman wrote:
> int get_jited_program_text(int fd, char *text, size_t text_sz)
>
You need to give some context before the above function signature.
> Loads and disassembles jited instructions for program pointed to by fd.
> Much like 'bpftool prog dump jited ...'.
>
> The code and makefile changes are inspired by jit_disasm.c from bpftool.
> Use llvm libraries to disassemble BPF program instead of libbfd to avoid
> issues with disassembly output stability pointed out in [1].
>
> Selftests makefile uses Makefile.feature to detect if LLVM libraries
> are available. If that is not the case selftests build proceeds but
> the function returns -ENOTSUP at runtime.
>
> [1] commit eb9d1acf634b ("bpftool: Add LLVM as default library for disassembling JIT-ed programs")
>
> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
LGTM except a few nits below.
Acked-by: Yonghong Song <yonghong.song@linux.dev>
> ---
> tools/testing/selftests/bpf/.gitignore | 1 +
> tools/testing/selftests/bpf/Makefile | 51 +++-
> .../selftests/bpf/jit_disasm_helpers.c | 228 ++++++++++++++++++
> .../selftests/bpf/jit_disasm_helpers.h | 10 +
> 4 files changed, 288 insertions(+), 2 deletions(-)
> create mode 100644 tools/testing/selftests/bpf/jit_disasm_helpers.c
> create mode 100644 tools/testing/selftests/bpf/jit_disasm_helpers.h
>
> diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore
> index 8f14d8faeb0b..7de4108771a0 100644
> --- a/tools/testing/selftests/bpf/.gitignore
> +++ b/tools/testing/selftests/bpf/.gitignore
> @@ -8,6 +8,7 @@ test_lru_map
> test_lpm_map
> test_tag
> FEATURE-DUMP.libbpf
> +FEATURE-DUMP.selftests
> fixdep
> /test_progs
> /test_progs-no_alu32
> diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
> index f54185e96a95..b1a52739d9e7 100644
> --- a/tools/testing/selftests/bpf/Makefile
> +++ b/tools/testing/selftests/bpf/Makefile
> @@ -33,6 +33,13 @@ OPT_FLAGS ?= $(if $(RELEASE),-O2,-O0)
> LIBELF_CFLAGS := $(shell $(PKG_CONFIG) libelf --cflags 2>/dev/null)
> LIBELF_LIBS := $(shell $(PKG_CONFIG) libelf --libs 2>/dev/null || echo -lelf)
>
> +ifeq ($(srctree),)
> +srctree := $(patsubst %/,%,$(dir $(CURDIR)))
> +srctree := $(patsubst %/,%,$(dir $(srctree)))
> +srctree := $(patsubst %/,%,$(dir $(srctree)))
> +srctree := $(patsubst %/,%,$(dir $(srctree)))
> +endif
> +
> CFLAGS += -g $(OPT_FLAGS) -rdynamic \
> -Wall -Werror -fno-omit-frame-pointer \
> $(GENFLAGS) $(SAN_CFLAGS) $(LIBELF_CFLAGS) \
> @@ -55,6 +62,11 @@ progs/test_sk_lookup.c-CFLAGS := -fno-strict-aliasing
> progs/timer_crash.c-CFLAGS := -fno-strict-aliasing
> progs/test_global_func9.c-CFLAGS := -fno-strict-aliasing
>
> +# Some utility functions use LLVM libraries
> +jit_disasm_helpers.c-CFLAGS = $(LLVM_CFLAGS)
> +test_progs-LDLIBS = $(LLVM_LDLIBS)
> +test_progs-LDFLAGS = $(LLVM_LDFLAGS)
> +
> ifneq ($(LLVM),)
> # Silence some warnings when compiled with clang
> CFLAGS += -Wno-unused-command-line-argument
> @@ -164,6 +176,31 @@ endef
>
> include ../lib.mk
>
> +NON_CHECK_FEAT_TARGETS := clean docs-clean
> +CHECK_FEAT := $(filter-out $(NON_CHECK_FEAT_TARGETS),$(or $(MAKECMDGOALS), "none"))
> +ifneq ($(CHECK_FEAT),)
> +FEATURE_USER := .selftests
> +FEATURE_TESTS := llvm
> +FEATURE_DISPLAY := $(FEATURE_TESTS)
> +
> +# Makefile.feature expects OUTPUT to end with a slash
> +$(let OUTPUT,$(OUTPUT)/,\
> + $(eval include ../../../build/Makefile.feature))
> +endif
> +
> +ifeq ($(feature-llvm),1)
> + LLVM_CFLAGS += -DHAVE_LLVM_SUPPORT
> + LLVM_CONFIG_LIB_COMPONENTS := mcdisassembler all-targets
> + # both llvm-config and lib.mk add -D_GNU_SOURCE, which ends up as conflict
> + LLVM_CFLAGS += $(filter-out -D_GNU_SOURCE,$(shell $(LLVM_CONFIG) --cflags))
> + LLVM_LDLIBS += $(shell $(LLVM_CONFIG) --libs $(LLVM_CONFIG_LIB_COMPONENTS))
> + ifeq ($(shell $(LLVM_CONFIG) --shared-mode),static)
> + LLVM_LDLIBS += $(shell $(LLVM_CONFIG) --system-libs $(LLVM_CONFIG_LIB_COMPONENTS))
> + LLVM_LDLIBS += -lstdc++
> + endif
> + LLVM_LDFLAGS += $(shell $(LLVM_CONFIG) --ldflags)
> +endif
> +
> SCRATCH_DIR := $(OUTPUT)/tools
> BUILD_DIR := $(SCRATCH_DIR)/build
> INCLUDE_DIR := $(SCRATCH_DIR)/include
> @@ -488,6 +525,7 @@ define DEFINE_TEST_RUNNER
>
> TRUNNER_OUTPUT := $(OUTPUT)$(if $2,/)$2
> TRUNNER_BINARY := $1$(if $2,-)$2
> +TRUNNER_BASE_NAME := $1
> TRUNNER_TEST_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.test.o, \
> $$(notdir $$(wildcard $(TRUNNER_TESTS_DIR)/*.c)))
> TRUNNER_EXTRA_OBJS := $$(patsubst %.c,$$(TRUNNER_OUTPUT)/%.o, \
> @@ -611,6 +649,10 @@ ifeq ($(filter clean docs-clean,$(MAKECMDGOALS)),)
> include $(wildcard $(TRUNNER_TEST_OBJS:.o=.d))
> endif
>
> +# add per extra obj CFGLAGS definitions
> +$(foreach N,$(patsubst $(TRUNNER_OUTPUT)/%.o,%,$(TRUNNER_EXTRA_OBJS)), \
> + $(eval $(TRUNNER_OUTPUT)/$(N).o: CFLAGS += $($(N).c-CFLAGS)))
> +
> $(TRUNNER_EXTRA_OBJS): $(TRUNNER_OUTPUT)/%.o: \
> %.c \
> $(TRUNNER_EXTRA_HDRS) \
> @@ -627,6 +669,9 @@ ifneq ($2:$(OUTPUT),:$(shell pwd))
> $(Q)rsync -aq $$^ $(TRUNNER_OUTPUT)/
> endif
>
> +$(OUTPUT)/$(TRUNNER_BINARY): LDLIBS += $$($(TRUNNER_BASE_NAME)-LDLIBS)
> +$(OUTPUT)/$(TRUNNER_BINARY): LDFLAGS += $$($(TRUNNER_BASE_NAME)-LDFLAGS)
> +
> # some X.test.o files have runtime dependencies on Y.bpf.o files
> $(OUTPUT)/$(TRUNNER_BINARY): | $(TRUNNER_BPF_OBJS)
>
> @@ -636,7 +681,7 @@ $(OUTPUT)/$(TRUNNER_BINARY): $(TRUNNER_TEST_OBJS) \
> $(TRUNNER_BPFTOOL) \
> | $(TRUNNER_BINARY)-extras
> $$(call msg,BINARY,,$$@)
> - $(Q)$$(CC) $$(CFLAGS) $$(filter %.a %.o,$$^) $$(LDLIBS) -o $$@
> + $(Q)$$(CC) $$(CFLAGS) $$(filter %.a %.o,$$^) $$(LDLIBS) $$(LDFLAGS) -o $$@
> $(Q)$(RESOLVE_BTFIDS) --btf $(TRUNNER_OUTPUT)/btf_data.bpf.o $$@
> $(Q)ln -sf $(if $2,..,.)/tools/build/bpftool/$(USE_BOOTSTRAP)bpftool \
> $(OUTPUT)/$(if $2,$2/)bpftool
> @@ -655,6 +700,7 @@ TRUNNER_EXTRA_SOURCES := test_progs.c \
> cap_helpers.c \
> unpriv_helpers.c \
> netlink_helpers.c \
> + jit_disasm_helpers.c \
> test_loader.c \
> xsk.c \
> disasm.c \
> @@ -797,7 +843,8 @@ EXTRA_CLEAN := $(SCRATCH_DIR) $(HOST_SCRATCH_DIR) \
> $(addprefix $(OUTPUT)/,*.o *.d *.skel.h *.lskel.h *.subskel.h \
> no_alu32 cpuv4 bpf_gcc bpf_testmod.ko \
> bpf_test_no_cfi.ko \
> - liburandom_read.so)
> + liburandom_read.so) \
> + $(OUTPUT)/FEATURE-DUMP.selftests
>
> .PHONY: docs docs-clean
>
> diff --git a/tools/testing/selftests/bpf/jit_disasm_helpers.c b/tools/testing/selftests/bpf/jit_disasm_helpers.c
> new file mode 100644
> index 000000000000..ae704f7c5ee7
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/jit_disasm_helpers.c
> @@ -0,0 +1,228 @@
> +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
> +#include <bpf/bpf.h>
> +#include <bpf/libbpf.h>
> +#include <test_progs.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>
> +
> +#define MAX_LOCAL_LABELS 32
Some bpf progs, e.g., pyperf600.bpf.o has thousands of lables.
But since this file is for selftest and we expect the test
case will be simpler, so the above number '32' should be okay.
But it would be good to add a comment for this.
> +
> +static bool llvm_initialized;
> +
> +struct local_labels {
> + bool print_phase;
> + __u32 prog_len;
> + __u32 cnt;
> + __u32 pcs[MAX_LOCAL_LABELS];
> + char names[MAX_LOCAL_LABELS][4];
> +};
> +
> +/* Depending on labels->print_phase:
> + * - if false: record save jump labels within the program in labels->pcs;
> + * - if true: if ref_value is in labels->pcs, return corresponding labels->name
> + */
> +static const char *lookup_symbol(void *data, uint64_t ref_value, uint64_t *ref_type,
> + uint64_t ref_pc, const char **ref_name)
> +{
> + struct local_labels *labels = data;
> + uint64_t type = *ref_type;
> + int i;
> +
> + *ref_type = LLVMDisassembler_ReferenceType_InOut_None;
> + *ref_name = NULL;
> + if (type != LLVMDisassembler_ReferenceType_In_Branch)
> + return NULL;
> + for (i = 0; i < labels->cnt; ++i)
> + if (labels->pcs[i] == ref_value)
> + return labels->names[i];
> + if (!labels->print_phase && labels->cnt < MAX_LOCAL_LABELS &&
> + ref_value < labels->prog_len)
> + labels->pcs[labels->cnt++] = ref_value;
Maybe the following easy to understand?
if (labels->print_phase) {
for (i = 0; i < labels->cnt; ++i)
if (labels->pcs[i] == ref_value)
return labels->names[i];
} else {
if (labels->cnt < MAX_LOCAL_LABELS && ref_value < labels->prog_len)
labels->pcs[labels->cnt++] = ref_value;
}
> + return NULL;
> +}
> +
> +static int disasm_insn(LLVMDisasmContextRef ctx, uint8_t *image, __u32 len, __u32 pc,
> + char *buf, __u32 buf_sz)
> +{
> + int i, cnt;
> +
> + cnt = LLVMDisasmInstruction(ctx, image + pc, len - pc, pc,
> + buf, buf_sz);
> + if (cnt > 0)
> + return cnt;
> + PRINT_FAIL("Can't disasm instruction at offset %d:", pc);
> + for (i = 0; i < 16 && pc + i < len; ++i)
> + printf(" %02x", image[pc + i]);
> + printf("\n");
> + return -EINVAL;
> +}
> +
> +static int cmp_u32(const void *_a, const void *_b)
> +{
> + __u32 a = *(__u32 *)_a;
> + __u32 b = *(__u32 *)_b;
> +
> + if (a < b)
> + return -1;
> + if (a > b)
> + return 1;
> + return 0;
> +}
> +
> +static int disasm_one_func(FILE *text_out, uint8_t *image, __u32 len)
> +{
> + char *label, *colon, *triple = NULL;
> + LLVMDisasmContextRef ctx = NULL;
> + struct local_labels labels = {};
> + __u32 *label_pc, pc;
> + int i, cnt, err = 0;
> + char buf[256];
buf[16]?
> +
> + triple = LLVMGetDefaultTargetTriple();
> + ctx = LLVMCreateDisasm(triple, &labels, 0, NULL, lookup_symbol);
> + if (!ASSERT_OK_PTR(ctx, "LLVMCreateDisasm")) {
> + err = -EINVAL;
> + goto out;
> + }
> +
> + cnt = LLVMSetDisasmOptions(ctx, LLVMDisassembler_Option_PrintImmHex);
> + if (!ASSERT_EQ(cnt, 1, "LLVMSetDisasmOptions")) {
> + err = -EINVAL;
> + goto out;
> + }
> +
> + /* discover labels */
> + labels.prog_len = len;
> + pc = 0;
> + while (pc < len) {
> + cnt = disasm_insn(ctx, image, len, pc, buf, 1);
> + if (cnt < 0) {
> + err = cnt;
> + goto out;
> + }
> + pc += cnt;
> + }
> + qsort(labels.pcs, labels.cnt, sizeof(*labels.pcs), cmp_u32);
[...]
next prev parent reply other threads:[~2024-08-15 19:27 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-08-09 1:05 [PATCH bpf-next 0/4] __jited_x86 test tag to check x86 assembly after jit Eduard Zingerman
2024-08-09 1:05 ` [PATCH bpf-next 1/4] selftests/bpf: less spam in the log for message matching Eduard Zingerman
2024-08-09 1:05 ` [PATCH bpf-next 2/4] selftests/bpf: utility function to get program disassembly after jit Eduard Zingerman
2024-08-13 16:05 ` Yonghong Song
2024-08-13 22:01 ` Eduard Zingerman
2024-08-15 19:27 ` Yonghong Song [this message]
2024-08-15 19:34 ` Eduard Zingerman
2024-08-15 21:06 ` Andrii Nakryiko
2024-08-15 21:50 ` Eduard Zingerman
2024-08-15 22:04 ` Andrii Nakryiko
2024-08-19 19:45 ` Eduard Zingerman
2024-08-19 21:05 ` Andrii Nakryiko
2024-08-09 1:05 ` [PATCH bpf-next 3/4] selftests/bpf: __jited_x86 test tag to check x86 assembly " Eduard Zingerman
2024-08-15 21:11 ` Andrii Nakryiko
2024-08-15 21:48 ` Eduard Zingerman
2024-08-09 1:05 ` [PATCH bpf-next 4/4] selftests/bpf: validate jit behaviour for tail calls Eduard Zingerman
2024-08-15 21:15 ` Andrii Nakryiko
2024-08-15 21:42 ` Eduard Zingerman
2024-08-15 22:07 ` Andrii Nakryiko
2024-08-15 22:10 ` Eduard Zingerman
2024-08-15 22:14 ` Andrii Nakryiko
2024-08-15 22:19 ` Eduard Zingerman
2024-08-15 21:32 ` Yonghong Song
2024-08-15 21:47 ` Eduard Zingerman
2024-08-15 22:09 ` Andrii Nakryiko
2024-08-15 22:16 ` Eduard Zingerman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=de45cb69-8116-4589-a0ba-9e77ba1c3e60@linux.dev \
--to=yonghong.song@linux.dev \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=hffilwlqm@gmail.com \
--cc=kernel-team@fb.com \
--cc=martin.lau@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox