xref: /linux/tools/bpf/bpftool/jit_disasm.c (revision 8e07e0e3964ca4e23ce7b68e2096fe660a888942)
1 // SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
2 /*
3  * Based on:
4  *
5  * Minimal BPF JIT image disassembler
6  *
7  * Disassembles BPF JIT compiler emitted opcodes back to asm insn's for
8  * debugging or verification purposes.
9  *
10  * Copyright 2013 Daniel Borkmann <daniel@iogearbox.net>
11  * Licensed under the GNU General Public License, version 2.0 (GPLv2)
12  */
13 
14 #ifndef _GNU_SOURCE
15 #define _GNU_SOURCE
16 #endif
17 #include <stdio.h>
18 #include <stdarg.h>
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <string.h>
23 #include <sys/stat.h>
24 #include <limits.h>
25 #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>
37 #include <tools/dis-asm-compat.h>
38 #endif
39 
40 #include "json_writer.h"
41 #include "main.h"
42 
43 static int oper_count;
44 
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 		triple = LLVMNormalizeTargetTriple(arch);
89 	else
90 		triple = LLVMGetDefaultTargetTriple();
91 	if (!triple) {
92 		p_err("Failed to retrieve triple");
93 		return -1;
94 	}
95 	*ctx = LLVMCreateDisasm(triple, NULL, 0, NULL, symbol_lookup_callback);
96 	LLVMDisposeMessage(triple);
97 
98 	if (!*ctx) {
99 		p_err("Failed to create disassembler");
100 		return -1;
101 	}
102 
103 	return 0;
104 }
105 
106 static void destroy_context(disasm_ctx_t *ctx)
107 {
108 	LLVMDisposeMessage(*ctx);
109 }
110 
111 static int
112 disassemble_insn(disasm_ctx_t *ctx, unsigned char *image, ssize_t len, int pc)
113 {
114 	char buf[256];
115 	int count;
116 
117 	count = LLVMDisasmInstruction(*ctx, image + pc, len - pc, pc,
118 				      buf, sizeof(buf));
119 	if (json_output)
120 		printf_json(buf);
121 	else
122 		printf("%s", buf);
123 
124 	return count;
125 }
126 
127 int disasm_init(void)
128 {
129 	LLVMInitializeAllTargetInfos();
130 	LLVMInitializeAllTargetMCs();
131 	LLVMInitializeAllDisassemblers();
132 	return 0;
133 }
134 #endif /* HAVE_LLVM_SUPPORT */
135 
136 #ifdef HAVE_LIBBFD_SUPPORT
137 #define DISASM_SPACER "\t"
138 
139 typedef struct {
140 	struct disassemble_info *info;
141 	disassembler_ftype disassemble;
142 	bfd *bfdf;
143 } disasm_ctx_t;
144 
145 static int get_exec_path(char *tpath, size_t size)
146 {
147 	const char *path = "/proc/self/exe";
148 	ssize_t len;
149 
150 	len = readlink(path, tpath, size - 1);
151 	if (len <= 0)
152 		return -1;
153 
154 	tpath[len] = 0;
155 
156 	return 0;
157 }
158 
159 static int printf_json(void *out, const char *fmt, va_list ap)
160 {
161 	char *s;
162 	int err;
163 
164 	err = vasprintf(&s, fmt, ap);
165 	if (err < 0)
166 		return -1;
167 
168 	if (!oper_count) {
169 		int i;
170 
171 		/* Strip trailing spaces */
172 		i = strlen(s) - 1;
173 		while (s[i] == ' ')
174 			s[i--] = '\0';
175 
176 		jsonw_string_field(json_wtr, "operation", s);
177 		jsonw_name(json_wtr, "operands");
178 		jsonw_start_array(json_wtr);
179 		oper_count++;
180 	} else if (!strcmp(fmt, ",")) {
181 		   /* Skip */
182 	} else {
183 		jsonw_string(json_wtr, s);
184 		oper_count++;
185 	}
186 	free(s);
187 	return 0;
188 }
189 
190 static int fprintf_json(void *out, const char *fmt, ...)
191 {
192 	va_list ap;
193 	int r;
194 
195 	va_start(ap, fmt);
196 	r = printf_json(out, fmt, ap);
197 	va_end(ap);
198 
199 	return r;
200 }
201 
202 static int fprintf_json_styled(void *out,
203 			       enum disassembler_style style __maybe_unused,
204 			       const char *fmt, ...)
205 {
206 	va_list ap;
207 	int r;
208 
209 	va_start(ap, fmt);
210 	r = printf_json(out, fmt, ap);
211 	va_end(ap);
212 
213 	return r;
214 }
215 
216 static int init_context(disasm_ctx_t *ctx, const char *arch,
217 			const char *disassembler_options,
218 			unsigned char *image, ssize_t len)
219 {
220 	struct disassemble_info *info;
221 	char tpath[PATH_MAX];
222 	bfd *bfdf;
223 
224 	memset(tpath, 0, sizeof(tpath));
225 	if (get_exec_path(tpath, sizeof(tpath))) {
226 		p_err("failed to create disassembler (get_exec_path)");
227 		return -1;
228 	}
229 
230 	ctx->bfdf = bfd_openr(tpath, NULL);
231 	if (!ctx->bfdf) {
232 		p_err("failed to create disassembler (bfd_openr)");
233 		return -1;
234 	}
235 	if (!bfd_check_format(ctx->bfdf, bfd_object)) {
236 		p_err("failed to create disassembler (bfd_check_format)");
237 		goto err_close;
238 	}
239 	bfdf = ctx->bfdf;
240 
241 	ctx->info = malloc(sizeof(struct disassemble_info));
242 	if (!ctx->info) {
243 		p_err("mem alloc failed");
244 		goto err_close;
245 	}
246 	info = ctx->info;
247 
248 	if (json_output)
249 		init_disassemble_info_compat(info, stdout,
250 					     (fprintf_ftype) fprintf_json,
251 					     fprintf_json_styled);
252 	else
253 		init_disassemble_info_compat(info, stdout,
254 					     (fprintf_ftype) fprintf,
255 					     fprintf_styled);
256 
257 	/* Update architecture info for offload. */
258 	if (arch) {
259 		const bfd_arch_info_type *inf = bfd_scan_arch(arch);
260 
261 		if (inf) {
262 			bfdf->arch_info = inf;
263 		} else {
264 			p_err("No libbfd support for %s", arch);
265 			goto err_free;
266 		}
267 	}
268 
269 	info->arch = bfd_get_arch(bfdf);
270 	info->mach = bfd_get_mach(bfdf);
271 	if (disassembler_options)
272 		info->disassembler_options = disassembler_options;
273 	info->buffer = image;
274 	info->buffer_length = len;
275 
276 	disassemble_init_for_target(info);
277 
278 #ifdef DISASM_FOUR_ARGS_SIGNATURE
279 	ctx->disassemble = disassembler(info->arch,
280 					bfd_big_endian(bfdf),
281 					info->mach,
282 					bfdf);
283 #else
284 	ctx->disassemble = disassembler(bfdf);
285 #endif
286 	if (!ctx->disassemble) {
287 		p_err("failed to create disassembler");
288 		goto err_free;
289 	}
290 	return 0;
291 
292 err_free:
293 	free(info);
294 err_close:
295 	bfd_close(ctx->bfdf);
296 	return -1;
297 }
298 
299 static void destroy_context(disasm_ctx_t *ctx)
300 {
301 	free(ctx->info);
302 	bfd_close(ctx->bfdf);
303 }
304 
305 static int
306 disassemble_insn(disasm_ctx_t *ctx, __maybe_unused unsigned char *image,
307 		 __maybe_unused ssize_t len, int pc)
308 {
309 	return ctx->disassemble(pc, ctx->info);
310 }
311 
312 int disasm_init(void)
313 {
314 	bfd_init();
315 	return 0;
316 }
317 #endif /* HAVE_LIBBPFD_SUPPORT */
318 
319 int disasm_print_insn(unsigned char *image, ssize_t len, int opcodes,
320 		      const char *arch, const char *disassembler_options,
321 		      const struct btf *btf,
322 		      const struct bpf_prog_linfo *prog_linfo,
323 		      __u64 func_ksym, unsigned int func_idx,
324 		      bool linum)
325 {
326 	const struct bpf_line_info *linfo = NULL;
327 	unsigned int nr_skip = 0;
328 	int count, i, pc = 0;
329 	disasm_ctx_t ctx;
330 
331 	if (!len)
332 		return -1;
333 
334 	if (init_context(&ctx, arch, disassembler_options, image, len))
335 		return -1;
336 
337 	if (json_output)
338 		jsonw_start_array(json_wtr);
339 	do {
340 		if (prog_linfo) {
341 			linfo = bpf_prog_linfo__lfind_addr_func(prog_linfo,
342 								func_ksym + pc,
343 								func_idx,
344 								nr_skip);
345 			if (linfo)
346 				nr_skip++;
347 		}
348 
349 		if (json_output) {
350 			jsonw_start_object(json_wtr);
351 			oper_count = 0;
352 			if (linfo)
353 				btf_dump_linfo_json(btf, linfo, linum);
354 			jsonw_name(json_wtr, "pc");
355 			jsonw_printf(json_wtr, "\"0x%x\"", pc);
356 		} else {
357 			if (linfo)
358 				btf_dump_linfo_plain(btf, linfo, "; ",
359 						     linum);
360 			printf("%4x:" DISASM_SPACER, pc);
361 		}
362 
363 		count = disassemble_insn(&ctx, image, len, pc);
364 
365 		if (json_output) {
366 			/* Operand array, was started in fprintf_json. Before
367 			 * that, make sure we have a _null_ value if no operand
368 			 * other than operation code was present.
369 			 */
370 			if (oper_count == 1)
371 				jsonw_null(json_wtr);
372 			jsonw_end_array(json_wtr);
373 		}
374 
375 		if (opcodes) {
376 			if (json_output) {
377 				jsonw_name(json_wtr, "opcodes");
378 				jsonw_start_array(json_wtr);
379 				for (i = 0; i < count; ++i)
380 					jsonw_printf(json_wtr, "\"0x%02hhx\"",
381 						     (uint8_t)image[pc + i]);
382 				jsonw_end_array(json_wtr);
383 			} else {
384 				printf("\n\t");
385 				for (i = 0; i < count; ++i)
386 					printf("%02x ",
387 					       (uint8_t)image[pc + i]);
388 			}
389 		}
390 		if (json_output)
391 			jsonw_end_object(json_wtr);
392 		else
393 			printf("\n");
394 
395 		pc += count;
396 	} while (count > 0 && pc < len);
397 	if (json_output)
398 		jsonw_end_array(json_wtr);
399 
400 	destroy_context(&ctx);
401 
402 	return 0;
403 }
404