Skip to content

Commit eb9d1ac

Browse files
qmonnetAlexei Starovoitov
authored andcommitted
bpftool: Add LLVM as default library for disassembling JIT-ed programs
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. For building bpftool, we have to detect how the libbfd version on the system behaves, which is why we have to handle features disassembler-four-args and disassembler-init-styled in the Makefile. When it comes to shipping bpftool, this has also caused issues with several distribution maintainers unwilling to support the feature (see for example Debian's page for binutils-dev, which ships libbfd: "Note that building Debian packages which depend on the shared libbfd is Not Allowed." [0]). For these reasons, we add support for LLVM as an alternative to libbfd for disassembling instructions of JIT-ed programs. Thanks to the preparation work in the previous commits, it's easy to add the library by passing the relevant compilation options in the Makefile, and by adding the functions for setting up the LLVM disassembler in file jit_disasm.c. The LLVM disassembler requires the LLVM development package (usually llvm-dev or llvm-devel). The expectation is that the interface for this disassembler will be more stable. There is a note in LLVM's Developer Policy [1] stating that the stability for the C API is "best effort" and not guaranteed, but at least there is some effort to keep compatibility when possible (which hasn't really been the case for libbfd so far). Furthermore, the Debian page for the related LLVM package does not caution against linking to the lib, as binutils-dev page does. Naturally, the display of disassembled instructions comes with a few minor differences. Here is a sample output with libbfd (already supported before this patch): # bpftool prog dump jited id 56 bpf_prog_6deef7357e7b4530: 0: nopl 0x0(%rax,%rax,1) 5: xchg %ax,%ax 7: push %rbp 8: mov %rsp,%rbp b: push %rbx c: push %r13 e: push %r14 10: mov %rdi,%rbx 13: movzwq 0xb4(%rbx),%r13 1b: xor %r14d,%r14d 1e: or $0x2,%r14d 22: mov $0x1,%eax 27: cmp $0x2,%r14 2b: jne 0x000000000000002f 2d: xor %eax,%eax 2f: pop %r14 31: pop %r13 33: pop %rbx 34: leave 35: ret LLVM supports several variants that we could set when initialising the disassembler, for example with: LLVMSetDisasmOptions(*ctx, LLVMDisassembler_Option_AsmPrinterVariant); but the default printer is used for now. Here is the output with LLVM: # bpftool prog dump jited id 56 bpf_prog_6deef7357e7b4530: 0: nopl (%rax,%rax) 5: nop 7: pushq %rbp 8: movq %rsp, %rbp b: pushq %rbx c: pushq %r13 e: pushq %r14 10: movq %rdi, %rbx 13: movzwq 180(%rbx), %r13 1b: xorl %r14d, %r14d 1e: orl $2, %r14d 22: movl $1, %eax 27: cmpq $2, %r14 2b: jne 0x2f 2d: xorl %eax, %eax 2f: popq %r14 31: popq %r13 33: popq %rbx 34: leave 35: retq The LLVM disassembler comes as the default choice, with libbfd as a fall-back. Of course, we could replace libbfd entirely and avoid supporting two different libraries. One reason for keeping libbfd is that, right now, 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 motivation is that libbfd supports nfp instruction for Netronome's SmartNICs and can be used to disassemble offloaded programs, something that LLVM cannot do. If libbfd's interface breaks again in the future, we might reconsider keeping support for it. [0] https://packages.debian.org/buster/binutils-dev [1] https://llvm.org/docs/DeveloperPolicy.html#c-api-changes Signed-off-by: Quentin Monnet <[email protected]> Tested-by: Niklas Söderlund <[email protected]> Acked-by: Yonghong Song <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent e1947c7 commit eb9d1ac

File tree

3 files changed

+141
-23
lines changed

3 files changed

+141
-23
lines changed

tools/bpf/bpftool/Makefile

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ RM ?= rm -f
9595
FEATURE_USER = .bpftool
9696

9797
FEATURE_TESTS := clang-bpf-co-re
98+
FEATURE_TESTS += llvm
9899
FEATURE_TESTS += libcap
99100
FEATURE_TESTS += libbfd
100101
FEATURE_TESTS += libbfd-liberty
@@ -103,6 +104,7 @@ FEATURE_TESTS += disassembler-four-args
103104
FEATURE_TESTS += disassembler-init-styled
104105

105106
FEATURE_DISPLAY := clang-bpf-co-re
107+
FEATURE_DISPLAY += llvm
106108
FEATURE_DISPLAY += libcap
107109
FEATURE_DISPLAY += libbfd
108110
FEATURE_DISPLAY += libbfd-liberty
@@ -137,27 +139,37 @@ all: $(OUTPUT)bpftool
137139

