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
43static int oper_count;
44
45#ifdef HAVE_LLVM_SUPPORT
46#define DISASM_SPACER
47
48typedef LLVMDisasmContextRef disasm_ctx_t;
49
50static 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 */
70static const char *
71symbol_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
80static int
81init_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
106static void destroy_context(disasm_ctx_t *ctx)
107{
108 LLVMDisposeMessage(*ctx);
109}
110
111static int
112disassemble_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
127int 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
139typedef struct {
140 struct disassemble_info *info;
141 disassembler_ftype disassemble;
142 bfd *bfdf;
143} disasm_ctx_t;
144
145static 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
159static 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
190static 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
202static 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
216static 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
292err_free:
293 free(info);
294err_close:
295 bfd_close(ctx->bfdf);
296 return -1;
297}
298
299static void destroy_context(disasm_ctx_t *ctx)
300{
301 free(ctx->info);
302 bfd_close(ctx->bfdf);
303}
304
305static int
306disassemble_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
312int disasm_init(void)
313{
314 bfd_init();
315 return 0;
316}
317#endif /* HAVE_LIBBPFD_SUPPORT */
318
319int 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(self: 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(self: json_wtr);
351 oper_count = 0;
352 if (linfo)
353 btf_dump_linfo_json(btf, linfo, linum);
354 jsonw_name(self: json_wtr, name: "pc");
355 jsonw_printf(self: json_wtr, fmt: "\"0x%x\"", pc);
356 } else {
357 if (linfo)
358 btf_dump_linfo_plain(btf, linfo, prefix: "; ",
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(self: json_wtr);
372 jsonw_end_array(self: json_wtr);
373 }
374
375 if (opcodes) {
376 if (json_output) {
377 jsonw_name(self: json_wtr, name: "opcodes");
378 jsonw_start_array(self: json_wtr);
379 for (i = 0; i < count; ++i)
380 jsonw_printf(self: json_wtr, fmt: "\"0x%02hhx\"",
381 (uint8_t)image[pc + i]);
382 jsonw_end_array(self: 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(self: 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(self: json_wtr);
399
400 destroy_context(&ctx);
401
402 return 0;
403}
404

source code of linux/tools/bpf/bpftool/jit_disasm.c