mirror of
https://github.com/torvalds/linux.git
synced 2026-05-28 17:13:52 +02:00
selftests/bpf: utility function to get program disassembly after jit
This commit adds a utility function to get disassembled text for jited
representation of a BPF program designated by file descriptor.
Function prototype looks as follows:
int get_jited_program_text(int fd, char *text, size_t text_sz)
Where 'fd' is a file descriptor for the program, 'text' and 'text_sz'
refer to a destination buffer for disassembled text.
Output format looks as follows:
18: 77 06 ja L0
1a: 50 pushq %rax
1b: 48 89 e0 movq %rsp, %rax
1e: eb 01 jmp L1
20: 50 L0: pushq %rax
21: 50 L1: pushq %rax
^ ^^^^^^^^ ^ ^^^^^^^^^^^^^^^^^^
| binary insn | textual insn
| representation | representation
| |
instruction offset inferred local label name
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 -EOPNOTSUPP at runtime.
[1] commit eb9d1acf63 ("bpftool: Add LLVM as default library for disassembling JIT-ed programs")
Acked-by: Yonghong Song <yonghong.song@linux.dev>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20240820102357.3372779-6-eddyz87@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
parent
f8d161756d
commit
b991fc5207
1
tools/testing/selftests/bpf/.gitignore
vendored
1
tools/testing/selftests/bpf/.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) \
|
||||
|
|
@ -60,6 +67,9 @@ progs/timer_crash.c-CFLAGS := -fno-strict-aliasing
|
|||
progs/test_global_func9.c-CFLAGS := -fno-strict-aliasing
|
||||
progs/verifier_nocsr.c-CFLAGS := -fno-strict-aliasing
|
||||
|
||||
# Some utility functions use LLVM libraries
|
||||
jit_disasm_helpers.c-CFLAGS = $(LLVM_CFLAGS)
|
||||
|
||||
ifneq ($(LLVM),)
|
||||
# Silence some warnings when compiled with clang
|
||||
CFLAGS += -Wno-unused-command-line-argument
|
||||
|
|
@ -168,6 +178,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
|
||||
|
|
@ -612,6 +647,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) \
|
||||
|
|
@ -628,6 +667,9 @@ ifneq ($2:$(OUTPUT),:$(shell pwd))
|
|||
$(Q)rsync -aq $$^ $(TRUNNER_OUTPUT)/
|
||||
endif
|
||||
|
||||
$(OUTPUT)/$(TRUNNER_BINARY): LDLIBS += $$(LLVM_LDLIBS)
|
||||
$(OUTPUT)/$(TRUNNER_BINARY): LDFLAGS += $$(LLVM_LDFLAGS)
|
||||
|
||||
# some X.test.o files have runtime dependencies on Y.bpf.o files
|
||||
$(OUTPUT)/$(TRUNNER_BINARY): | $(TRUNNER_BPF_OBJS)
|
||||
|
||||
|
|
@ -637,7 +679,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
|
||||
|
|
@ -656,6 +698,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 \
|
||||
|
|
@ -798,7 +841,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
|
||||
|
||||
|
|
|
|||
234
tools/testing/selftests/bpf/jit_disasm_helpers.c
Normal file
234
tools/testing/selftests/bpf/jit_disasm_helpers.c
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
// 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>
|
||||
|
||||
/* The intent is to use get_jited_program_text() for small test
|
||||
* programs written in BPF assembly, thus assume that 32 local labels
|
||||
* would be sufficient.
|
||||
*/
|
||||
#define MAX_LOCAL_LABELS 32
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
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;
|
||||
/* Depending on labels->print_phase either discover local labels or
|
||||
* return a name assigned with local jump target:
|
||||
* - if print_phase is true and ref_value is in labels->pcs,
|
||||
* return corresponding labels->name.
|
||||
* - if print_phase is false, save program-local jump targets
|
||||
* in labels->pcs;
|
||||
*/
|
||||
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[64];
|
||||
|
||||
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);
|
||||
for (i = 0; i < labels.cnt; ++i)
|
||||
/* use (i % 100) to avoid format truncation warning */
|
||||
snprintf(labels.names[i], sizeof(labels.names[i]), "L%d", i % 100);
|
||||
|
||||
/* now print with labels */
|
||||
labels.print_phase = true;
|
||||
pc = 0;
|
||||
while (pc < len) {
|
||||
cnt = disasm_insn(ctx, image, len, pc, buf, sizeof(buf));
|
||||
if (cnt < 0) {
|
||||
err = cnt;
|
||||
goto out;
|
||||
}
|
||||
label_pc = bsearch(&pc, labels.pcs, labels.cnt, sizeof(*labels.pcs), cmp_u32);
|
||||
label = "";
|
||||
colon = "";
|
||||
if (label_pc) {
|
||||
label = labels.names[label_pc - labels.pcs];
|
||||
colon = ":";
|
||||
}
|
||||
fprintf(text_out, "%x:\t", pc);
|
||||
for (i = 0; i < cnt; ++i)
|
||||
fprintf(text_out, "%02x ", image[pc + i]);
|
||||
for (i = cnt * 3; i < 12 * 3; ++i)
|
||||
fputc(' ', text_out);
|
||||
fprintf(text_out, "%s%s%s\n", label, colon, buf);
|
||||
pc += cnt;
|
||||
}
|
||||
|
||||
out:
|
||||
if (triple)
|
||||
LLVMDisposeMessage(triple);
|
||||
if (ctx)
|
||||
LLVMDisasmDispose(ctx);
|
||||
return err;
|
||||
}
|
||||
|
||||
int get_jited_program_text(int fd, char *text, size_t text_sz)
|
||||
{
|
||||
struct bpf_prog_info info = {};
|
||||
__u32 info_len = sizeof(info);
|
||||
__u32 jited_funcs, len, pc;
|
||||
__u32 *func_lens = NULL;
|
||||
FILE *text_out = NULL;
|
||||
uint8_t *image = NULL;
|
||||
int i, err = 0;
|
||||
|
||||
if (!llvm_initialized) {
|
||||
LLVMInitializeAllTargetInfos();
|
||||
LLVMInitializeAllTargetMCs();
|
||||
LLVMInitializeAllDisassemblers();
|
||||
llvm_initialized = 1;
|
||||
}
|
||||
|
||||
text_out = fmemopen(text, text_sz, "w");
|
||||
if (!ASSERT_OK_PTR(text_out, "open_memstream")) {
|
||||
err = -errno;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* first call is to find out jited program len */
|
||||
err = bpf_prog_get_info_by_fd(fd, &info, &info_len);
|
||||
if (!ASSERT_OK(err, "bpf_prog_get_info_by_fd #1"))
|
||||
goto out;
|
||||
|
||||
len = info.jited_prog_len;
|
||||
image = malloc(len);
|
||||
if (!ASSERT_OK_PTR(image, "malloc(info.jited_prog_len)")) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
jited_funcs = info.nr_jited_func_lens;
|
||||
func_lens = malloc(jited_funcs * sizeof(__u32));
|
||||
if (!ASSERT_OK_PTR(func_lens, "malloc(info.nr_jited_func_lens)")) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.jited_prog_insns = (__u64)image;
|
||||
info.jited_prog_len = len;
|
||||
info.jited_func_lens = (__u64)func_lens;
|
||||
info.nr_jited_func_lens = jited_funcs;
|
||||
err = bpf_prog_get_info_by_fd(fd, &info, &info_len);
|
||||
if (!ASSERT_OK(err, "bpf_prog_get_info_by_fd #2"))
|
||||
goto out;
|
||||
|
||||
for (pc = 0, i = 0; i < jited_funcs; ++i) {
|
||||
fprintf(text_out, "func #%d:\n", i);
|
||||
disasm_one_func(text_out, image + pc, func_lens[i]);
|
||||
fprintf(text_out, "\n");
|
||||
pc += func_lens[i];
|
||||
}
|
||||
|
||||
out:
|
||||
if (text_out)
|
||||
fclose(text_out);
|
||||
if (image)
|
||||
free(image);
|
||||
if (func_lens)
|
||||
free(func_lens);
|
||||
return err;
|
||||
}
|
||||
|
||||
#else /* HAVE_LLVM_SUPPORT */
|
||||
|
||||
int get_jited_program_text(int fd, char *text, size_t text_sz)
|
||||
{
|
||||
if (env.verbosity >= VERBOSE_VERY)
|
||||
printf("compiled w/o llvm development libraries, can't dis-assembly binary code");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
#endif /* HAVE_LLVM_SUPPORT */
|
||||
10
tools/testing/selftests/bpf/jit_disasm_helpers.h
Normal file
10
tools/testing/selftests/bpf/jit_disasm_helpers.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
|
||||
#ifndef __JIT_DISASM_HELPERS_H
|
||||
#define __JIT_DISASM_HELPERS_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
int get_jited_program_text(int fd, char *text, size_t text_sz);
|
||||
|
||||
#endif /* __JIT_DISASM_HELPERS_H */
|
||||
Loading…
Reference in New Issue
Block a user