138140
SRCS := $(wildcard *.c)
139141

140-
ifeq ($(feature-libbfd),1)
141-
LIBS += -lbfd -ldl -lopcodes
142-
else ifeq ($(feature-libbfd-liberty),1)
143-
LIBS += -lbfd -ldl -lopcodes -liberty
144-
else ifeq ($(feature-libbfd-liberty-z),1)
145-
LIBS += -lbfd -ldl -lopcodes -liberty -lz
146-
endif
147-
148-
# If one of the above feature combinations is set, we support libbfd
149-
ifneq ($(filter -lbfd,$(LIBS)),)
150-
CFLAGS += -DHAVE_LIBBFD_SUPPORT
151-
152-
# Libbfd interface changed over time, figure out what we need
153-
ifeq ($(feature-disassembler-four-args), 1)
154-
CFLAGS += -DDISASM_FOUR_ARGS_SIGNATURE
142+
ifeq ($(feature-llvm),1)
143+
# If LLVM is available, use it for JIT disassembly
144+
CFLAGS += -DHAVE_LLVM_SUPPORT
145+
LLVM_CONFIG_LIB_COMPONENTS := mcdisassembler all-targets
146+
CFLAGS += $(shell $(LLVM_CONFIG) --cflags --libs $(LLVM_CONFIG_LIB_COMPONENTS))
147+
LIBS += $(shell $(LLVM_CONFIG) --libs $(LLVM_CONFIG_LIB_COMPONENTS))
148+
LDFLAGS += $(shell $(LLVM_CONFIG) --ldflags)
149+
else
150+
# Fall back on libbfd
151+
ifeq ($(feature-libbfd),1)
152+
LIBS += -lbfd -ldl -lopcodes
153+
else ifeq ($(feature-libbfd-liberty),1)
154+
LIBS += -lbfd -ldl -lopcodes -liberty
155+
else ifeq ($(feature-libbfd-liberty-z),1)
156+
LIBS += -lbfd -ldl -lopcodes -liberty -lz
155157
endif
156-
ifeq ($(feature-disassembler-init-styled), 1)
157-
CFLAGS += -DDISASM_INIT_STYLED
158+
159+
# If one of the above feature combinations is set, we support libbfd
160+
ifneq ($(filter -lbfd,$(LIBS)),)
161+
CFLAGS += -DHAVE_LIBBFD_SUPPORT
162+
163+
# Libbfd interface changed over time, figure out what we need
164+
ifeq ($(feature-disassembler-four-args), 1)
165+
CFLAGS += -DDISASM_FOUR_ARGS_SIGNATURE
166+
endif
167+
ifeq ($(feature-disassembler-init-styled), 1)
168+
CFLAGS += -DDISASM_INIT_STYLED
169+
endif
158170
endif
159171
endif
160-
ifeq ($(filter -DHAVE_LIBBFD_SUPPORT,$(CFLAGS)),)
172+
ifeq ($(filter -DHAVE_LLVM_SUPPORT -DHAVE_LIBBFD_SUPPORT,$(CFLAGS)),)
161173
# No support for JIT disassembly
162174
SRCS := $(filter-out jit_disasm.c,$(SRCS))
163175
endif

tools/bpf/bpftool/jit_disasm.c

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,123 @@
2020
#include <stdlib.h>
2121
#include <unistd.h>
2222
#include <string.h>
23-
#include <bfd.h>
24-
#include <dis-asm.h>
2523
#include <sys/stat.h>
2624
#include <limits.h>
2725
#include <bpf/libbpf.h>
26+
27+
#ifdef HAVE_LLVM_SUPPORT
28+
#include <llvm-c/Core.h>
29+
#include <llvm-c/Disassembler.h>
30+
#include <llvm-c/Target.h>
31+
#include <llvm-c/TargetMachine.h>
32+
#endif
33+
34+
#ifdef HAVE_LIBBFD_SUPPORT
35+
#include <bfd.h>
36+
#include <dis-asm.h>
2837
#include <tools/dis-asm-compat.h>
38+
#endif
2939

3040
#include "json_writer.h"
3141
#include "main.h"
3242

3343
static int oper_count;
3444

