1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
3 #include <bpf/libbpf.h>
4 #include <test_progs.h>
6 #ifdef HAVE_LLVM_SUPPORT
8 #include <llvm-c/Core.h>
9 #include <llvm-c/Disassembler.h>
10 #include <llvm-c/Target.h>
11 #include <llvm-c/TargetMachine.h>
13 /* The intent is to use get_jited_program_text() for small test
14 * programs written in BPF assembly, thus assume that 32 local labels
15 * would be sufficient.
17 #define MAX_LOCAL_LABELS 32
19 /* Local labels are encoded as 'L42', this requires 4 bytes of storage:
20 * 3 characters + zero byte
22 #define LOCAL_LABEL_LEN 4
24 static bool llvm_initialized
;
30 __u32 pcs
[MAX_LOCAL_LABELS
];
31 char names
[MAX_LOCAL_LABELS
][LOCAL_LABEL_LEN
];
34 static const char *lookup_symbol(void *data
, uint64_t ref_value
, uint64_t *ref_type
,
35 uint64_t ref_pc
, const char **ref_name
)
37 struct local_labels
*labels
= data
;
38 uint64_t type
= *ref_type
;
41 *ref_type
= LLVMDisassembler_ReferenceType_InOut_None
;
43 if (type
!= LLVMDisassembler_ReferenceType_In_Branch
)
45 /* Depending on labels->print_phase either discover local labels or
46 * return a name assigned with local jump target:
47 * - if print_phase is true and ref_value is in labels->pcs,
48 * return corresponding labels->name.
49 * - if print_phase is false, save program-local jump targets
52 if (labels
->print_phase
) {
53 for (i
= 0; i
< labels
->cnt
; ++i
)
54 if (labels
->pcs
[i
] == ref_value
)
55 return labels
->names
[i
];
57 if (labels
->cnt
< MAX_LOCAL_LABELS
&& ref_value
< labels
->prog_len
)
58 labels
->pcs
[labels
->cnt
++] = ref_value
;
63 static int disasm_insn(LLVMDisasmContextRef ctx
, uint8_t *image
, __u32 len
, __u32 pc
,
64 char *buf
, __u32 buf_sz
)
68 cnt
= LLVMDisasmInstruction(ctx
, image
+ pc
, len
- pc
, pc
,
72 PRINT_FAIL("Can't disasm instruction at offset %d:", pc
);
73 for (i
= 0; i
< 16 && pc
+ i
< len
; ++i
)
74 printf(" %02x", image
[pc
+ i
]);
79 static int cmp_u32(const void *_a
, const void *_b
)
81 __u32 a
= *(__u32
*)_a
;
82 __u32 b
= *(__u32
*)_b
;
91 static int disasm_one_func(FILE *text_out
, uint8_t *image
, __u32 len
)
93 char *label
, *colon
, *triple
= NULL
;
94 LLVMDisasmContextRef ctx
= NULL
;
95 struct local_labels labels
= {};
100 triple
= LLVMGetDefaultTargetTriple();
101 ctx
= LLVMCreateDisasm(triple
, &labels
, 0, NULL
, lookup_symbol
);
102 if (!ASSERT_OK_PTR(ctx
, "LLVMCreateDisasm")) {
107 cnt
= LLVMSetDisasmOptions(ctx
, LLVMDisassembler_Option_PrintImmHex
);
108 if (!ASSERT_EQ(cnt
, 1, "LLVMSetDisasmOptions")) {
113 /* discover labels */
114 labels
.prog_len
= len
;
117 cnt
= disasm_insn(ctx
, image
, len
, pc
, buf
, 1);
124 qsort(labels
.pcs
, labels
.cnt
, sizeof(*labels
.pcs
), cmp_u32
);
125 for (i
= 0; i
< labels
.cnt
; ++i
)
126 /* gcc is unable to infer upper bound for labels.cnt and assumes
127 * it to be U32_MAX. U32_MAX takes 10 decimal digits.
128 * snprintf below prints into labels.names[*],
129 * which has space only for two digits and a letter.
130 * To avoid truncation warning use (i % MAX_LOCAL_LABELS),
131 * which informs gcc about printed value upper bound.
133 snprintf(labels
.names
[i
], sizeof(labels
.names
[i
]), "L%d", i
% MAX_LOCAL_LABELS
);
135 /* now print with labels */
136 labels
.print_phase
= true;
139 cnt
= disasm_insn(ctx
, image
, len
, pc
, buf
, sizeof(buf
));
144 label_pc
= bsearch(&pc
, labels
.pcs
, labels
.cnt
, sizeof(*labels
.pcs
), cmp_u32
);
148 label
= labels
.names
[label_pc
- labels
.pcs
];
151 fprintf(text_out
, "%x:\t", pc
);
152 for (i
= 0; i
< cnt
; ++i
)
153 fprintf(text_out
, "%02x ", image
[pc
+ i
]);
154 for (i
= cnt
* 3; i
< 12 * 3; ++i
)
155 fputc(' ', text_out
);
156 fprintf(text_out
, "%s%s%s\n", label
, colon
, buf
);
162 LLVMDisposeMessage(triple
);
164 LLVMDisasmDispose(ctx
);
168 int get_jited_program_text(int fd
, char *text
, size_t text_sz
)
170 struct bpf_prog_info info
= {};
171 __u32 info_len
= sizeof(info
);
172 __u32 jited_funcs
, len
, pc
;
173 __u32
*func_lens
= NULL
;
174 FILE *text_out
= NULL
;
175 uint8_t *image
= NULL
;
178 if (!llvm_initialized
) {
179 LLVMInitializeAllTargetInfos();
180 LLVMInitializeAllTargetMCs();
181 LLVMInitializeAllDisassemblers();
182 llvm_initialized
= 1;
185 text_out
= fmemopen(text
, text_sz
, "w");
186 if (!ASSERT_OK_PTR(text_out
, "open_memstream")) {
191 /* first call is to find out jited program len */
192 err
= bpf_prog_get_info_by_fd(fd
, &info
, &info_len
);
193 if (!ASSERT_OK(err
, "bpf_prog_get_info_by_fd #1"))
196 len
= info
.jited_prog_len
;
198 if (!ASSERT_OK_PTR(image
, "malloc(info.jited_prog_len)")) {
203 jited_funcs
= info
.nr_jited_func_lens
;
204 func_lens
= malloc(jited_funcs
* sizeof(__u32
));
205 if (!ASSERT_OK_PTR(func_lens
, "malloc(info.nr_jited_func_lens)")) {
210 memset(&info
, 0, sizeof(info
));
211 info
.jited_prog_insns
= (__u64
)image
;
212 info
.jited_prog_len
= len
;
213 info
.jited_func_lens
= (__u64
)func_lens
;
214 info
.nr_jited_func_lens
= jited_funcs
;
215 err
= bpf_prog_get_info_by_fd(fd
, &info
, &info_len
);
216 if (!ASSERT_OK(err
, "bpf_prog_get_info_by_fd #2"))
219 for (pc
= 0, i
= 0; i
< jited_funcs
; ++i
) {
220 fprintf(text_out
, "func #%d:\n", i
);
221 disasm_one_func(text_out
, image
+ pc
, func_lens
[i
]);
222 fprintf(text_out
, "\n");
236 #else /* HAVE_LLVM_SUPPORT */
238 int get_jited_program_text(int fd
, char *text
, size_t text_sz
)
240 if (env
.verbosity
>= VERBOSE_VERY
)
241 printf("compiled w/o llvm development libraries, can't dis-assembly binary code");
245 #endif /* HAVE_LLVM_SUPPORT */