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(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 | |