45+
#ifdef HAVE_LLVM_SUPPORT
46+
#define DISASM_SPACER
47+
48+
typedef LLVMDisasmContextRef disasm_ctx_t;
49+
50+
static int printf_json(char *s)
51+
{
52+
s = strtok(s, " \t");
53+
jsonw_string_field(json_wtr, "operation", s);
54+
55+
jsonw_name(json_wtr, "operands");
56+
jsonw_start_array(json_wtr);
57+
oper_count = 1;
58+
59+
while ((s = strtok(NULL, " \t,()")) != 0) {
60+
jsonw_string(json_wtr, s);
61+
oper_count++;
62+
}
63+
return 0;
64+
}
65+
66+
/* This callback to set the ref_type is necessary to have the LLVM disassembler
67+
* print PC-relative addresses instead of byte offsets for branch instruction
68+
* targets.
69+
*/
70+
static const char *
71+
symbol_lookup_callback(__maybe_unused void *disasm_info,
72+
__maybe_unused uint64_t ref_value,
73+
uint64_t *ref_type, __maybe_unused uint64_t ref_PC,
74+
__maybe_unused const char **ref_name)
75+
{
76+
*ref_type = LLVMDisassembler_ReferenceType_InOut_None;
77+
return NULL;
78+
}
79+
80+
static int
81+
init_context(disasm_ctx_t *ctx, const char *arch,
82+
__maybe_unused const char *disassembler_options,
83+
__maybe_unused unsigned char *image, __maybe_unused ssize_t len)
84+
{
85+
char *triple;
86+
87+
if (arch) {
88+
p_err("Architecture %s not supported", arch);
89+
return -1;
90+
}
91+
92+
triple = LLVMGetDefaultTargetTriple();
93+
if (!triple) {
94+
p_err("Failed to retrieve triple");
95+
return -1;
96+
}
97+
*ctx = LLVMCreateDisasm(triple, NULL, 0, NULL, symbol_lookup_callback);
98+
LLVMDisposeMessage(triple);
99+
100+
if (!*ctx) {
101+
p_err("Failed to create disassembler");
102+
return -1;
103+
}
104+
105+
return 0;
106+
}
107+
108+
static void destroy_context(disasm_ctx_t *ctx)
109+
{
110+
LLVMDisposeMessage(*ctx);
111+
}
112+
113+
static int
114+
disassemble_insn(disasm_ctx_t *ctx, unsigned char *image, ssize_t len, int pc)
115+
{
116+
char buf[256];
117+
int count;
118+
119+
count = LLVMDisasmInstruction(*ctx, image + pc, len - pc, pc,
120+
buf, sizeof(buf));
121+
if (json_output)
122+
printf_json(buf);
123+
else
124+
printf("%s", buf);
125+
126+
return count;
127+
}
128+
129+
int disasm_init(void)
130+
{
131+
LLVMInitializeNativeTarget();
132+
LLVMInitializeNativeDisassembler();
133+
return 0;
134+
}
135+
#endif /* HAVE_LLVM_SUPPORT */
136+
137+
#ifdef HAVE_LIBBFD_SUPPORT
138+
#define DISASM_SPACER "\t"
139+
35140
typedef struct {
36141
struct disassemble_info *info;
37142
disassembler_ftype disassemble;
@@ -210,6 +315,7 @@ int disasm_init(void)
210315
bfd_init();
211316
return 0;
212317
}
318+
#endif /* HAVE_LIBBPFD_SUPPORT */
213319

214320
int disasm_print_insn(unsigned char *image, ssize_t len, int opcodes,
215321
const char *arch, const char *disassembler_options,
@@ -252,7 +358,7 @@ int disasm_print_insn(unsigned char *image, ssize_t len, int opcodes,
252358
if (linfo)
253359
btf_dump_linfo_plain(btf, linfo, "; ",
254360
linum);
255-
printf("%4x:\t", pc);
361+
printf("%4x:" DISASM_SPACER, pc);
256362
}
257363

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

tools/bpf/bpftool/main.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ int map_parse_fds(int *argc, char ***argv, int **fds);
172172
int map_parse_fd_and_info(int *argc, char ***argv, void *info, __u32 *info_len);
173173

174174
struct bpf_prog_linfo;
175-
#ifdef HAVE_LIBBFD_SUPPORT
175+
#if defined(HAVE_LLVM_SUPPORT) || defined(HAVE_LIBBFD_SUPPORT)
176176
int disasm_print_insn(unsigned char *image, ssize_t len, int opcodes,
177177
const char *arch, const char *disassembler_options,
178178
const struct btf *btf,
@@ -193,7 +193,7 @@ int disasm_print_insn(unsigned char *image, ssize_t len, int opcodes,
193193
}
194194
static inline int disasm_init(void)
195195
{
196-
p_err("No libbfd support");
196+
p_err("No JIT disassembly support");
197197
return -1;
198198
}
199199
#endif

0 commit comments

Comments
 (0)