xref: /linux/tools/testing/selftests/bpf/jit_disasm_helpers.c (revision 557d0cc3f2520feba45360beeafb93203b3230e0)
1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2 #include <bpf/bpf.h>
3 #include <bpf/libbpf.h>
4 #include <test_progs.h>
5 
6 #ifdef HAVE_LLVM_SUPPORT
7 
8 #include <llvm-c/Core.h>
9 #include <llvm-c/Disassembler.h>
10 #include <llvm-c/Target.h>
11 #include <llvm-c/TargetMachine.h>
12 
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.
16  */
17 #define MAX_LOCAL_LABELS 32
18 
19 /* Local labels are encoded as 'L42', this requires 4 bytes of storage:
20  * 3 characters + zero byte
21  */
22 #define LOCAL_LABEL_LEN 4
23 
24 static bool llvm_initialized;
25 
26 struct local_labels {
27 	bool print_phase;
28 	__u32 prog_len;
29 	__u32 cnt;
30 	__u32 pcs[MAX_LOCAL_LABELS];
31 	char names[MAX_LOCAL_LABELS][LOCAL_LABEL_LEN];
32 };
33 
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)
36 {
37 	struct local_labels *labels = data;
38 	uint64_t type = *ref_type;
39 	int i;
40 
41 	*ref_type = LLVMDisassembler_ReferenceType_InOut_None;
42 	*ref_name = NULL;
43 	if (type != LLVMDisassembler_ReferenceType_In_Branch)
44 		return NULL;
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
50 	 *   in labels->pcs;
51 	 */
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];
56 	} else {
57 		if (labels->cnt < MAX_LOCAL_LABELS && ref_value < labels->prog_len)
58 			labels->pcs[labels->cnt++] = ref_value;
59 	}
60 	return NULL;
61 }
62 
63 static int disasm_insn(LLVMDisasmContextRef ctx, uint8_t *image, __u32 len, __u32 pc,
64 		       char *buf, __u32 buf_sz)
65 {
66 	int i, cnt;
67 
68 	cnt = LLVMDisasmInstruction(ctx, image + pc, len - pc, pc,
69 				    buf, buf_sz);
70 	if (cnt > 0)
71 		return cnt;
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]);
75 	printf("\n");
76 	return -EINVAL;
77 }
78 
79 static int cmp_u32(const void *_a, const void *_b)
80 {
81 	__u32 a = *(__u32 *)_a;
82 	__u32 b = *(__u32 *)_b;
83 
84 	if (a < b)
85 		return -1;
86 	if (a > b)
87 		return 1;
88 	return 0;
89 }
90 
91 static int disasm_one_func(FILE *text_out, uint8_t *image, __u32 len)
92 {
93 	char *label, *colon, *triple = NULL;
94 	LLVMDisasmContextRef ctx = NULL;
95 	struct local_labels labels = {};
96 	__u32 *label_pc, pc;
97 	int i, cnt, err = 0;
98 	char buf[64];
99 	char *cpu, *features;
100 
101 	triple = LLVMGetDefaultTargetTriple();
102 
103 	cpu = LLVMGetHostCPUName();
104 	features = LLVMGetHostCPUFeatures();
105 
106 	ctx = LLVMCreateDisasmCPUFeatures(triple, cpu, features, &labels, 0, NULL, lookup_symbol);
107 
108 	LLVMDisposeMessage(cpu);
109 	LLVMDisposeMessage(features);
110 
111 	if (!ASSERT_OK_PTR(ctx, "LLVMCreateDisasmCPUFeatures")) {
112 		err = -EINVAL;
113 		goto out;
114 	}
115 
116 	cnt = LLVMSetDisasmOptions(ctx, LLVMDisassembler_Option_PrintImmHex);
117 	if (!ASSERT_EQ(cnt, 1, "LLVMSetDisasmOptions")) {
118 		err = -EINVAL;
119 		goto out;
120 	}
121 
122 	/* discover labels */
123 	labels.prog_len = len;
124 	pc = 0;
125 	while (pc < len) {
126 		cnt = disasm_insn(ctx, image, len, pc, buf, 1);
127 		if (cnt < 0) {
128 			err = cnt;
129 			goto out;
130 		}
131 		pc += cnt;
132 	}
133 	qsort(labels.pcs, labels.cnt, sizeof(*labels.pcs), cmp_u32);
134 	/* gcc is unable to infer upper bound for labels.cnt and
135 	 * assumes it to be U32_MAX. U32_MAX takes 10 decimal digits.
136 	 * snprintf below prints into labels.names[*], which has space
137 	 * only for two digits and a letter.  To avoid truncation
138 	 * warning use (i < MAX_LOCAL_LABELS), which informs gcc about
139 	 * printed value upper bound.
140 	 */
141 	for (i = 0; i < labels.cnt && i < MAX_LOCAL_LABELS; ++i)
142 		snprintf(labels.names[i], sizeof(labels.names[i]), "L%d", i);
143 
144 	/* now print with labels */
145 	labels.print_phase = true;
146 	pc = 0;
147 	while (pc < len) {
148 		cnt = disasm_insn(ctx, image, len, pc, buf, sizeof(buf));
149 		if (cnt < 0) {
150 			err = cnt;
151 			goto out;
152 		}
153 		label_pc = bsearch(&pc, labels.pcs, labels.cnt, sizeof(*labels.pcs), cmp_u32);
154 		label = "";
155 		colon = "";
156 		if (label_pc) {
157 			label = labels.names[label_pc - labels.pcs];
158 			colon = ":";
159 		}
160 		fprintf(text_out, "%x:\t", pc);
161 		for (i = 0; i < cnt; ++i)
162 			fprintf(text_out, "%02x ", image[pc + i]);
163 		for (i = cnt * 3; i < 12 * 3; ++i)
164 			fputc(' ', text_out);
165 		fprintf(text_out, "%s%s%s\n", label, colon, buf);
166 		pc += cnt;
167 	}
168 
169 out:
170 	if (triple)
171 		LLVMDisposeMessage(triple);
172 	if (ctx)
173 		LLVMDisasmDispose(ctx);
174 	return err;
175 }
176 
177 int get_jited_program_text(int fd, char *text, size_t text_sz)
178 {
179 	struct bpf_prog_info info = {};
180 	__u32 info_len = sizeof(info);
181 	__u32 jited_funcs, len, pc;
182 	__u32 *func_lens = NULL;
183 	FILE *text_out = NULL;
184 	uint8_t *image = NULL;
185 	int i, err = 0;
186 
187 	if (!llvm_initialized) {
188 		LLVMInitializeAllTargetInfos();
189 		LLVMInitializeAllTargetMCs();
190 		LLVMInitializeAllDisassemblers();
191 		llvm_initialized = 1;
192 	}
193 
194 	text_out = fmemopen(text, text_sz, "w");
195 	if (!ASSERT_OK_PTR(text_out, "open_memstream")) {
196 		err = -errno;
197 		goto out;
198 	}
199 
200 	/* first call is to find out jited program len */
201 	err = bpf_prog_get_info_by_fd(fd, &info, &info_len);
202 	if (!ASSERT_OK(err, "bpf_prog_get_info_by_fd #1"))
203 		goto out;
204 
205 	len = info.jited_prog_len;
206 	image = malloc(len);
207 	if (!ASSERT_OK_PTR(image, "malloc(info.jited_prog_len)")) {
208 		err = -ENOMEM;
209 		goto out;
210 	}
211 
212 	jited_funcs = info.nr_jited_func_lens;
213 	func_lens = malloc(jited_funcs * sizeof(__u32));
214 	if (!ASSERT_OK_PTR(func_lens, "malloc(info.nr_jited_func_lens)")) {
215 		err = -ENOMEM;
216 		goto out;
217 	}
218 
219 	memset(&info, 0, sizeof(info));
220 	info.jited_prog_insns = (__u64)image;
221 	info.jited_prog_len = len;
222 	info.jited_func_lens = (__u64)func_lens;
223 	info.nr_jited_func_lens = jited_funcs;
224 	err = bpf_prog_get_info_by_fd(fd, &info, &info_len);
225 	if (!ASSERT_OK(err, "bpf_prog_get_info_by_fd #2"))
226 		goto out;
227 
228 	for (pc = 0, i = 0; i < jited_funcs; ++i) {
229 		fprintf(text_out, "func #%d:\n", i);
230 		disasm_one_func(text_out, image + pc, func_lens[i]);
231 		fprintf(text_out, "\n");
232 		pc += func_lens[i];
233 	}
234 
235 out:
236 	if (text_out)
237 		fclose(text_out);
238 	if (image)
239 		free(image);
240 	if (func_lens)
241 		free(func_lens);
242 	return err;
243 }
244 
245 #else /* HAVE_LLVM_SUPPORT */
246 
247 int get_jited_program_text(int fd, char *text, size_t text_sz)
248 {
249 	if (env.verbosity >= VERBOSE_VERY)
250 		printf("compiled w/o llvm development libraries, can't dis-assembly binary code");
251 	return -EOPNOTSUPP;
252 }
253 
254 #endif /* HAVE_LLVM_SUPPORT */
255